From 54bab169572a4c413f462975e5a79d512f27c71f Mon Sep 17 00:00:00 2001 From: chenl1 Date: Thu, 5 Mar 2026 14:00:13 -0500 Subject: [PATCH 1/7] Add default fallback icon for sources in session manager Signed-off-by: chenl1 --- .../session_manager/fallback_thumbnail.png | Bin 0 -> 773 bytes .../session_manager/session_manager.mu.in | 27 ++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/plugins/rv-packages/session_manager/fallback_thumbnail.png diff --git a/src/plugins/rv-packages/session_manager/fallback_thumbnail.png b/src/plugins/rv-packages/session_manager/fallback_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..e6591d42aec02f0586f1a71aab943a6ed55454a0 GIT binary patch literal 773 zcmeAS@N?(olHy`uVBq!ia0vp^DL`z&!2~2*&YbFJU|`zi>Eakt!T9E8;>Je?0tX78 z?Q3S_X=oH?kT}4=!NA-O+0KY|LxgdwfElr? z-+y0d2rX7TE!NHI+ZH)ZOe`oxNUvr>`(egS4+|{h`ZaU5M{8v+YMvVI6yey_vg>YM z&Et;0b@e-A{FV#1#(x+0o$HsrH|{d8^-~Yl)oQL$woVgbHs9>2`jl-oHz=S_WkXib zl1s*{0-RANt@})0vWtlp?b%MWYTbTIr(s zyGDOa!bKg?sL0}}5l=!Q!}MmPUV9z1By^_liz%F?d*dvx@<^}QzH~w2)1wnrkN&J# zwm_0CV!p>l2UUh!Ic73bouyw3Yu-DpFW2CqeJw_0%H!lUGd>n=|6lX1RdTPHf8oSe z)s=RizAOo{4B2=iukz@k$X6RfdwoQ^JJYsC+1|@F%}$EGx@5-$EwSs&t9S7&P!rc| zTh)71b@jxFB8RsfIpuvyNZVS|c#X``oDD`(jSid&_mb}BWuC_Oeyz9U>dF;%yzYwy z^5N&mf{tlhu5OH`<9{$&>l-PX3+ zjtlB@7i)>>Delym@-b(h|M3+vomyhMpZUDaxm#FS)U|*?eyZY23tyn48yHw;JuNzE z5--B4-FYy Date: Tue, 10 Mar 2026 09:30:00 -0400 Subject: [PATCH 2/7] Add thumbnail icon that can be scrubed to source and inputs Signed-off-by: chenl1 --- .../session_manager/session_manager.mu.in | 157 +++++++++++++++--- 1 file changed, 135 insertions(+), 22 deletions(-) diff --git a/src/plugins/rv-packages/session_manager/session_manager.mu.in b/src/plugins/rv-packages/session_manager/session_manager.mu.in index 4ee1e3536..22d666c5d 100755 --- a/src/plugins/rv-packages/session_manager/session_manager.mu.in +++ b/src/plugins/rv-packages/session_manager/session_manager.mu.in @@ -601,6 +601,73 @@ documentation: """ QStandardItemModel with modified drag and drop mime types. """; +class: FilmstripWidget : QLabel +{ + QImage _strip; + int _frameWidth; // width of each frame in the filmstrip (240 px) + bool _loaded; + QTimer _leaveTimer; + + method: FilmstripWidget (FilmstripWidget; QWidget parent) + { + QLabel.QLabel(this, parent); + _frameWidth = 240; + _loaded = false; + setScaledContents(true); + setMouseTracking(true); + _leaveTimer = QTimer(this); + _leaveTimer.setInterval(100); + connect(_leaveTimer, QTimer.timeout, checkMouseLeft); + } + + method: checkMouseLeft (void;) + { + if (!underMouse()) + { + showFrame(0); + _leaveTimer.stop(); + } + } + + method: showFrame (void; int fx) + { + let frame = _strip.copy(QRect(fx, 0, _frameWidth, _strip.height())); + setPixmap(QPixmap.fromImage(frame, Qt.AutoColor)); + } + + method: loadStrip (void; string path, QPixmap fallback) + { + let img = QImage(path, nil); + if (!img.isNull()) + { + _strip = img; + _loaded = true; + showFrame(0); + } + else + { + setPixmap(fallback); + } + } + + method: mouseMoveEvent (void; QMouseEvent event) + { + if (_loaded) + { + let mx = event.position().toPoint().x(), + nat_w = _strip.width(), + prop_x = float(mx) / 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; + showFrame(clamped); + if (!_leaveTimer.isActive()) _leaveTimer.start(); + } + QWidget.mouseMoveEvent(this, event); + } +} + class: NodeModel : QStandardItemModel { method: NodeModel (NodeModel; QObject parent) @@ -1366,15 +1433,25 @@ class: SessionManagerMode : MinorMode 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)), + vindex = indexOfItem(vnodes, 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; @@ -1663,6 +1740,59 @@ 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); + + // Filmstrip thumbnail + let thumb = FilmstripWidget(widget); + thumb.setFixedSize(QSize(80, 45)); + thumb.loadStrip(auxFilePath("filmstrip.jpg"), + _fallbackSourceIcon.pixmap(QSize(80, 45))); + layout.addWidget(thumb); + + // 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 + { + let sn = sourceNodeOfGroup(node), + 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;"); + metaLabel.setSizePolicy(5, 5); // QSizePolicy::Preferred + metaLabel.setMinimumWidth(1); // absolute minimum overrides minimumSizeHint, allows shrinking + textLayout.addWidget(metaLabel); + textLayout.addStretch(1); + + layout.addWidget(textWidget, 1); + + widget; + } + method: newNodeRow (void; QStandardItem parentItem, string node, @@ -1697,24 +1827,7 @@ class: SessionManagerMode : MinorMode { item.setText(""); item.setSizeHint(QSize(-1, 55)); - - let widget = QWidget(nil, 0), - layout = QHBoxLayout(widget); - widget.setStyleSheet("background: transparent;"); - layout.setContentsMargins(8, 0, 8, 0); - layout.setSpacing(10); - - let thumbLabel = QLabel(widget); - thumbLabel.setPixmap(_fallbackSourceIcon.pixmap(QSize(80, 40), - QIcon.Normal, QIcon.Off)); - layout.addWidget(thumbLabel); - - let nameLabel = QLabel(uiName(node), widget); - nameLabel.setStyleSheet("color: white; background: transparent;"); - layout.addWidget(nameLabel); - layout.addStretch(1); - - _viewTreeView.setIndexWidget(_viewModel.indexFromItem(item), widget); + _viewTreeView.setIndexWidget(_viewModel.indexFromItem(item), makeSourceRowWidget(node)); } // From 40add4993d6c944483aa6c99b5d96301e1fea955 Mon Sep 17 00:00:00 2001 From: chenl1 Date: Tue, 10 Mar 2026 11:55:44 -0400 Subject: [PATCH 3/7] Refactor away some dead code Signed-off-by: chenl1 --- .../rv-packages/session_manager/session_manager.mu.in | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/plugins/rv-packages/session_manager/session_manager.mu.in b/src/plugins/rv-packages/session_manager/session_manager.mu.in index 22d666c5d..5bc0b2885 100755 --- a/src/plugins/rv-packages/session_manager/session_manager.mu.in +++ b/src/plugins/rv-packages/session_manager/session_manager.mu.in @@ -1428,15 +1428,13 @@ 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], isSource = nodeType(innode) == "RVSourceGroup", - item = QStandardItem(iconForNode(innode), uiName(innode)), - vindex = indexOfItem(vnodes, innode); + item = QStandardItem(iconForNode(innode), uiName(innode)); item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsEnabled); item.setData(QVariant(innode), Qt.UserRole + 2); From 498dedd2112eb0f3fe59fa5e92609ce4b3bd2fe0 Mon Sep 17 00:00:00 2001 From: chenl1 Date: Tue, 10 Mar 2026 17:27:42 -0400 Subject: [PATCH 4/7] Remove unwanted code Signed-off-by: chenl1 --- src/plugins/rv-packages/session_manager/session_manager.mu.in | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/plugins/rv-packages/session_manager/session_manager.mu.in b/src/plugins/rv-packages/session_manager/session_manager.mu.in index 5bc0b2885..dd2bdc984 100755 --- a/src/plugins/rv-packages/session_manager/session_manager.mu.in +++ b/src/plugins/rv-packages/session_manager/session_manager.mu.in @@ -1781,8 +1781,6 @@ class: SessionManagerMode : MinorMode let metaLabel = QLabel(if meta == "" then "—" else meta, textWidget); metaLabel.setStyleSheet("color: #888888; background: transparent; font-size: 9px;"); - metaLabel.setSizePolicy(5, 5); // QSizePolicy::Preferred - metaLabel.setMinimumWidth(1); // absolute minimum overrides minimumSizeHint, allows shrinking textLayout.addWidget(metaLabel); textLayout.addStretch(1); From 241076b435f5ef10b874307595ff0aa56c3fea31 Mon Sep 17 00:00:00 2001 From: chenl1 Date: Tue, 17 Mar 2026 13:12:10 -0400 Subject: [PATCH 5/7] Add thumbnail class and event sending for python plugin Signed-off-by: chenl1 --- .../session_manager/session_manager.mu.in | 154 +++++++++++++----- 1 file changed, 113 insertions(+), 41 deletions(-) diff --git a/src/plugins/rv-packages/session_manager/session_manager.mu.in b/src/plugins/rv-packages/session_manager/session_manager.mu.in index dd2bdc984..5eb885534 100755 --- a/src/plugins/rv-packages/session_manager/session_manager.mu.in +++ b/src/plugins/rv-packages/session_manager/session_manager.mu.in @@ -601,12 +601,31 @@ 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; // width of each frame in the filmstrip (240 px) bool _loaded; - QTimer _leaveTimer; method: FilmstripWidget (FilmstripWidget; QWidget parent) { @@ -615,56 +634,84 @@ class: FilmstripWidget : QLabel _loaded = false; setScaledContents(true); setMouseTracking(true); - _leaveTimer = QTimer(this); - _leaveTimer.setInterval(100); - connect(_leaveTimer, QTimer.timeout, checkMouseLeft); } - method: checkMouseLeft (void;) + method: showFrameAtX (void; int mouseX) { - if (!underMouse()) - { - showFrame(0); - _leaveTimer.stop(); - } - } - - method: showFrame (void; int fx) - { - let frame = _strip.copy(QRect(fx, 0, _frameWidth, _strip.height())); + 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: loadStrip (void; string path, QPixmap fallback) + method: isLoaded (bool;) { _loaded; } + + method: load (void; string path) { - let img = QImage(path, nil); + let img = QImage(path, ""); if (!img.isNull()) { _strip = img; _loaded = true; - showFrame(0); - } - else - { - setPixmap(fallback); } } method: mouseMoveEvent (void; QMouseEvent event) { - if (_loaded) + 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, 80, 45)); + _thumbnail.show(); + + _filmstrip = FilmstripWidget(this); + _filmstrip.setGeometry(QRect(0, 0, 80, 45)); + _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) { - let mx = event.position().toPoint().x(), - nat_w = _strip.width(), - prop_x = float(mx) / 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; - showFrame(clamped); - if (!_leaveTimer.isActive()) _leaveTimer.start(); + if (_filmstrip.isLoaded()) + { + _filmstrip.showFrameAtX(mapFromGlobal(QCursor.pos()).x()); + _filmstrip.show(); + _thumbnail.hide(); + } + return true; } - QWidget.mouseMoveEvent(this, event); + else if (e.type() == QEvent.HoverLeave) + { + _filmstrip.hide(); + _thumbnail.show(); + return true; + } + return QWidget.event(this, e); } } @@ -1094,8 +1141,8 @@ class: SessionManagerMode : MinorMode method: colorAdjustedIcon (QIcon; string rpath, bool invertSense) { let bg = QApplication.palette().color(QPalette.Active, @MU_QT_QPALETTE_COLORROLE@), - icon0 = QImage(regex.replace("48x48", rpath, "out"), nil), - icon1 = QImage(rpath, nil), + icon0 = QImage(regex.replace("48x48", rpath, "out"), ""), + icon1 = QImage(rpath, ""), swap = invertSense != _darkUI, qimage = if swap then icon0 else icon1; @@ -1746,12 +1793,37 @@ class: SessionManagerMode : MinorMode layout.setContentsMargins(8, 0, 8, 0); layout.setSpacing(5); - // Filmstrip thumbnail - let thumb = FilmstripWidget(widget); - thumb.setFixedSize(QSize(80, 45)); - thumb.loadStrip(auxFilePath("filmstrip.jpg"), - _fallbackSourceIcon.pixmap(QSize(80, 45))); - layout.addWidget(thumb); + // Source preview: stacks thumbnail (default) and filmstrip (on hover) + let preview = SourcePreviewWidget(widget); + preview.setFixedSize(QSize(80, 45)); + preview.setFallback(_fallbackSourceIcon.pixmap(QSize(80, 45))); + + try + { + let sn = sourceNodeOfGroup(node); + + // Fire events that can get picked up by Python plugins used to + // download filmstrips and thumbnails + sendInternalEvent("fetch-version-filmstrip", sn); + sendInternalEvent("fetch-version-thumbnail", sn); + + // Load synchronously from disk if files are already cached + // (sendInternalEvent is synchronous so files may already be there) + let trackingInfo = getStringProperty(sn + ".tracking.info"); + for_index (i; trackingInfo) + { + if (trackingInfo[i] == "id" && i + 1 < trackingInfo.size()) + { + let base = QDir.tempPath() + "/rv_thumbnails/" + trackingInfo[i+1]; + if (io.path.exists(base + "_filmstrip.jpg")) preview.loadStrip(base + "_filmstrip.jpg"); + if (io.path.exists(base + "_thumbnail.jpg")) preview.loadThumbnail(base + "_thumbnail.jpg"); + break; + } + } + } + catch (...) { ; } + + layout.addWidget(preview); // Text column let textWidget = QWidget(widget), From 4426f3d9ef5cacba8ebb8eca16f62f5f959c84b5 Mon Sep 17 00:00:00 2001 From: chenl1 Date: Tue, 17 Mar 2026 13:40:50 -0400 Subject: [PATCH 6/7] Remove unnecessary changes Signed-off-by: chenl1 --- src/plugins/rv-packages/session_manager/session_manager.mu.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/rv-packages/session_manager/session_manager.mu.in b/src/plugins/rv-packages/session_manager/session_manager.mu.in index 5eb885534..8e393eb43 100755 --- a/src/plugins/rv-packages/session_manager/session_manager.mu.in +++ b/src/plugins/rv-packages/session_manager/session_manager.mu.in @@ -1141,8 +1141,8 @@ class: SessionManagerMode : MinorMode method: colorAdjustedIcon (QIcon; string rpath, bool invertSense) { let bg = QApplication.palette().color(QPalette.Active, @MU_QT_QPALETTE_COLORROLE@), - icon0 = QImage(regex.replace("48x48", rpath, "out"), ""), - icon1 = QImage(rpath, ""), + icon0 = QImage(regex.replace("48x48", rpath, "out"), nil), + icon1 = QImage(rpath, nil), swap = invertSense != _darkUI, qimage = if swap then icon0 else icon1; From 3ac39701f4482f6a5b77cb5f9017c00da500d5c5 Mon Sep 17 00:00:00 2001 From: chenl1 Date: Thu, 19 Mar 2026 12:05:51 -0400 Subject: [PATCH 7/7] Remove SG related and cache handling code Signed-off-by: chenl1 --- .../session_manager/session_manager.mu.in | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/plugins/rv-packages/session_manager/session_manager.mu.in b/src/plugins/rv-packages/session_manager/session_manager.mu.in index 8e393eb43..4aed51b6e 100755 --- a/src/plugins/rv-packages/session_manager/session_manager.mu.in +++ b/src/plugins/rv-packages/session_manager/session_manager.mu.in @@ -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); @@ -624,13 +628,13 @@ class: ThumbnailWidget : QLabel class: FilmstripWidget : QLabel { QImage _strip; - int _frameWidth; // width of each frame in the filmstrip (240 px) + int _frameWidth; bool _loaded; method: FilmstripWidget (FilmstripWidget; QWidget parent) { QLabel.QLabel(this, parent); - _frameWidth = 240; + _frameWidth = FILMSTRIP_FRAME_WIDTH; _loaded = false; setScaledContents(true); setMouseTracking(true); @@ -681,11 +685,11 @@ class: SourcePreviewWidget : QWidget setAttribute(Qt.WA_Hover, true); _thumbnail = ThumbnailWidget(this); - _thumbnail.setGeometry(QRect(0, 0, 80, 45)); + _thumbnail.setGeometry(QRect(0, 0, PREVIEW_WIDTH, PREVIEW_HEIGHT)); _thumbnail.show(); _filmstrip = FilmstripWidget(this); - _filmstrip.setGeometry(QRect(0, 0, 80, 45)); + _filmstrip.setGeometry(QRect(0, 0, PREVIEW_WIDTH, PREVIEW_HEIGHT)); _filmstrip.hide(); } @@ -1795,31 +1799,21 @@ class: SessionManagerMode : MinorMode // Source preview: stacks thumbnail (default) and filmstrip (on hover) let preview = SourcePreviewWidget(widget); - preview.setFixedSize(QSize(80, 45)); - preview.setFallback(_fallbackSourceIcon.pixmap(QSize(80, 45))); + preview.setFixedSize(QSize(PREVIEW_WIDTH, PREVIEW_HEIGHT)); + preview.setFallback(_fallbackSourceIcon.pixmap(QSize(PREVIEW_WIDTH, PREVIEW_HEIGHT))); try { let sn = sourceNodeOfGroup(node); - // Fire events that can get picked up by Python plugins used to - // download filmstrips and thumbnails - sendInternalEvent("fetch-version-filmstrip", sn); - sendInternalEvent("fetch-version-thumbnail", sn); + // 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); - // Load synchronously from disk if files are already cached - // (sendInternalEvent is synchronous so files may already be there) - let trackingInfo = getStringProperty(sn + ".tracking.info"); - for_index (i; trackingInfo) - { - if (trackingInfo[i] == "id" && i + 1 < trackingInfo.size()) - { - let base = QDir.tempPath() + "/rv_thumbnails/" + trackingInfo[i+1]; - if (io.path.exists(base + "_filmstrip.jpg")) preview.loadStrip(base + "_filmstrip.jpg"); - if (io.path.exists(base + "_thumbnail.jpg")) preview.loadThumbnail(base + "_thumbnail.jpg"); - break; - } - } + if (filmstripPath != "" && io.path.exists(filmstripPath)) + preview.loadStrip(filmstripPath); + if (thumbnailPath != "" && io.path.exists(thumbnailPath)) + preview.loadThumbnail(thumbnailPath); } catch (...) { ; }