-
Notifications
You must be signed in to change notification settings - Fork 206
feat: SG-42241: RV: Implement the ability to fetch filmstrips and thumbnails for sources in coming into RV's Session Manager #1200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
54bab16
6a0a08e
40add49
498dedd
241076b
4426f3d
3ac3970
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,6 +20,10 @@ ViewSubComponent := 2; | |||||||||||||||||||
| LayerSubComponent := 3; | ||||||||||||||||||||
| ChannelSubComponent := 4; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| FILMSTRIP_FRAME_WIDTH := 240; | ||||||||||||||||||||
| PREVIEW_WIDTH := 80; | ||||||||||||||||||||
| PREVIEW_HEIGHT := 45; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| \: itemNode (string; QStandardItem item) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| let d = item.data(Qt.UserRole + 2); | ||||||||||||||||||||
|
|
@@ -601,6 +605,120 @@ documentation: """ | |||||||||||||||||||
| QStandardItemModel with modified drag and drop mime types. | ||||||||||||||||||||
| """; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Displays a static thumbnail image. Falls back to a placeholder pixmap if none loaded. | ||||||||||||||||||||
| class: ThumbnailWidget : QLabel | ||||||||||||||||||||
| { | ||||||||||||||||||||
| method: ThumbnailWidget (ThumbnailWidget; QWidget parent) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| QLabel.QLabel(this, parent); | ||||||||||||||||||||
| setScaledContents(true); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| method: setFallback (void; QPixmap px) { setPixmap(px); } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| method: load (void; string path) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| let px = QPixmap.fromImage(QImage(path, ""), Qt.AutoColor); | ||||||||||||||||||||
| if (!px.isNull()) setPixmap(px); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Displays a scrubable filmstrip that will show the corresponding frame | ||||||||||||||||||||
| // based on mouse position | ||||||||||||||||||||
| class: FilmstripWidget : QLabel | ||||||||||||||||||||
| { | ||||||||||||||||||||
| QImage _strip; | ||||||||||||||||||||
| int _frameWidth; | ||||||||||||||||||||
| bool _loaded; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| method: FilmstripWidget (FilmstripWidget; QWidget parent) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| QLabel.QLabel(this, parent); | ||||||||||||||||||||
| _frameWidth = FILMSTRIP_FRAME_WIDTH; | ||||||||||||||||||||
| _loaded = false; | ||||||||||||||||||||
| setScaledContents(true); | ||||||||||||||||||||
| setMouseTracking(true); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| method: showFrameAtX (void; int mouseX) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| if (!_loaded) return; | ||||||||||||||||||||
| let nat_w = _strip.width(), | ||||||||||||||||||||
| prop_x = float(mouseX) / float(width()), | ||||||||||||||||||||
| fx = int(prop_x * float(nat_w) / float(_frameWidth) + 0.5) * _frameWidth, | ||||||||||||||||||||
| clamped = if fx > nat_w - _frameWidth then nat_w - _frameWidth | ||||||||||||||||||||
| else if fx < 0 then 0 | ||||||||||||||||||||
| else fx; | ||||||||||||||||||||
| let frame = _strip.copy(QRect(clamped, 0, _frameWidth, _strip.height())); | ||||||||||||||||||||
| setPixmap(QPixmap.fromImage(frame, Qt.AutoColor)); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| method: isLoaded (bool;) { _loaded; } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| method: load (void; string path) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| let img = QImage(path, ""); | ||||||||||||||||||||
| if (!img.isNull()) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| _strip = img; | ||||||||||||||||||||
| _loaded = true; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| method: mouseMoveEvent (void; QMouseEvent event) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| showFrameAtX(event.position().toPoint().x()); | ||||||||||||||||||||
| QWidget.mouseMoveEvent(this, event); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Stacks a FilmstripWidget and ThumbnailWidget. Shows thumbnail by default; | ||||||||||||||||||||
| // on hover enter shows the filmstrip scrubbed to the cursor position. | ||||||||||||||||||||
| class: SourcePreviewWidget : QWidget | ||||||||||||||||||||
| { | ||||||||||||||||||||
| FilmstripWidget _filmstrip; | ||||||||||||||||||||
| ThumbnailWidget _thumbnail; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| method: SourcePreviewWidget (SourcePreviewWidget; QWidget parent) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| QWidget.QWidget(this, parent); | ||||||||||||||||||||
| setAttribute(Qt.WA_Hover, true); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| _thumbnail = ThumbnailWidget(this); | ||||||||||||||||||||
| _thumbnail.setGeometry(QRect(0, 0, PREVIEW_WIDTH, PREVIEW_HEIGHT)); | ||||||||||||||||||||
| _thumbnail.show(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| _filmstrip = FilmstripWidget(this); | ||||||||||||||||||||
| _filmstrip.setGeometry(QRect(0, 0, PREVIEW_WIDTH, PREVIEW_HEIGHT)); | ||||||||||||||||||||
| _filmstrip.hide(); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| method: setFallback (void; QPixmap px) { _thumbnail.setFallback(px); } | ||||||||||||||||||||
| method: loadStrip (void; string path) { _filmstrip.load(path); } | ||||||||||||||||||||
| method: loadThumbnail (void; string path) { _thumbnail.load(path); } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| method: event (bool; QEvent e) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| if (e.type() == QEvent.HoverEnter) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| if (_filmstrip.isLoaded()) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| _filmstrip.showFrameAtX(mapFromGlobal(QCursor.pos()).x()); | ||||||||||||||||||||
| _filmstrip.show(); | ||||||||||||||||||||
| _thumbnail.hide(); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return true; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| else if (e.type() == QEvent.HoverLeave) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| _filmstrip.hide(); | ||||||||||||||||||||
| _thumbnail.show(); | ||||||||||||||||||||
| return true; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return QWidget.event(this, e); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| class: NodeModel : QStandardItemModel | ||||||||||||||||||||
| { | ||||||||||||||||||||
| method: NodeModel (NodeModel; QObject parent) | ||||||||||||||||||||
|
|
@@ -980,6 +1098,7 @@ class: SessionManagerMode : MinorMode | |||||||||||||||||||
| QIcon _layerIcon; | ||||||||||||||||||||
| QIcon _channelIcon; | ||||||||||||||||||||
| QIcon _videoIcon; | ||||||||||||||||||||
| QIcon _fallbackSourceIcon; | ||||||||||||||||||||
| bool _inputOrderLock; | ||||||||||||||||||||
| bool _disableUpdates; | ||||||||||||||||||||
| bool _progressiveLoadingInProgress; | ||||||||||||||||||||
|
|
@@ -1360,20 +1479,28 @@ class: SessionManagerMode : MinorMode | |||||||||||||||||||
| _inputOrderLock = true; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| _inputsModel.clear(); | ||||||||||||||||||||
| let connections = nodeInputs(node), | ||||||||||||||||||||
| vnodes = viewNodes(); | ||||||||||||||||||||
| let connections = nodeInputs(node); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| for_index (i; connections) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| let innode = connections[i], | ||||||||||||||||||||
| item = QStandardItem(iconForNode(innode), uiName(innode)), | ||||||||||||||||||||
| vindex = indexOfItem(vnodes, innode); | ||||||||||||||||||||
| let innode = connections[i], | ||||||||||||||||||||
| isSource = nodeType(innode) == "RVSourceGroup", | ||||||||||||||||||||
| item = QStandardItem(iconForNode(innode), uiName(innode)); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsEnabled); | ||||||||||||||||||||
| item.setData(QVariant(innode), Qt.UserRole + 2); | ||||||||||||||||||||
| item.setEditable(false); | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| if (isSource) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| item.setText(""); | ||||||||||||||||||||
| item.setSizeHint(QSize(-1, 55)); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| _inputsModel.appendRow(item); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (isSource) | ||||||||||||||||||||
| _inputsView.setIndexWidget(_inputsModel.indexFromItem(item), makeSourceRowWidget(innode)); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| _inputOrderLock = false; | ||||||||||||||||||||
|
|
@@ -1662,6 +1789,72 @@ class: SessionManagerMode : MinorMode | |||||||||||||||||||
| item; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| method: makeSourceRowWidget (QWidget; string node) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| let widget = QWidget(nil, 0), | ||||||||||||||||||||
| layout = QHBoxLayout(widget); | ||||||||||||||||||||
| widget.setStyleSheet("background: transparent;"); | ||||||||||||||||||||
| layout.setContentsMargins(8, 0, 8, 0); | ||||||||||||||||||||
| layout.setSpacing(5); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Source preview: stacks thumbnail (default) and filmstrip (on hover) | ||||||||||||||||||||
| let preview = SourcePreviewWidget(widget); | ||||||||||||||||||||
| preview.setFixedSize(QSize(PREVIEW_WIDTH, PREVIEW_HEIGHT)); | ||||||||||||||||||||
| preview.setFallback(_fallbackSourceIcon.pixmap(QSize(PREVIEW_WIDTH, PREVIEW_HEIGHT))); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| try | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would avoid doing try catch blocks around several lines, and with a catch-all. I would keep it specifically for the line that could throw:
Suggested change
Since |
||||||||||||||||||||
| { | ||||||||||||||||||||
| let sn = sourceNodeOfGroup(node); | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are declaring twice sn in this method (i.e. here and at line 1841). You should reuse the variable instead of calling |
||||||||||||||||||||
|
|
||||||||||||||||||||
| // Fire events that fetch filmstrip/thumbnail and return file paths | ||||||||||||||||||||
| let filmstripPath = sendInternalEvent("session_manager-get-filmstrip-path", sn); | ||||||||||||||||||||
| let thumbnailPath = sendInternalEvent("session_manager-get-thumbnail-path", sn); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (filmstripPath != "" && io.path.exists(filmstripPath)) | ||||||||||||||||||||
| preview.loadStrip(filmstripPath); | ||||||||||||||||||||
| if (thumbnailPath != "" && io.path.exists(thumbnailPath)) | ||||||||||||||||||||
| preview.loadThumbnail(thumbnailPath); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| catch (...) { ; } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| layout.addWidget(preview); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Text column | ||||||||||||||||||||
| let textWidget = QWidget(widget), | ||||||||||||||||||||
| textLayout = QVBoxLayout(textWidget); | ||||||||||||||||||||
| textWidget.setStyleSheet("background: transparent;"); | ||||||||||||||||||||
| textLayout.setSpacing(3); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| let nameLabel = QLabel(uiName(node), textWidget); | ||||||||||||||||||||
| nameLabel.setStyleSheet("color: #d0d0d0; background: transparent;"); | ||||||||||||||||||||
| textLayout.addWidget(nameLabel); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| string meta = ""; | ||||||||||||||||||||
| try | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can remove the try catch statement here, and simply guard what is inside of it by checking that the source node is not nil before proceeding. For the media property, You can simply check if |
||||||||||||||||||||
| { | ||||||||||||||||||||
| let sn = sourceNodeOfGroup(node), | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rename |
||||||||||||||||||||
| mprop = getStringProperty(sn + ".media.movie"); | ||||||||||||||||||||
| if (!mprop.empty()) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| let parts = io.path.basename(mprop.front()).split("."); | ||||||||||||||||||||
| if (parts.size() > 1) meta = parts.back(); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| catch (exception exc) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| print("WARNING: Could not get media type info for %s - %s\n" % (uiName(node), exc)); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| let metaLabel = QLabel(if meta == "" then "—" else meta, textWidget); | ||||||||||||||||||||
| metaLabel.setStyleSheet("color: #888888; background: transparent; font-size: 9px;"); | ||||||||||||||||||||
| textLayout.addWidget(metaLabel); | ||||||||||||||||||||
| textLayout.addStretch(1); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| layout.addWidget(textWidget, 1); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| widget; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| method: newNodeRow (void; | ||||||||||||||||||||
| QStandardItem parentItem, | ||||||||||||||||||||
| string node, | ||||||||||||||||||||
|
|
@@ -1692,6 +1885,13 @@ class: SessionManagerMode : MinorMode | |||||||||||||||||||
| if (node == viewNode()) head(tail(statusItems)).setText("\u2714"); | ||||||||||||||||||||
| addRow(parentItem, item : statusItems); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (source) | ||||||||||||||||||||
| { | ||||||||||||||||||||
| item.setText(""); | ||||||||||||||||||||
| item.setSizeHint(QSize(-1, 55)); | ||||||||||||||||||||
| _viewTreeView.setIndexWidget(_viewModel.indexFromItem(item), makeSourceRowWidget(node)); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // | ||||||||||||||||||||
| // Tabs in tooltips make win32 Qt crash. | ||||||||||||||||||||
| // | ||||||||||||||||||||
|
|
@@ -2929,6 +3129,7 @@ class: SessionManagerMode : MinorMode | |||||||||||||||||||
| _viewTreeView.setDragDropMode(QAbstractItemView.DragDrop); | ||||||||||||||||||||
| _viewTreeView.setDefaultDropAction(Qt.MoveAction); | ||||||||||||||||||||
| _viewTreeView.setExpandsOnDoubleClick(false); | ||||||||||||||||||||
| _viewTreeView.setIndentation(10); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| _inputsView.setModel(_inputsModel); | ||||||||||||||||||||
| _inputsView.setDragEnabled(true); | ||||||||||||||||||||
|
|
@@ -2986,6 +3187,7 @@ class: SessionManagerMode : MinorMode | |||||||||||||||||||
| _channelIcon = auxIcon("channel.png", true); | ||||||||||||||||||||
| _layerIcon = auxIcon("layer.png", true); | ||||||||||||||||||||
| _unknownTypeIcon = auxIcon("new_48x48.png", true); | ||||||||||||||||||||
| _fallbackSourceIcon = QIcon(auxFilePath("fallback_thumbnail.png")); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| _addButton.setDefaultAction(addAction); | ||||||||||||||||||||
| _deleteButton.setDefaultAction(deleteAction); | ||||||||||||||||||||
|
|
||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see you added several setStyleSheet calls, and I believe we should be using our .qss files to set properties based on themes instead. Also, I know users can set a specific font size in the RV preferences tab. You might want to validate how that works with the metalabel font size property you added below