diff --git a/app/dialog/preferences/preferences.cpp b/app/dialog/preferences/preferences.cpp index 596e6bb84..dd6c955a7 100644 --- a/app/dialog/preferences/preferences.cpp +++ b/app/dialog/preferences/preferences.cpp @@ -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); diff --git a/app/dialog/preferences/tabs/preferencesaudiotab.cpp b/app/dialog/preferences/tabs/preferencesaudiotab.cpp index 19cad4815..399f0f4e1 100644 --- a/app/dialog/preferences/tabs/preferencesaudiotab.cpp +++ b/app/dialog/preferences/tabs/preferencesaudiotab.cpp @@ -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() diff --git a/app/dialog/preferences/tabs/preferencesaudiotab.h b/app/dialog/preferences/tabs/preferencesaudiotab.h index 20b62d39d..7c82a84ff 100644 --- a/app/dialog/preferences/tabs/preferencesaudiotab.h +++ b/app/dialog/preferences/tabs/preferencesaudiotab.h @@ -24,10 +24,12 @@ #include #include +#include #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(); diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp index 12f819ac9..db2adb294 100644 --- a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp +++ b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp @@ -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"), diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.h b/app/dialog/preferences/tabs/preferencesbehaviortab.h index d9fda9216..cc2fd4f55 100644 --- a/app/dialog/preferences/tabs/preferencesbehaviortab.h +++ b/app/dialog/preferences/tabs/preferencesbehaviortab.h @@ -24,6 +24,7 @@ #include #include +#include #include #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 &items); @@ -61,9 +65,9 @@ private: QMap config_map_; - QComboBox *graphics_backend_combobox_; - Category category_; + + QComboBox *graphics_backend_combobox_; }; } diff --git a/app/dialog/preferences/tabs/preferencesgeneraltab.cpp b/app/dialog/preferences/tabs/preferencesgeneraltab.cpp index 1a7f94dc7..df771fb47 100644 --- a/app/dialog/preferences/tabs/preferencesgeneraltab.cpp +++ b/app/dialog/preferences/tabs/preferencesgeneraltab.cpp @@ -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) diff --git a/app/dialog/preferences/tabs/preferencesgeneraltab.h b/app/dialog/preferences/tabs/preferencesgeneraltab.h index 841a36db0..89f6fdd50 100644 --- a/app/dialog/preferences/tabs/preferencesgeneraltab.h +++ b/app/dialog/preferences/tabs/preferencesgeneraltab.h @@ -57,6 +57,10 @@ private: IntegerSlider *autorecovery_interval_; IntegerSlider *autorecovery_maximum_; + + QCheckBox *hover_focus_; + QCheckBox *slider_ladder_; + QCheckBox *scroll_zooms_; }; } diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index f13df8bbd..1c42a327b 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -557,7 +557,9 @@ void PreviewAutoCacher::TryRender() copy, QtUtils::ValueToPtr(t->property("viewer")), t->property("time").value(), 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; } diff --git a/app/render/renderticket.cpp b/app/render/renderticket.cpp index 63faf4040..991ba0168 100644 --- a/app/render/renderticket.cpp +++ b/app/render/renderticket.cpp @@ -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); } } diff --git a/app/ts/zh_CN.ts b/app/ts/zh_CN.ts index 7768edd03..efa8b1fad 100644 --- a/app/ts/zh_CN.ts +++ b/app/ts/zh_CN.ts @@ -5919,7 +5919,12 @@ Do you wish to paste values onto the existing nodes or paste new nodes? 在查看器中打开 - + + Show in Parameter Editor + 在参数编辑器中显示 + + + P&roperties 属性(&R) @@ -6438,7 +6443,7 @@ This is equivalent to multiplying a video by a number between 0.0 and 1.0. Enable audio scrubbing - 启用音频清理 + 启用音频搓擦 @@ -6545,11 +6550,7 @@ This is equivalent to multiplying a video by a number between 0.0 and 1.0.Enable slider ladder 启用滑块阶梯 - - - Setting - - + Scrolling zooms by default @@ -6561,30 +6562,30 @@ This is equivalent to multiplying a video by a number between 0.0 and 1.0.默认情况下,滚动将移动视图,按住Ctrl/Cmd将使其变为缩放。启用此选项将切换这些视图,默认情况下滚动将缩放,而按住Ctrl/Cmd将移动视图。 - + Rendering - + 渲染 - + Graphics Backend - + 图形后端 - + 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. - + 选择 Oak 在下次启动时应请求的图形 API。Vulkan 是实验性的:在大多数系统上它会回退到 OpenGL,或使用尚未完全验证的原型 Vulkan 路径。 - - + + OpenGL - + OpenGL - + Vulkan (experimental) - + Vulkan(实验性) @@ -6611,24 +6612,40 @@ This is equivalent to multiplying a video by a number between 0.0 and 1.0. - Behavior - 行为 - - - - Disk - 磁盘 - - - Audio 音频 - - Keyboard - 键盘 + + Timeline + 时间线 + + + Playback + 回放 + + + + Project + 项目 + + + + Nodes + 节点 + + + + Rendering + 渲染 + + + + Disk + 磁盘 + + olive::PreferencesDiskTab diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 07d816c86..48281d378 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -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(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_); diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index b28e60326..8b4ec47cb 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -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();