UI: flatten preferences behavior tabs and add node parameter editor shortcut

- Move Behavior-General options (hover focus, slider ladder, scroll zooms)
  into the General preferences tab.
- Move Behavior-Audio option (audio scrubbing) into the Audio preferences tab.
- Promote remaining Behavior categories (Timeline, Playback, Project, Nodes,
  Rendering) to top-level sidebar entries without the 'Behavior - ' prefix.
- Update Chinese translations for the new sidebar titles and fill unfinished
  Behavior tab strings.

NodeView:
- Remove double-click jump-to-parameter-editor behavior; keep expand/collapse.
- Add right-click context menu item 'Show in Parameter Editor'.
- Add Shift+P shortcut bound to the same action.

Render:
- Fix crash in PreviewAutoCacher::ClearSingleFrameRenders when proxy playback
  causes a render ticket to finish synchronously before the watcher pointer is
  returned. Defer the watcher Finished signal via queued connection so the
  caller can safely register the watcher before it is deleted.
This commit is contained in:
2026-07-13 10:19:30 +08:00
parent f57ffa482f
commit 5c8f80480d
12 changed files with 168 additions and 85 deletions
+11 -15
View File
@@ -45,22 +45,18 @@ PreferencesDialog::PreferencesDialog(MainWindow *main_window, int start_tab)
AddTab(new PreferencesGeneralTab(), tr("General"));
AddTab(new PreferencesAppearanceTab(), tr("Appearance"));
AddTab(new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryGeneral),
tr("Behavior - General"));
AddTab(new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryAudio),
tr("Behavior - Audio"));
AddTab(new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryTimeline),
tr("Behavior - Timeline"));
AddTab(new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryPlayback),
tr("Behavior - Playback"));
AddTab(new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryProject),
tr("Behavior - Project"));
AddTab(new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryNodes),
tr("Behavior - Nodes"));
AddTab(new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryRendering),
tr("Behavior - Rendering"));
AddTab(new PreferencesDiskTab(), tr("Disk"));
AddTab(new PreferencesAudioTab(), tr("Audio"));
AddTab(new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryTimeline),
tr("Timeline"));
AddTab(new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryPlayback),
tr("Playback"));
AddTab(new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryProject),
tr("Project"));
AddTab(new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryNodes),
tr("Nodes"));
AddTab(new PreferencesBehaviorTab(PreferencesBehaviorTab::kCategoryRendering),
tr("Rendering"));
AddTab(new PreferencesDiskTab(), tr("Disk"));
AddTab(new PreferencesKeyboardTab(main_window), tr("Keyboard"));
SetCurrentTab(start_tab);
@@ -54,6 +54,11 @@ PreferencesAudioTab::PreferencesAudioTab()
audio_tab_layout->addLayout(main_layout);
}
audio_scrubbing_ = new QCheckBox(
PreferencesBehaviorTab::BehaviorPrefTr("Enable audio scrubbing"));
audio_scrubbing_->setChecked(OLIVE_CONFIG("AudioScrubbing").toBool());
audio_tab_layout->addWidget(audio_scrubbing_);
{
QGroupBox *groupbox = new QGroupBox();
audio_tab_layout->addWidget(groupbox);
@@ -232,6 +237,8 @@ void PreferencesAudioTab::Accept(MultiUndoCommand *command)
.to_string());
emit AudioManager::instance() -> OutputParamsChanged();
OLIVE_CONFIG("AudioScrubbing") = audio_scrubbing_->isChecked();
}
void PreferencesAudioTab::RefreshBackends()
@@ -24,10 +24,12 @@
#include <QComboBox>
#include <QPushButton>
#include <QCheckBox>
#include "dialog/configbase/configdialogbase.h"
#include "dialog/export/exportaudiotab.h"
#include "dialog/export/exportformatcombobox.h"
#include "preferencesbehaviortab.h"
namespace olive
{
@@ -70,6 +72,8 @@ private:
ExportAudioTab *record_options_;
QCheckBox *audio_scrubbing_;
private slots:
void RefreshBackends();
@@ -36,23 +36,6 @@ PreferencesBehaviorTab::PreferencesBehaviorTab(Category category)
layout->setAlignment(Qt::AlignTop);
switch (category_) {
case kCategoryGeneral:
AddItem(tr("Enable hover focus"), QStringLiteral("HoverFocus"),
tr("Panels will be considered focused when the mouse cursor is "
"over them without having to click them."));
AddItem(tr("Enable slider ladder"), QStringLiteral("UseSliderLadders"));
AddItem(
tr("Scrolling zooms by default"), QStringLiteral("ScrollZooms"),
tr("By default, scrolling will move the view around, and holding "
"Ctrl/Cmd will make it zoom instead. Enabling this will switch "
"those, scrolling will zoom by default, and holding Ctrl/Cmd will "
"move the view instead."));
break;
case kCategoryAudio:
AddItem(tr("Enable audio scrubbing"), QStringLiteral("AudioScrubbing"));
break;
case kCategoryTimeline:
AddItems({
{ tr("Auto-Seek to Imported Clips"),
@@ -24,6 +24,7 @@
#include <QCheckBox>
#include <QComboBox>
#include <QCoreApplication>
#include <QVBoxLayout>
#include "dialog/configbase/configdialogbase.h"
@@ -35,8 +36,6 @@ class PreferencesBehaviorTab : public ConfigDialogBaseTab {
Q_OBJECT
public:
enum Category {
kCategoryGeneral,
kCategoryAudio,
kCategoryTimeline,
kCategoryPlayback,
kCategoryProject,
@@ -48,11 +47,16 @@ public:
virtual void Accept(MultiUndoCommand *command) override;
static QString BehaviorPrefTr(const char *text)
{
return QCoreApplication::translate("olive::PreferencesBehaviorTab", text);
}
private:
struct Item {
QString text;
QString config_key;
QString tooltip;
QString tooltip = QString();
};
void AddItems(const QVector<Item> &items);
@@ -61,9 +65,9 @@ private:
QMap<QCheckBox *, QString> config_map_;
QComboBox *graphics_backend_combobox_;
Category category_;
QComboBox *graphics_backend_combobox_;
};
}
@@ -28,6 +28,7 @@
#include "common/autoscroll.h"
#include "core.h"
#include "preferencesbehaviortab.h"
namespace olive
{
@@ -167,6 +168,29 @@ PreferencesGeneralTab::PreferencesGeneralTab()
autorecovery_layout->addWidget(browse_autorecoveries, row, 1);
}
{
QGroupBox *behavior_groupbox = new QGroupBox(PreferencesBehaviorTab::BehaviorPrefTr("Behavior"));
QVBoxLayout *behavior_layout = new QVBoxLayout(behavior_groupbox);
layout->addWidget(behavior_groupbox);
hover_focus_ = new QCheckBox(PreferencesBehaviorTab::BehaviorPrefTr("Enable hover focus"));
hover_focus_->setToolTip(PreferencesBehaviorTab::BehaviorPrefTr(
"Panels will be considered focused when the mouse cursor is over them without having to click them."));
hover_focus_->setChecked(OLIVE_CONFIG("HoverFocus").toBool());
behavior_layout->addWidget(hover_focus_);
slider_ladder_ = new QCheckBox(PreferencesBehaviorTab::BehaviorPrefTr("Enable slider ladder"));
slider_ladder_->setChecked(OLIVE_CONFIG("UseSliderLadders").toBool());
behavior_layout->addWidget(slider_ladder_);
scroll_zooms_ = new QCheckBox(PreferencesBehaviorTab::BehaviorPrefTr("Scrolling zooms by default"));
scroll_zooms_->setToolTip(PreferencesBehaviorTab::BehaviorPrefTr(
"By default, scrolling will move the view around, and holding Ctrl/Cmd will make it zoom instead. "
"Enabling this will switch those, scrolling will zoom by default, and holding Ctrl/Cmd will move the view instead."));
scroll_zooms_->setChecked(OLIVE_CONFIG("ScrollZooms").toBool());
behavior_layout->addWidget(scroll_zooms_);
}
layout->addStretch();
}
@@ -201,6 +225,10 @@ void PreferencesGeneralTab::Accept(MultiUndoCommand *command)
QVariant::fromValue(autorecovery_maximum_->GetValue());
Core::instance()->SetAutorecoveryInterval(
autorecovery_interval_->GetValue());
OLIVE_CONFIG("HoverFocus") = hover_focus_->isChecked();
OLIVE_CONFIG("UseSliderLadders") = slider_ladder_->isChecked();
OLIVE_CONFIG("ScrollZooms") = scroll_zooms_->isChecked();
}
void PreferencesGeneralTab::AddLanguage(const QString &locale_name)
@@ -57,6 +57,10 @@ private:
IntegerSlider *autorecovery_interval_;
IntegerSlider *autorecovery_maximum_;
QCheckBox *hover_focus_;
QCheckBox *slider_ladder_;
QCheckBox *scroll_zooms_;
};
}
+9 -1
View File
@@ -557,7 +557,9 @@ void PreviewAutoCacher::TryRender()
copy, QtUtils::ValueToPtr<ViewerOutput>(t->property("viewer")),
t->property("time").value<rational>(), nullptr,
t->property("dry").toBool());
video_immediate_passthroughs_[watcher].append(t);
if (watcher) {
video_immediate_passthroughs_[watcher].append(t);
}
} else {
qWarning() << "Failed to find copied node for SFR ticket, requeueing";
single_frame_render_ = t;
@@ -709,6 +711,12 @@ RenderTicketWatcher *PreviewAutoCacher::RenderFrame(Node *node,
watcher->SetTicket(RenderManager::instance()->RenderFrame(rvp));
// If the ticket finished synchronously, VideoRendered has already deleted the
// watcher. The caller must not use this pointer in that case.
if (!running_video_tasks_.contains(watcher)) {
return nullptr;
}
return watcher;
}
+5 -3
View File
@@ -157,9 +157,11 @@ void RenderTicketWatcher::SetTicket(RenderTicketPtr ticket)
&RenderTicketWatcher::TicketFinished);
if (!ticket_->IsRunning(false) && ticket_->GetFinishCount(false) > 0) {
// Ticket has already finished before, so we emit a signal
locker.unlock();
TicketFinished();
// Ticket has already finished before, so we emit a signal asynchronously
// to avoid deleting this watcher before the caller has a chance to use
// the returned pointer.
QMetaObject::invokeMethod(this, &RenderTicketWatcher::TicketFinished,
Qt::QueuedConnection);
}
}
+48 -31
View File
@@ -5919,7 +5919,12 @@ Do you wish to paste values onto the existing nodes or paste new nodes?</source>
<translation></translation>
</message>
<message>
<location filename="../widget/nodeview/nodeview.cpp" line="865"/>
<location filename="../widget/nodeview/nodeview.cpp" line="863"/>
<source>Show in Parameter Editor</source>
<translation></translation>
</message>
<message>
<location filename="../widget/nodeview/nodeview.cpp" line="869"/>
<source>P&amp;roperties</source>
<translation>(&amp;R)</translation>
</message>
@@ -6438,7 +6443,7 @@ This is equivalent to multiplying a video by a number between 0.0 and 1.0.</sour
<message>
<location filename="../dialog/preferences/tabs/preferencesbehaviortab.cpp" line="57"/>
<source>Enable audio scrubbing</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../dialog/preferences/tabs/preferencesbehaviortab.cpp" line="60"/>
@@ -6545,11 +6550,7 @@ This is equivalent to multiplying a video by a number between 0.0 and 1.0.</sour
<source>Enable slider ladder</source>
<translation></translation>
</message>
<message>
<location filename="../dialog/preferences/tabs/preferencesbehaviortab.cpp" line="41"/>
<source>Setting</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../dialog/preferences/tabs/preferencesbehaviortab.cpp" line="51"/>
<source>Scrolling zooms by default</source>
@@ -6561,30 +6562,30 @@ This is equivalent to multiplying a video by a number between 0.0 and 1.0.</sour
<translation>Ctrl/Cmd将使其变为缩放Ctrl/Cmd将移动视图</translation>
</message>
<message>
<location filename="../dialog/preferences/tabs/preferencesbehaviortab.cpp" line="102"/>
<location filename="../dialog/preferences/tabs/preferencesbehaviortab.cpp" line="76"/>
<source>Rendering</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../dialog/preferences/tabs/preferencesbehaviortab.cpp" line="104"/>
<location filename="../dialog/preferences/tabs/preferencesbehaviortab.cpp" line="78"/>
<source>Graphics Backend</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../dialog/preferences/tabs/preferencesbehaviortab.cpp" line="106"/>
<location filename="../dialog/preferences/tabs/preferencesbehaviortab.cpp" line="80"/>
<source>Selects the graphics API Oak should request on next launch. Vulkan is experimental: on most systems it will fall back to OpenGL or use a prototype Vulkan path that is not yet fully validated.</source>
<translation type="unfinished"></translation>
<translation> Oak APIVulkan 退 OpenGL使 Vulkan </translation>
</message>
<message>
<location filename="../dialog/preferences/tabs/preferencesbehaviortab.cpp" line="111"/>
<location filename="../dialog/preferences/tabs/preferencesbehaviortab.cpp" line="125"/>
<location filename="../dialog/preferences/tabs/preferencesbehaviortab.cpp" line="85"/>
<location filename="../dialog/preferences/tabs/preferencesbehaviortab.cpp" line="99"/>
<source>OpenGL</source>
<translation></translation>
<translation>OpenGL</translation>
</message>
<message>
<location filename="../dialog/preferences/tabs/preferencesbehaviortab.cpp" line="112"/>
<location filename="../dialog/preferences/tabs/preferencesbehaviortab.cpp" line="86"/>
<source>Vulkan (experimental)</source>
<translation type="unfinished"></translation>
<translation>Vulkan</translation>
</message>
<message>
<location filename="../dialog/preferences/tabs/preferencesbehaviortab.cpp" line="126"/>
@@ -6611,24 +6612,40 @@ This is equivalent to multiplying a video by a number between 0.0 and 1.0.</sour
</message>
<message>
<location filename="../dialog/preferences/preferences.cpp" line="48"/>
<source>Behavior</source>
<translation></translation>
</message>
<message>
<location filename="../dialog/preferences/preferences.cpp" line="49"/>
<source>Disk</source>
<translation></translation>
</message>
<message>
<location filename="../dialog/preferences/preferences.cpp" line="50"/>
<source>Audio</source>
<translation></translation>
</message>
<message>
<location filename="../dialog/preferences/preferences.cpp" line="51"/>
<source>Keyboard</source>
<translation></translation>
<location filename="../dialog/preferences/preferences.cpp" line="49"/>
<source>Timeline</source>
<translation>线</translation>
</message>
<message>
<location filename="../dialog/preferences/preferences.cpp" line="50"/>
<source>Playback</source>
<translation></translation>
</message>
<message>
<location filename="../dialog/preferences/preferences.cpp" line="51"/>
<source>Project</source>
<translation></translation>
</message>
<message>
<location filename="../dialog/preferences/preferences.cpp" line="52"/>
<source>Nodes</source>
<translation></translation>
</message>
<message>
<location filename="../dialog/preferences/preferences.cpp" line="53"/>
<source>Rendering</source>
<translation></translation>
</message>
<message>
<location filename="../dialog/preferences/preferences.cpp" line="54"/>
<source>Disk</source>
<translation></translation>
</message>
</context>
<context>
<name>olive::PreferencesDiskTab</name>
+39 -13
View File
@@ -58,6 +58,7 @@ NodeView::NodeView(QWidget *parent)
, overlay_view_(nullptr)
, scale_(1.0)
, dont_emit_selection_signals_(false)
, show_in_param_editor_action_(nullptr)
{
setScene(&scene_);
SetDefaultDragMode(RubberBandDrag);
@@ -73,6 +74,12 @@ NodeView::NodeView(QWidget *parent)
SetFlowDirection(NodeViewCommon::kLeftToRight);
show_in_param_editor_action_ = new QAction(tr("Show in Parameter Editor"), this);
Menu::ConformItem(show_in_param_editor_action_, QStringLiteral("shownodeparams"), QKeySequence(tr("Shift+P")));
show_in_param_editor_action_->setShortcutContext(Qt::WindowShortcut);
addAction(show_in_param_editor_action_);
connect(show_in_param_editor_action_, &QAction::triggered, this, &NodeView::ShowSelectedNodeInParamEditor);
UpdateSceneBoundingRect();
connect(&scene_, &QGraphicsScene::changed, this,
&NodeView::UpdateSceneBoundingRect);
@@ -643,19 +650,6 @@ void NodeView::mouseDoubleClickEvent(QMouseEvent *event)
dynamic_cast<NodeViewItem *>(itemAt(event->pos()));
if (item_at_cursor) {
item_at_cursor->ToggleExpanded();
if (PanelManager::instance()) {
if (PanelWidget *panel = PanelManager::instance()
->GetPanelWithName(
QStringLiteral("ParamPanel"))) {
panel->show();
panel->raise();
panel->setFocus(Qt::OtherFocusReason);
}
}
// Scroll the parameter editor to this node
emit NodeSelectionChangedWithContexts(
{ { item_at_cursor->GetNode(), item_at_cursor->GetContext() } });
}
}
}
@@ -865,6 +859,14 @@ void NodeView::ShowContextMenu(const QPoint &pos)
m.addSeparator();
// Show in Parameter Editor
QAction *show_in_param_editor_action = m.addAction(
tr("Show in Parameter Editor"));
show_in_param_editor_action->setShortcut(
show_in_param_editor_action_->shortcut());
connect(show_in_param_editor_action, &QAction::triggered, this,
&NodeView::ShowSelectedNodeInParamEditor);
// Properties
QAction *properties_action = m.addAction(tr("P&roperties"));
connect(properties_action, &QAction::triggered, this,
@@ -1641,6 +1643,30 @@ void NodeView::ShowNodeProperties()
}
}
void NodeView::ShowSelectedNodeInParamEditor()
{
if (selected_nodes_.isEmpty()) {
return;
}
NodeViewItem *item = scene_.GetSelectedItems().first();
if (!item || !item->GetNode()) {
return;
}
if (PanelManager::instance()) {
if (PanelWidget *panel = PanelManager::instance()->GetPanelWithName(
QStringLiteral("ParamPanel"))) {
panel->show();
panel->raise();
panel->setFocus(Qt::OtherFocusReason);
}
}
emit NodeSelectionChangedWithContexts(
{ { item->GetNode(), item->GetContext() } });
}
void NodeView::LabelSelectedNodes()
{
Core::instance()->LabelNodes(selected_nodes_);
+4
View File
@@ -241,6 +241,8 @@ private:
bool dont_emit_selection_signals_;
QAction *show_in_param_editor_action_;
static const double kMinimumScale;
static const int kMaximumContexts;
@@ -287,6 +289,8 @@ private slots:
void ShowNodeProperties();
void ShowSelectedNodeInParamEditor();
void ItemAboutToBeDeleted(NodeViewItem *item);
void CloseOverlay();