From 2a4e5a5fa3a16b1ecad26a2ff1aa449602fae10e Mon Sep 17 00:00:00 2001 From: Alexandre Prokoudine Date: Sun, 5 Sep 2021 23:58:34 +0300 Subject: [PATCH 01/94] Update Russain translation --- app/ts/ru_RU.ts | 1002 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 735 insertions(+), 267 deletions(-) diff --git a/app/ts/ru_RU.ts b/app/ts/ru_RU.ts index 2e6123ff7..15ca0fcd0 100644 --- a/app/ts/ru_RU.ts +++ b/app/ts/ru_RU.ts @@ -108,7 +108,7 @@ NodeFactory - + None Нет @@ -321,7 +321,7 @@ 8-bit - 8 бит + 8 бит @@ -382,37 +382,42 @@ main - + Show this help text Показать этот справочный текст - + Show application version Показать версию приложения - + Start in full-screen mode Запуститься в полноэкранном режиме - + Export only (No GUI) Только эксперт (без графического интерфейса) - + Override language with file Запустить с локализацией из заданного файла - + qm-file файл qm - + + Launch with debug console + + + + Project to open on startup Какой проект открыть при запуске @@ -500,30 +505,15 @@ olive::Block - + Length Длительность - - Media In - Факт. начало - - - + Enabled Включено - - - Speed - Скорость - - - - Reverse - Развернуть - olive::BlurFilterNode @@ -581,20 +571,35 @@ olive::ClipBlock - + Clip Клип - + A time-based node that represents a media source. - + Buffer Буфер + + + Media In + Факт. начало + + + + Speed + Скорость + + + + Reverse + Развернуть + olive::ColorCoding @@ -735,7 +740,7 @@ Color management configuration for project. - + Настройка управления цветом в проекте. @@ -848,129 +853,150 @@ Выполняется импорт… - + Import footage... Импортировать видеоматериал... - + Failed to import footage Не удалось импортировать видеоматериал - + Failed to find active Project panel Не удалось найти активную панель проекта - + Failed to create new folder Не удалось создать папку - - + + Failed to find active project Не удалось найти активный проект - + New Folder Новая папка - + Failed to create new sequence Не удалось создать последовательность - + Possible image sequence detected Обнаружена возможная последовательность изображений - + The file '%1' looks like it might be part of an image sequence. Would you like to import it as such? - + You must specify a project file to export Необходимо указать проектный файл, который экспортировать - + Specified project does not exist Указанный проект не существует - + Failed to open startup file Не удалось открыть стартовый файл - + The project "%1" doesn't exist. A new project will be started instead. Проект "%1" не существует. Вместо него будет создан новый проект. - - + + Missing OpenTimelineIO Libraries Отсутствуют библиотеки OpenTimelineIO - - + + This build was compiled without OpenTimelineIO and therefore cannot open OpenTimelineIO files. Это сборка подготовлена без OpenTimelineIO, поэтому вы не сможете открыть файлы OpenTimelineIO. - - + + Error Ошибка - + This Sequence is empty. There is nothing to export. Эта последовательность пустая. В ней нет данных для экспорта. - + No valid sequence detected. Make sure a sequence is loaded and it has a connected Viewer node. - - + + + Revert + + + + + This project has not yet been saved, therefore there is no last saved state to revert to. + + + + + The project "%1" is already open. By re-opening it, the project will revert to its last saved state. Any unsaved changes will be lost. Do you wish to continue? + + + + + This will revert the project "%1" back to its last saved state. All unsaved changes will be lost. Do you wish to continue? + + + + + Auto-Recovery Error Ошибка при автовосстановлении - + Failed to save auto-recovery to "%1". Olive may not have permission to this directory. - + Olive Project Проект Olive - + OpenTimelineIO OpenTimelineIO - + The following projects had unsaved changes when Olive forcefully quit. Would you like to load them? В следующих проектах не были сохранены изменения, когда Olive упал. Хотите загрузить их? - + Found auto-recoveries but failed to load the auto-recovery index. Auto-recover projects will have to be opened manually. Your recoverable projects are still available at: %1 @@ -979,87 +1005,87 @@ Your recoverable projects are still available at: %1 Восстанавливаемые проекты по-прежнему доступны здесь: %1 - + The following project versions have been auto-saved: Следующие версии проектов были автоматически сохранены: - + Save Project As Сохранить проект как - + Load Project Загрузка проекта - + Label Node Метка ноды - + Set node label Указать метку ноды - + Sequence %1 Последовательность %1 - + Cannot open recent project Не удалось открыть недавний проект - + The project "%1" doesn't exist. Would you like to remove this file from the recent list? Проект"%1" не существует. Хотите удалить этот файл из списка недавно открывавшихся? - + Unsaved Changes Несохраненные изменения - + The project '%1' has unsaved changes. Would you like to save them? В проекте '%1' остались несохраненные изменения. Сохранить их? - + Save Сохранить - + Save All Сохранить все - + Don't Save Не сохранять - + Don't Save All Не сохранять все - + Failed to cache sequence Не удалось закэшировать последовательность - + No active viewer found with this sequence. Для этой последовательности не найден активный монитор. - + Open Project Открыть проект @@ -1756,22 +1782,22 @@ Your recoverable projects are still available at: %1 olive::ExportTask - + Exporting "%1" Экспортируется "%1" - + Failed to create encoder Не удалось создать кодировщик - + Failed to open file: %1 Не удалось открыть файл: %1 - + Failed to overwrite "%1". Export has been saved as "%2" instead. Не удалось перезаписать "%1". Экспорт сохранен под именем "%2". @@ -1894,8 +1920,8 @@ Your recoverable projects are still available at: %1 - - + + Failed to write interleaved packet @@ -1935,32 +1961,32 @@ Your recoverable projects are still available at: %1 - + Failed to allocate AVStream - + Failed to allocate AVCodecContext - + Failed to open encoder - + Failed to copy codec parameters to stream - + Failed to create resampling context - + Failed to create audio frame @@ -2011,7 +2037,7 @@ Your recoverable projects are still available at: %1 Organize several items into a single collection. - + Организовать объекты в единую коллекцию. @@ -2152,7 +2178,7 @@ Your recoverable projects are still available at: %1 Custom Frame Rate - + Заказная частота кадров @@ -2322,22 +2348,22 @@ Your recoverable projects are still available at: %1 olive::KeyframeViewBase - + Linear Линейный - + Bezier Безье - + Hold Константа - + P&roperties С&войства @@ -2345,17 +2371,17 @@ Your recoverable projects are still available at: %1 olive::LoadOTIOTask - + Failed to load OpenTimelineIO from file "%1" Не удалось загрузить OpenTimelineIO из файла "%1" - + Unknown OpenTimelineIO root element Неизвестный корневой элемент OpenTimelineIO - + Failed to load clip Не удалось загрузить клип @@ -2363,427 +2389,738 @@ Your recoverable projects are still available at: %1 olive::MainMenu - + + Ctrl+O + Ctrl+O + + + + Ctrl+S + Ctrl+S + + + + Ctrl+Shift+S + Ctrl+Shift+S + + + + F12 + F12 + + + + Ctrl+I + Ctrl+I + + + + Ctrl+M + Ctrl+M + + + + Ctrl+Z + Ctrl+Z + + + + Ctrl+Shift+Z + Ctrl+Shift+Z + + + + Backspace + Backspace + + + + Ctrl+A + Ctrl+A + + + + Ctrl+Shift+A + Ctrl+Shift+A + + + + , + , + + + + . + . + + + + Q + Q + + + + W + W + + + + Ctrl+Alt+Q + Ctrl+Alt+Q + + + + Ctrl+Alt+W + Ctrl+Alt+W + + + + Alt+Left + Alt+Left + + + + Alt+Right + Alt+Right + + + + [ + [ + + + + ] + ] + + + + ; + ; + + + + ' + + + + + M + М + + + + = + = + + + + - + - + + + + Ctrl+= + Ctrl+= + + + + Ctrl+- + Ctrl+- + + + + \ + +\ + + + + F11 + F11 + + + + Home + Home + + + + Left + Слева + + + + Space + Space + + + + Shift+Space + Shift+Space + + + + Right + Справа + + + + End + End + + + + Up + Up + + + + Down + Down + + + + Shift+I + Shift+I + + + + Shift+O + Shift+O + + + + J + J + + + + K + K + + + + L + Б + + + + ` + ` + + + + V + V + + + + X + X + + + + B + B + + + + N + N + + + + C + C + + + + Y + Y + + + + U + + + + + H + H + + + + Z + Z + + + + T + T + + + + A + A + + + + R + R + + + + S + S + + + + Ctrl+, + Ctrl+, + + + + / + / + + + &Save '%1' Со&хранить '%1' - + Save '%1' &As Сохранить '%1' к&ак - + Close '%1' Закрыть '%1' - + Close All Except '%1' Закрыть все кроме '%1' - + &Save Project &Сохранить проект - + Save Project &As Сохранить проект &как - + Close Project Закрыть проект - + Close All Except Current Project Закрыть все проекты кроме текущего - + (None) (нет) - + &File &Файл - + &New &Создать - + &Open Project &Открыть проект - + Open &Recent Открыть из &недавнего - + &Clear Recent List О&чистить список недавних - + Sa&ve All Projects Сохра&нить все проекты - + + Revert + + + + &Import... &Импортировать… - + &Export &Экспортировать - + &Media... &Медиаданные… - + Close All Projects Закрыть все проекты - + E&xit В&ыход - + &Edit &Правка - + Delete (alt) Удалить (alt) - + Insert Вставить - + Overwrite Переписать - + Select &All Выд&елить всё - + Deselect All Снять выделение - + Ripple to In Point Сдвиг до точки входа - + Ripple to Out Point Сдвиг до точки выхода - + Edit to In Point Правка до точки входа - + Edit to Out Point Правка до точки выхода - + Nudge Left Сдвинуть влево - + Nudge Right Сдвинуть вправо - + Move In Point to Playhead Точка входа к указателю воспроизведения - + Move Out Point to Playhead Точка выхода к указателю воспроизведения - + Delete In/Out Point Удалить точку входа/выхода - + Ripple Delete In/Out Point Удалить со сдвигом точку входа/выхода - + Set/Edit Marker Установить/Изменить маркер - + &View &Вид - + Zoom In Приблизить - + Zoom Out Отдалить - + Increase Track Height Увеличить высоту дорожки - + Decrease Track Height Уменьшить высоту дорожки - + Toggle Show All Показывать весь проект - + Full Screen Полноэкранный режим - + Full Screen Viewer Просмотр в полноэкранном режиме - + &Playback Вос&произведение - + Go to Start К началу - + Previous Frame К предыдущему кадру - + Play/Pause Воспроизведение/Пауза - + Play In to Out Проиграть от входа до выхода - + Next Frame К следующему кадру - + Go to End В конец - + Go to Previous Cut К предыдущему отрезу - + Go to Next Cut К следующему отрезу - + Go to In Point К точке входа - + Go to Out Point К точке выхода - + Shuttle Left Уменьшить скорость - + Shuttle Stop Пауза - + Shuttle Right Увеличить скорость - + Loop Петля - + &Sequence П&оследовательность - + Cache Entire Sequence Закэшировать всю последовательность - + Cache Sequence In/Out Закэшировать вход/выход последовательности - + Clear Disk Cache Очистить дисковый кэш - + &Window &Окно - + Maximize Panel Развернуть панель - + Lock Panels Закрепить панели - + Reset to Default Layout Вернуть исходный вид панелей - + &Tools &Инструменты - + Pointer Tool Указатель - + Edit Tool Выделение - + Ripple Tool Монтаж со сдвигом - + Rolling Tool Монтаж с совмещением - + Razor Tool Подрезка - + Slip Tool Прокрутка с совмещением - + Slide Tool Прокрутка - + Hand Tool Навигация - + Zoom Tool Масштаб - + Transition Tool Переход - + + Add Tool + Добавление + + + + Record Tool + Запись + + + Enable Snapping Включить прилипание - + Preferences Параметры - + &Help &Справка - + A&ction Search &Найти команду - + Send &Feedback... &Дать обратную связь… - + &About... &О программе… @@ -2970,6 +3307,91 @@ This driver is known to have stability and performance issues with Olive. It is olive::MenuShared + + + Ctrl+N + Ctrl+N + + + + Ctrl+Shift+N + Ctrl+Shift+N + + + + Ctrl+X + Ctrl+X + + + + Ctrl+C + Ctrl+C + + + + Ctrl+V + Ctrl+V + + + + Ctrl+Shift+V + Ctrl+Shift+V + + + + Ctrl+D + + + + + Del + Del + + + + Shift+Del + Shift+Del + + + + Ctrl+K + Ctrl+K + + + + Ctrl+R + Ctrl+R + + + + I + I + + + + O + O + + + + G + + + + + Ctrl+Shift+D + Ctrl+Shift+D + + + + Ctrl+L + Ctrl+L + + + + Shift+E + Shift+E + &Project @@ -3149,73 +3571,73 @@ This driver is known to have stability and performance issues with Olive. It is Apply a pixelated mosaic filter to video. - + Применить эффект пикселизованной мозаики olive::Node - + Input Вход - + Output Выход - + General Общие - + Distort Искажения - + Math Математика - + Color Цвет - + Filter Фильтр - + Timeline Монтажный стол - + Generator Генератор - + Channel Канал - + Transition Переход - + Project Проект - + Uncategorized Без категории @@ -3284,7 +3706,7 @@ This driver is known to have stability and performance issues with Olive. It is ... - + ... @@ -3311,12 +3733,12 @@ This driver is known to have stability and performance issues with Olive. It is Node Properties - + Свойства узла Name: - Название: + Название: @@ -3390,68 +3812,68 @@ This driver is known to have stability and performance issues with Olive. It is W - + W olive::NodeView - + Label Метка - + Open in Viewer - + Открыть в мониторе - + Smooth Edges Плавные края - + Filter Фильтр - + Show All Nodes Показывать все ноды - + Show Selected Показывать выбранное - + Direction Направление - + Top to Bottom Сверху вниз - + Bottom to Top Снизу вверх - + Left to Right Слева направо - + Right to Left Справа налево - + Add Добавить @@ -3465,11 +3887,6 @@ This driver is known to have stability and performance issues with Olive. It is - Mini-Map - Миникарта - - - Toggle Mini-Map Включить или выключить миникарту @@ -3478,17 +3895,17 @@ This driver is known to have stability and performance issues with Olive. It is olive::PanNode - + Pan Панорама Adjust the stereo panning of an audio source. - + Поменять стереопанорамирование источника звука - + Samples Сэмплы @@ -3586,7 +4003,7 @@ This driver is known to have stability and performance issues with Olive. It is Points - + Точек @@ -3597,7 +4014,7 @@ This driver is known to have stability and performance issues with Olive. It is olive::PreCacheTask - + Pre-caching %1:%2 Предкэширование %1:%2 @@ -4025,38 +4442,38 @@ This driver is known to have stability and performance issues with Olive. It is Вы действительно хотите сбросить все клавиатурные комбинации к исходным значениям? - + Import Keyboard Shortcuts Импортировать клавиатурные комбинации - - + + Error saving shortcuts Ошибка при сохранении клавиатурных комбинаций - + Failed to open file for reading Не удалось открыть файл для чтения - + Export Keyboard Shortcuts Экспортировать клавиатурные комбинации - + Export Shortcuts Экспортировать клавиатурные комбинации - + Shortcuts exported successfully Комбинации успешно экспортированы - + Failed to open file for writing Не удалось открыть файл для записи @@ -4152,7 +4569,7 @@ Are you sure you wish to delete this footage? For "%1" - + Для "%1" @@ -4233,17 +4650,17 @@ Are you sure you wish to delete this footage? olive::ProjectPanel - + Folder Папка - + Project Проект - + (none) (нет) @@ -4421,17 +4838,17 @@ Are you sure you wish to delete this footage? Exporting project to OpenTimelineIO - + Экспорт проекта в OpenTimelineIO Project contains no sequences to export. - + В проекте нет экспортируемых последовательностей. Failed to serialize sequence "%1" - + Не удалось сериализовать последовательность "%1" @@ -4490,7 +4907,7 @@ Are you sure you wish to delete this footage? Set As Default - + Использовать по умолчанию @@ -4662,6 +5079,52 @@ Are you sure you wish to delete this footage? Монитор последовательностей + + olive::ShapeNode + + + Shape + Фигура + + + + Generate a 2D primitive shape. + Нарисовать геометрический примитив. + + + + Type + Тип + + + + Rectangle + Прямоугольник + + + + Ellipse + Эллипс + + + + olive::ShapeNodeBase + + + Position + Позиция + + + + Size + Размер + + + + Color + Цвет + + olive::SliderBase @@ -4737,7 +5200,7 @@ Are you sure you wish to delete this footage? Rippling is a stub and will not do anything. Do you wish to continue? - + @@ -4789,17 +5252,17 @@ Are you sure you wish to delete this footage? olive::SubtitleBlock - + Subtitle Субтитры - + A time-based node representing a single subtitle element for a certain period of time. - + Text Текст @@ -4950,7 +5413,7 @@ Are you sure you wish to delete this footage? olive::TimelinePanel - + Timeline Монтажный стол @@ -4970,18 +5433,18 @@ Duration: %3 olive::TimelineWidget - - + + Properties Свойства - + Use Audio Time Units - + Show Waveforms Показывать волновую форму @@ -4997,67 +5460,67 @@ Duration: %3 olive::Toolbar - + Pointer Tool Указатель - + Edit Tool Выделение - + Ripple Tool Монтаж со сдвигом - + Rolling Tool Монтаж с совмещением - + Razor Tool Подрезка - + Slip Tool Прокрутка с совмещением - + Slide Tool Прокрутка - + Hand Tool Навигация - + Zoom Tool Масштаб - + Transition Tool Переход - + Record Tool Запись - + Add Tool Добавить - + Toggle Snapping Переключить прилипание @@ -5184,32 +5647,37 @@ Duration: %3 olive::TransitionBlock - + From От - + To До - + Curve Кривая - + + Center Offset + + + + Linear Линейный - + Exponential Экспоненциальный - + Logarithmic Логарифмический From b02bc4c31d8e10e0ef3edf42d69d566d900d6a58 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:34:05 -0800 Subject: [PATCH 02/94] cmake: always define appversion macros --- app/CMakeLists.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 5d63cfa75..85ec313a6 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -66,9 +66,7 @@ add_library(olive-version-obj version.h ) target_link_libraries(olive-version-obj PRIVATE Qt5::Core) -if(DEFINED GIT_HASH) - target_compile_options(olive-version-obj PRIVATE -DAPPVERSION="${PROJECT_VERSION}" -DAPPVERSIONLONG="${PROJECT_LONG_VERSION}" ) -endif() +target_compile_options(olive-version-obj PRIVATE -DAPPVERSION="${PROJECT_VERSION}" -DAPPVERSIONLONG="${PROJECT_LONG_VERSION}" ) # Add main library add_library(libolive-editor From 2f5afb63b772b82cf9c40ba82c610aec15b71d55 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Wed, 10 Nov 2021 15:59:01 +0000 Subject: [PATCH 03/94] viewer: force screen to stay awake during playback --- app/widget/viewer/viewer.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 8b115682c..fc9c4bb1b 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -30,6 +30,10 @@ #include #include +#ifdef _WIN32 +#include "winbase.h" +#endif + #include "audio/audiomanager.h" #include "common/clamp.h" #include "common/power.h" @@ -702,6 +706,10 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) QueueNextAudioBuffer(); } } + // Force screen to stay awake +#ifdef _WIN32 + SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_CONTINUOUS); +#endif } void ViewerWidget::PauseInternal() @@ -742,6 +750,11 @@ void ViewerWidget::PauseInternal() prequeuing_video_ = false; prequeuing_audio_ = 0; + + // Reset screen timeout timer +#ifdef _WIN32 + SetThreadExecutionState(ES_CONTINUOUS); +#endif } void ViewerWidget::PushScrubbedAudio() From 9c650883c28e9f4e4044e2d15cf3d02649216346 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 10 Nov 2021 16:33:59 -0800 Subject: [PATCH 04/94] implement context item in node view --- app/node/CMakeLists.txt | 2 + app/node/group.cpp | 30 +++++++ app/node/group.h | 43 +++++++++ app/node/output/track/track.h | 45 ++++++++-- app/widget/nodeview/CMakeLists.txt | 2 + app/widget/nodeview/nodeview.cpp | 43 ++++++++- app/widget/nodeview/nodeview.h | 5 ++ app/widget/nodeview/nodeviewcontext.cpp | 114 ++++++++++++++++++++++++ app/widget/nodeview/nodeviewcontext.h | 39 ++++++++ app/widget/nodeview/nodeviewitem.cpp | 4 + app/widget/nodeview/nodeviewscene.cpp | 25 ++++++ app/widget/nodeview/nodeviewscene.h | 6 ++ 12 files changed, 347 insertions(+), 11 deletions(-) create mode 100644 app/node/group.cpp create mode 100644 app/node/group.h create mode 100644 app/widget/nodeview/nodeviewcontext.cpp create mode 100644 app/widget/nodeview/nodeviewcontext.h diff --git a/app/node/CMakeLists.txt b/app/node/CMakeLists.txt index d10d5746f..6cd34ca5b 100644 --- a/app/node/CMakeLists.txt +++ b/app/node/CMakeLists.txt @@ -34,6 +34,8 @@ set(OLIVE_SOURCES node/globals.h node/graph.cpp node/graph.h + node/group.cpp + node/group.h node/hashtraverser.cpp node/hashtraverser.h node/inputdragger.cpp diff --git a/app/node/group.cpp b/app/node/group.cpp new file mode 100644 index 000000000..7b7ab0c87 --- /dev/null +++ b/app/node/group.cpp @@ -0,0 +1,30 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "group.h" + +namespace olive { + +NodeGroup::NodeGroup() +{ + +} + +} diff --git a/app/node/group.h b/app/node/group.h new file mode 100644 index 000000000..2f15bbb71 --- /dev/null +++ b/app/node/group.h @@ -0,0 +1,43 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef NODEGROUP_H +#define NODEGROUP_H + +#include "node.h" + +namespace olive { + +class NodeGroup : public Node +{ + Q_OBJECT +public: + NodeGroup(); + + void SetNodes(Node *nodes); + +private: + QVector nodes_; + +}; + +} + +#endif // NODEGROUP_H diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 97bb1e7b6..d6179eaf4 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -168,17 +168,46 @@ public: QString ToString() const { - QString type_string; - - if (type_ == Track::kVideo) { - type_string = QStringLiteral("v"); - } else if (type_ == Track::kAudio) { - type_string = QStringLiteral("a"); - } else { + QString type_string = TypeToString(type_); + if (type_string.isEmpty()) { return QString(); + } else { + return QStringLiteral("%1:%2").arg(type_string, QString::number(index_)); + } + } + + /// For IDs that shouldn't change between localizations + static QString TypeToString(Type type) + { + switch (type) { + case kVideo: + return QStringLiteral("v"); + case kAudio: + return QStringLiteral("a"); + case kSubtitle: + return QStringLiteral("s"); + case kCount: + break; } - return QStringLiteral("%1:%2").arg(type_string, QString::number(index_)); + return QString(); + } + + /// For human-facing strings + static QString TypeToTranslatedString(Type type) + { + switch (type) { + case kVideo: + return tr("V"); + case kAudio: + return tr("A"); + case kSubtitle: + return tr("S"); + case kCount: + break; + } + + return QString(); } static Type TypeFromString(const QString& s) diff --git a/app/widget/nodeview/CMakeLists.txt b/app/widget/nodeview/CMakeLists.txt index e73798ae5..e7f94c762 100644 --- a/app/widget/nodeview/CMakeLists.txt +++ b/app/widget/nodeview/CMakeLists.txt @@ -19,6 +19,8 @@ set(OLIVE_SOURCES widget/nodeview/nodeview.cpp widget/nodeview/nodeview.h widget/nodeview/nodeviewcommon.h + widget/nodeview/nodeviewcontext.cpp + widget/nodeview/nodeviewcontext.h widget/nodeview/nodeviewedge.cpp widget/nodeview/nodeviewedge.h widget/nodeview/nodeviewitem.cpp diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index a375ede4f..b694992b3 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -29,6 +29,7 @@ #include "node/audio/volume/volume.h" #include "node/distort/transform/transformdistortnode.h" #include "node/factory.h" +#include "node/group.h" #include "node/traverser.h" #include "widget/menu/menushared.h" #include "widget/timebased/timebasedview.h" @@ -62,7 +63,7 @@ NodeView::NodeView(QWidget *parent) : ConnectSelectionChangedSignal(); - SetFlowDirection(NodeViewCommon::kTopToBottom); + SetFlowDirection(NodeViewCommon::kLeftToRight); UpdateSceneBoundingRect(); connect(&scene_, &QGraphicsScene::changed, this, &NodeView::UpdateSceneBoundingRect); @@ -85,7 +86,23 @@ NodeView::~NodeView() void NodeView::SetGraph(NodeGraph *graph, const QVector &nodes) { - bool graph_changed = graph_ != graph; + // Remove contexts that are no longer in the list + foreach (Node *n, filter_nodes_) { + if (!nodes.contains(n)) { + scene_.RemoveContext(n); + } + } + + // Add contexts that are now in the list + foreach (Node *n, nodes) { + if (!filter_nodes_.contains(n)) { + scene_.AddContext(n); + } + } + + filter_nodes_ = nodes; + + /*bool graph_changed = graph_ != graph; bool context_changed = last_set_filter_nodes_ != nodes; if (graph_changed || context_changed) { @@ -144,7 +161,7 @@ void NodeView::SetGraph(NodeGraph *graph, const QVector &nodes) // Center on something QMetaObject::invokeMethod(this, &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); } - } + }*/ } void NodeView::ClearGraph() @@ -850,6 +867,15 @@ void NodeView::ShowContextMenu(const QPoint &pos) Core::instance()->LabelNodes(scene_.GetSelectedNodes()); }); + // Grouping + if (selected.size() == 1 && dynamic_cast(selected.first()->GetNode())) { + QAction *ungroup_action = m.addAction(tr("Ungroup")); + connect(ungroup_action, &QAction::triggered, this, &NodeView::UngroupNodes); + } else { + QAction *group_action = m.addAction(tr("Group")); + connect(group_action, &QAction::triggered, this, &NodeView::GroupNodes); + } + // Color menu MenuShared::instance()->AddColorCodingMenu(&m); @@ -1603,6 +1629,17 @@ void NodeView::RepositionContexts() } } +void NodeView::GroupNodes() +{ + /*NodeGroup *group = new NodeGroup(); + selected_nodes_*/ +} + +void NodeView::UngroupNodes() +{ + static_cast(selected_nodes_.first()); +} + NodeViewItem *NodeView::UpdateNodeItem(Node *node, bool ignore_own_context) { // Get UI item or create if it doesn't exist diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index db03afc08..941120361 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -27,6 +27,7 @@ #include "node/graph.h" #include "node/nodecopypaste.h" #include "nodeviewedge.h" +#include "nodeviewcontext.h" #include "nodeviewminimap.h" #include "nodeviewscene.h" #include "widget/handmovableview/handmovableview.h" @@ -303,6 +304,10 @@ private slots: void RepositionContexts(); + void GroupNodes(); + + void UngroupNodes(); + }; } diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp new file mode 100644 index 000000000..ad1bdc6ac --- /dev/null +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -0,0 +1,114 @@ +#include "nodeviewcontext.h" + +#include +#include +#include +#include +#include + +#include "node/block/block.h" +#include "node/output/track/track.h" +#include "nodeviewitem.h" +#include "ui/colorcoding.h" + +namespace olive { + +#define super QGraphicsRectItem + +NodeViewContext::NodeViewContext(QGraphicsItem *item) : + super(item) +{ + setFlag(ItemIsMovable); + setFlag(ItemIsSelectable); + + // Set default label text + SetContext(nullptr); +} + +void NodeViewContext::SetContext(Node *node) +{ + context_ = node; + + if (context_) { + if (Block *block = dynamic_cast(node)) { + lbl_ = QCoreApplication::translate("NodeViewContext", + "%1 [%2] :: %3 - %4").arg(block->GetLabelAndName(), + Track::Reference::TypeToTranslatedString(block->track()->type()), + block->in().toString(), + block->out().toString()); + } else { + lbl_ = node->GetLabelAndName(); + } + + Color c = node->color(); + setPen(QPen(c.toQColor(), 2)); + + c.set_alpha(0.5f); + setBrush(c.toQColor()); + } else { + lbl_ = QCoreApplication::translate("NodeViewContext", "(None)"); + } +} + +void NodeViewContext::AddChild(Node *node) +{ + NodeViewItem *item = new NodeViewItem(this); + item->SetNode(node); +} + +qreal GetTextOffset(const QFontMetricsF &fm) +{ + return fm.height()/2; +} + +void NodeViewContext::UpdateRect() +{ + QFont f; + QFontMetricsF fm(f); + qreal lbl_offset = GetTextOffset(fm); + + QRectF rect = childrenBoundingRect(); + int pad = NodeViewItem::DefaultItemHeight(); + rect.adjust(-pad, - lbl_offset*2 - fm.height() - pad, pad, pad); + setRect(rect); +} + +void NodeViewContext::SetFlowDirection(NodeViewCommon::FlowDirection dir) +{ + auto children = childItems(); + foreach (auto child, children) { + if (NodeViewItem *item = dynamic_cast(child)) { + item->SetFlowDirection(dir); + } + } +} + +void NodeViewContext::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + QPen p = pen(); + + if (option->state & QStyle::State_Selected) { + p.setStyle(Qt::DotLine); + } + + painter->setPen(p); + painter->setBrush(brush()); + + int rounded = painter->fontMetrics().height(); + painter->drawRoundedRect(rect(), rounded, rounded); + + painter->setPen(context_ ? ColorCoding::GetUISelectorColor(context_->color()) : Qt::white); + + int offset = GetTextOffset(painter->fontMetrics()); + + QRectF text_rect = rect(); + text_rect.adjust(offset, offset, -offset, -offset); + painter->drawText(text_rect, lbl_); +} + +QVariant NodeViewContext::itemChange(GraphicsItemChange change, const QVariant &value) +{ + return super::itemChange(change, value); +} + +} diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h new file mode 100644 index 000000000..4e1a8a866 --- /dev/null +++ b/app/widget/nodeview/nodeviewcontext.h @@ -0,0 +1,39 @@ +#ifndef NODEVIEWCONTEXT_H +#define NODEVIEWCONTEXT_H + +#include +#include + +#include "node/node.h" +#include "nodeviewcommon.h" + +namespace olive { + +class NodeViewContext : public QGraphicsRectItem +{ +public: + NodeViewContext(QGraphicsItem *item = nullptr); + + void SetContext(Node *node); + + void AddChild(Node *node); + + void UpdateRect(); + + void SetFlowDirection(NodeViewCommon::FlowDirection dir); + + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; + +protected: + virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; + +private: + Node *context_; + + QString lbl_; + +}; + +} + +#endif // NODEVIEWCONTEXT_H diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 73e2d8019..fa362af72 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -406,6 +406,10 @@ QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, cons { if (change == ItemPositionHasChanged && node_) { ReadjustAllEdges(); + + if (NodeViewContext *ctx = dynamic_cast(parentItem())) { + ctx->UpdateRect(); + } } return QGraphicsItem::itemChange(change, value); diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index 878fd52a4..56de3d250 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -194,6 +194,31 @@ void NodeViewScene::RemoveEdge(Node *output, const NodeInput &input) } } +NodeViewContext *NodeViewScene::AddContext(Node *node) +{ + NodeViewContext *context_item = context_map_.value(node); + + if (!context_item) { + context_item = new NodeViewContext(); + context_item->SetContext(node); + context_item->setPos(0, 0); + addItem(context_item); + + const NodeGraph::PositionMap &map = node->parent()->GetNodesForContext(node); + for (auto it=map.cbegin(); it!=map.cend(); it++) { + context_item->AddChild(it.key()); + } + context_item->UpdateRect(); + } + + return context_item; +} + +void NodeViewScene::RemoveContext(Node *node) +{ + delete context_map_.take(node); +} + int NodeViewScene::DetermineWeight(Node *n) { QVector inputs = n->GetImmediateDependencies(); diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index 29cf5bbb7..4f6357dee 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -25,6 +25,7 @@ #include #include "node/graph.h" +#include "nodeviewcontext.h" #include "nodeviewedge.h" #include "nodeviewitem.h" @@ -99,6 +100,9 @@ public slots: NodeViewEdge *AddEdge(Node *output, const NodeInput& input); void RemoveEdge(Node *output, const NodeInput& input); + NodeViewContext *AddContext(Node *node); + void RemoveContext(Node *node); + /** * @brief Set whether edges in this scene should be curved or not */ @@ -113,6 +117,8 @@ private: void DisconnectNode(Node *n); + QHash context_map_; + QHash item_map_; QVector edges_; From 603303904627cfd98b79e4aca9d557f1d6791b5e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 10 Nov 2021 21:59:54 -0800 Subject: [PATCH 05/94] improved connector UI in node view --- app/node/output/track/track.cpp | 3 +- app/node/output/track/track.h | 15 +- app/node/output/track/tracklist.cpp | 2 + app/widget/nodeview/CMakeLists.txt | 2 + app/widget/nodeview/nodeview.cpp | 5 +- app/widget/nodeview/nodeviewcontext.cpp | 80 +++++++++- app/widget/nodeview/nodeviewcontext.h | 9 ++ app/widget/nodeview/nodeviewedge.cpp | 8 +- app/widget/nodeview/nodeviewitem.cpp | 140 ++++++++++++------ app/widget/nodeview/nodeviewitem.h | 22 ++- app/widget/nodeview/nodeviewitemconnector.cpp | 81 ++++++++++ app/widget/nodeview/nodeviewitemconnector.h | 40 +++++ app/widget/nodeview/nodeviewscene.cpp | 61 ++------ app/widget/nodeview/nodeviewscene.h | 26 ---- app/widget/timelinewidget/tool/import.cpp | 6 +- 15 files changed, 352 insertions(+), 148 deletions(-) create mode 100644 app/widget/nodeview/nodeviewitemconnector.cpp create mode 100644 app/widget/nodeview/nodeviewitemconnector.h diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 41eda30f2..e1cb208d6 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -41,7 +41,8 @@ const QString Track::kMutedInput = QStringLiteral("muted_in"); Track::Track() : track_type_(Track::kNone), index_(-1), - locked_(false) + locked_(false), + sequence_(nullptr) { AddInput(kBlockInput, NodeValue::kNone, InputFlags(kInputFlagArray | kInputFlagNotKeyframable)); diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index d6179eaf4..2e9b44ab3 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -26,6 +26,8 @@ namespace olive { +class Sequence; + /** * @brief A time traversal Node for sorting through one channel/track of Blocks */ @@ -388,9 +390,18 @@ public: bool IsLocked() const; - int GetArrayIndexFromBlock(Block* block) const; + Sequence *sequence() const + { + return sequence_; + } + + void set_sequence(Sequence *sequence) + { + sequence_ = sequence; + } + static const double kTrackHeightDefault; static const double kTrackHeightMinimum; static const double kTrackHeightInterval; @@ -472,6 +483,8 @@ private: bool locked_; + Sequence *sequence_; + private slots: void BlockLengthChanged(); diff --git a/app/node/output/track/tracklist.cpp b/app/node/output/track/tracklist.cpp index c9f35e26f..f5d233515 100644 --- a/app/node/output/track/tracklist.cpp +++ b/app/node/output/track/tracklist.cpp @@ -83,6 +83,7 @@ void TrackList::TrackConnected(Node *node, int element) connect(track, &Track::TrackLengthChanged, this, &TrackList::UpdateTotalLength); track->set_type(type_); + track->set_sequence(parent()); emit TrackListChanged(); @@ -121,6 +122,7 @@ void TrackList::TrackDisconnected(Node *node, int element) track->SetIndex(-1); track->set_type(Track::kNone); + track->set_sequence(nullptr); disconnect(track, &Track::TrackLengthChanged, this, &TrackList::UpdateTotalLength); diff --git a/app/widget/nodeview/CMakeLists.txt b/app/widget/nodeview/CMakeLists.txt index e7f94c762..12512695f 100644 --- a/app/widget/nodeview/CMakeLists.txt +++ b/app/widget/nodeview/CMakeLists.txt @@ -25,6 +25,8 @@ set(OLIVE_SOURCES widget/nodeview/nodeviewedge.h widget/nodeview/nodeviewitem.cpp widget/nodeview/nodeviewitem.h + widget/nodeview/nodeviewitemconnector.cpp + widget/nodeview/nodeviewitemconnector.h widget/nodeview/nodeviewminimap.cpp widget/nodeview/nodeviewminimap.h widget/nodeview/nodeviewscene.cpp diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index b694992b3..86b1c5711 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -847,7 +847,7 @@ void NodeView::UpdateSelectionCache() void NodeView::ShowContextMenu(const QPoint &pos) { - if (!graph_) { + if (filter_nodes_.isEmpty()) { return; } @@ -1023,7 +1023,6 @@ void NodeView::RemoveNode(Node *node) scene_.RemoveEdge(it->second, it->first); } positions_.remove(scene_.item_map().value(node)); - scene_.RemoveNode(node); } void NodeView::AddEdge(Node *output, const NodeInput &input) @@ -1645,8 +1644,6 @@ NodeViewItem *NodeView::UpdateNodeItem(Node *node, bool ignore_own_context) // Get UI item or create if it doesn't exist NodeViewItem *item = scene_.item_map().value(node); if (!item) { - item = scene_.AddNode(node); - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { if (scene_.item_map().contains(it->second)) { scene_.AddEdge(it->second, it->first); diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index ad1bdc6ac..0e8262b3e 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -6,8 +6,11 @@ #include #include +#include "core.h" #include "node/block/block.h" +#include "node/graph.h" #include "node/output/track/track.h" +#include "node/project/sequence/sequence.h" #include "nodeviewitem.h" #include "ui/colorcoding.h" @@ -31,11 +34,12 @@ void NodeViewContext::SetContext(Node *node) if (context_) { if (Block *block = dynamic_cast(node)) { + rational timebase = block->track()->sequence()->GetVideoParams().frame_rate_as_time_base(); lbl_ = QCoreApplication::translate("NodeViewContext", - "%1 [%2] :: %3 - %4").arg(block->GetLabelAndName(), - Track::Reference::TypeToTranslatedString(block->track()->type()), - block->in().toString(), - block->out().toString()); + "%1 [%2] :: %3 - %4").arg(block->GetLabelAndName(), + Track::Reference::TypeToTranslatedString(block->track()->type()), + Timecode::time_to_timecode(block->in(), timebase, Core::instance()->GetTimecodeDisplay()), + Timecode::time_to_timecode(block->out(), timebase, Core::instance()->GetTimecodeDisplay())); } else { lbl_ = node->GetLabelAndName(); } @@ -52,8 +56,38 @@ void NodeViewContext::SetContext(Node *node) void NodeViewContext::AddChild(Node *node) { + if (!context_) { + return; + } + NodeViewItem *item = new NodeViewItem(this); item->SetNode(node); + item->SetNodePosition(context_->parent()->GetNodesForContext(context_).value(node)); + item->SetFlowDirection(flow_dir_); + + if (node == context_) { + item->SetLabelAsOutput(true); + } + + for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) { + foreach (auto child, childItems()) { + if (NodeViewItem *other_item = dynamic_cast(child)) { + if (other_item->GetNode() == it->second.node()) { + AddEdgeInternal(node, it->second, item, other_item); + } + } + } + } + + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + foreach (auto child, childItems()) { + if (NodeViewItem *other_item = dynamic_cast(child)) { + if (it->second == other_item->GetNode()) { + AddEdgeInternal(it->second, it->first, other_item, item); + } + } + } + } } qreal GetTextOffset(const QFontMetricsF &fm) @@ -75,12 +109,31 @@ void NodeViewContext::UpdateRect() void NodeViewContext::SetFlowDirection(NodeViewCommon::FlowDirection dir) { - auto children = childItems(); - foreach (auto child, children) { + flow_dir_ = dir; + + foreach (auto child, childItems()) { if (NodeViewItem *item = dynamic_cast(child)) { item->SetFlowDirection(dir); } } + + foreach (auto child, childItems()) { + if (NodeViewEdge *edge = dynamic_cast(child)) { + edge->SetFlowDirection(dir); + } + } +} + +void NodeViewContext::SetCurvedEdges(bool e) +{ + curved_edges_ = e; + + const QList &children = childItems(); + foreach (auto child, children) { + if (NodeViewEdge *edge = dynamic_cast(child)) { + edge->SetCurved(e); + } + } } void NodeViewContext::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) @@ -97,7 +150,7 @@ void NodeViewContext::paint(QPainter *painter, const QStyleOptionGraphicsItem *o int rounded = painter->fontMetrics().height(); painter->drawRoundedRect(rect(), rounded, rounded); - painter->setPen(context_ ? ColorCoding::GetUISelectorColor(context_->color()) : Qt::white); + painter->setPen(widget->palette().text().color()); int offset = GetTextOffset(painter->fontMetrics()); @@ -111,4 +164,17 @@ QVariant NodeViewContext::itemChange(GraphicsItemChange change, const QVariant & return super::itemChange(change, value); } +NodeViewEdge* NodeViewContext::AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to) +{ + NodeViewEdge* edge_ui = new NodeViewEdge(output, input, from, to, this); + + edge_ui->SetFlowDirection(flow_dir_); + edge_ui->SetCurved(curved_edges_); + + from->AddEdge(edge_ui); + to->AddEdge(edge_ui); + + return edge_ui; +} + } diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index 4e1a8a866..a5141e32b 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -6,6 +6,7 @@ #include "node/node.h" #include "nodeviewcommon.h" +#include "nodeviewedge.h" namespace olive { @@ -22,16 +23,24 @@ public: void SetFlowDirection(NodeViewCommon::FlowDirection dir); + void SetCurvedEdges(bool e); + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; protected: virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; private: + NodeViewEdge *AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to); + Node *context_; QString lbl_; + NodeViewCommon::FlowDirection flow_dir_; + + bool curved_edges_; + }; } diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index 42dd19e06..5498fbefb 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -128,9 +128,11 @@ void NodeViewEdge::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti painter->drawPath(path()); // Draw arrow - painter->setPen(Qt::NoPen); - painter->setBrush(edge_color); - painter->drawPolygon(arrow_); + if (!connected_) { + painter->setPen(Qt::NoPen); + painter->setBrush(edge_color); + painter->drawPolygon(arrow_); + } } void NodeViewEdge::Init() diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index fa362af72..2eb766863 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -46,7 +46,8 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) : hide_titlebar_(false), highlighted_index_(-1), flow_dir_(NodeViewCommon::kLeftToRight), - prevent_removing_(false) + prevent_removing_(false), + label_as_output_(false) { // Set flags for this widget setFlag(QGraphicsItem::ItemIsMovable); @@ -66,7 +67,8 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) : title_bar_rect_ = QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height); setRect(title_bar_rect_); - output_triangle_.resize(3); + input_connector_ = new NodeViewItemConnector(this); + output_connector_ = new NodeViewItemConnector(this); } QPointF NodeViewItem::GetNodePosition() const @@ -208,6 +210,11 @@ int NodeViewItem::GetIndexAt(QPointF pt) const void NodeViewItem::SetNode(Node *n) { + if (node_) { + disconnect(n, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); + disconnect(n, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); + } + node_ = n; node_inputs_.clear(); @@ -220,6 +227,11 @@ void NodeViewItem::SetNode(Node *n) node_inputs_.append(input); } } + + input_connector_->setVisible(!node_inputs_.isEmpty()); + + connect(n, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); + connect(n, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); } update(); @@ -234,6 +246,7 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) expanded_ = e; hide_titlebar_ = hide_titlebar; + input_connector_->setVisible(!expanded_); if (expanded_ && !node_inputs_.isEmpty()) { // Create new rect @@ -252,7 +265,11 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) update(); + UpdateConnectorPositions(); + ReadjustAllEdges(); + + UpdateContextRect(); } void NodeViewItem::ToggleExpanded() @@ -298,8 +315,14 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti painter->setPen(app_pal.color(QPalette::Text)); - QString node_label = node_->GetLabel(); - QString node_shortname = node_->ShortName(); + QString node_label, node_shortname; + + if (label_as_output_) { + node_shortname = QCoreApplication::translate("NodeViewItem", "Output"); + } else { + node_label = node_->GetLabel(); + node_shortname = node_->ShortName(); + } int icon_size = painter->fontMetrics().height()/2; @@ -335,41 +358,6 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti painter->setBrush(Qt::NoBrush); painter->drawRect(rect()); - - // Draw output triangle - painter->setPen(Qt::NoPen); - painter->setBrush(app_pal.color(QPalette::Text)); - int triangle_sz = title_bar_rect_.height() / 2; - int triangle_sz_half = triangle_sz / 2; - - switch (flow_dir_) { - case NodeViewCommon::kLeftToRight: - // Triangle pointing right - output_triangle_[0] = QPointF(rect().right(), rect().center().y() - triangle_sz_half); - output_triangle_[1] = QPointF(rect().right() + triangle_sz_half, rect().center().y()); - output_triangle_[2] = QPointF(rect().right(), rect().center().y() + triangle_sz_half); - break; - case NodeViewCommon::kTopToBottom: - // Triangle pointing down - output_triangle_[0] = QPointF(rect().center().x() - triangle_sz_half, rect().bottom()); - output_triangle_[1] = QPointF(rect().center().x(), rect().bottom() + triangle_sz_half); - output_triangle_[2] = QPointF(rect().center().x() + triangle_sz_half, rect().bottom()); - break; - case NodeViewCommon::kBottomToTop: - // Triangle pointing up - output_triangle_[0] = QPointF(rect().center().x() - triangle_sz_half, rect().top()); - output_triangle_[1] = QPointF(rect().center().x(), rect().top() - triangle_sz_half); - output_triangle_[2] = QPointF(rect().center().x() + triangle_sz_half, rect().top()); - break; - case NodeViewCommon::kRightToLeft: - // Triangle pointing left - output_triangle_[0] = QPointF(rect().left(), rect().center().y() - triangle_sz_half); - output_triangle_[1] = QPointF(rect().left() - triangle_sz_half, rect().center().y()); - output_triangle_[2] = QPointF(rect().left(), rect().center().y() + triangle_sz_half); - break; - } - - painter->drawPolygon(output_triangle_); } void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) @@ -407,9 +395,7 @@ QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, cons if (change == ItemPositionHasChanged && node_) { ReadjustAllEdges(); - if (NodeViewContext *ctx = dynamic_cast(parentItem())) { - ctx->UpdateRect(); - } + UpdateContextRect(); } return QGraphicsItem::itemChange(change, value); @@ -422,6 +408,13 @@ void NodeViewItem::ReadjustAllEdges() } } +void NodeViewItem::UpdateContextRect() +{ + if (NodeViewContext *ctx = dynamic_cast(parentItem())) { + ctx->UpdateRect(); + } +} + void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& rect, Qt::Alignment vertical_align, int icon_size, bool draw_arrow) { QFontMetrics fm = painter->fontMetrics(); @@ -487,6 +480,13 @@ void NodeViewItem::SetHighlightedIndex(int index) update(); } +void NodeViewItem::SetLabelAsOutput(bool e) +{ + label_as_output_ = e; + output_connector_->setVisible(!e); + update(); +} + QRectF NodeViewItem::GetInputRect(int index) const { QRectF r = title_bar_rect_; @@ -504,28 +504,45 @@ QRectF NodeViewItem::GetInputRect(int index) const QPointF NodeViewItem::GetInputPoint(const QString &input, int element, const QPointF& source_pos) const { - return pos() + GetInputPointInternal(node_inputs_.indexOf(input), source_pos); + if (expanded_) { + return pos() + GetInputPointInternal(node_inputs_.indexOf(input), source_pos); + } else { + return pos() + input_connector_->pos(); + } } QPointF NodeViewItem::GetOutputPoint() const { + QPointF p = pos() + output_connector_->pos(); + QRectF r = output_connector_->boundingRect(); + switch (flow_dir_) { case NodeViewCommon::kLeftToRight: default: - return pos() + QPointF(rect().right(), rect().center().y()); + p.setX(p.x() + r.width()); + break; case NodeViewCommon::kRightToLeft: - return pos() + QPointF(rect().left(), rect().center().y()); + p.setX(p.x() - r.width()); + break; case NodeViewCommon::kTopToBottom: - return pos() + QPointF(rect().center().x(), rect().bottom()); + p.setY(p.y() + r.height()); + break; case NodeViewCommon::kBottomToTop: - return pos() + QPointF(rect().center().x(), rect().top()); + p.setY(p.y() - r.height()); + break; } + + return p; } void NodeViewItem::SetFlowDirection(NodeViewCommon::FlowDirection dir) { flow_dir_ = dir; + input_connector_->SetFlowDirection(dir); + output_connector_->SetFlowDirection(dir); + + UpdateConnectorPositions(); UpdateNodePosition(); } @@ -556,4 +573,33 @@ void NodeViewItem::UpdateNodePosition() setPos(NodeToScreenPoint(cached_node_pos_, flow_dir_)); } +void NodeViewItem::UpdateConnectorPositions() +{ + QRectF output_rect = output_connector_->boundingRect(); + + switch (flow_dir_) { + case NodeViewCommon::kLeftToRight: + input_connector_->setPos(rect().left() - output_rect.width(), rect().center().y()); + output_connector_->setPos(rect().right(), rect().center().y()); + break; + case NodeViewCommon::kRightToLeft: + input_connector_->setPos(rect().right() + output_rect.width(), rect().center().y()); + output_connector_->setPos(rect().left(), rect().center().y()); + break; + case NodeViewCommon::kTopToBottom: + input_connector_->setPos(rect().center().x(), rect().top() - output_rect.height()); + output_connector_->setPos(rect().center().x(), rect().bottom()); + break; + case NodeViewCommon::kBottomToTop: + input_connector_->setPos(rect().center().x(), rect().bottom() + output_rect.height()); + output_connector_->setPos(rect().center().x(), rect().top()); + break; + } +} + +void NodeViewItem::NodeAppearanceChanged() +{ + update(); +} + } diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 3c611d481..bd2cd7ac1 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -28,6 +28,7 @@ #include "node/node.h" #include "nodeviewcommon.h" +#include "nodeviewitemconnector.h" namespace olive { @@ -40,8 +41,9 @@ class NodeViewEdge; * * To retrieve the NodeViewItem for a certain Node, use NodeView::NodeToUIObject(). */ -class NodeViewItem : public QGraphicsRectItem +class NodeViewItem : public QObject, public QGraphicsRectItem { + Q_OBJECT public: NodeViewItem(QGraphicsItem* parent = nullptr); @@ -125,11 +127,13 @@ public: return prevent_removing_; } - const QPolygonF &GetOutputTriangle() const + QPolygonF GetOutputTriangle() const { - return output_triangle_; + return output_connector_->polygon(); } + void SetLabelAsOutput(bool e); + protected: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; @@ -143,6 +147,8 @@ protected: private: void ReadjustAllEdges(); + void UpdateContextRect(); + void DrawNodeTitle(QPainter *painter, QString text, const QRectF &rect, Qt::Alignment vertical_align, int icon_size, bool draw_arrow); /** @@ -160,6 +166,8 @@ private: */ void UpdateNodePosition(); + void UpdateConnectorPositions(); + /** * @brief Reference to attached Node */ @@ -195,7 +203,13 @@ private: bool prevent_removing_; - QPolygonF output_triangle_; + NodeViewItemConnector *input_connector_; + NodeViewItemConnector *output_connector_; + + bool label_as_output_; + +private slots: + void NodeAppearanceChanged(); }; diff --git a/app/widget/nodeview/nodeviewitemconnector.cpp b/app/widget/nodeview/nodeviewitemconnector.cpp new file mode 100644 index 000000000..650c33ccf --- /dev/null +++ b/app/widget/nodeview/nodeviewitemconnector.cpp @@ -0,0 +1,81 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "nodeviewitemconnector.h" + +#include +#include +#include +#include + +#include "nodeviewitem.h" + +namespace olive { + +NodeViewItemConnector::NodeViewItemConnector(QGraphicsItem *parent) : + QGraphicsPolygonItem(parent) +{ + QColor c = qApp->palette().text().color(); + setPen(QPen(c, NodeViewItem::DefaultItemBorder())); + setBrush(c); +} + +void NodeViewItemConnector::SetFlowDirection(NodeViewCommon::FlowDirection dir) +{ + QFont f; + QFontMetricsF fm(f); + + int triangle_sz = fm.height()/2; + int triangle_sz_half = triangle_sz / 2; + + QPolygonF p; + p.resize(3); + + switch (dir) { + case NodeViewCommon::kLeftToRight: + // Triangle pointing right + p[0] = QPointF(0, -triangle_sz_half); + p[1] = QPointF(triangle_sz_half, 0); + p[2] = QPointF(0, triangle_sz_half); + break; + case NodeViewCommon::kTopToBottom: + // Triangle pointing down + p[0] = QPointF(-triangle_sz_half, 0); + p[1] = QPointF(0, triangle_sz_half); + p[2] = QPointF(triangle_sz_half, 0); + break; + case NodeViewCommon::kBottomToTop: + // Triangle pointing up + p[0] = QPointF(-triangle_sz_half, 0); + p[1] = QPointF(0, -triangle_sz_half); + p[2] = QPointF(triangle_sz_half, 0); + break; + case NodeViewCommon::kRightToLeft: + // Triangle pointing left + p[0] = QPointF(0, -triangle_sz_half); + p[1] = QPointF(-triangle_sz_half, 0); + p[2] = QPointF(0, triangle_sz_half); + break; + } + + setPolygon(p); +} + +} diff --git a/app/widget/nodeview/nodeviewitemconnector.h b/app/widget/nodeview/nodeviewitemconnector.h new file mode 100644 index 000000000..6dfc7d9d3 --- /dev/null +++ b/app/widget/nodeview/nodeviewitemconnector.h @@ -0,0 +1,40 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef NODEVIEWITEMCONNECTOR_H +#define NODEVIEWITEMCONNECTOR_H + +#include + +#include "nodeviewcommon.h" + +namespace olive { + +class NodeViewItemConnector : public QGraphicsPolygonItem +{ +public: + NodeViewItemConnector(QGraphicsItem *parent = nullptr); + + void SetFlowDirection(NodeViewCommon::FlowDirection dir); +}; + +} + +#endif // NODEVIEWITEMCONNECTOR_H diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index 56de3d250..feb2598b3 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -38,19 +38,13 @@ void NodeViewScene::SetFlowDirection(NodeViewCommon::FlowDirection direction) { direction_ = direction; - { - // Iterate over node items setting direction - QHash::const_iterator i; - for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) { - i.value()->SetFlowDirection(direction_); - } + foreach (NodeViewContext *ctx, context_map_) { + ctx->SetFlowDirection(direction_); } - { - // Iterate over edge items setting direction - foreach (NodeViewEdge* edge, edges_) { - edge->SetFlowDirection(direction_); - } + // Iterate over edge items setting direction + foreach (NodeViewEdge* edge, edges_) { + edge->SetFlowDirection(direction_); } } @@ -66,7 +60,6 @@ void NodeViewScene::clear() selectedItems(); for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) { - DisconnectNode(it.key()); delete it.value(); } item_map_.clear(); @@ -150,28 +143,6 @@ QVector NodeViewScene::GetSelectedEdges() const return edges; } -NodeViewItem* NodeViewScene::AddNode(Node* node) -{ - NodeViewItem* item = new NodeViewItem(); - - item->SetFlowDirection(direction_); - item->SetNode(node); - - addItem(item); - item_map_.insert(node, item); - - ConnectNode(node); - - return item; -} - -void NodeViewScene::RemoveNode(Node *node) -{ - DisconnectNode(node); - - delete item_map_.take(node); -} - NodeViewEdge* NodeViewScene::AddEdge(Node *output, const NodeInput &input) { NodeViewEdge *edge = EdgeToUIObject(output, input); @@ -202,6 +173,8 @@ NodeViewContext *NodeViewScene::AddContext(Node *node) context_item = new NodeViewContext(); context_item->SetContext(node); context_item->setPos(0, 0); + context_item->SetFlowDirection(GetFlowDirection()); + context_item->SetCurvedEdges(GetEdgesAreCurved()); addItem(context_item); const NodeGraph::PositionMap &map = node->parent()->GetNodesForContext(node); @@ -209,6 +182,8 @@ NodeViewContext *NodeViewScene::AddContext(Node *node) context_item->AddChild(it.key()); } context_item->UpdateRect(); + + context_map_.insert(node, context_item); } return context_item; @@ -250,18 +225,6 @@ NodeViewEdge* NodeViewScene::AddEdgeInternal(Node *output, const NodeInput& inpu return edge_ui; } -void NodeViewScene::ConnectNode(Node *n) -{ - connect(n, &Node::LabelChanged, this, &NodeViewScene::NodeAppearanceChanged); - connect(n, &Node::ColorChanged, this, &NodeViewScene::NodeAppearanceChanged); -} - -void NodeViewScene::DisconnectNode(Node *n) -{ - disconnect(n, &Node::ColorChanged, this, &NodeViewScene::NodeAppearanceChanged); - disconnect(n, &Node::LabelChanged, this, &NodeViewScene::NodeAppearanceChanged); -} - Qt::Orientation NodeViewScene::GetFlowOrientation() const { return NodeViewCommon::GetFlowOrientation(direction_); @@ -283,10 +246,4 @@ void NodeViewScene::SetEdgesAreCurved(bool curved) } } -void NodeViewScene::NodeAppearanceChanged() -{ - // Force item to update - item_map_.value(static_cast(sender()))->update(); -} - } diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index 4f6357dee..f37ebbdcd 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -81,22 +81,6 @@ public: } public slots: - /** - * @brief Slot when a Node is added to a graph (SetGraph() connects this) - * - * This should NEVER be called directly, only connected to a NodeGraph. To add a Node to the NodeGraph - * use NodeGraph::AddNode(). - */ - NodeViewItem *AddNode(Node* node); - - /** - * @brief Slot when a Node is removed from a graph (SetGraph() connects this) - * - * This should NEVER be called directly, only connected to a NodeGraph. To remove a Node from the NodeGraph - * use NodeGraph::RemoveNode(). - */ - void RemoveNode(Node* node); - NodeViewEdge *AddEdge(Node *output, const NodeInput& input); void RemoveEdge(Node *output, const NodeInput& input); @@ -113,10 +97,6 @@ private: NodeViewEdge* AddEdgeInternal(Node *output, const NodeInput &input, NodeViewItem* from, NodeViewItem* to); - void ConnectNode(Node *n); - - void DisconnectNode(Node *n); - QHash context_map_; QHash item_map_; @@ -129,12 +109,6 @@ private: bool curved_edges_; -private slots: - /** - * @brief Receiver for when a node's label has changed - */ - void NodeAppearanceChanged(); - }; } diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index 5f379679f..9ad1a6a74 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -397,7 +397,7 @@ void ImportTool::DropGhosts(bool insert) command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0), false)); // Position footage in its context - command->add_child(new NodeSetPositionCommand(footage_stream.footage, clip, QPointF(-2, 0), false)); + command->add_child(new NodeSetPositionCommand(footage_stream.footage, clip, QPointF(-3, 0), false)); switch (Track::Reference::TypeFromString(footage_stream.output)) { case Track::kVideo: @@ -409,7 +409,7 @@ void ImportTool::DropGhosts(bool insert) command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(transform, TransformDistortNode::kTextureInput))); command->add_child(new NodeEdgeAddCommand(transform, NodeInput(clip, ClipBlock::kBufferIn))); - command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(-1, 0), false)); + command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(-2, 0), false)); break; } case Track::kAudio: @@ -421,7 +421,7 @@ void ImportTool::DropGhosts(bool insert) command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(volume_node, VolumeNode::kSamplesInput))); command->add_child(new NodeEdgeAddCommand(volume_node, NodeInput(clip, ClipBlock::kBufferIn))); - command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(-1, 0), false)); + command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(-2, 0), false)); break; } default: From 65446fd70b23eae56f0a1045d0a6213a2fdce09f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 10 Nov 2021 22:28:37 -0800 Subject: [PATCH 06/94] export: use export timebase instead of original --- app/task/export/export.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/task/export/export.cpp b/app/task/export/export.cpp index 8c4f50c82..93c7d251c 100644 --- a/app/task/export/export.cpp +++ b/app/task/export/export.cpp @@ -36,6 +36,8 @@ ExportTask::ExportTask(ViewerOutput *viewer_node, // Adjust video params to have no divider VideoParams vp = viewer_node->GetVideoParams(); vp.set_divider(1); + vp.set_time_base(params.video_params().time_base()); + vp.set_frame_rate(params.video_params().frame_rate()); set_video_params(vp); set_audio_params(viewer_node->GetAudioParams()); From 0c92bc3f08110b63265eed8a44285c6634c1478a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 10 Nov 2021 22:32:44 -0800 Subject: [PATCH 07/94] fixed some compile issues on non-msvc --- app/node/output/track/track.h | 2 ++ app/widget/nodeview/nodeview.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 2e9b44ab3..a7953ff09 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -189,6 +189,7 @@ public: case kSubtitle: return QStringLiteral("s"); case kCount: + case kNone: break; } @@ -206,6 +207,7 @@ public: case kSubtitle: return tr("S"); case kCount: + case kNone: break; } diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 86b1c5711..818f55095 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -1636,7 +1636,7 @@ void NodeView::GroupNodes() void NodeView::UngroupNodes() { - static_cast(selected_nodes_.first()); + //static_cast(selected_nodes_.first()); } NodeViewItem *NodeView::UpdateNodeItem(Node *node, bool ignore_own_context) From ab8d9e14e86fad14ccb44652f92791aac8ddd6c2 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 11 Nov 2021 16:14:37 -0800 Subject: [PATCH 08/94] moved sleep prevention code for further abstraction, implemented macOS --- CMakeLists.txt | 4 +++ app/widget/viewer/CMakeLists.txt | 2 ++ app/widget/viewer/viewer.cpp | 13 +++------ app/widget/viewer/viewerpreventsleep.cpp | 34 ++++++++++++++++++++++++ app/widget/viewer/viewerpreventsleep.h | 10 +++++++ 5 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 app/widget/viewer/viewerpreventsleep.cpp create mode 100644 app/widget/viewer/viewerpreventsleep.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6524c5e8c..35b402073 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,6 +156,10 @@ else() endif() endif() +if (APPLE) + list(APPEND OLIVE_LIBRARIES "-framework IOKit") +endif() + # Generate Git hash set(PROJECT_LONG_VERSION ${PROJECT_VERSION}) if(EXISTS "${CMAKE_SOURCE_DIR}/.git") diff --git a/app/widget/viewer/CMakeLists.txt b/app/widget/viewer/CMakeLists.txt index 23b401cfe..345702e69 100644 --- a/app/widget/viewer/CMakeLists.txt +++ b/app/widget/viewer/CMakeLists.txt @@ -26,6 +26,8 @@ set(OLIVE_SOURCES widget/viewer/viewerdisplay.h widget/viewer/viewerplaybacktimer.cpp widget/viewer/viewerplaybacktimer.h + widget/viewer/viewerpreventsleep.cpp + widget/viewer/viewerpreventsleep.h widget/viewer/viewerqueue.h widget/viewer/viewersafemargininfo.h widget/viewer/viewersizer.cpp diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index fc9c4bb1b..d97cda0ac 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -30,10 +30,6 @@ #include #include -#ifdef _WIN32 -#include "winbase.h" -#endif - #include "audio/audiomanager.h" #include "common/clamp.h" #include "common/power.h" @@ -44,6 +40,7 @@ #include "node/project/project.h" #include "render/rendermanager.h" #include "task/taskmanager.h" +#include "viewerpreventsleep.h" #include "widget/menu/menu.h" #include "window/mainwindow/mainwindow.h" @@ -707,9 +704,7 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) } } // Force screen to stay awake -#ifdef _WIN32 - SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_CONTINUOUS); -#endif + PreventSleep(true); } void ViewerWidget::PauseInternal() @@ -752,9 +747,7 @@ void ViewerWidget::PauseInternal() prequeuing_audio_ = 0; // Reset screen timeout timer -#ifdef _WIN32 - SetThreadExecutionState(ES_CONTINUOUS); -#endif + PreventSleep(false); } void ViewerWidget::PushScrubbedAudio() diff --git a/app/widget/viewer/viewerpreventsleep.cpp b/app/widget/viewer/viewerpreventsleep.cpp new file mode 100644 index 000000000..8de97c7c1 --- /dev/null +++ b/app/widget/viewer/viewerpreventsleep.cpp @@ -0,0 +1,34 @@ +#include "viewerpreventsleep.h" + +#include + +#if defined(Q_OS_WINDOWS) +#include +#elif defined(Q_OS_MAC) +#include +#endif + +namespace olive { + +#if defined(Q_OS_MAC) +IOPMAssertionID assertionID = 0; +#endif + +void PreventSleep(bool on) +{ +#if defined(Q_OS_WINDOWS) + SetThreadExecutionState(on ? ES_DISPLAY_REQUIRED | ES_CONTINUOUS : ES_CONTINUOUS); +#elif defined(Q_OS_MAC) + if (on) { + static const CFStringRef reasonForActivity = CFSTR("Video Playback"); + + IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, + kIOPMAssertionLevelOn, reasonForActivity, &assertionID); + } else if (assertionID) { + IOPMAssertionRelease(assertionID); + assertionID = 0; + } +#endif +} + +} diff --git a/app/widget/viewer/viewerpreventsleep.h b/app/widget/viewer/viewerpreventsleep.h new file mode 100644 index 000000000..482ef3a11 --- /dev/null +++ b/app/widget/viewer/viewerpreventsleep.h @@ -0,0 +1,10 @@ +#ifndef VIEWERPREVENTSLEEP_H +#define VIEWERPREVENTSLEEP_H + +namespace olive { + +void PreventSleep(bool on); + +} + +#endif // VIEWERPREVENTSLEEP_H From da3dc765a4b98e858a050cdea00ec3377f9850e1 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 12 Nov 2021 10:48:39 -0800 Subject: [PATCH 09/94] implement inhibiting sleep on linux during video playback Fixes #1791 --- CMakeLists.txt | 14 +++++++-- app/widget/viewer/viewerpreventsleep.cpp | 40 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 35b402073..e8c28db2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,8 +81,7 @@ list(APPEND OLIVE_LIBRARIES ${OPENEXR_LIBRARIES}) list(APPEND OLIVE_INCLUDE_DIRS ${OPENEXR_INCLUDES}) # Link Qt 5 -find_package(Qt5 5.6 REQUIRED - COMPONENTS +set(QT_LIBRARIES Core Gui Widgets @@ -90,8 +89,15 @@ find_package(Qt5 5.6 REQUIRED Svg LinguistTools Concurrent +) +if (UNIX AND NOT APPLE) + list(APPEND QT_LIBRARIES DBus) +endif() +find_package(Qt5 5.6 REQUIRED + COMPONENTS + ${QT_LIBRARIES} OPTIONAL_COMPONENTS - Network + Network ) if (NOT Qt5Network_FOUND) message(" Qt5::Network module not found, crash reporting will be disabled.") @@ -158,6 +164,8 @@ endif() if (APPLE) list(APPEND OLIVE_LIBRARIES "-framework IOKit") +elseif(UNIX) + list(APPEND OLIVE_LIBRARIES Qt5::DBus) endif() # Generate Git hash diff --git a/app/widget/viewer/viewerpreventsleep.cpp b/app/widget/viewer/viewerpreventsleep.cpp index 8de97c7c1..1d50f918d 100644 --- a/app/widget/viewer/viewerpreventsleep.cpp +++ b/app/widget/viewer/viewerpreventsleep.cpp @@ -6,12 +6,16 @@ #include #elif defined(Q_OS_MAC) #include +#elif defined(Q_OS_LINUX) +#include #endif namespace olive { #if defined(Q_OS_MAC) IOPMAssertionID assertionID = 0; +#elif defined(Q_OS_LINUX) + #endif void PreventSleep(bool on) @@ -28,6 +32,42 @@ void PreventSleep(bool on) IOPMAssertionRelease(assertionID); assertionID = 0; } +#elif defined(Q_OS_LINUX) + QDBusConnection bus = QDBusConnection::sessionBus(); + if(bus.isConnected()) { + static const QStringList sleep_services = { + QStringLiteral("org.freedesktop.ScreenSaver"), + //QStringLiteral("org.gnome.SessionManager") + }; + static const QStringList sleep_paths = { + QStringLiteral("/org/freedesktop/ScreenSaver"), + //QStringLiteral("/org/gnome/SessionManager") + }; + static QVector sleep_cookies; + + // Initialize vector to 0 + if (sleep_cookies.isEmpty()) { + sleep_cookies.resize(sleep_services.size()); + sleep_cookies.fill(0); + } + + for (int i=0; i reply; + + if (on) { + reply = interface.call(QStringLiteral("Inhibit"), QStringLiteral("Olive Video Editor"), QStringLiteral("Video Playback")); + } else { + reply = interface.call(QStringLiteral("UnInhibit"), sleep_cookies.at(i)); + } + + if (reply.isValid()) { + sleep_cookies[i] = reply.value(); + } + } + } + } #endif } From 3c6bc80c8f9db19508097cf298ac96cecb11f1d3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 12 Nov 2021 11:06:06 -0800 Subject: [PATCH 10/94] further improved new node view --- app/widget/nodeview/nodeview.cpp | 2 +- app/widget/nodeview/nodeviewcommon.h | 10 ++ app/widget/nodeview/nodeviewcontext.cpp | 19 +++- app/widget/nodeview/nodeviewcontext.h | 4 + app/widget/nodeview/nodeviewedge.cpp | 2 +- app/widget/nodeview/nodeviewitem.cpp | 121 ++++++++++++++++-------- app/widget/nodeview/nodeviewitem.h | 18 ++-- 7 files changed, 124 insertions(+), 52 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 818f55095..d5e85fcfb 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -1535,7 +1535,7 @@ void NodeView::PositionNewEdge(const QPoint &pos) if (highlight_index >= 0) { create_edge_dst_input_ = create_edge_dst_->GetInputAtIndex(highlight_index); create_edge_->SetPoints(create_edge_src_->GetOutputPoint(), - create_edge_dst_->GetInputPoint(create_edge_dst_input_.input(), create_edge_dst_input_.element(), create_edge_src_->pos()), + create_edge_dst_->GetInputPoint(create_edge_dst_input_.input(), create_edge_dst_input_.element()), true); } else { create_edge_dst_input_.Reset(); diff --git a/app/widget/nodeview/nodeviewcommon.h b/app/widget/nodeview/nodeviewcommon.h index 03c5f8dcf..d0541fba4 100644 --- a/app/widget/nodeview/nodeviewcommon.h +++ b/app/widget/nodeview/nodeviewcommon.h @@ -44,6 +44,16 @@ public: } } + static bool IsFlowVertical(FlowDirection dir) + { + return dir == kTopToBottom || dir == kBottomToTop; + } + + static bool IsFlowHorizontal(FlowDirection dir) + { + return dir == kLeftToRight || dir == kRightToLeft; + } + static bool DirectionsAreOpposing(FlowDirection a, FlowDirection b) { return ((a == NodeViewCommon::kLeftToRight && b == NodeViewCommon::kRightToLeft) || (a == NodeViewCommon::kRightToLeft && b == NodeViewCommon::kLeftToRight) diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index 0e8262b3e..db22be143 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -21,9 +22,6 @@ namespace olive { NodeViewContext::NodeViewContext(QGraphicsItem *item) : super(item) { - setFlag(ItemIsMovable); - setFlag(ItemIsSelectable); - // Set default label text SetContext(nullptr); } @@ -101,10 +99,13 @@ void NodeViewContext::UpdateRect() QFontMetricsF fm(f); qreal lbl_offset = GetTextOffset(fm); - QRectF rect = childrenBoundingRect(); + QRectF cbr = childrenBoundingRect(); + QRectF rect = cbr; int pad = NodeViewItem::DefaultItemHeight(); rect.adjust(-pad, - lbl_offset*2 - fm.height() - pad, pad, pad); setRect(rect); + + last_titlebar_height_ = rect.y() + (cbr.y() - rect.y()); } void NodeViewContext::SetFlowDirection(NodeViewCommon::FlowDirection dir) @@ -164,6 +165,16 @@ QVariant NodeViewContext::itemChange(GraphicsItemChange change, const QVariant & return super::itemChange(change, value); } +void NodeViewContext::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + bool clicked_inside_titlebar = (event->pos().y() < last_titlebar_height_); + + setFlag(ItemIsMovable, clicked_inside_titlebar); + setFlag(ItemIsSelectable, clicked_inside_titlebar); + + super::mousePressEvent(event); +} + NodeViewEdge* NodeViewContext::AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to) { NodeViewEdge* edge_ui = new NodeViewEdge(output, input, from, to, this); diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index a5141e32b..cf35b6fd2 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -30,6 +30,8 @@ public: protected: virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + private: NodeViewEdge *AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to); @@ -41,6 +43,8 @@ private: bool curved_edges_; + int last_titlebar_height_; + }; } diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index 5498fbefb..f6df8ce73 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -60,7 +60,7 @@ void NodeViewEdge::Adjust() { // Draw a line between the two SetPoints(from_item()->GetOutputPoint(), - to_item()->GetInputPoint(input_.input(), input_.element(), from_item()->pos()), + to_item()->GetInputPoint(input_.input(), input_.element()), to_item()->IsExpanded()); } diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 2eb766863..520d2c961 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -67,7 +67,6 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) : title_bar_rect_ = QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height); setRect(title_bar_rect_); - input_connector_ = new NodeViewItemConnector(this); output_connector_ = new NodeViewItemConnector(this); } @@ -219,6 +218,8 @@ void NodeViewItem::SetNode(Node *n) node_inputs_.clear(); + ClearInputConnectors(); + if (node_) { node_->Retranslate(); @@ -228,7 +229,7 @@ void NodeViewItem::SetNode(Node *n) } } - input_connector_->setVisible(!node_inputs_.isEmpty()); + UpdateInputConnectors(); connect(n, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); connect(n, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); @@ -246,7 +247,6 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) expanded_ = e; hide_titlebar_ = hide_titlebar; - input_connector_->setVisible(!expanded_); if (expanded_ && !node_inputs_.isEmpty()) { // Create new rect @@ -263,13 +263,15 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) setRect(title_bar_rect_); } - update(); + UpdateInputConnectorFlowDirections(); UpdateConnectorPositions(); ReadjustAllEdges(); UpdateContextRect(); + + update(); } void NodeViewItem::ToggleExpanded() @@ -403,6 +405,8 @@ QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, cons void NodeViewItem::ReadjustAllEdges() { + UpdateInputConnectorFlowDirections(); + UpdateInputConnectorPositions(); foreach (NodeViewEdge* edge, edges_) { edge->Adjust(); } @@ -502,13 +506,15 @@ QRectF NodeViewItem::GetInputRect(int index) const return r; } -QPointF NodeViewItem::GetInputPoint(const QString &input, int element, const QPointF& source_pos) const +QPointF NodeViewItem::GetInputPoint(const QString &input, int element) const { - if (expanded_) { - return pos() + GetInputPointInternal(node_inputs_.indexOf(input), source_pos); - } else { - return pos() + input_connector_->pos(); + int index = node_inputs_.indexOf(input); + + if (index < 0 || index >= int(input_connectors_.size())) { + return QPointF(); } + + return pos() + input_connectors_[index]->pos(); } QPointF NodeViewItem::GetOutputPoint() const @@ -539,64 +545,101 @@ void NodeViewItem::SetFlowDirection(NodeViewCommon::FlowDirection dir) { flow_dir_ = dir; - input_connector_->SetFlowDirection(dir); + UpdateInputConnectorFlowDirections(); output_connector_->SetFlowDirection(dir); UpdateConnectorPositions(); UpdateNodePosition(); } -QPointF NodeViewItem::GetInputPointInternal(int index, const QPointF& source_pos) const -{ - QRectF input_rect = GetInputRect(index); - - Qt::Orientation flow_orientation = NodeViewCommon::GetFlowOrientation(flow_dir_); - - if (flow_orientation == Qt::Horizontal || IsExpanded()) { - if (flow_dir_ == NodeViewCommon::kLeftToRight - || (flow_orientation == Qt::Vertical && source_pos.x() < pos().x())) { - return QPointF(input_rect.left(), input_rect.center().y()); - } else { - return QPointF(input_rect.right(), input_rect.center().y()); - } - } else { - if (flow_dir_ == NodeViewCommon::kTopToBottom) { - return QPointF(input_rect.center().x(), input_rect.top()); - } else { - return QPointF(input_rect.center().x(), input_rect.bottom()); - } - } -} - void NodeViewItem::UpdateNodePosition() { setPos(NodeToScreenPoint(cached_node_pos_, flow_dir_)); } +void NodeViewItem::UpdateInputConnectors() +{ + int old_sz = input_connectors_.size(); + + input_connectors_.resize(node_inputs_.size()); + for (size_t i=old_sz; i(this); + } +} + +void NodeViewItem::ClearInputConnectors() +{ + input_connectors_.clear(); +} + void NodeViewItem::UpdateConnectorPositions() { - QRectF output_rect = output_connector_->boundingRect(); + UpdateInputConnectorPositions(); switch (flow_dir_) { case NodeViewCommon::kLeftToRight: - input_connector_->setPos(rect().left() - output_rect.width(), rect().center().y()); - output_connector_->setPos(rect().right(), rect().center().y()); + output_connector_->setPos(rect().right(), title_bar_rect_.center().y()); break; case NodeViewCommon::kRightToLeft: - input_connector_->setPos(rect().right() + output_rect.width(), rect().center().y()); - output_connector_->setPos(rect().left(), rect().center().y()); + output_connector_->setPos(rect().left(), title_bar_rect_.center().y()); break; case NodeViewCommon::kTopToBottom: - input_connector_->setPos(rect().center().x(), rect().top() - output_rect.height()); output_connector_->setPos(rect().center().x(), rect().bottom()); break; case NodeViewCommon::kBottomToTop: - input_connector_->setPos(rect().center().x(), rect().bottom() + output_rect.height()); output_connector_->setPos(rect().center().x(), rect().top()); break; } } +void NodeViewItem::UpdateInputConnectorPositions() +{ + QRectF output_rect = output_connector_->boundingRect(); + + // Input connector flow directions change conditionally + for (size_t i=0; isetPos(rect().left() - output_rect.width(), GetInputRect(i).center().y()); + break; + case NodeViewCommon::kRightToLeft: + input_connectors_[i]->setPos(rect().right() + output_rect.width(), GetInputRect(i).center().y()); + break; + case NodeViewCommon::kTopToBottom: + input_connectors_[i]->setPos(rect().center().x(), rect().top() - output_rect.height()); + break; + case NodeViewCommon::kBottomToTop: + input_connectors_[i]->setPos(rect().center().x(), rect().bottom() + output_rect.height()); + break; + } + } +} + +void NodeViewItem::UpdateInputConnectorFlowDirections() +{ + for (size_t i=0; iSetFlowDirection(GetFlowDirectionForInput(i)); + } +} + +NodeViewCommon::FlowDirection NodeViewItem::GetFlowDirectionForInput(int index) +{ + if (!expanded_ || NodeViewCommon::IsFlowHorizontal(flow_dir_)) { + return flow_dir_; + } else { + foreach (NodeViewEdge *edge, edges_) { + if (edge->input().input() == node_inputs_.at(index)) { + if (edge->from_item()->x() < this->x()) { + return NodeViewCommon::kLeftToRight; + } else { + return NodeViewCommon::kRightToLeft; + } + } + } + return NodeViewCommon::kLeftToRight; + } +} + void NodeViewItem::NodeAppearanceChanged() { update(); diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index bd2cd7ac1..c1ff35387 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -80,7 +80,7 @@ public: /** * @brief Returns GLOBAL point that edges should connect to for any NodeParam member of this object */ - QPointF GetInputPoint(const QString& input, int element, const QPointF &source_pos) const; + QPointF GetInputPoint(const QString& input, int element) const; QPointF GetOutputPoint() const; @@ -156,18 +156,22 @@ private: */ QRectF GetInputRect(int index) const; - /** - * @brief Returns local point that edges should connect to for a NodeInput in array node_inputs_[index] - */ - QPointF GetInputPointInternal(int index, const QPointF &source_pos) const; - /** * @brief Internal update function when logical position changes */ void UpdateNodePosition(); + void UpdateInputConnectors(); + + void ClearInputConnectors(); + void UpdateConnectorPositions(); + void UpdateInputConnectorPositions(); + void UpdateInputConnectorFlowDirections(); + + NodeViewCommon::FlowDirection GetFlowDirectionForInput(int index); + /** * @brief Reference to attached Node */ @@ -203,7 +207,7 @@ private: bool prevent_removing_; - NodeViewItemConnector *input_connector_; + std::vector > input_connectors_; NodeViewItemConnector *output_connector_; bool label_as_output_; From f4db7cd330f6afbff63410085fe2fed71236456c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 12 Nov 2021 11:06:28 -0800 Subject: [PATCH 11/94] fixed untranslated line in trig node --- app/node/math/trigonometry/trigonometry.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/node/math/trigonometry/trigonometry.cpp b/app/node/math/trigonometry/trigonometry.cpp index d60087621..3899cc6e4 100644 --- a/app/node/math/trigonometry/trigonometry.cpp +++ b/app/node/math/trigonometry/trigonometry.cpp @@ -74,6 +74,8 @@ void TrigonometryNode::Retranslate() SetComboBoxStrings(kMethodIn, strings); SetInputName(kMethodIn, tr("Method")); + + SetInputName(kXIn, tr("Value")); } void TrigonometryNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const From 854ca0a7a9f77a42211a6fd826645b77f01b5752 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 12 Nov 2021 11:51:13 -0800 Subject: [PATCH 12/94] reimplemented edge connect/disconnect --- app/widget/nodeview/nodeview.cpp | 35 +++++------ app/widget/nodeview/nodeviewedge.cpp | 23 +------ app/widget/nodeview/nodeviewedge.h | 11 ---- app/widget/nodeview/nodeviewitem.cpp | 61 ++++++++++++------- app/widget/nodeview/nodeviewitem.h | 11 ++-- app/widget/nodeview/nodeviewitemconnector.cpp | 5 +- app/widget/nodeview/nodeviewitemconnector.h | 11 +++- 7 files changed, 75 insertions(+), 82 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index d5e85fcfb..31be310c6 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -473,30 +473,27 @@ void NodeView::mousePressEvent(QMouseEvent *event) { if (HandPress(event)) return; + QGraphicsItem* item = itemAt(event->pos()); + if (event->button() == Qt::LeftButton) { - // See if we're dragging the arrow of an edge - QPointF scene_pt = mapToScene(event->pos()); - - for (NodeViewEdge *edge_item : scene_.edges()) { - if (edge_item->arrow_bounding_rect().contains(scene_pt)) { - create_edge_src_ = scene_.NodeToUIObject(edge_item->output()); - create_edge_ = edge_item; - create_edge_already_exists_ = true; + // Determine if user clicked on a connector + if (NodeViewItemConnector *connector = dynamic_cast(item)) { + NodeViewItem *attached_item = static_cast(connector->parentItem()); + if (connector->IsOutput()) { + CreateNewEdge(attached_item, event->pos()); return; - } - } - - // See if we're dragging the arrow of a node - for (NodeViewItem *node_item : scene_.item_map()) { - if (node_item->GetOutputTriangle().boundingRect().translated(node_item->pos()).contains(scene_pt)) { - CreateNewEdge(node_item, event->pos()); + } else { + NodeViewEdge *edge_item = attached_item->GetEdgeFromInputConnector(connector); + if (edge_item) { + create_edge_src_ = edge_item->from_item(); + create_edge_ = edge_item; + create_edge_already_exists_ = true; + } return; } } } - QGraphicsItem* item = itemAt(event->pos()); - if (event->button() == Qt::RightButton) { if (!item || !item->isSelected()) { // Qt doesn't do this by default for some reason @@ -668,13 +665,13 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } // Update contexts - if (!removed_edges.empty()) { + /*if (!removed_edges.empty()) { UpdateContextsFromEdgeRemove(command, removed_edges); } if (added_edge.first) { UpdateContextsFromEdgeAdd(command, added_edge, removed_edges); - } + }*/ Core::instance()->undo_stack()->pushIfHasChildren(command); return; diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index f6df8ce73..977b88cbd 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -126,13 +126,6 @@ void NodeViewEdge::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti painter->setPen(QPen(edge_color, edge_width_)); painter->setBrush(Qt::NoBrush); painter->drawPath(path()); - - // Draw arrow - if (!connected_) { - painter->setPen(Qt::NoPen); - painter->setBrush(edge_color); - painter->drawPolygon(arrow_); - } } void NodeViewEdge::Init() @@ -149,7 +142,6 @@ void NodeViewEdge::Init() // Use font metrics to set edge width for basic high DPI support edge_width_ = QFontMetrics(QFont()).height() / 12; - arrow_size_ = QFontMetrics(QFont()).height() / 2; } void NodeViewEdge::UpdateCurve() @@ -185,7 +177,7 @@ void NodeViewEdge::UpdateCurve() path.cubicTo(cp1, cp2, end); if (!qFuzzyCompare(start.x(), end.x())) { - double continue_x = end.x() - qCos(angle)*arrow_size_; + double continue_x = end.x() - qCos(angle); double x1 = start.x(); double x2 = cp1.x(); @@ -215,18 +207,7 @@ void NodeViewEdge::UpdateCurve() } - setPath(path); - - const double arrow_angle = 150.0 * M_PI / 180.0; - QVector arrow_points(4); - arrow_points[0] = end; - arrow_points[1] = end + QPointF(qCos(angle + arrow_angle) * arrow_size_, qSin(angle + arrow_angle) * arrow_size_); - arrow_points[2] = end + QPointF(qCos(angle - arrow_angle) * arrow_size_, qSin(angle - arrow_angle) * arrow_size_); - arrow_points[3] = end; - - arrow_ = QPolygonF(arrow_points); - arrow_bounding_rect_ = arrow_.boundingRect(); - arrow_bounding_rect_.adjust(-arrow_size_, -arrow_size_, arrow_size_, arrow_size_); + setPath(mapFromScene(path)); } } diff --git a/app/widget/nodeview/nodeviewedge.h b/app/widget/nodeview/nodeviewedge.h index 6240aeb64..de8d10720 100644 --- a/app/widget/nodeview/nodeviewedge.h +++ b/app/widget/nodeview/nodeviewedge.h @@ -70,11 +70,6 @@ public: return to_item_; } - const QRectF arrow_bounding_rect() const - { - return arrow_bounding_rect_; - } - void Adjust(); /** @@ -144,12 +139,6 @@ private: bool curved_; - QPolygonF arrow_; - - int arrow_size_; - - QRectF arrow_bounding_rect_; - QPointF cached_start_; QPointF cached_end_; bool cached_input_is_expanded_; diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 520d2c961..b3e4ee4e9 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -67,7 +67,7 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) : title_bar_rect_ = QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height); setRect(title_bar_rect_); - output_connector_ = new NodeViewItemConnector(this); + output_connector_ = new NodeViewItemConnector(true, this); } QPointF NodeViewItem::GetNodePosition() const @@ -196,7 +196,7 @@ void NodeViewItem::RemoveEdge(NodeViewEdge *edge) int NodeViewItem::GetIndexAt(QPointF pt) const { - pt -= pos(); + pt -= this->scenePos(); for (int i=0; iRetranslate(); @@ -473,6 +472,17 @@ void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& text); } +NodeViewEdge *NodeViewItem::GetEdgeFromInputIndex(int index) +{ + foreach (NodeViewEdge *edge, edges_) { + if (edge->input().input() == node_inputs_.at(index)) { + return edge; + } + } + + return nullptr; +} + void NodeViewItem::SetHighlightedIndex(int index) { if (highlighted_index_ == index) { @@ -491,6 +501,23 @@ void NodeViewItem::SetLabelAsOutput(bool e) update(); } +NodeViewEdge *NodeViewItem::GetEdgeFromInputConnector(NodeViewItemConnector *connector) +{ + ssize_t index = -1; + for (ssize_t i=0; ipos(); + return input_connectors_[index]->scenePos(); } QPointF NodeViewItem::GetOutputPoint() const { - QPointF p = pos() + output_connector_->pos(); + QPointF p = output_connector_->scenePos(); QRectF r = output_connector_->boundingRect(); switch (flow_dir_) { @@ -563,15 +590,10 @@ void NodeViewItem::UpdateInputConnectors() input_connectors_.resize(node_inputs_.size()); for (size_t i=old_sz; i(this); + input_connectors_[i] = std::make_unique(false, this); } } -void NodeViewItem::ClearInputConnectors() -{ - input_connectors_.clear(); -} - void NodeViewItem::UpdateConnectorPositions() { UpdateInputConnectorPositions(); @@ -627,16 +649,13 @@ NodeViewCommon::FlowDirection NodeViewItem::GetFlowDirectionForInput(int index) if (!expanded_ || NodeViewCommon::IsFlowHorizontal(flow_dir_)) { return flow_dir_; } else { - foreach (NodeViewEdge *edge, edges_) { - if (edge->input().input() == node_inputs_.at(index)) { - if (edge->from_item()->x() < this->x()) { - return NodeViewCommon::kLeftToRight; - } else { - return NodeViewCommon::kRightToLeft; - } - } + NodeViewEdge *edge = GetEdgeFromInputIndex(index); + + if (!edge || edge->from_item()->x() < this->x()) { + return NodeViewCommon::kLeftToRight; + } else { + return NodeViewCommon::kRightToLeft; } - return NodeViewCommon::kLeftToRight; } } diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index c1ff35387..0f11574ed 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -127,13 +127,10 @@ public: return prevent_removing_; } - QPolygonF GetOutputTriangle() const - { - return output_connector_->polygon(); - } - void SetLabelAsOutput(bool e); + NodeViewEdge *GetEdgeFromInputConnector(NodeViewItemConnector *connector); + protected: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; @@ -151,6 +148,8 @@ private: void DrawNodeTitle(QPainter *painter, QString text, const QRectF &rect, Qt::Alignment vertical_align, int icon_size, bool draw_arrow); + NodeViewEdge *GetEdgeFromInputIndex(int index); + /** * @brief Returns local rect of a NodeInput in array node_inputs_[index] */ @@ -163,8 +162,6 @@ private: void UpdateInputConnectors(); - void ClearInputConnectors(); - void UpdateConnectorPositions(); void UpdateInputConnectorPositions(); diff --git a/app/widget/nodeview/nodeviewitemconnector.cpp b/app/widget/nodeview/nodeviewitemconnector.cpp index 650c33ccf..2ab919d77 100644 --- a/app/widget/nodeview/nodeviewitemconnector.cpp +++ b/app/widget/nodeview/nodeviewitemconnector.cpp @@ -29,8 +29,9 @@ namespace olive { -NodeViewItemConnector::NodeViewItemConnector(QGraphicsItem *parent) : - QGraphicsPolygonItem(parent) +NodeViewItemConnector::NodeViewItemConnector(bool is_output, QGraphicsItem *parent) : + QGraphicsPolygonItem(parent), + output_(is_output) { QColor c = qApp->palette().text().color(); setPen(QPen(c, NodeViewItem::DefaultItemBorder())); diff --git a/app/widget/nodeview/nodeviewitemconnector.h b/app/widget/nodeview/nodeviewitemconnector.h index 6dfc7d9d3..4da1e2fca 100644 --- a/app/widget/nodeview/nodeviewitemconnector.h +++ b/app/widget/nodeview/nodeviewitemconnector.h @@ -30,9 +30,18 @@ namespace olive { class NodeViewItemConnector : public QGraphicsPolygonItem { public: - NodeViewItemConnector(QGraphicsItem *parent = nullptr); + NodeViewItemConnector(bool is_output, QGraphicsItem *parent = nullptr); void SetFlowDirection(NodeViewCommon::FlowDirection dir); + + bool IsOutput() const + { + return output_; + } + +private: + bool output_; + }; } From d62f269277e3fa120e27a6b0aaa53f428d01f349 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 12 Nov 2021 12:07:19 -0800 Subject: [PATCH 13/94] use windows header rather than winbase --- app/widget/viewer/viewerpreventsleep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/viewer/viewerpreventsleep.cpp b/app/widget/viewer/viewerpreventsleep.cpp index 1d50f918d..42d8c5a0f 100644 --- a/app/widget/viewer/viewerpreventsleep.cpp +++ b/app/widget/viewer/viewerpreventsleep.cpp @@ -3,7 +3,7 @@ #include #if defined(Q_OS_WINDOWS) -#include +#include #elif defined(Q_OS_MAC) #include #elif defined(Q_OS_LINUX) From 575d3bd13c7147893f2497063583a373d2b285e4 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 12 Nov 2021 12:22:00 -0800 Subject: [PATCH 14/94] only show task if using custom range --- app/render/previewautocacher.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 03a94efd9..2c121745d 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -730,9 +730,9 @@ void PreviewAutoCacher::RequeueFrames() delayed_requeue_timer_.stop(); if (viewer_node_ + && (viewer_node_->GetVideoAutoCacheEnabled() || use_custom_range_) && viewer_node_->video_frame_cache()->HasInvalidatedRanges(viewer_node_->GetVideoLength()) && hash_tasks_.isEmpty() - && (viewer_node_->GetVideoAutoCacheEnabled() || use_custom_range_) && !IsRenderingCustomRange()) { TimeRange using_range = use_custom_range_ ? custom_autocache_range_ : cache_range_; @@ -743,11 +743,13 @@ void PreviewAutoCacher::RequeueFrames() emit StopCacheProxyTasks(); - CustomCacheTask *cct = new CustomCacheTask(viewer_node_->GetLabelOrName()); - connect(this, &PreviewAutoCacher::StopCacheProxyTasks, cct, &CustomCacheTask::Finish); - connect(this, &PreviewAutoCacher::SignalCacheProxyTaskProgress, cct, &CustomCacheTask::ProgressChanged); - connect(cct, &CustomCacheTask::Cancelled, this, &PreviewAutoCacher::CacheProxyTaskCancelled); - TaskManager::instance()->AddTask(cct); + if (use_custom_range_) { + CustomCacheTask *cct = new CustomCacheTask(viewer_node_->GetLabelOrName()); + connect(this, &PreviewAutoCacher::StopCacheProxyTasks, cct, &CustomCacheTask::Finish); + connect(this, &PreviewAutoCacher::SignalCacheProxyTaskProgress, cct, &CustomCacheTask::ProgressChanged); + connect(cct, &CustomCacheTask::Cancelled, this, &PreviewAutoCacher::CacheProxyTaskCancelled); + TaskManager::instance()->AddTask(cct); + } use_custom_range_ = false; From 8befda8dd0e650ad0f257991a4cee393e0307bad Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 15 Nov 2021 15:47:05 -0800 Subject: [PATCH 15/94] viewer: improved audio queue behavior --- app/widget/viewer/viewer.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index d97cda0ac..d883a8383 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -433,7 +433,7 @@ void ViewerWidget::QueueNextAudioBuffer() // Clamp queue end by zero and the audio length queue_end = clamp(queue_end, rational(0), GetConnectedNode()->GetAudioLength()); - if (queue_end == audio_playback_queue_time_) { + if (queue_end <= audio_playback_queue_time_) { // This will queue nothing, so stop the loop here if (prequeuing_audio_) { DecrementPrequeuedAudio(); @@ -480,8 +480,6 @@ void ViewerWidget::ReceivedAudioBufferForPlayback() if (prequeuing_audio_) { // Add to prequeued audio buffer prequeued_audio_.append(pack); - - DecrementPrequeuedAudio(); } else { // Push directly to audio manager AudioManager::instance()->PushToOutput(GetConnectedNode()->GetAudioParams(), pack); @@ -490,6 +488,10 @@ void ViewerWidget::ReceivedAudioBufferForPlayback() } } + if (prequeuing_audio_) { + DecrementPrequeuedAudio(); + } + delete watcher; } } From 93be94bc2cb89fdf97e7b4131ac0da22c4e1390e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 16 Nov 2021 23:58:34 -0800 Subject: [PATCH 16/94] more work --- app/core.cpp | 2 +- app/node/graph.cpp | 53 +- app/node/graph.h | 53 -- app/node/node.cpp | 189 ++--- app/node/node.h | 178 +---- app/node/nodecopypaste.cpp | 6 +- app/node/output/track/track.cpp | 2 +- app/node/param.cpp | 9 + app/node/param.h | 2 + app/node/project/folder/folder.cpp | 22 +- app/node/project/folder/folder.h | 8 +- app/node/project/project.cpp | 27 +- app/node/project/sequence/sequence.cpp | 2 +- app/panel/node/node.cpp | 1 + app/panel/node/node.h | 20 +- app/widget/nodeview/nodeview.cpp | 661 ++---------------- app/widget/nodeview/nodeview.h | 57 +- app/widget/nodeview/nodeviewcontext.cpp | 162 +++-- app/widget/nodeview/nodeviewcontext.h | 24 +- app/widget/nodeview/nodeviewedge.cpp | 14 + app/widget/nodeview/nodeviewedge.h | 2 + app/widget/nodeview/nodeviewitem.cpp | 12 +- app/widget/nodeview/nodeviewitem.h | 4 +- app/widget/nodeview/nodeviewminimap.cpp | 10 +- app/widget/nodeview/nodeviewminimap.h | 2 + app/widget/nodeview/nodeviewscene.cpp | 60 +- app/widget/nodeview/nodeviewscene.h | 5 - app/widget/timelinewidget/tool/add.cpp | 6 +- app/widget/timelinewidget/tool/import.cpp | 14 +- app/widget/timelinewidget/tool/tool.cpp | 2 + app/widget/timelinewidget/tool/tool.h | 2 + app/widget/timelinewidget/tool/transition.cpp | 8 +- .../undo/timelineundogeneral.cpp | 171 ++--- .../timelinewidget/undo/timelineundogeneral.h | 10 +- .../undo/timelineundopointer.cpp | 21 - .../timelinewidget/undo/timelineundopointer.h | 1 - .../timelinewidget/undo/timelineundosplit.cpp | 8 - .../timelinewidget/undo/timelineundosplit.h | 6 +- app/window/mainwindow/mainwindow.cpp | 14 +- 39 files changed, 493 insertions(+), 1357 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index 48830651a..82a6b98fc 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -437,7 +437,7 @@ void Core::CreateNewSequence() command->add_child(new NodeAddCommand(active_project, new_sequence)); command->add_child(new FolderAddChild(GetSelectedFolderInActiveProject(), new_sequence)); - command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0), false)); + command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0))); // Create and connect default nodes to new sequence new_sequence->add_default_nodes(command); diff --git a/app/node/graph.cpp b/app/node/graph.cpp index a2d04b233..f652340fb 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -44,45 +44,6 @@ void NodeGraph::Clear() } } -qreal NodeGraph::GetNodeContextHeight(Node *context) -{ - const PositionMap &map = position_map_.value(context); - - qreal top = 0, bottom = 0; - - foreach (const QPointF &pt, map) { - top = qMin(pt.y(), top); - bottom = qMax(pt.y(), bottom); - } - - return bottom - top; -} - -int NodeGraph::GetNumberOfContextsNodeIsIn(Node *node) const -{ - int count = 0; - - for (auto it=position_map_.cbegin(); it!=position_map_.cend(); it++) { - if (it.value().contains(node)) { - count++; - } - } - - return count; -} - -bool NodeGraph::NodeOutputsToContext(Node *node) const -{ - for (auto it=position_map_.cbegin(); it!=position_map_.cend(); it++) { - const PositionMap &pm = it.value(); - if (pm.contains(node) && node->OutputsTo(it.key(), true)) { - return true; - } - } - - return false; -} - void NodeGraph::childEvent(QChildEvent *event) { super::childEvent(event); @@ -116,18 +77,10 @@ void NodeGraph::childEvent(QChildEvent *event) emit NodeRemoved(node); emit node->RemovedFromGraph(this); - for (auto it=position_map_.begin(); it!=position_map_.end(); it++) { - PositionMap &map = it.value(); - for (auto jt=map.begin(); jt!=map.end(); ) { - if (jt.key() == node) { - jt = map.erase(jt); - emit NodePositionRemoved(node, it.key()); - } else { - jt++; - } - } + // Remove from any contexts + foreach (Node *context, node_children_) { + context->RemoveNodeFromContext(node); } - } } } diff --git a/app/node/graph.h b/app/node/graph.h index 5e0aa5be2..21c8f023e 100644 --- a/app/node/graph.h +++ b/app/node/graph.h @@ -63,55 +63,6 @@ public: return default_nodes_; } - bool NodeMapContainsNode(Node* node, Node* context) const - { - return position_map_.value(context).contains(node); - } - - QPointF GetNodePosition(Node* node, Node* context) - { - return position_map_.value(context).value(node); - } - - void SetNodePosition(Node* node, Node* context, const QPointF& pos) - { - position_map_[context].insert(node, pos); - emit NodePositionAdded(node, context, pos); - } - - void RemoveNodePosition(Node* node, Node* context) - { - PositionMap& map = position_map_[context]; - map.remove(node); - if (map.isEmpty()) { - position_map_.remove(context); - } - emit NodePositionRemoved(node, context); - } - - bool ContextContainsNode(Node *node, Node *context) - { - return position_map_[context].contains(node); - } - - qreal GetNodeContextHeight(Node *context); - - using PositionMap = QHash; - - const PositionMap &GetNodesForContext(Node *context) - { - return position_map_[context]; - } - - const QMap &GetPositionMap() const - { - return position_map_; - } - - int GetNumberOfContextsNodeIsIn(Node *node) const; - - bool NodeOutputsToContext(Node *node) const; - signals: /** * @brief Signal emitted when a Node is added to the graph @@ -148,10 +99,6 @@ private: QVector default_nodes_; - QMap position_map_; - - PositionMap root_position_map_; - }; } diff --git a/app/node/node.cpp b/app/node/node.cpp index d79710962..b1b45ccf6 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -243,6 +243,36 @@ QIcon Node::icon() const return icon::New; } +QPointF Node::GetNodePositionInContext(Node *node) +{ + return context_positions_.value(node); +} + +bool Node::SetNodePositionInContext(Node *node, const QPointF &pos) +{ + bool added = !ContextContainsNode(node); + context_positions_.insert(node, pos); + + if (added) { + emit NodeAddedToContext(node); + } + + emit NodePositionInContextChanged(node, pos); + + return added; +} + +bool Node::RemoveNodeFromContext(Node *node) +{ + if (ContextContainsNode(node)) { + context_positions_.remove(node); + emit NodeRemovedFromContext(node); + return true; + } else { + return false; + } +} + Color Node::color() const { int c; @@ -429,6 +459,11 @@ void Node::SaveInput(QXmlStreamWriter *writer, const QString &id) const writer->writeEndElement(); // subelements } +bool Node::IsInputHidden(const QString &input) const +{ + return (GetInputFlags(input) & kInputFlagHidden); +} + bool Node::IsInputConnectable(const QString &input) const { return !(GetInputFlags(input) & kInputFlagNotConnectable); @@ -1196,13 +1231,10 @@ Node *Node::CopyNodeAndDependencyGraphMinusItemsInternal(QMap& cre command->add_child(new NodeSetValueHintCommand(copied_input, node->GetValueHintForInput(input.input(), input.element()))); } - if (node->parent()->GetPositionMap().contains(node)) { - // This node is a context, copy the context - const NodeGraph::PositionMap &map = node->parent()->GetPositionMap().value(node); - for (auto it=map.cbegin(); it!=map.cend(); it++) { - // Add either the copy (if it exists) or the original node to the context - command->add_child(new NodeSetPositionCommand(created.value(it.key(), it.key()), copy, it.value(), false)); - } + const PositionMap &map = node->GetContextPositions(); + for (auto it=map.cbegin(); it!=map.cend(); it++) { + // Add either the copy (if it exists) or the original node to the context + command->add_child(new NodeSetPositionCommand(created.value(it.key(), it.key()), copy, it.value())); } return copy; @@ -1229,13 +1261,10 @@ Node *Node::CopyNodeInGraph(Node *node, MultiUndoCommand *command) command->add_child(new NodeCopyInputsCommand(node, copy, true)); - if (node->parent()->GetPositionMap().contains(node)) { - // This node is a context, copy the context - const NodeGraph::PositionMap &map = node->parent()->GetPositionMap().value(node); - for (auto it=map.cbegin(); it!=map.cend(); it++) { - // Add to the context - command->add_child(new NodeSetPositionCommand(it.key(), copy, it.value(), false)); - } + const PositionMap &map = node->GetContextPositions(); + for (auto it=map.cbegin(); it!=map.cend(); it++) { + // Add to the context + command->add_child(new NodeSetPositionCommand(it.key(), copy, it.value())); } } @@ -2318,119 +2347,58 @@ Project *Node::ArrayResizeCommand::GetRelevantProject() const return node_->project(); } -void NodeSetPositionAndShiftSurroundingsCommand::redo() -{ - if (commands_.isEmpty()) { - // Move first node - NodeSetPositionCommand* set_pos_command = new NodeSetPositionCommand(node_, relative_, position_, move_dependencies_); - set_pos_command->redo_now(); - commands_.append(set_pos_command); - - // Get bounding rect - qreal bounding_rect_sz = 1.0; - qreal bounding_rect_half_sz = bounding_rect_sz * 0.5; - QRectF bounding_rect(position_.x() - bounding_rect_half_sz, position_.y() - bounding_rect_half_sz, bounding_rect_sz, bounding_rect_sz); - - // Start moving other nodes - foreach (Node* surrounding, node_->parent()->nodes()) { - if (surrounding != node_) { - QPointF surrounding_position = node_->parent()->GetNodePosition(surrounding, relative_); - if (bounding_rect.contains(surrounding_position)) { - QPointF new_pos = surrounding_position; - - qreal move_rate = 0.50; - - if (surrounding_position.y() < position_.y()) { - move_rate = -move_rate; - } - - new_pos.setY(new_pos.y() + move_rate); - - auto sur_command = new NodeSetPositionAndShiftSurroundingsCommand(surrounding, relative_, new_pos, true); - sur_command->redo(); - commands_.append(sur_command); - } - } - } - } else { - for (int i=0; iredo_now(); - } - } -} - void NodeSetPositionCommand::redo() { - graph_ = node_->parent(); - if (!(added_ = !graph_->NodeMapContainsNode(node_, relevant_))) { - old_pos_ = graph_->GetNodePosition(node_, relevant_); + if (!(added_ = !context_->ContextContainsNode(node_))) { + old_pos_ = context_->GetNodePositionInContext(node_); + } + + if (added_) { + context_->SetNodePositionInContext(node_, pos_); + } else { + move(context_, node_, pos_ - old_pos_, move_deps_); } - graph_->SetNodePosition(node_, relevant_, pos_); } void NodeSetPositionCommand::undo() { if (added_) { - graph_->RemoveNodePosition(node_, relevant_); + context_->RemoveNodeFromContext(node_); } else { - graph_->SetNodePosition(node_, relevant_, old_pos_); + move(context_, node_, old_pos_ - pos_, move_deps_); } } -void NodeSetPositionAsChildCommand::redo() +void NodeSetPositionCommand::move(Node *context, Node *node, const QPointF &diff, bool recursive) { - if (!sub_command_) { - // Calculate position of node - NodeGraph *graph = parent_->parent(); - QPointF pos = graph->GetNodePosition(parent_, relative_); + QPointF p = context->GetNodePositionInContext(node); + p += diff; + context->SetNodePositionInContext(node, p); - // This is a dependency, so we'll place it one X before - pos.setX(pos.x() - 1); - - // The Y will be calculated using the index and child count - pos.setY(pos.y() - (double(child_count_)*0.5) + this_index_ + 0.5); - - sub_command_ = new MultiUndoCommand(); - if (shift_surroundings_) { - sub_command_->add_child(new NodeSetPositionAndShiftSurroundingsCommand(node_, relative_, pos, true)); - } else { - sub_command_->add_child(new NodeSetPositionCommand(node_, relative_, pos, true)); + if (recursive) { + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + Node *output = it->second; + if (context->ContextContainsNode(output)) { + move(context, output, diff, recursive); + } } } - - sub_command_->redo_now(); -} - -void NodeSetPositionToOffsetOfAnotherNodeCommand::redo() -{ - NodeGraph *graph = node_->parent(); - old_pos_ = graph->GetNodePosition(node_, relative_); - graph->SetNodePosition(node_, relative_, graph->GetNodePosition(other_node_, relative_) + offset_); -} - -void NodeSetPositionToOffsetOfAnotherNodeCommand::undo() -{ - NodeGraph *graph = node_->parent(); - graph->SetNodePosition(node_, relative_, old_pos_); } void NodeRemovePositionFromContextCommand::redo() { - NodeGraph *graph = node_->parent(); - - contained_ = graph->ContextContainsNode(node_, context_); + contained_ = context_->ContextContainsNode(node_); if (contained_) { - old_pos_ = graph->GetNodePosition(node_, context_); - graph->RemoveNodePosition(node_, context_); + old_pos_ = context_->GetNodePositionInContext(node_); + context_->RemoveNodeFromContext(node_); } } void NodeRemovePositionFromContextCommand::undo() { if (contained_) { - NodeGraph *graph = node_->parent(); - graph->SetNodePosition(node_, context_, old_pos_); + context_->SetNodePositionInContext(node_, old_pos_); } } @@ -2438,28 +2406,21 @@ void NodeRemovePositionFromAllContextsCommand::redo() { NodeGraph *graph = node_->parent(); - if (points_.empty()) { - // No points yet, let's see what points we should remove - auto map = graph->GetPositionMap(); - for (auto it=map.cbegin(); it!=map.cend(); it++) { - if (it.value().contains(node_)) { - points_.insert({it.key(), it.value().value(node_)}); - } + foreach (Node* context, graph->nodes()) { + if (context->ContextContainsNode(node_)) { + contexts_.insert({context, context->GetNodePositionInContext(node_)}); + context->RemoveNodeFromContext(node_); } } - - for (auto it=points_.cbegin(); it!=points_.cend(); it++) { - graph->RemoveNodePosition(node_, it->first); - } } void NodeRemovePositionFromAllContextsCommand::undo() { - NodeGraph *graph = node_->parent(); - - for (auto it=points_.crbegin(); it!=points_.crend(); it++) { - graph->SetNodePosition(node_, it->first, it->second); + for (auto it = contexts_.crbegin(); it != contexts_.crend(); it++) { + it->first->SetNodePositionInContext(node_, it->second); } + + contexts_.clear(); } void Node::ValueHint::Hash(QCryptographicHash &hash) const diff --git a/app/node/node.h b/app/node/node.h index b22af3fff..fceb08d1c 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -208,6 +208,23 @@ public: return HasInputWithID(id); } + using PositionMap = QHash; + const PositionMap &GetContextPositions() const + { + return context_positions_; + } + + bool ContextContainsNode(Node *node) const + { + return context_positions_.contains(node); + } + + QPointF GetNodePositionInContext(Node *node); + + bool SetNodePositionInContext(Node *node, const QPointF &pos); + + bool RemoveNodeFromContext(Node *node); + /** * @brief Retrieve the color of this node */ @@ -248,6 +265,7 @@ public: void LoadInput(QXmlStreamReader* reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled); void SaveInput(QXmlStreamWriter* writer, const QString& id) const; + bool IsInputHidden(const QString& input) const; bool IsInputConnectable(const QString& input) const; bool IsInputKeyframable(const QString& input) const; @@ -867,7 +885,8 @@ protected: kInputFlagNormal = 0x0, kInputFlagArray = 0x1, kInputFlagNotKeyframable = 0x2, - kInputFlagNotConnectable = 0x4 + kInputFlagNotConnectable = 0x4, + kInputFlagHidden = 0x8 }; class InputFlags { @@ -1037,6 +1056,12 @@ signals: void RemovedFromGraph(NodeGraph* graph); + void NodeAddedToContext(Node *node); + + void NodePositionInContextChanged(Node *node, const QPointF &pos); + + void NodeRemovedFromContext(Node *node); + private: class ArrayInsertCommand : public UndoCommand { @@ -1258,6 +1283,8 @@ private: QMap value_hints_; + PositionMap context_positions_; + private slots: /** * @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time @@ -1367,10 +1394,10 @@ using NodePtr = std::shared_ptr; class NodeSetPositionCommand : public UndoCommand { public: - NodeSetPositionCommand(Node* node, Node* relevant, const QPointF& pos, bool move_dependencies_relatively) + NodeSetPositionCommand(Node* node, Node* context, const QPointF& pos, bool move_dependencies_relatively = false) { node_ = node; - relevant_ = relevant; + context_ = context; pos_ = pos; move_deps_ = move_dependencies_relatively; } @@ -1386,151 +1413,14 @@ protected: virtual void undo() override; private: + static void move(Node *context, Node *node, const QPointF &diff, bool recursive); + Node* node_; - Node* relevant_; + Node* context_; QPointF pos_; QPointF old_pos_; bool added_; bool move_deps_; - NodeGraph *graph_; - -}; - -class NodeSetPositionAndShiftSurroundingsCommand : public UndoCommand -{ -public: - NodeSetPositionAndShiftSurroundingsCommand(Node* node, Node *relative, const QPointF& pos, bool move_dependencies_relatively) : - node_(node), - relative_(relative), - position_(pos), - move_dependencies_(move_dependencies_relatively) - {} - - virtual ~NodeSetPositionAndShiftSurroundingsCommand() override - { - qDeleteAll(commands_); - } - - virtual Project * GetRelevantProject() const override - { - return node_->project(); - } - -protected: - virtual void redo() override; - - virtual void undo() override - { - for (int i=commands_.size()-1; i>=0; i--) { - commands_.at(i)->undo_now(); - } - } - -private: - Node* node_; - - Node *relative_; - - QPointF position_; - - bool move_dependencies_; - - QVector commands_; - -}; - -class NodeSetPositionAsChildCommand : public UndoCommand -{ -public: - NodeSetPositionAsChildCommand(Node* node, Node* parent, Node *relative, double this_index, int child_count, bool shift_surroundings) : - node_(node), - parent_(parent), - relative_(relative), - this_index_(this_index), - child_count_(child_count), - shift_surroundings_(shift_surroundings), - sub_command_(nullptr) - { - } - - virtual ~NodeSetPositionAsChildCommand() override - { - delete sub_command_; - } - - virtual Project * GetRelevantProject() const override - { - return node_->project(); - } - -protected: - virtual void redo() override; - - virtual void undo() override - { - sub_command_->undo_now(); - } - -private: - Node* node_; - Node* parent_; - Node *relative_; - - double this_index_; - int child_count_; - - bool shift_surroundings_; - - MultiUndoCommand* sub_command_; - -}; - -class NodePositionCloseChildGapCommand : public UndoCommand -{ -public: - NodePositionCloseChildGapCommand(Node *parent, void *relative, int remove_index, int child_count, bool shift_surroundings); - - virtual Project * GetRelevantProject() const override - { - return parent_->project(); - } - -protected: - virtual void redo() override; - - virtual void undo() override; - -private: - Node *parent_; - -}; - -class NodeSetPositionToOffsetOfAnotherNodeCommand : public UndoCommand -{ -public: - NodeSetPositionToOffsetOfAnotherNodeCommand(Node* node, Node* other_node, Node *relative, const QPointF& offset) : - node_(node), - other_node_(other_node), - relative_(relative), - offset_(offset) - {} - - virtual Project * GetRelevantProject() const override - { - return node_->project(); - } - -protected: - virtual void redo() override; - - virtual void undo() override; - -private: - Node* node_; - Node* other_node_; - Node *relative_; - QPointF offset_; - QPointF old_pos_; }; @@ -1585,7 +1475,7 @@ protected: private: Node *node_; - std::map points_; + std::map contexts_; }; diff --git a/app/node/nodecopypaste.cpp b/app/node/nodecopypaste.cpp index 2d66d72ae..0de513820 100644 --- a/app/node/nodecopypaste.cpp +++ b/app/node/nodecopypaste.cpp @@ -53,11 +53,11 @@ void NodeCopyPasteService::CopyNodesToClipboard(const QVector &nodes, vo writer.writeStartElement(QStringLiteral("contexts")); foreach (Node* n, nodes) { // Determine if this node is a context - if (n->parent()->GetPositionMap().contains(n)) { + if (!n->GetContextPositions().isEmpty()) { writer.writeStartElement(QStringLiteral("context")); writer.writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(n))); - const NodeGraph::PositionMap &map = n->parent()->GetNodesForContext(n); + const Node::PositionMap &map = n->GetContextPositions(); for (auto it=map.cbegin(); it!=map.cend(); it++) { writer.writeStartElement(QStringLiteral("node")); Project::SavePosition(&writer, it.key(), it.value()); @@ -220,7 +220,7 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { Node *subnode = xml_node_data.node_ptrs.value(jt.key()); if (subnode) { - command->add_child(new NodeSetPositionCommand(subnode, context, jt.value(), false)); + command->add_child(new NodeSetPositionCommand(subnode, context, jt.value())); } } } diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index e1cb208d6..9506f4513 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -44,7 +44,7 @@ Track::Track() : locked_(false), sequence_(nullptr) { - AddInput(kBlockInput, NodeValue::kNone, InputFlags(kInputFlagArray | kInputFlagNotKeyframable)); + AddInput(kBlockInput, NodeValue::kNone, InputFlags(kInputFlagArray | kInputFlagNotKeyframable | kInputFlagHidden)); // Since blocks are time based, we can handle the invalidate timing a little more intelligently // on our end diff --git a/app/node/param.cpp b/app/node/param.cpp index 9f6bd8c4d..f0a685abe 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -33,6 +33,15 @@ QString NodeInput::name() const } } +bool NodeInput::IsHidden() const +{ + if (IsValid()) { + return node_->IsInputHidden(input_); + } else { + return false; + } +} + bool NodeInput::IsConnected() const { if (IsValid()) { diff --git a/app/node/param.h b/app/node/param.h index 63aa413f7..3de4b6727 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -115,6 +115,8 @@ public: return node_ && !input_.isEmpty() && element_ >= -1; } + bool IsHidden() const; + bool IsConnected() const; bool IsKeyframing() const; diff --git a/app/node/project/folder/folder.cpp b/app/node/project/folder/folder.cpp index feae6d612..7b017923f 100644 --- a/app/node/project/folder/folder.cpp +++ b/app/node/project/folder/folder.cpp @@ -113,19 +113,12 @@ void Folder::InputDisconnectedEvent(const QString &input, int element, Node *out } } -FolderAddChild::FolderAddChild(Folder *folder, Node *child, bool autoposition) : +FolderAddChild::FolderAddChild(Folder *folder, Node *child) : folder_(folder), - child_(child), - autoposition_(autoposition), - position_command_(nullptr) + child_(child) { } -FolderAddChild::~FolderAddChild() -{ - delete position_command_; -} - Project *FolderAddChild::GetRelevantProject() const { return folder_->project(); @@ -136,21 +129,10 @@ void FolderAddChild::redo() int array_index = folder_->InputArraySize(Folder::kChildInput); folder_->InputArrayAppend(Folder::kChildInput, false); Node::ConnectEdge(child_, NodeInput(folder_, Folder::kChildInput, array_index)); - - if (autoposition_) { - if (!position_command_) { - position_command_ = new NodeSetPositionAsChildCommand(child_, folder_, folder_->project()->root(), array_index, array_index+1, true); - } - position_command_->redo_now(); - } } void FolderAddChild::undo() { - if (position_command_) { - position_command_->undo_now(); - } - Node::DisconnectEdge(child_, NodeInput(folder_, Folder::kChildInput, folder_->InputArraySize(Folder::kChildInput)-1)); folder_->InputArrayRemoveLast(Folder::kChildInput); } diff --git a/app/node/project/folder/folder.h b/app/node/project/folder/folder.h index 1303ce7d7..12677e57b 100644 --- a/app/node/project/folder/folder.h +++ b/app/node/project/folder/folder.h @@ -208,9 +208,7 @@ private: class FolderAddChild : public UndoCommand { public: - FolderAddChild(Folder* folder, Node* child, bool autoposition = true); - - virtual ~FolderAddChild() override; + FolderAddChild(Folder* folder, Node* child); virtual Project * GetRelevantProject() const override; @@ -224,10 +222,6 @@ private: Node* child_; - bool autoposition_; - - NodeSetPositionAsChildCommand* position_command_; - }; } diff --git a/app/node/project/project.cpp b/app/node/project/project.cpp index f865e28c8..64d7cb107 100644 --- a/app/node/project/project.cpp +++ b/app/node/project/project.cpp @@ -44,19 +44,16 @@ Project::Project() : root_->setParent(this); root_->SetLabel(tr("Root")); root_->SetCanBeDeleted(false); - SetNodePosition(root_, root_, QPointF(0, 0)); // Adds a color manager "node" to this project so that it synchronizes color_manager_ = new ColorManager(); color_manager_->setParent(this); - SetNodePosition(color_manager_, root_, QPointF(1, 0)); color_manager_->SetCanBeDeleted(false); AddDefaultNode(color_manager_); // Same with project settings settings_ = new ProjectSettingsNode(); settings_->setParent(this); - SetNodePosition(settings_, root_, QPointF(2, 0)); settings_->SetCanBeDeleted(false); AddDefaultNode(settings_); @@ -167,7 +164,7 @@ void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, uint Node *node = xml_node_data.node_ptrs.value(node_ptr); if (node) { - SetNodePosition(node, context, node_pos); + context->SetNodePositionInContext(node, node_pos); } else { qWarning() << "Failed to find pointer for node position"; reader->skipCurrentElement(); @@ -230,20 +227,22 @@ void Project::Save(QXmlStreamWriter *writer) const writer->writeStartElement(QStringLiteral("positions")); - for (auto it=GetPositionMap().cbegin(); it!=GetPositionMap().cend(); it++) { - writer->writeStartElement(QStringLiteral("context")); + foreach (Node* context, nodes()) { + const Node::PositionMap &map = context->GetContextPositions(); - writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(it.key()))); + if (!map.isEmpty()) { + writer->writeStartElement(QStringLiteral("context")); - const PositionMap &map = it.value(); + writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(context))); - for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { - writer->writeStartElement(QStringLiteral("node")); - SavePosition(writer, jt.key(), jt.value()); - writer->writeEndElement(); // node + for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { + writer->writeStartElement(QStringLiteral("node")); + SavePosition(writer, jt.key(), jt.value()); + writer->writeEndElement(); // node + } + + writer->writeEndElement(); // context } - - writer->writeEndElement(); // context } writer->writeEndElement(); // positions diff --git a/app/node/project/sequence/sequence.cpp b/app/node/project/sequence/sequence.cpp index 27b17708f..0cb443188 100644 --- a/app/node/project/sequence/sequence.cpp +++ b/app/node/project/sequence/sequence.cpp @@ -41,7 +41,7 @@ Sequence::Sequence() // Create track input QString track_input_id = kTrackInputFormat.arg(i); - AddInput(track_input_id, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray)); + AddInput(track_input_id, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); IgnoreInvalidationsFrom(track_input_id); diff --git a/app/panel/node/node.cpp b/app/panel/node/node.cpp index 591f7a867..3f0e3a4a2 100644 --- a/app/panel/node/node.cpp +++ b/app/panel/node/node.cpp @@ -38,6 +38,7 @@ NodePanel::NodePanel(QWidget *parent) : // Create NodeView widget node_view_ = new NodeView(this); outer_layout->addWidget(node_view_); + connect(this, &NodePanel::visibilityChanged, node_view_, &NodeView::CenterOnItemsBoundingRect); // Connect toolbar to NodeView connect(toolbar_, &NodeViewToolBar::MiniMapEnabledToggled, node_view_, &NodeView::SetMiniMapEnabled); diff --git a/app/panel/node/node.h b/app/panel/node/node.h index d145bb91d..20eccc79a 100644 --- a/app/panel/node/node.h +++ b/app/panel/node/node.h @@ -36,20 +36,15 @@ class NodePanel : public PanelWidget public: NodePanel(QWidget* parent); - NodeGraph* GetGraph() const + void SetContexts(const QVector &nodes) { - return node_view_->GetGraph(); + node_view_->SetContexts(nodes); + toolbar_->setEnabled(!nodes.isEmpty()); } - void SetGraph(NodeGraph *graph, const QVector &nodes) + void CloseContextsBelongingToProject(Project *project) { - node_view_->SetGraph(graph, nodes); - toolbar_->setEnabled(graph); - } - - void ClearGraph() - { - node_view_->ClearGraph(); + node_view_->CloseContextsBelongingToProject(project); } const QVector &GetCurrentContexts() const @@ -113,11 +108,6 @@ public slots: node_view_->Select(nodes, center_view_on_item); } - void SelectWithDependencies(const QVector& nodes, bool center_view_on_item) - { - node_view_->SelectWithDependencies(nodes, center_view_on_item); - } - signals: void NodesSelected(const QVector& nodes); diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 31be310c6..4c8385d9d 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -42,15 +42,13 @@ const double NodeView::kMinimumScale = 0.1; NodeView::NodeView(QWidget *parent) : HandMovableView(parent), - graph_(nullptr), drop_edge_(nullptr), create_edge_(nullptr), create_edge_dst_(nullptr), create_edge_dst_temp_expanded_(false), paste_command_(nullptr), - filter_mode_(kFilterShowSelective), scale_(1.0), - queue_reposition_contexts_(false) + first_show_(true) { setScene(&scene_); SetDefaultDragMode(RubberBandDrag); @@ -84,10 +82,10 @@ NodeView::~NodeView() ClearGraph(); } -void NodeView::SetGraph(NodeGraph *graph, const QVector &nodes) +void NodeView::SetContexts(const QVector &nodes) { // Remove contexts that are no longer in the list - foreach (Node *n, filter_nodes_) { + foreach (Node *n, contexts_) { if (!nodes.contains(n)) { scene_.RemoveContext(n); } @@ -95,86 +93,38 @@ void NodeView::SetGraph(NodeGraph *graph, const QVector &nodes) // Add contexts that are now in the list foreach (Node *n, nodes) { - if (!filter_nodes_.contains(n)) { + if (!contexts_.contains(n)) { scene_.AddContext(n); } } - filter_nodes_ = nodes; + contexts_ = nodes; - /*bool graph_changed = graph_ != graph; - bool context_changed = last_set_filter_nodes_ != nodes; + CenterOnItemsBoundingRect(); +} - if (graph_changed || context_changed) { - // Clear nodes if necessary - bool refresh_required = (graph_changed && filter_mode_ == kFilterShowAll) - || (context_changed && filter_mode_ == kFilterShowSelective); - bool nodes_visible = (graph && filter_mode_ == kFilterShowAll) - || (!nodes.isEmpty() && filter_mode_ == kFilterShowSelective); +void NodeView::CloseContextsBelongingToProject(Project *project) +{ + QVector new_contexts = contexts_; - if (refresh_required) { - DeselectAll(); - positions_.clear(); - scene_.clear(); - context_offsets_.clear(); + for (auto it = new_contexts.begin(); it != new_contexts.end(); ) { + if ((*it)->project() == project) { + it = new_contexts.erase(it); + } else { + it++; } + } - // Handle graph change - if (graph_changed) { - if (graph_) { - // Disconnect from current graph - disconnect(graph_, &NodeGraph::NodeRemoved, this, &NodeView::RemoveNode); - disconnect(graph_, &NodeGraph::InputConnected, this, &NodeView::AddEdge); - disconnect(graph_, &NodeGraph::InputDisconnected, this, &NodeView::RemoveEdge); - disconnect(graph_, &NodeGraph::NodePositionAdded, this, &NodeView::AddNodePosition); - disconnect(graph_, &NodeGraph::NodePositionRemoved, this, &NodeView::RemoveNodePosition); - } - - graph_ = graph; - - if (graph_) { - // Connect to new graph - connect(graph_, &NodeGraph::NodeRemoved, this, &NodeView::RemoveNode); - connect(graph_, &NodeGraph::InputConnected, this, &NodeView::AddEdge); - connect(graph_, &NodeGraph::InputDisconnected, this, &NodeView::RemoveEdge); - connect(graph_, &NodeGraph::NodePositionAdded, this, &NodeView::AddNodePosition); - connect(graph_, &NodeGraph::NodePositionRemoved, this, &NodeView::RemoveNodePosition); - } - } - - if (context_changed) { - last_set_filter_nodes_ = nodes; - - if (filter_mode_ == kFilterShowSelective) { - filter_nodes_ = nodes; - } - } - - if (refresh_required && nodes_visible) { - if (filter_mode_ == kFilterShowAll) { - // Just make the filter nodes all of the graph's contexts - filter_nodes_ = graph->GetPositionMap().keys().toVector(); - } - - RepositionContexts(); - - // Center on something - QMetaObject::invokeMethod(this, &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); - } - }*/ + SetContexts(new_contexts); } void NodeView::ClearGraph() { - SetGraph(nullptr, QVector()); + SetContexts(QVector()); } void NodeView::DeleteSelected() { - if (!graph_) { - return; - } - MultiUndoCommand* command = new MultiUndoCommand(); { @@ -189,9 +139,6 @@ void NodeView::DeleteSelected() command->add_child(new NodeEdgeRemoveCommand(edge->output(), edge->input())); removed_connections[i] = {edge->output(), edge->input()}; } - - // Update contexts - UpdateContextsFromEdgeRemove(command, removed_connections); } } @@ -219,10 +166,6 @@ void NodeView::DeleteSelected() void NodeView::SelectAll() { - if (!graph_) { - return; - } - // Optimization: rather than respond to every single item being selected, ignore the signal and // then handle them all at the end. DisconnectSelectionChangedSignal(); @@ -249,7 +192,7 @@ void NodeView::SelectAll() void NodeView::DeselectAll() { - if (!graph_ || selected_nodes_.isEmpty()) { + if (selected_nodes_.isEmpty()) { return; } @@ -268,10 +211,6 @@ void NodeView::DeselectAll() void NodeView::Select(QVector nodes, bool center_view_on_item) { - if (!graph_) { - return; - } - // Optimization: rather than respond to every single item being selected, ignore the signal and // then handle them all at the end. DisconnectSelectionChangedSignal(); @@ -331,39 +270,13 @@ void NodeView::Select(QVector nodes, bool center_view_on_item) selected_nodes_ = nodes; } -void NodeView::SelectWithDependencies(QVector nodes, bool center_view_on_item) -{ - if (!graph_) { - return; - } - - int original_length = nodes.size(); - for (int i=0;i dependencies = nodes.at(i)->GetDependencies(); - - foreach (Node *d, dependencies) { - if (scene_.item_map().contains(d) && !nodes.contains(d)) { - nodes.append(d); - } - } - } - - Select(nodes, center_view_on_item); -} - void NodeView::CopySelected(bool cut) { - if (!graph_) { + if (selected_nodes_.isEmpty()) { return; } - QVector selected = scene_.GetSelectedNodes(); - - if (selected.isEmpty()) { - return; - } - - CopyNodesToClipboard(selected); + CopyNodesToClipboard(selected_nodes_); if (cut) { DeleteSelected(); @@ -409,43 +322,41 @@ void NodeView::keyPressEvent(QKeyEvent *event) case Qt::Key_Up: case Qt::Key_Down: { - if (graph_) { - MultiUndoCommand *pos_command = new MultiUndoCommand(); - for (Node *n : qAsConst(selected_nodes_)) { - for (Node *context : qAsConst(filter_nodes_)) { - if (graph_->GetNodesForContext(context).contains(n)) { - QPointF old_pos = graph_->GetNodePosition(n, context); + MultiUndoCommand *pos_command = new MultiUndoCommand(); + for (Node *n : qAsConst(selected_nodes_)) { + for (Node *context : qAsConst(contexts_)) { + if (context->ContextContainsNode(n)) { + QPointF old_pos = context->GetNodePositionInContext(n); - // Determine one pixel in scene units - double movement_amt = 1.0 / scale_; + // Determine one pixel in scene units + double movement_amt = 1.0 / scale_; - // Translate to 2D movement - QPointF node_movement; - switch (event->key()) { - case Qt::Key_Left: - node_movement.setX(-movement_amt); - break; - case Qt::Key_Right: - node_movement.setX(movement_amt); - break; - case Qt::Key_Up: - node_movement.setY(-movement_amt); - break; - case Qt::Key_Down: - node_movement.setY(movement_amt); - break; - } - - // Translate from screen units into node units - node_movement = NodeViewItem::ScreenToNodePoint(node_movement, scene_.GetFlowDirection()); - - // Move command - pos_command->add_child(new NodeSetPositionCommand(n, context, old_pos + node_movement, false)); + // Translate to 2D movement + QPointF node_movement; + switch (event->key()) { + case Qt::Key_Left: + node_movement.setX(-movement_amt); + break; + case Qt::Key_Right: + node_movement.setX(movement_amt); + break; + case Qt::Key_Up: + node_movement.setY(-movement_amt); + break; + case Qt::Key_Down: + node_movement.setY(movement_amt); + break; } + + // Translate from screen units into node units + node_movement = NodeViewItem::ScreenToNodePoint(node_movement, scene_.GetFlowDirection()); + + // Move command + pos_command->add_child(new NodeSetPositionCommand(n, context, old_pos + node_movement)); } } - Core::instance()->undo_stack()->pushIfHasChildren(pos_command); } + Core::instance()->undo_stack()->pushIfHasChildren(pos_command); break; } case Qt::Key_Escape: @@ -664,15 +575,6 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) create_edge_dst_ = nullptr; } - // Update contexts - /*if (!removed_edges.empty()) { - UpdateContextsFromEdgeRemove(command, removed_edges); - } - - if (added_edge.first) { - UpdateContextsFromEdgeAdd(command, added_edge, removed_edges); - }*/ - Core::instance()->undo_stack()->pushIfHasChildren(command); return; } @@ -689,38 +591,6 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } } - { - // If any node positions changed, set them in their contexts now - MultiUndoCommand *set_pos_command = new MultiUndoCommand(); - for (auto it=positions_.begin(); it!=positions_.end(); it++) { - NodeViewItem *item = it.key(); - Position &pos_data = it.value(); - QPointF current_item_pos = item->GetNodePosition(); - Node *node = pos_data.node; - - if (pos_data.original_item_pos != current_item_pos) { - QPointF diff = current_item_pos - pos_data.original_item_pos; - - for (Node *context : qAsConst(filter_nodes_)) { - if (graph_->ContextContainsNode(node, context)) { - QPointF current_node_pos_in_context = graph_->GetNodePosition(node, context); - current_node_pos_in_context += diff; - set_pos_command->add_child(new NodeSetPositionCommand(node, context, current_node_pos_in_context, false)); - } - } - - pos_data.original_item_pos = current_item_pos; - } - } - if (set_pos_command->child_count()) { - set_pos_command->redo_now(); - command->add_child(set_pos_command); - } else { - delete set_pos_command; - } - } - - if (!attached_items_.isEmpty()) { { // Dropped attached item onto an edge, connect it between them @@ -757,7 +627,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) bool removed = false; QVector relevant_contexts; - for (Node *context : qAsConst(filter_nodes_)) { + for (Node *context : qAsConst(contexts_)) { if (attached_node->OutputsTo(context, true)) { relevant_contexts.append(context); } else { @@ -768,7 +638,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) if (removed && !relevant_contexts.isEmpty()) { for (Node *relevant : qAsConst(relevant_contexts)) { - remove_pos_subcommand->add_child(new NodeSetPositionCommand(attached_node, relevant, GetEstimatedPositionForContext(attached.item, relevant), false)); + remove_pos_subcommand->add_child(new NodeSetPositionCommand(attached_node, relevant, GetEstimatedPositionForContext(attached.item, relevant))); } remove_pos_command->add_child(remove_pos_subcommand); @@ -844,7 +714,7 @@ void NodeView::UpdateSelectionCache() void NodeView::ShowContextMenu(const QPoint &pos) { - if (filter_nodes_.isEmpty()) { + if (contexts_.isEmpty()) { return; } @@ -896,17 +766,6 @@ void NodeView::ShowContextMenu(const QPoint &pos) m.addSeparator(); - Menu* filter_menu = new Menu(tr("Filter"), &m); - m.addMenu(filter_menu); - - filter_menu->AddActionWithData(tr("Show All Nodes"), kFilterShowAll, filter_mode_); - - filter_menu->AddActionWithData(tr("Show Selected"), kFilterShowSelective, filter_mode_); - - connect(filter_menu, &Menu::triggered, this, &NodeView::ContextMenuFilterChanged); - - - Menu* direction_menu = new Menu(tr("Direction"), &m); m.addMenu(direction_menu); @@ -940,23 +799,20 @@ void NodeView::ShowContextMenu(const QPoint &pos) void NodeView::CreateNodeSlot(QAction *action) { - if (!graph_) { - return; - } - - Node* new_node = NodeFactory::CreateFromMenuAction(action); + qDebug() << "STUB!"; + /*Node* new_node = NodeFactory::CreateFromMenuAction(action); if (new_node) { paste_command_ = new MultiUndoCommand(); paste_command_->add_child(new NodeAddCommand(graph_, new_node)); - for (Node *context : qAsConst(filter_nodes_)) { + for (Node *context : qAsConst(contexts_)) { paste_command_->add_child(new NodeSetPositionCommand(new_node, context, QPointF(0, 0), false)); } paste_command_->add_child(new NodeViewAttachNodesToCursor(this, {new_node})); paste_command_->redo_now(); this->setFocus(); - } + }*/ } void NodeView::ContextMenuSetDirection(QAction *action) @@ -964,26 +820,6 @@ void NodeView::ContextMenuSetDirection(QAction *action) SetFlowDirection(static_cast(action->data().toInt())); } -void NodeView::ContextMenuFilterChanged(QAction *action) -{ - FilterMode mode = static_cast(action->data().toInt()); - - if (filter_mode_ != mode) { - // Store temporary graph variables - NodeGraph *graph = graph_; - QVector nodes = last_set_filter_nodes_; - - // Unset graph with current filter mode - ClearGraph(); - - // Change filter mode - filter_mode_ = mode; - - // Re-set graph with new filter mode - SetGraph(graph, nodes); - } -} - void NodeView::OpenSelectedNodeInViewer() { QVector selected = scene_.GetSelectedNodes(); @@ -994,100 +830,6 @@ void NodeView::OpenSelectedNodeInViewer() } } -// Commenting out because there shouldn't be any situations where a node would be added without -// being in a context. We're keeping RemoveNode as a fail-safe because it could provide crash -// resistance where RemoveNodePosition might be missed. -//void NodeView::AddNode(Node *node) -//{ -// if (filter_mode_ == kFilterShowAll) { -// scene_.AddNode(node); -// } -//} - -void NodeView::RemoveNode(Node *node) -{ - for (auto it=attached_items_.begin(); it!=attached_items_.end(); ) { - if (it->item->GetNode() == node) { - it = attached_items_.erase(it); - } else { - it++; - } - } - for (const Node::OutputConnection &oc : node->output_connections()) { - scene_.RemoveEdge(oc.first, oc.second); - } - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - scene_.RemoveEdge(it->second, it->first); - } - positions_.remove(scene_.item_map().value(node)); -} - -void NodeView::AddEdge(Node *output, const NodeInput &input) -{ - Node *output_node = output; - Node *input_node = input.node(); - - if (scene_.item_map().contains(output_node) && scene_.item_map().contains(input_node)) { - scene_.AddEdge(output, input); - } -} - -void NodeView::RemoveEdge(Node *output, const NodeInput &input) -{ - scene_.RemoveEdge(output, input); -} - -void NodeView::AddNodePosition(Node *node, Node *relative) -{ - bool listening_to_node = filter_nodes_.contains(relative); - - if (!listening_to_node) { - if (filter_mode_ == kFilterShowAll) { - // We're not listening to this context, but because we're showing all, add it - filter_nodes_.append(relative); - } else { - // Ignore signal - return; - } - } - - // Reposition contexts because one of their heights may have changed or a new one may have been - // added - UpdateNodeItem(node); - - if (filter_mode_ == kFilterShowAll) { - queue_reposition_contexts_ = true; - viewport()->update(); - } -} - -void NodeView::RemoveNodePosition(Node *node, Node *relative) -{ - if (filter_nodes_.contains(relative)) { - NodeViewItem *item = scene_.item_map().value(node); - - if (item && !item->GetPreventRemoving()) { - // Determine if any other contexts have this node - bool found = false; - - for (Node *context : qAsConst(filter_nodes_)) { - if (graph_->ContextContainsNode(node, context)) { - found = true; - break; - } - } - - if (!found) { - RemoveNode(node); - } - } - - if (filter_mode_ == kFilterShowAll) { - RepositionContexts(); - } - } -} - void NodeView::UpdateSceneBoundingRect() { // Get current items bounding rect @@ -1229,13 +971,6 @@ bool NodeView::event(QEvent *event) bool NodeView::eventFilter(QObject *object, QEvent *event) { - if (object == viewport() && event->type() == QEvent::Paint) { - if (queue_reposition_contexts_) { - RepositionContexts(); - queue_reposition_contexts_ = false; - } - } - return super::eventFilter(object, event); } @@ -1259,7 +994,8 @@ void NodeView::CopyNodesToClipboardInternal(QXmlStreamWriter *writer, const QVec void NodeView::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, void *userdata) { - NodeGraph::PositionMap *map = static_cast(userdata); + qDebug() << "STUB!"; + /*NodeGraph::PositionMap *map = static_cast(userdata); while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("pos")) { @@ -1295,7 +1031,7 @@ void NodeView::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNode } else { reader->skipCurrentElement(); } - } + }*/ } void NodeView::ZoomFromKeyboard(double multiplier) @@ -1310,148 +1046,6 @@ void NodeView::ZoomFromKeyboard(double multiplier) ZoomIntoCursorPosition(nullptr, multiplier, cursor_pos); } -bool NodeView::DetermineIfNodeIsFloatingInContext(Node *node, Node *context, Node *source, const Node::OutputConnections &removed_edges, const Node::OutputConnection &added_edge) -{ - // Determines whether `node` outputs to another node in `context` besides `source` - for (const Node::OutputConnection &conn : node->output_connections()) { - Node *output_candidate = conn.second.node(); - - if (output_candidate == source) { - continue; - } - - if (graph_->ContextContainsNode(output_candidate, context)) { - if (!output_candidate->OutputsTo(source, true, removed_edges, added_edge)) { - return true; - } - } - } - - return false; -} - -void NodeView::UpdateContextsFromEdgeRemove(MultiUndoCommand *command, const Node::OutputConnections &remove_edges) -{ - // For each edge we remove, determine if we should remove the node from a context as well - for (const Node::OutputConnection &edge : remove_edges) { - Node *output_node = edge.first; - QVector contexts_to_remove_from; - int contexts_containing = 0; - - for (auto it=graph_->GetPositionMap().cbegin(); it!=graph_->GetPositionMap().cend(); it++) { - Node *context = it.key(); - - if (it.value().contains(output_node)) { - bool currently_outputs = output_node->OutputsTo(context, true); - bool will_output_after_operation = output_node->OutputsTo(context, true, remove_edges); - - if (currently_outputs && !will_output_after_operation) { - // Will remove - contexts_to_remove_from.append(context); - } - - contexts_containing++; - } - } - - // Removing from all current contexts, convert to a floating node (i.e. don't remove from the context) - if (contexts_to_remove_from.size() != contexts_containing) { - // Not removing from all contexts, can remove - bool removing_from_all_current_contexts = true; - - for (Node *context : qAsConst(filter_nodes_)) { - if (graph_->ContextContainsNode(output_node, context)) { - if (!contexts_to_remove_from.contains(context)) { - removing_from_all_current_contexts = false; - break; - } - } - } - - for (Node *context : qAsConst(contexts_to_remove_from)) { - RecursivelyRemoveFloatingNodeFromContext(command, output_node, context, output_node, remove_edges, Node::OutputConnection(), removing_from_all_current_contexts); - } - } - } -} - -void NodeView::RecursivelyRemoveFloatingNodeFromContext(MultiUndoCommand *command, Node *node, Node *context, Node *source, const Node::OutputConnections &removed_edges, const Node::OutputConnection &added_edge, bool prevent_removing) -{ - if (prevent_removing) { - command->add_child(new NodeViewItemPreventRemovingCommand(this, node, true)); - } - - command->add_child(new NodeRemovePositionFromContextCommand(node, context)); - - // Remove any dependency from the context that's also floating - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - Node *dependency = it->second; - - // Determine if this node happens to output to anything else in the context (which may be - // another floating node that won't be removed by this operation) - if (!DetermineIfNodeIsFloatingInContext(dependency, context, source, removed_edges, added_edge)) { - RecursivelyRemoveFloatingNodeFromContext(command, dependency, context, source, removed_edges, added_edge, prevent_removing); - } - } -} - -void NodeView::RecursivelyAddNodeToContext(MultiUndoCommand *command, Node *node, Node *context) -{ - NodeViewItem *item = scene_.item_map().value(node); - - if (item) { - command->add_child(new NodeSetPositionCommand(node, context, GetEstimatedPositionForContext(item, context), false)); - - // Add dependency - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - Node *dependency = it->second; - RecursivelyAddNodeToContext(command, dependency, context); - } - } -} - -void NodeView::UpdateContextsFromEdgeAdd(MultiUndoCommand *command, const Node::OutputConnection &added_edge, const Node::OutputConnections &removed_edges) -{ - // Determine if node currently does NOT output to a context that it WILL after this operation - QVector contexts_to_add_to; - Node *connecting_node = added_edge.first; - Node *input_node = added_edge.second.node(); - for (auto it=graph_->GetPositionMap().cbegin(); it!=graph_->GetPositionMap().cend(); it++) { - if (it.value().contains(input_node)) { - contexts_to_add_to.append(it.key()); - } - } - - if (!contexts_to_add_to.isEmpty()) { - // Determine whether the node is currently "floating", i.e. it outputs to none of the contexts - // that it currently belongs to. If so, we will take ownership of it with this node. - bool node_is_floating = true; - QVector current_contexts; - for (auto it=graph_->GetPositionMap().cbegin(); it!=graph_->GetPositionMap().cend(); it++) { - if (it.value().contains(connecting_node)) { - if (connecting_node->OutputsTo(it.key(), true, removed_edges)) { - node_is_floating = false; - break; - } else { - current_contexts.append(it.key()); - } - } - } - - if (node_is_floating) { - // This action will unfloat this node, so remove it from all current contexts - for (Node *context : qAsConst(current_contexts)) { - RecursivelyRemoveFloatingNodeFromContext(command, connecting_node, context, connecting_node, removed_edges, added_edge, false); - } - } - - // Add nodes to contexts - for (Node *context : qAsConst(contexts_to_add_to)) { - RecursivelyAddNodeToContext(command, connecting_node, context); - } - } -} - QPointF NodeView::GetEstimatedPositionForContext(NodeViewItem *item, Node *context) const { return item->GetNodePosition() - context_offsets_.value(context); @@ -1545,86 +1139,6 @@ void NodeView::PositionNewEdge(const QPoint &pos) create_edge_->SetConnected(create_edge_dst_input_.IsValid()); } -void NodeView::RepositionContexts() -{ - // Determine which contexts are root-level - QVector processing_filters = filter_nodes_; - - // Level counter as we iterate through the list a few times - int level = 0; - - // Root-level positioning variables - qreal last_offset = 0; - int additional_spacing = 0; - - while (!processing_filters.isEmpty()) { - QVector contexts_on_this_level; - - for (int i=0; iContextContainsNode(context, other_context)) { - this_level = false; - break; - } - } - - if (this_level) { - contexts_on_this_level.append(context); - } - } - - for (Node *context : qAsConst(contexts_on_this_level)) { - if (level == 0) { - const NodeGraph::PositionMap &map = graph_->GetNodesForContext(context); - - // First determine the total "height" of this graph and how much we need to offset it - qreal top = 0; - qreal bottom = 0; - for (auto it=map.cbegin(); it!=map.cend(); it++) { - const QPointF &node_pos_in_context = it.value(); - top = qMin(node_pos_in_context.y(), top); - bottom = qMax(node_pos_in_context.y(), bottom); - } - - last_offset += (additional_spacing + (bottom - top)); - additional_spacing = 1; - context_offsets_.insert(context, QPointF(0, last_offset)); - } else { - // Create/update item - NodeViewItem *item = UpdateNodeItem(context, true); - - // Get position generated by UpdateNodeItem - QPointF context_pos = item->GetNodePosition(); - - // Adjust by the context node's position in its own context (this will usually be 0,0) - context_pos -= graph_->GetNodesForContext(context).value(context); - - // Insert this context's offset - context_offsets_.insert(context, context_pos); - } - - // Remove from list so we don't process again - processing_filters.removeOne(context); - } - - level++; - } - - // Now that we've positioned all the contexts, position all other nodes relative to those contexts - for (Node *context : qAsConst(filter_nodes_)) { - const NodeGraph::PositionMap &map = graph_->GetNodesForContext(context); - for (auto it=map.cbegin(); it!=map.cend(); it++) { - UpdateNodeItem(it.key()); - } - } -} - void NodeView::GroupNodes() { /*NodeGroup *group = new NodeGroup(); @@ -1636,54 +1150,11 @@ void NodeView::UngroupNodes() //static_cast(selected_nodes_.first()); } -NodeViewItem *NodeView::UpdateNodeItem(Node *node, bool ignore_own_context) -{ - // Get UI item or create if it doesn't exist - NodeViewItem *item = scene_.item_map().value(node); - if (!item) { - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - if (scene_.item_map().contains(it->second)) { - scene_.AddEdge(it->second, it->first); - } - } - - for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) { - if (scene_.item_map().contains(it->second.node())) { - scene_.AddEdge(it->first, it->second); - } - } - } - - // Determine "view" position by averaging the Y value and "min"ing the X value of all contexts - QPointF item_pos(std::numeric_limits::max(), 0.0); - int average_count = 0; - for (Node *context : qAsConst(filter_nodes_)) { - if (context == node && ignore_own_context) { - continue; - } - - if (graph_->GetNodesForContext(context).contains(node)) { - QPointF this_context_pos = graph_->GetNodePosition(node, context); - this_context_pos += context_offsets_.value(context); - - item_pos.setX(qMin(item_pos.x(), this_context_pos.x())); - item_pos.setY(item_pos.y() + this_context_pos.y()); - average_count++; - } - } - item_pos.setY(item_pos.y() / average_count); - - // Set position - item->SetNodePosition(item_pos); - positions_.insert(item, {node, item_pos}); - - return item; -} - void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) { + /* // If no graph, do nothing - if (!graph_) { + if (contexts_.isEmpty()) { return; } @@ -1696,7 +1167,7 @@ void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) new_nodes = PasteNodesFromClipboard(graph_, paste_command_, &map); for (auto it=new_nodes.cbegin(); it!=new_nodes.cend(); it++) { - for (Node *context : qAsConst(filter_nodes_)) { + for (Node *context : qAsConst(contexts_)) { paste_command_->add_child(new NodeSetPositionCommand(*it, context, map.value(*it), false)); } } @@ -1707,7 +1178,7 @@ void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) Node *src = duplicate_nodes.at(i); Node *copy = new_nodes.at(i); - for (Node *context : qAsConst(filter_nodes_)) { + for (Node *context : qAsConst(contexts_)) { QPointF p = scene_.item_map().value(src)->GetNodePosition(); paste_command_->add_child(new NodeSetPositionCommand(copy, context, p, false)); } @@ -1725,6 +1196,7 @@ void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) paste_command_->add_child(new NodeViewAttachNodesToCursor(this, new_nodes)); paste_command_->redo_now(); + */ } NodeView::NodeViewAttachNodesToCursor::NodeViewAttachNodesToCursor(NodeView *view, const QVector &nodes) : @@ -1745,8 +1217,7 @@ void NodeView::NodeViewAttachNodesToCursor::undo() Project *NodeView::NodeViewAttachNodesToCursor::GetRelevantProject() const { - // Will either return a project or a nullptr which is also acceptable - return dynamic_cast(view_->graph_); + return nullptr; } void NodeView::NodeViewItemPreventRemovingCommand::redo() diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 941120361..8616974f5 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -49,12 +49,9 @@ public: virtual ~NodeView() override; - NodeGraph* GetGraph() const - { - return graph_; - } + void SetContexts(const QVector &nodes); - void SetGraph(NodeGraph *graph, const QVector &nodes); + void CloseContextsBelongingToProject(Project *project); void ClearGraph(); @@ -67,7 +64,6 @@ public: void DeselectAll(); void Select(QVector nodes, bool center_view_on_item); - void SelectWithDependencies(QVector nodes, bool center_view_on_item); void CopySelected(bool cut); void Paste(); @@ -82,7 +78,7 @@ public: const QVector &GetCurrentContexts() const { - return filter_nodes_; + return contexts_; } public slots: @@ -98,6 +94,8 @@ public slots: delete m; } + void CenterOnItemsBoundingRect(); + signals: void NodesSelected(const QVector& nodes); @@ -137,12 +135,6 @@ private: void ZoomFromKeyboard(double multiplier); - bool DetermineIfNodeIsFloatingInContext(Node *node, Node *context, Node *source, const Node::OutputConnections &removed_edges, const Node::OutputConnection &added_edge); - void UpdateContextsFromEdgeRemove(MultiUndoCommand *command, const Node::OutputConnections &remove_edges); - void UpdateContextsFromEdgeAdd(MultiUndoCommand *command, const Node::OutputConnection &added_edge, const Node::OutputConnections &removed_edges = Node::OutputConnections()); - void RecursivelyAddNodeToContext(MultiUndoCommand *command, Node *node, Node *context); - void RecursivelyRemoveFloatingNodeFromContext(MultiUndoCommand *command, Node *node, Node *context, Node *source, const Node::OutputConnections &removed_edges, const Node::OutputConnection &added_edge, bool prevent_removing); - QPointF GetEstimatedPositionForContext(NodeViewItem *item, Node *context) const; Menu *CreateAddMenu(Menu *parent); @@ -151,8 +143,6 @@ private: void PositionNewEdge(const QPoint &pos); - NodeViewItem *UpdateNodeItem(Node *node, bool ignore_own_context = false); - void PasteNodesInternal(const QVector &duplicate_nodes = QVector()); class NodeViewAttachNodesToCursor : public UndoCommand @@ -176,8 +166,6 @@ private: NodeViewMiniMap *minimap_; - NodeGraph* graph_; - struct AttachedItem { NodeViewItem* item; QPointF original_pos; @@ -227,21 +215,7 @@ private: QVector selected_nodes_; - enum FilterMode { - kFilterShowAll, - kFilterShowSelective - }; - - struct Position { - Node *node; - QPointF original_item_pos; - }; - - QMap positions_; - - FilterMode filter_mode_; - - QVector filter_nodes_; + QVector contexts_; QVector last_set_filter_nodes_; QMap context_offsets_; @@ -249,7 +223,7 @@ private: bool create_edge_already_exists_; - bool queue_reposition_contexts_; + bool first_show_; static const double kMinimumScale; @@ -274,36 +248,19 @@ private slots: */ void ContextMenuSetDirection(QAction* action); - /** - * @brief Receiver for the user changing the filter - */ - void ContextMenuFilterChanged(QAction* action); - /** * @brief Opens the selected node in a Viewer */ void OpenSelectedNodeInViewer(); - //void AddNode(Node *node); - void RemoveNode(Node *node); - void AddEdge(Node *output, const NodeInput& input); - void RemoveEdge(Node *output, const NodeInput& input); - - void AddNodePosition(Node *node, Node *relative); - void RemoveNodePosition(Node *node, Node *relative); - void UpdateSceneBoundingRect(); - void CenterOnItemsBoundingRect(); - void RepositionMiniMap(); void UpdateViewportOnMiniMap(); void MoveToScenePoint(const QPointF &pos); - void RepositionContexts(); - void GroupNodes(); void UngroupNodes(); diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index db22be143..1b52624a6 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -19,37 +19,29 @@ namespace olive { #define super QGraphicsRectItem -NodeViewContext::NodeViewContext(QGraphicsItem *item) : - super(item) +NodeViewContext::NodeViewContext(Node *context, QGraphicsItem *item) : + super(item), + context_(context) { - // Set default label text - SetContext(nullptr); -} - -void NodeViewContext::SetContext(Node *node) -{ - context_ = node; - - if (context_) { - if (Block *block = dynamic_cast(node)) { - rational timebase = block->track()->sequence()->GetVideoParams().frame_rate_as_time_base(); - lbl_ = QCoreApplication::translate("NodeViewContext", - "%1 [%2] :: %3 - %4").arg(block->GetLabelAndName(), - Track::Reference::TypeToTranslatedString(block->track()->type()), - Timecode::time_to_timecode(block->in(), timebase, Core::instance()->GetTimecodeDisplay()), - Timecode::time_to_timecode(block->out(), timebase, Core::instance()->GetTimecodeDisplay())); - } else { - lbl_ = node->GetLabelAndName(); - } - - Color c = node->color(); - setPen(QPen(c.toQColor(), 2)); - - c.set_alpha(0.5f); - setBrush(c.toQColor()); + if (Block *block = dynamic_cast(context_)) { + rational timebase = block->track()->sequence()->GetVideoParams().frame_rate_as_time_base(); + lbl_ = QCoreApplication::translate("NodeViewContext", + "%1 [%2] :: %3 - %4").arg(block->GetLabelAndName(), + Track::Reference::TypeToTranslatedString(block->track()->type()), + Timecode::time_to_timecode(block->in(), timebase, Core::instance()->GetTimecodeDisplay()), + Timecode::time_to_timecode(block->out(), timebase, Core::instance()->GetTimecodeDisplay())); } else { - lbl_ = QCoreApplication::translate("NodeViewContext", "(None)"); + lbl_ = context_->GetLabelAndName(); } + + const Node::PositionMap &map = context_->GetContextPositions(); + for (auto it=map.cbegin(); it!=map.cend(); it++) { + AddChild(it.key()); + } + + connect(context_, &Node::NodeAddedToContext, this, &NodeViewContext::AddChild, Qt::DirectConnection); + connect(context_, &Node::NodePositionInContextChanged, this, &NodeViewContext::SetChildPosition, Qt::DirectConnection); + connect(context_, &Node::NodeRemovedFromContext, this, &NodeViewContext::RemoveChild, Qt::DirectConnection); } void NodeViewContext::AddChild(Node *node) @@ -59,33 +51,73 @@ void NodeViewContext::AddChild(Node *node) } NodeViewItem *item = new NodeViewItem(this); - item->SetNode(node); - item->SetNodePosition(context_->parent()->GetNodesForContext(context_).value(node)); + item->SetNode(node, context_); + item->SetNodePosition(context_->GetNodePositionInContext(node)); item->SetFlowDirection(flow_dir_); + connect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); + connect(node, &Node::InputDisconnected, this, &NodeViewContext::ChildInputDisconnected); + + item_map_.insert(node, item); + if (node == context_) { item->SetLabelAsOutput(true); } for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) { - foreach (auto child, childItems()) { - if (NodeViewItem *other_item = dynamic_cast(child)) { - if (other_item->GetNode() == it->second.node()) { - AddEdgeInternal(node, it->second, item, other_item); - } + if (!it->second.IsHidden()) { + if (NodeViewItem *other_item = item_map_.value(it->second.node())) { + AddEdgeInternal(node, it->second, item, other_item); } } } for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - foreach (auto child, childItems()) { - if (NodeViewItem *other_item = dynamic_cast(child)) { - if (it->second == other_item->GetNode()) { - AddEdgeInternal(it->second, it->first, other_item, item); - } + if (!it->first.IsHidden()) { + if (NodeViewItem *other_item = item_map_.value(it->second)) { + AddEdgeInternal(it->second, it->first, other_item, item); } } } + + UpdateRect(); +} + +void NodeViewContext::SetChildPosition(Node *node, const QPointF &pos) +{ + item_map_.value(node)->SetNodePosition(pos); +} + +void NodeViewContext::RemoveChild(Node *node) +{ + disconnect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); + disconnect(node, &Node::InputDisconnected, this, &NodeViewContext::ChildInputDisconnected); + + delete item_map_.take(node); +} + +void NodeViewContext::ChildInputConnected(Node *output, const NodeInput &input) +{ + // Add edge + if (!input.IsHidden()) { + if (NodeViewItem* output_item = item_map_.value(output)) { + AddEdgeInternal(output, input, output_item, item_map_.value(input.node())); + } + } +} + +bool NodeViewContext::ChildInputDisconnected(Node *output, const NodeInput &input) +{ + // Remove edge + for (int i=0; ioutput() == output && edges_.at(i)->input() == input) { + delete edges_.at(i); + edges_.removeAt(i); + return true; + } + } + + return false; } qreal GetTextOffset(const QFontMetricsF &fm) @@ -105,23 +137,19 @@ void NodeViewContext::UpdateRect() rect.adjust(-pad, - lbl_offset*2 - fm.height() - pad, pad, pad); setRect(rect); - last_titlebar_height_ = rect.y() + (cbr.y() - rect.y()); + last_titlebar_height_ = rect.y() + (cbr.y() - rect.y()) - pad; } void NodeViewContext::SetFlowDirection(NodeViewCommon::FlowDirection dir) { flow_dir_ = dir; - foreach (auto child, childItems()) { - if (NodeViewItem *item = dynamic_cast(child)) { - item->SetFlowDirection(dir); - } + foreach (NodeViewItem *item, item_map_) { + item->SetFlowDirection(dir); } - foreach (auto child, childItems()) { - if (NodeViewEdge *edge = dynamic_cast(child)) { - edge->SetFlowDirection(dir); - } + foreach (NodeViewEdge *edge, edges_) { + edge->SetFlowDirection(dir); } } @@ -129,29 +157,40 @@ void NodeViewContext::SetCurvedEdges(bool e) { curved_edges_ = e; - const QList &children = childItems(); - foreach (auto child, children) { - if (NodeViewEdge *edge = dynamic_cast(child)) { - edge->SetCurved(e); - } + foreach (NodeViewEdge *edge, edges_) { + edge->SetCurved(e); } } void NodeViewContext::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { - QPen p = pen(); - + // Set pen and brush + Color color = context_->color(); + QColor c = color.toQColor(); + QPen pen(c, 2); if (option->state & QStyle::State_Selected) { - p.setStyle(Qt::DotLine); + pen.setStyle(Qt::DotLine); } + painter->setPen(pen); - painter->setPen(p); - painter->setBrush(brush()); + QColor bg = c; + bg.setAlpha(128); + painter->setBrush(bg); + // Draw semi-transparent rect for whole item int rounded = painter->fontMetrics().height(); painter->drawRoundedRect(rect(), rounded, rounded); - painter->setPen(widget->palette().text().color()); + // Draw solid background for titlebar + QRectF titlebar_rect = rect(); + titlebar_rect.setHeight(last_titlebar_height_ - rect().top()); + painter->setClipRect(titlebar_rect); + painter->setBrush(c); + painter->drawRoundedRect(rect(), rounded, rounded); + painter->setClipping(false); + + // Draw titlebar text + painter->setPen(ColorCoding::GetUISelectorColor(color)); int offset = GetTextOffset(painter->fontMetrics()); @@ -182,8 +221,7 @@ NodeViewEdge* NodeViewContext::AddEdgeInternal(Node *output, const NodeInput& in edge_ui->SetFlowDirection(flow_dir_); edge_ui->SetCurved(curved_edges_); - from->AddEdge(edge_ui); - to->AddEdge(edge_ui); + edges_.append(edge_ui); return edge_ui; } diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index cf35b6fd2..4ef21f68f 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -10,14 +10,11 @@ namespace olive { -class NodeViewContext : public QGraphicsRectItem +class NodeViewContext : public QObject, public QGraphicsRectItem { + Q_OBJECT public: - NodeViewContext(QGraphicsItem *item = nullptr); - - void SetContext(Node *node); - - void AddChild(Node *node); + NodeViewContext(Node *context, QGraphicsItem *item = nullptr); void UpdateRect(); @@ -27,6 +24,17 @@ public: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; +public slots: + void AddChild(Node *node); + + void SetChildPosition(Node *node, const QPointF &pos); + + void RemoveChild(Node *node); + + void ChildInputConnected(Node *output, const NodeInput& input); + + bool ChildInputDisconnected(Node *output, const NodeInput& input); + protected: virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; @@ -45,6 +53,10 @@ private: int last_titlebar_height_; + QMap item_map_; + + QVector edges_; + }; } diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index 977b88cbd..caf629007 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -46,6 +46,9 @@ NodeViewEdge::NodeViewEdge(Node *output, const NodeInput &input, { Init(); SetConnected(true); + + from_item_->AddEdge(this); + to_item_->AddEdge(this); } NodeViewEdge::NodeViewEdge(QGraphicsItem *parent) : @@ -56,6 +59,17 @@ NodeViewEdge::NodeViewEdge(QGraphicsItem *parent) : Init(); } +NodeViewEdge::~NodeViewEdge() +{ + if (from_item_) { + from_item_->RemoveEdge(this); + } + + if (to_item_) { + to_item_->RemoveEdge(this); + } +} + void NodeViewEdge::Adjust() { // Draw a line between the two diff --git a/app/widget/nodeview/nodeviewedge.h b/app/widget/nodeview/nodeviewedge.h index de8d10720..31a56664e 100644 --- a/app/widget/nodeview/nodeviewedge.h +++ b/app/widget/nodeview/nodeviewedge.h @@ -45,6 +45,8 @@ public: NodeViewEdge(QGraphicsItem* parent = nullptr); + virtual ~NodeViewEdge() override; + Node *output() const { return output_; diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index b3e4ee4e9..1c73f8ddd 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -42,6 +42,7 @@ namespace olive { NodeViewItem::NodeViewItem(QGraphicsItem *parent) : QGraphicsRectItem(parent), node_(nullptr), + context_(nullptr), expanded_(false), hide_titlebar_(false), highlighted_index_(-1), @@ -207,7 +208,7 @@ int NodeViewItem::GetIndexAt(QPointF pt) const return -1; } -void NodeViewItem::SetNode(Node *n) +void NodeViewItem::SetNode(Node *n, Node *context) { if (node_) { disconnect(n, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); @@ -215,6 +216,7 @@ void NodeViewItem::SetNode(Node *n) } node_ = n; + context_ = context; node_inputs_.clear(); input_connectors_.clear(); @@ -223,7 +225,7 @@ void NodeViewItem::SetNode(Node *n) node_->Retranslate(); foreach (const QString& input, node_->inputs()) { - if (node_->IsInputConnectable(input)) { + if (node_->IsInputConnectable(input) && !node_->IsInputHidden(input)) { node_inputs_.append(input); } } @@ -327,9 +329,11 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti int icon_size = painter->fontMetrics().height()/2; + bool draw_arrow = !node_inputs_.isEmpty(); + if (node_label.isEmpty()) { // Draw shortname only - DrawNodeTitle(painter, node_shortname, title_bar_rect_, Qt::AlignVCenter, icon_size, true); + DrawNodeTitle(painter, node_shortname, title_bar_rect_, Qt::AlignVCenter, icon_size, draw_arrow); } else { int text_pad = DefaultTextPadding()/2; QRectF safe_label_bounds = title_bar_rect_.adjusted(text_pad, text_pad, -text_pad, -text_pad); @@ -337,7 +341,7 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti qreal font_sz = f.pointSizeF(); f.setPointSizeF(font_sz * 0.8); painter->setFont(f); - DrawNodeTitle(painter, node_label, safe_label_bounds, Qt::AlignTop, icon_size, true); + DrawNodeTitle(painter, node_label, safe_label_bounds, Qt::AlignTop, icon_size, draw_arrow); f.setPointSizeF(font_sz * 0.6); painter->setFont(f); DrawNodeTitle(painter, node_shortname, safe_label_bounds, Qt::AlignBottom, icon_size, false); diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 0f11574ed..b92f531c7 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -53,7 +53,7 @@ public: /** * @brief Set the Node to correspond to this widget */ - void SetNode(Node* n); + void SetNode(Node* n, Node *context); /** * @brief Get currently attached node @@ -174,6 +174,8 @@ private: */ Node* node_; + Node *context_; + /** * @brief Cached list of node inputs */ diff --git a/app/widget/nodeview/nodeviewminimap.cpp b/app/widget/nodeview/nodeviewminimap.cpp index d1e20791c..8dc99c914 100644 --- a/app/widget/nodeview/nodeviewminimap.cpp +++ b/app/widget/nodeview/nodeviewminimap.cpp @@ -38,6 +38,7 @@ NodeViewMiniMap::NodeViewMiniMap(NodeViewScene *scene, QWidget *parent) : setViewportUpdateMode(FullViewportUpdate); setFrameShape(QFrame::Panel); setFrameShadow(QFrame::Plain); + setMouseTracking(true); QMetaObject::invokeMethod(this, &NodeViewMiniMap::SetDefaultSize, Qt::QueuedConnection); @@ -87,7 +88,7 @@ void NodeViewMiniMap::resizeEvent(QResizeEvent *event) void NodeViewMiniMap::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { - if (event->pos().x() <= resize_triangle_sz_ && event->pos().y() <= resize_triangle_sz_) { + if (MouseInsideResizeTriangle(event)) { // Resizing! resizing_ = true; resize_anchor_ = QCursor::pos(); @@ -107,6 +108,8 @@ void NodeViewMiniMap::mouseMoveEvent(QMouseEvent *event) } else { EmitMoveSignal(event); } + } else { + setCursor(MouseInsideResizeTriangle(event) ? Qt::SizeFDiagCursor : Qt::ArrowCursor); } } @@ -135,6 +138,11 @@ void NodeViewMiniMap::SetDefaultSize() } } +bool NodeViewMiniMap::MouseInsideResizeTriangle(QMouseEvent *event) +{ + return event->pos().x() <= resize_triangle_sz_ && event->pos().y() <= resize_triangle_sz_; +} + void NodeViewMiniMap::EmitMoveSignal(QMouseEvent *event) { emit MoveToScenePoint(mapToScene(event->pos())); diff --git a/app/widget/nodeview/nodeviewminimap.h b/app/widget/nodeview/nodeviewminimap.h index a63db1993..e155b54f2 100644 --- a/app/widget/nodeview/nodeviewminimap.h +++ b/app/widget/nodeview/nodeviewminimap.h @@ -57,6 +57,8 @@ private slots: void SetDefaultSize(); private: + bool MouseInsideResizeTriangle(QMouseEvent *event); + void EmitMoveSignal(QMouseEvent *event); int resize_triangle_sz_; diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index feb2598b3..2e04a2ae6 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -143,45 +143,25 @@ QVector NodeViewScene::GetSelectedEdges() const return edges; } -NodeViewEdge* NodeViewScene::AddEdge(Node *output, const NodeInput &input) -{ - NodeViewEdge *edge = EdgeToUIObject(output, input); - - if (!edge) { - edge = AddEdgeInternal(output, input, NodeToUIObject(output), NodeToUIObject(input.node())); - } - - return edge; -} - -void NodeViewScene::RemoveEdge(Node *output, const NodeInput &input) -{ - NodeViewEdge* edge = EdgeToUIObject(output, input); - if (edge) { - edge->from_item()->RemoveEdge(edge); - edge->to_item()->RemoveEdge(edge); - edges_.removeOne(edge); - delete edge; - } -} - NodeViewContext *NodeViewScene::AddContext(Node *node) { NodeViewContext *context_item = context_map_.value(node); if (!context_item) { - context_item = new NodeViewContext(); - context_item->SetContext(node); - context_item->setPos(0, 0); + context_item = new NodeViewContext(node); + context_item->SetFlowDirection(GetFlowDirection()); context_item->SetCurvedEdges(GetEdgesAreCurved()); - addItem(context_item); - const NodeGraph::PositionMap &map = node->parent()->GetNodesForContext(node); - for (auto it=map.cbegin(); it!=map.cend(); it++) { - context_item->AddChild(it.key()); + QPointF pos(0, 0); + QRectF item_rect = context_item->rect(); + while (!items(item_rect).isEmpty()) { + pos.setY(pos.y() + item_rect.height()); + item_rect = context_item->rect().translated(pos); } - context_item->UpdateRect(); + context_item->setPos(pos); + + addItem(context_item); context_map_.insert(node, context_item); } @@ -209,22 +189,6 @@ int NodeViewScene::DetermineWeight(Node *n) return qMax(1, weight); } -NodeViewEdge* NodeViewScene::AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to) -{ - NodeViewEdge* edge_ui = new NodeViewEdge(output, input, from, to); - - edge_ui->SetFlowDirection(direction_); - edge_ui->SetCurved(curved_edges_); - - from->AddEdge(edge_ui); - to->AddEdge(edge_ui); - - addItem(edge_ui); - edges_.append(edge_ui); - - return edge_ui; -} - Qt::Orientation NodeViewScene::GetFlowOrientation() const { return NodeViewCommon::GetFlowOrientation(direction_); @@ -240,8 +204,8 @@ void NodeViewScene::SetEdgesAreCurved(bool curved) if (curved_edges_ != curved) { curved_edges_ = curved; - foreach (NodeViewEdge* e, edges_) { - e->SetCurved(curved_edges_); + foreach (NodeViewContext *ctx, context_map_) { + ctx->SetCurvedEdges(curved_edges_); } } } diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index f37ebbdcd..db49aca89 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -81,9 +81,6 @@ public: } public slots: - NodeViewEdge *AddEdge(Node *output, const NodeInput& input); - void RemoveEdge(Node *output, const NodeInput& input); - NodeViewContext *AddContext(Node *node); void RemoveContext(Node *node); @@ -95,8 +92,6 @@ public slots: private: static int DetermineWeight(Node* n); - NodeViewEdge* AddEdgeInternal(Node *output, const NodeInput &input, NodeViewItem* from, NodeViewItem* to); - QHash context_map_; QHash item_map_; diff --git a/app/widget/timelinewidget/tool/add.cpp b/app/widget/timelinewidget/tool/add.cpp index 07343ccd3..a9c9d3615 100644 --- a/app/widget/timelinewidget/tool/add.cpp +++ b/app/widget/timelinewidget/tool/add.cpp @@ -116,7 +116,7 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event) NodeGraph* graph = static_cast(parent()->GetConnectedNode()->parent()); command->add_child(new NodeAddCommand(graph, clip)); - command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0), false)); + command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0))); command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()), track.index(), clip, @@ -155,10 +155,10 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event) } if (node_to_add) { - QPointF extra_node_offset(-1, 0); + QPointF extra_node_offset(kDefaultDistanceFromOutput, 0); command->add_child(new NodeAddCommand(graph, node_to_add)); command->add_child(new NodeEdgeAddCommand(node_to_add, NodeInput(clip, ClipBlock::kBufferIn))); - command->add_child(new NodeSetPositionCommand(node_to_add, clip, extra_node_offset, false)); + command->add_child(new NodeSetPositionCommand(node_to_add, clip, extra_node_offset)); } Core::instance()->undo_stack()->push(command); diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index 9ad1a6a74..f9ab4b6f8 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -355,7 +355,7 @@ void ImportTool::DropGhosts(bool insert) command->add_child(new NodeAddCommand(dst_graph, new_sequence)); command->add_child(new FolderAddChild(Core::instance()->GetSelectedFolderInActiveProject(), new_sequence)); - command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0), false)); + command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0))); new_sequence->add_default_nodes(command); FootageToGhosts(0, dragged_footage_, new_sequence->GetVideoParams().time_base(), 0); @@ -394,10 +394,14 @@ void ImportTool::DropGhosts(bool insert) command->add_child(new NodeAddCommand(dst_graph, clip)); // Position clip in its own context - command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0), false)); + command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0))); + + int dep_pos = kDefaultDistanceFromOutput; // Position footage in its context - command->add_child(new NodeSetPositionCommand(footage_stream.footage, clip, QPointF(-3, 0), false)); + command->add_child(new NodeSetPositionCommand(footage_stream.footage, clip, QPointF(dep_pos, 0))); + + dep_pos++; switch (Track::Reference::TypeFromString(footage_stream.output)) { case Track::kVideo: @@ -409,7 +413,7 @@ void ImportTool::DropGhosts(bool insert) command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(transform, TransformDistortNode::kTextureInput))); command->add_child(new NodeEdgeAddCommand(transform, NodeInput(clip, ClipBlock::kBufferIn))); - command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(-2, 0), false)); + command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(dep_pos, 0))); break; } case Track::kAudio: @@ -421,7 +425,7 @@ void ImportTool::DropGhosts(bool insert) command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(volume_node, VolumeNode::kSamplesInput))); command->add_child(new NodeEdgeAddCommand(volume_node, NodeInput(clip, ClipBlock::kBufferIn))); - command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(-2, 0), false)); + command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(dep_pos, 0))); break; } default: diff --git a/app/widget/timelinewidget/tool/tool.cpp b/app/widget/timelinewidget/tool/tool.cpp index 752409b78..2612b9697 100644 --- a/app/widget/timelinewidget/tool/tool.cpp +++ b/app/widget/timelinewidget/tool/tool.cpp @@ -24,6 +24,8 @@ namespace olive { +const int TimelineTool::kDefaultDistanceFromOutput = -4; + TimelineTool::TimelineTool(TimelineWidget *parent) : dragging_(false), parent_(parent) diff --git a/app/widget/timelinewidget/tool/tool.h b/app/widget/timelinewidget/tool/tool.h index d229e1ad3..c627f542e 100644 --- a/app/widget/timelinewidget/tool/tool.h +++ b/app/widget/timelinewidget/tool/tool.h @@ -84,6 +84,8 @@ protected: TimelineCoordinate drag_start_; + static const int kDefaultDistanceFromOutput; + private: TimelineWidget* parent_; diff --git a/app/widget/timelinewidget/tool/transition.cpp b/app/widget/timelinewidget/tool/transition.cpp index f1a1f4fdd..594659ccb 100644 --- a/app/widget/timelinewidget/tool/transition.cpp +++ b/app/widget/timelinewidget/tool/transition.cpp @@ -113,7 +113,7 @@ void TransitionTool::MouseRelease(TimelineViewMouseEvent *event) command->add_child(new NodeAddCommand(static_cast(parent()->GetConnectedNode()->parent()), transition)); - command->add_child(new NodeSetPositionCommand(transition, transition, QPointF(0, 0), false)); + command->add_child(new NodeSetPositionCommand(transition, transition, QPointF(0, 0))); command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()), track.index(), @@ -138,8 +138,8 @@ void TransitionTool::MouseRelease(TimelineViewMouseEvent *event) command->add_child(new NodeEdgeAddCommand(in_block, NodeInput(transition, TransitionBlock::kInBlockInput))); - command->add_child(new NodeSetPositionCommand(out_block, transition, QPointF(-1, -0.5), false)); - command->add_child(new NodeSetPositionCommand(in_block, transition, QPointF(-1, 0.5), false)); + command->add_child(new NodeSetPositionCommand(out_block, transition, QPointF(-1, -0.5))); + command->add_child(new NodeSetPositionCommand(in_block, transition, QPointF(-1, 0.5))); } else { Block* block_to_transition = Node::ValueToPtr(ghost_->GetData(TimelineViewGhostItem::kAttachedBlock)); QString transition_input_to_connect; @@ -154,7 +154,7 @@ void TransitionTool::MouseRelease(TimelineViewMouseEvent *event) command->add_child(new NodeEdgeAddCommand(block_to_transition, NodeInput(transition, transition_input_to_connect))); - command->add_child(new NodeSetPositionCommand(block_to_transition, transition, QPointF(-1, 0), false)); + command->add_child(new NodeSetPositionCommand(block_to_transition, transition, QPointF(-1, 0))); } Core::instance()->undo_stack()->push(command); diff --git a/app/widget/timelinewidget/undo/timelineundogeneral.cpp b/app/widget/timelinewidget/undo/timelineundogeneral.cpp index 63958f250..9c8bbae14 100644 --- a/app/widget/timelinewidget/undo/timelineundogeneral.cpp +++ b/app/widget/timelinewidget/undo/timelineundogeneral.cpp @@ -73,131 +73,121 @@ void BlockSetMediaInCommand::undo() // // TimelineAddTrackCommand // -TimelineAddTrackCommand::TimelineAddTrackCommand(TrackList *timeline, bool automerge_tracks) +TimelineAddTrackCommand::TimelineAddTrackCommand(TrackList *timeline, bool automerge_tracks) : + timeline_(timeline), + merge_(nullptr), + position_command_(nullptr) { - timeline_ = timeline; - position_command_ = nullptr; - + // Create new track track_ = new Track(); track_->setParent(&memory_manager_); - if (timeline->GetTrackCount() > 0 && automerge_tracks) { - if (timeline_->type() == Track::kVideo) { - merge_ = new MergeNode(); - base_ = NodeInput(merge_, MergeNode::kBaseIn); - blend_ = NodeInput(merge_, MergeNode::kBlendIn); - } else if (timeline_->type() == Track::kAudio) { - merge_ = new MathNode(); - base_ = NodeInput(merge_, MathNode::kParamAIn); - blend_ = NodeInput(merge_, MathNode::kParamBIn); - } else { - merge_ = nullptr; - } - } else { - merge_ = nullptr; + // Determine what input to connect it to + QString relevant_input; + + if (timeline_->type() == Track::kVideo) { + relevant_input = Sequence::kTextureInput; + } else if (timeline_->type() == Track::kAudio) { + relevant_input = Sequence::kSamplesInput; } - if (merge_) { - merge_->setParent(&memory_manager_); + // If we have an input to connect to, set it as our `direct` connection + if (!relevant_input.isEmpty()) { + direct_ = NodeInput(timeline_->parent(), relevant_input); + + // If we're automerging and something is already connected, determine if/how to merge it + if (automerge_tracks && direct_.IsConnected()) { + if (timeline_->type() == Track::kVideo) { + // Use merge for video + merge_ = new MergeNode(); + base_ = NodeInput(merge_, MergeNode::kBaseIn); + blend_ = NodeInput(merge_, MergeNode::kBlendIn); + } else if (timeline_->type() == Track::kAudio) { + // Use math (add) for audio + merge_ = new MathNode(); + base_ = NodeInput(merge_, MathNode::kParamAIn); + blend_ = NodeInput(merge_, MathNode::kParamBIn); + } + + if (merge_) { + // If we got created a merge node, ensure it's parented + merge_->setParent(&memory_manager_); + } + } } } void TimelineAddTrackCommand::redo() { - // Add track + // Get sequence + Sequence* sequence = timeline_->parent(); + + // Add track to sequence track_->setParent(timeline_->GetParentGraph()); timeline_->ArrayAppend(); Node::ConnectEdge(track_, timeline_->track_input(timeline_->ArraySize() - 1)); + qreal position_factor = 0.5; + if (timeline_->type() == Track::kVideo) { + position_factor = -position_factor; + } + bool create_pos_command = (!position_command_ && (timeline_->type() == Track::kVideo || timeline_->type() == Track::kAudio)); + if (create_pos_command) { + position_command_ = new MultiUndoCommand(); + } + // Add merge if applicable - Track* last_track = nullptr; if (merge_) { + // Determine what was previously connected + Node *previous_connection = direct_.GetConnectedOutput(); + + // Add merge to graph merge_->setParent(timeline_->GetParentGraph()); - last_track = timeline_->GetTrackAt(timeline_->GetTrackCount()-2); - - // Whatever this track used to be connected to, connect the merge instead - const Node::OutputConnections edges = last_track->output_connections(); - for (const Node::OutputConnection& ic : edges) { - const NodeInput& i = ic.second; - - // Ignore the track input, but funnel everything else through our merge - if (i.node() != timeline_->parent() || i.input() != timeline_->track_input()) { - Node::DisconnectEdge(last_track, i); - Node::ConnectEdge(merge_, i); - } - } - - // Connect this as the "blend" track + // Connect merge between what used to be here + Node::DisconnectEdge(previous_connection, direct_); + Node::ConnectEdge(merge_, direct_); + Node::ConnectEdge(previous_connection, base_); Node::ConnectEdge(track_, blend_); - Node::ConnectEdge(last_track, base_); - } else if (timeline_->GetTrackCount() == 1) { - // If this was the first track we added, - QString relevant_input; - if (timeline_->type() == Track::kVideo) { - relevant_input = ViewerOutput::kTextureInput; - } else if (timeline_->type() == Track::kAudio) { - relevant_input = ViewerOutput::kSamplesInput; + if (create_pos_command) { + position_command_->add_child(new NodeSetPositionCommand(track_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, -position_factor))); + position_command_->add_child(new NodeSetPositionCommand(merge_, sequence, sequence->GetNodePositionInContext(sequence))); + position_command_->add_child(new NodeSetPositionCommand(merge_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, position_factor * timeline_->GetTrackCount()), true)); } + } else if (direct_.IsValid() && !direct_.IsConnected()) { + // If no merge, we have a direct connection, and nothing else is connected, connect this + Node::ConnectEdge(track_, direct_); - if (!relevant_input.isEmpty() && !timeline_->parent()->IsInputConnected(relevant_input)) { - direct_ = NodeInput(timeline_->parent(), relevant_input); - - Node::ConnectEdge(track_, direct_); - } else { - direct_ = NodeInput(); + if (create_pos_command) { + // Just position directly next to the context node + position_command_->add_child(new NodeSetPositionCommand(track_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, position_factor))); } } - // Position track in context - if (!position_command_) { - int track_count = timeline_->parent()->GetTracks().size(); - position_command_ = new MultiUndoCommand(); - - // Position either the merge or the track as an "element" - Node *node_to_position = merge_ ? merge_ : track_; - double node_index = track_count - 1; - if (merge_) { - node_index -= 1; - position_command_->add_child(new NodeRemovePositionFromContextCommand(last_track, timeline_->parent())); - } - - position_command_->add_child(new NodeSetPositionAsChildCommand(node_to_position, timeline_->parent(), timeline_->parent(), node_index, track_count, true)); - - // If we positioned a merge, position the tracks as children of the merge - if (merge_) { - // `last_track` should be non-null if `merge_` is non-null - position_command_->add_child(new NodeSetPositionAsChildCommand(last_track, merge_, timeline_->parent(), 0, 2, true)); - position_command_->add_child(new NodeSetPositionAsChildCommand(track_, merge_, timeline_->parent(), 1, 2, true)); - } + // Run position command if we created one + if (position_command_) { + position_command_->redo_now(); } - position_command_->redo_now(); } void TimelineAddTrackCommand::undo() { - position_command_->undo_now(); + if (position_command_) { + position_command_->undo_now(); + } // Remove merge if applicable if (merge_) { - // Assume whatever this merge is connected to USED to be connected to the last track - Track* last_track = timeline_->GetTrackAt(timeline_->GetTrackCount()-2); + Node *previous_connection = base_.GetConnectedOutput(); Node::DisconnectEdge(track_, blend_); - Node::DisconnectEdge(last_track, base_); - - // Make copy of edges since the node's internal array will change as we disconnect things - const Node::OutputConnections edges = merge_->output_connections(); - for (const Node::OutputConnection& ic : edges) { - const NodeInput& i = ic.second; - - Node::DisconnectEdge(merge_, i); - Node::ConnectEdge(last_track, i); - } + Node::DisconnectEdge(previous_connection, base_); + Node::DisconnectEdge(merge_, direct_); + Node::ConnectEdge(previous_connection, direct_); merge_->setParent(&memory_manager_); - } else if (direct_.IsValid()) { + } else if (direct_.IsValid() && direct_.GetConnectedOutput() == track_) { Node::DisconnectEdge(track_, direct_); } @@ -496,11 +486,6 @@ void TrackReplaceBlockWithGapCommand::redo() our_gap_->setParent(track_->parent()); track_->ReplaceBlock(block_, our_gap_); - - if (!position_command_) { - position_command_ = new NodeSetPositionAsChildCommand(our_gap_, track_, track_, our_gap_->index(), track_->Blocks().size(), true); - } - position_command_->redo_now(); } track_->EndOperation(); @@ -533,8 +518,6 @@ void TrackReplaceBlockWithGapCommand::undo() track_->ReplaceBlock(our_gap_, block_); our_gap_->setParent(&memory_manager_); - position_command_->undo_now(); - } else { // If we're here, assume that we extended an existing gap diff --git a/app/widget/timelinewidget/undo/timelineundogeneral.h b/app/widget/timelinewidget/undo/timelineundogeneral.h index 423f4da84..490d9b082 100644 --- a/app/widget/timelinewidget/undo/timelineundogeneral.h +++ b/app/widget/timelinewidget/undo/timelineundogeneral.h @@ -203,16 +203,10 @@ public: existing_gap_(nullptr), existing_merged_gap_(nullptr), our_gap_(nullptr), - handle_transitions_(handle_transitions), - position_command_(nullptr) + handle_transitions_(handle_transitions) { } - virtual ~TrackReplaceBlockWithGapCommand() override - { - delete position_command_; - } - virtual Project* GetRelevantProject() const override { return block_->project(); @@ -236,8 +230,6 @@ private: bool handle_transitions_; - NodeSetPositionAsChildCommand* position_command_; - QObject memory_manager_; QVector transition_remove_commands_; diff --git a/app/widget/timelinewidget/undo/timelineundopointer.cpp b/app/widget/timelinewidget/undo/timelineundopointer.cpp index 4f5c448b6..0f6501b19 100644 --- a/app/widget/timelinewidget/undo/timelineundopointer.cpp +++ b/app/widget/timelinewidget/undo/timelineundopointer.cpp @@ -324,7 +324,6 @@ TrackPlaceBlockCommand::~TrackPlaceBlockCommand() { delete ripple_remove_command_; qDeleteAll(add_track_commands_); - qDeleteAll(position_commands_); } void TrackPlaceBlockCommand::redo() @@ -367,14 +366,6 @@ void TrackPlaceBlockCommand::redo() } track->AppendBlock(insert_); - - if (position_commands_.isEmpty()) { - // Create position commands for insert and gap if necessary - if (gap_) { - position_commands_.append(new NodeSetPositionAsChildCommand(gap_, track, track, gap_->index(), track->Blocks().size(), true)); - } - position_commands_.append(new NodeSetPositionAsChildCommand(insert_, track, track, insert_->index(), track->Blocks().size(), true)); - } } else { // Place the Block at this point if (!ripple_remove_command_) { @@ -384,10 +375,6 @@ void TrackPlaceBlockCommand::redo() ripple_remove_command_->redo_now(); track->InsertBlockAfter(insert_, ripple_remove_command_->GetInsertionIndex()); - - if (position_commands_.isEmpty()) { - position_commands_.append(new NodeSetPositionAsChildCommand(insert_, track, track, insert_->index(), track->Blocks().size(), true)); - } } track->EndOperation(); @@ -397,18 +384,10 @@ void TrackPlaceBlockCommand::redo() foreach (const TimeRange &r, ranges_to_invalidate) { track->Node::InvalidateCache(r, Track::kBlockInput); } - - for (int i=0; iredo_now(); - } } void TrackPlaceBlockCommand::undo() { - for (int i=position_commands_.size()-1; i>=0; i--) { - position_commands_.at(i)->undo_now(); - } - Track* t = timeline_->GetTrackAt(track_index_); TimeRange insert_range(insert_->in(), insert_->out()); diff --git a/app/widget/timelinewidget/undo/timelineundopointer.h b/app/widget/timelinewidget/undo/timelineundopointer.h index fc7cf6566..4483d81d2 100644 --- a/app/widget/timelinewidget/undo/timelineundopointer.h +++ b/app/widget/timelinewidget/undo/timelineundopointer.h @@ -198,7 +198,6 @@ private: QVector add_track_commands_; QObject memory_manager_; TrackRippleRemoveAreaCommand* ripple_remove_command_; - QVector position_commands_; }; diff --git a/app/widget/timelinewidget/undo/timelineundosplit.cpp b/app/widget/timelinewidget/undo/timelineundosplit.cpp index 05c3b2707..06cdd1006 100644 --- a/app/widget/timelinewidget/undo/timelineundosplit.cpp +++ b/app/widget/timelinewidget/undo/timelineundosplit.cpp @@ -61,12 +61,6 @@ void BlockSplitCommand::redo() // Insert new block track->InsertBlockAfter(new_block(), block_); - // Position the block - if (!position_command_) { - position_command_ = new NodeSetPositionAsChildCommand(new_block(), track, track, new_block()->index(), track->Blocks().size(), true); - } - position_command_->redo_now(); - // If the block had an out transition, we move it to the new block moved_transition_ = NodeInput(); @@ -96,8 +90,6 @@ void BlockSplitCommand::undo() Node::ConnectEdge(block_, moved_transition_); } - position_command_->undo_now(); - block_->set_length_and_media_out(old_length_); track->RippleRemoveBlock(new_block()); diff --git a/app/widget/timelinewidget/undo/timelineundosplit.h b/app/widget/timelinewidget/undo/timelineundosplit.h index 82b57ebca..6984377f8 100644 --- a/app/widget/timelinewidget/undo/timelineundosplit.h +++ b/app/widget/timelinewidget/undo/timelineundosplit.h @@ -31,15 +31,13 @@ public: block_(block), new_block_(nullptr), point_(point), - reconnect_tree_command_(nullptr), - position_command_(nullptr) + reconnect_tree_command_(nullptr) { } virtual ~BlockSplitCommand() override { delete reconnect_tree_command_; - delete position_command_; } virtual Project* GetRelevantProject() const override @@ -70,8 +68,6 @@ private: NodeInput moved_transition_; - NodeSetPositionAsChildCommand* position_command_; - }; class BlockSplitPreservingLinksCommand : public UndoCommand { diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 8f1ace9ef..47c23c3cb 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -378,9 +378,7 @@ void MainWindow::ProjectClose(Project *p) } // Close project from NodeView - if (node_panel_->GetGraph() == p) { - node_panel_->ClearGraph(); - } + node_panel_->CloseContextsBelongingToProject(p); } void MainWindow::SetApplicationProgressStatus(ProgressStatus status) @@ -731,11 +729,7 @@ void MainWindow::UpdateNodePanelContextFromTimelinePanel(TimelinePanel *panel) context.append(viewer); } - QVector old_contexts = node_panel_->GetCurrentContexts(); - node_panel_->SetGraph(viewer ? viewer->parent() : nullptr, context); - if (viewer && context != old_contexts) { - node_panel_->SelectAll(); - } + node_panel_->SetContexts(context); } void MainWindow::FocusedPanelChanged(PanelWidget *panel) @@ -753,10 +747,6 @@ void MainWindow::FocusedPanelChanged(PanelWidget *panel) } else if (ProjectPanel* project = dynamic_cast(panel)) { // Signal project panel focus UpdateTitle(); - if (Project *p = project->project()) { - node_panel_->SetGraph(p, {p->root()}); - node_panel_->Select({p->color_manager(), p->settings()}, true); - } } } From 849b4437bcfcb705a7d0d9cd716c9c0a93523b25 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 16 Nov 2021 23:58:53 -0800 Subject: [PATCH 17/94] text node: use loadu in sse instruction --- app/node/generator/text/text.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/node/generator/text/text.cpp b/app/node/generator/text/text.cpp index bbd1e4f6b..f43908399 100644 --- a/app/node/generator/text/text.cpp +++ b/app/node/generator/text/text.cpp @@ -170,7 +170,7 @@ void TextGenerator::GenerateFrame(FramePtr frame, const GenerateJob& job) const // Transplant alpha channel to frame Color rgba = job.GetValue(kColorInput).data().value(); #if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM) - __m128 sse_color = _mm_load_ps(rgba.data()); + __m128 sse_color = _mm_loadu_ps(rgba.data()); #endif float *frame_dst = reinterpret_cast(frame->data()); From 2b21366ec66cff88ed47b90fde1b36552382d7b9 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 16 Nov 2021 23:59:14 -0800 Subject: [PATCH 18/94] playbackcontrols: keep buttons small --- app/widget/playbackcontrols/playbackcontrols.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/playbackcontrols/playbackcontrols.cpp b/app/widget/playbackcontrols/playbackcontrols.cpp index 80bae6381..db318ab98 100644 --- a/app/widget/playbackcontrols/playbackcontrols.cpp +++ b/app/widget/playbackcontrols/playbackcontrols.cpp @@ -39,7 +39,7 @@ PlaybackControls::PlaybackControls(QWidget *parent) : lower_control_layout->setSpacing(0); lower_control_layout->setMargin(0); - QSizePolicy lower_container_size_policy(QSizePolicy::Minimum, QSizePolicy::Expanding); + QSizePolicy lower_container_size_policy(QSizePolicy::Maximum, QSizePolicy::Expanding); lower_container_size_policy.setHorizontalStretch(1); // In the lower-left, we create a current timecode label wrapped in a QWidget for fixed sizing From a6af473f5322479838cee10898c001aca6a22983 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 17 Nov 2021 12:09:41 -0800 Subject: [PATCH 19/94] reimplemented node positioning and deleting --- app/node/graph.cpp | 13 ++++ app/node/graph.h | 2 + app/task/project/loadotio/loadotio.cpp | 14 ++-- app/widget/nodeview/nodeview.cpp | 52 ++++---------- app/widget/nodeview/nodeview.h | 2 + app/widget/nodeview/nodeviewcontext.cpp | 34 ++++++++- app/widget/nodeview/nodeviewcontext.h | 5 ++ app/widget/nodeview/nodeviewitem.cpp | 60 +++++++--------- app/widget/nodeview/nodeviewitem.h | 12 ++-- app/widget/nodeview/nodeviewscene.cpp | 55 +++++--------- app/widget/nodeview/nodeviewscene.h | 11 +-- app/widget/nodeview/nodeviewundo.cpp | 96 +++++++++++++++++++++++++ app/widget/nodeview/nodeviewundo.h | 36 ++++++++++ 13 files changed, 255 insertions(+), 137 deletions(-) diff --git a/app/node/graph.cpp b/app/node/graph.cpp index f652340fb..bdae43570 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -44,6 +44,19 @@ void NodeGraph::Clear() } } +int NodeGraph::GetNumberOfContextsNodeIsIn(Node *node, bool except_itself) const +{ + int count = 0; + + foreach (Node *ctx, node_children_) { + if (ctx->ContextContainsNode(node) && (!except_itself || ctx != node)) { + count++; + } + } + + return count; +} + void NodeGraph::childEvent(QChildEvent *event) { super::childEvent(event); diff --git a/app/node/graph.h b/app/node/graph.h index 21c8f023e..fdf923b56 100644 --- a/app/node/graph.h +++ b/app/node/graph.h @@ -63,6 +63,8 @@ public: return default_nodes_; } + int GetNumberOfContextsNodeIsIn(Node *node, bool except_itself = false) const; + signals: /** * @brief Signal emitted when a Node is added to the graph diff --git a/app/task/project/loadotio/loadotio.cpp b/app/task/project/loadotio/loadotio.cpp index bc1983c41..66d2fddf9 100644 --- a/app/task/project/loadotio/loadotio.cpp +++ b/app/task/project/loadotio/loadotio.cpp @@ -210,7 +210,7 @@ bool LoadOTIOTask::Run() block->setParent(sequence->parent()); // Position transition in its own context - sequence->parent()->SetNodePosition(block, block, QPointF(0, 0)); + block->SetNodePositionInContext(block, QPointF(0, 0)); } if (otio_block->schema_name() == "Gap") { @@ -218,7 +218,7 @@ bool LoadOTIOTask::Run() block->setParent(sequence->parent()); // Position transition in its own context - sequence->parent()->SetNodePosition(block, block, QPointF(0, 0)); + block->SetNodePositionInContext(block, QPointF(0, 0)); } // Update this after it's used but before any continue statements @@ -246,7 +246,7 @@ bool LoadOTIOTask::Run() QFileInfo info(probed_item->filename()); probed_item->SetLabel(info.fileName()); - FolderAddChild add(sequence_footage, probed_item, true); + FolderAddChild add(sequence_footage, probed_item); add.redo_now(); } @@ -254,10 +254,10 @@ bool LoadOTIOTask::Run() block->setParent(sequence->parent()); // Position clip in its own context - sequence->parent()->SetNodePosition(block, block, QPointF(0, 0)); + block->SetNodePositionInContext(block, QPointF(0, 0)); // Position footage in its context - sequence->parent()->SetNodePosition(probed_item, block, QPointF(-2, 0)); + block->SetNodePositionInContext(probed_item, QPointF(-2, 0)); if (track->type() == Track::kVideo) { @@ -266,14 +266,14 @@ bool LoadOTIOTask::Run() Node::ConnectEdge(probed_item, NodeInput(transform, TransformDistortNode::kTextureInput)); Node::ConnectEdge(transform, NodeInput(block, ClipBlock::kBufferIn)); - sequence->parent()->SetNodePosition(transform, block, QPointF(-1, 0)); + block->SetNodePositionInContext(transform, QPointF(-1, 0)); } else { VolumeNode* volume_node = new VolumeNode(); volume_node->setParent(sequence->parent()); Node::ConnectEdge(probed_item, NodeInput(volume_node, VolumeNode::kSamplesInput)); Node::ConnectEdge(volume_node, NodeInput(block, ClipBlock::kBufferIn)); - sequence->parent()->SetNodePosition(volume_node, block, QPointF(-1, 0)); + block->SetNodePositionInContext(volume_node, QPointF(-1, 0)); } } } diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 4c8385d9d..3da2c70f1 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -125,43 +125,7 @@ void NodeView::ClearGraph() void NodeView::DeleteSelected() { - MultiUndoCommand* command = new MultiUndoCommand(); - - { - // First remove any selected edges - QVector selected_edges = scene_.GetSelectedEdges(); - - if (!selected_edges.isEmpty()) { - Node::OutputConnections removed_connections(selected_edges.size()); - - for (int i=0; iadd_child(new NodeEdgeRemoveCommand(edge->output(), edge->input())); - removed_connections[i] = {edge->output(), edge->input()}; - } - } - } - - { - // Secondly remove any nodes - QVector selected_nodes = scene_.GetSelectedNodes(); - - // Ensure no nodes are "undeletable" - for (int i=0;iCanBeDeleted()) { - selected_nodes.removeAt(i); - i--; - } - } - - if (!selected_nodes.isEmpty()) { - for (Node* node : qAsConst(selected_nodes)) { - command->add_child(new NodeRemoveAndDisconnectCommand(node)); - } - } - } - - Core::instance()->undo_stack()->pushIfHasChildren(command); + scene_.DeleteSelected(); } void NodeView::SelectAll() @@ -428,6 +392,11 @@ void NodeView::mousePressEvent(QMouseEvent *event) } super::mousePressEvent(event); + + auto selected_items = scene_.GetSelectedItems(); + foreach (NodeViewItem *i, selected_items) { + dragging_nodes_.insert(i, i->GetNodePosition()); + } } void NodeView::mouseMoveEvent(QMouseEvent *event) @@ -658,6 +627,15 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) DetachItemsFromCursor(); } + for (auto it=dragging_nodes_.cbegin(); it!=dragging_nodes_.cend(); it++) { + NodeViewItem *i = it.key(); + QPointF current_pos = i->GetNodePosition(); + if (it.value() != current_pos) { + command->add_child(new NodeSetPositionCommand(i->GetNode(), i->GetContext(), current_pos)); + } + } + dragging_nodes_.clear(); + Core::instance()->undo_stack()->pushIfHasChildren(command); super::mouseReleaseEvent(event); diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 8616974f5..c9d7e0e43 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -219,6 +219,8 @@ private: QVector last_set_filter_nodes_; QMap context_offsets_; + QMap dragging_nodes_; + double scale_; bool create_edge_already_exists_; diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index 1b52624a6..564e6552a 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -50,9 +50,7 @@ void NodeViewContext::AddChild(Node *node) return; } - NodeViewItem *item = new NodeViewItem(this); - item->SetNode(node, context_); - item->SetNodePosition(context_->GetNodePositionInContext(node)); + NodeViewItem *item = new NodeViewItem(node, context_, this); item->SetFlowDirection(flow_dir_); connect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); @@ -162,6 +160,36 @@ void NodeViewContext::SetCurvedEdges(bool e) } } +void NodeViewContext::DeleteSelected(NodeViewDeleteCommand *command) +{ + // Delete any selected edges + foreach (NodeViewEdge *edge, edges_) { + if (edge->isSelected()) { + command->AddEdge(edge->output(), edge->input()); + } + } + + // Delete any selected nodes + foreach (NodeViewItem *node, item_map_) { + if (node->isSelected()) { + command->AddNode(node->GetNode(), context_); + } + } +} + +QVector NodeViewContext::GetSelectedItems() const +{ + QVector items; + + for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) { + if (it.value()->isSelected()) { + items.append(it.value()); + } + } + + return items; +} + void NodeViewContext::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { // Set pen and brush diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index 4ef21f68f..8a84751de 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -7,6 +7,7 @@ #include "node/node.h" #include "nodeviewcommon.h" #include "nodeviewedge.h" +#include "nodeviewundo.h" namespace olive { @@ -22,6 +23,10 @@ public: void SetCurvedEdges(bool e); + void DeleteSelected(NodeViewDeleteCommand *command); + + QVector GetSelectedItems() const; + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; public slots: diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 1c73f8ddd..009ea45ac 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -39,10 +39,10 @@ namespace olive { -NodeViewItem::NodeViewItem(QGraphicsItem *parent) : +NodeViewItem::NodeViewItem(Node* n, Node *context, QGraphicsItem *parent) : QGraphicsRectItem(parent), - node_(nullptr), - context_(nullptr), + node_(n), + context_(context), expanded_(false), hide_titlebar_(false), highlighted_index_(-1), @@ -69,6 +69,24 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) : setRect(title_bar_rect_); output_connector_ = new NodeViewItemConnector(true, this); + + // Set up node + node_->Retranslate(); + + foreach (const QString& input, node_->inputs()) { + if (node_->IsInputConnectable(input) && !node_->IsInputHidden(input)) { + node_inputs_.append(input); + } + } + + UpdateInputConnectors(); + + connect(node_, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); + connect(node_, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); + + SetNodePosition(context_->GetNodePositionInContext(node_)); + + SetExpanded(node_->property("expanded").toBool()); } QPointF NodeViewItem::GetNodePosition() const @@ -208,37 +226,6 @@ int NodeViewItem::GetIndexAt(QPointF pt) const return -1; } -void NodeViewItem::SetNode(Node *n, Node *context) -{ - if (node_) { - disconnect(n, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); - disconnect(n, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); - } - - node_ = n; - context_ = context; - - node_inputs_.clear(); - input_connectors_.clear(); - - if (node_) { - node_->Retranslate(); - - foreach (const QString& input, node_->inputs()) { - if (node_->IsInputConnectable(input) && !node_->IsInputHidden(input)) { - node_inputs_.append(input); - } - } - - UpdateInputConnectors(); - - connect(n, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); - connect(n, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); - } - - update(); -} - void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) { if (node_inputs_.isEmpty() @@ -248,6 +235,7 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) expanded_ = e; hide_titlebar_ = hide_titlebar; + node_->setProperty("expanded", e); if (expanded_ && !node_inputs_.isEmpty()) { // Create new rect @@ -507,8 +495,8 @@ void NodeViewItem::SetLabelAsOutput(bool e) NodeViewEdge *NodeViewItem::GetEdgeFromInputConnector(NodeViewItemConnector *connector) { - ssize_t index = -1; - for (ssize_t i=0; iSetFlowDirection(direction_); } - - // Iterate over edge items setting direction - foreach (NodeViewEdge* edge, edges_) { - edge->SetFlowDirection(direction_); - } } void NodeViewScene::clear() @@ -63,9 +59,6 @@ void NodeViewScene::clear() delete it.value(); } item_map_.clear(); - - qDeleteAll(edges_); - edges_.clear(); } void NodeViewScene::SelectAll() @@ -86,22 +79,22 @@ void NodeViewScene::DeselectAll() } } +void NodeViewScene::DeleteSelected() +{ + NodeViewDeleteCommand* command = new NodeViewDeleteCommand(); + + foreach (NodeViewContext *ctx, context_map_) { + ctx->DeleteSelected(command); + } + + Core::instance()->undo_stack()->push(command); +} + NodeViewItem *NodeViewScene::NodeToUIObject(Node *n) { return item_map_.value(n); } -NodeViewEdge *NodeViewScene::EdgeToUIObject(Node *output, const NodeInput& input) -{ - foreach (NodeViewEdge* edge, edges_) { - if (edge->output() == output && edge->input() == input) { - return edge; - } - } - - return nullptr; -} - QVector NodeViewScene::GetSelectedNodes() const { QHash::const_iterator iterator; @@ -118,29 +111,13 @@ QVector NodeViewScene::GetSelectedNodes() const QVector NodeViewScene::GetSelectedItems() const { - QHash::const_iterator iterator; - QVector selected; + QVector items; - for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) { - if (iterator.value()->isSelected()) { - selected.append(iterator.value()); - } + foreach (NodeViewContext *ctx, context_map_) { + items.append(ctx->GetSelectedItems()); } - return selected; -} - -QVector NodeViewScene::GetSelectedEdges() const -{ - QVector edges; - - foreach (NodeViewEdge* e, edges_) { - if (e->isSelected()) { - edges.append(e); - } - } - - return edges; + return items; } NodeViewContext *NodeViewScene::AddContext(Node *node) diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index db49aca89..0ef1adfab 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -42,6 +42,8 @@ public: void SelectAll(); void DeselectAll(); + void DeleteSelected(); + /** * @brief Retrieve the graphical widget corresponding to a specific Node * @@ -54,22 +56,15 @@ public: * in this view/scene), this function returns nullptr. */ NodeViewItem* NodeToUIObject(Node* n); - NodeViewEdge *EdgeToUIObject(Node *output, const NodeInput &input); QVector GetSelectedNodes() const; QVector GetSelectedItems() const; - QVector GetSelectedEdges() const; const QHash& item_map() const { return item_map_; } - const QVector& edges() const - { - return edges_; - } - Qt::Orientation GetFlowOrientation() const; NodeViewCommon::FlowDirection GetFlowDirection() const; @@ -96,8 +91,6 @@ private: QHash item_map_; - QVector edges_; - NodeGraph* graph_; NodeViewCommon::FlowDirection direction_; diff --git a/app/widget/nodeview/nodeviewundo.cpp b/app/widget/nodeview/nodeviewundo.cpp index 5d7af0263..54106070f 100644 --- a/app/widget/nodeview/nodeviewundo.cpp +++ b/app/widget/nodeview/nodeviewundo.cpp @@ -193,4 +193,100 @@ void NodeOverrideColorCommand::undo() node_->SetOverrideColor(old_index_); } +NodeViewDeleteCommand::NodeViewDeleteCommand() +{ +} + +void NodeViewDeleteCommand::AddNode(Node *node, Node *context) +{ + foreach (const NodePair &pair, nodes_) { + if (pair.first == node && pair.second == context) { + return; + } + } + + nodes_.append(NodePair({node, context})); + + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + if (context->ContextContainsNode(it->second)) { + AddEdge(it->second, it->first); + } + } + + for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) { + if (context->ContextContainsNode(it->second.node())) { + AddEdge(it->first, it->second); + } + } +} + +void NodeViewDeleteCommand::AddEdge(Node *output, const NodeInput &input) +{ + foreach (const Node::OutputConnection &edge, edges_) { + if (edge.first == output && edge.second == input) { + return; + } + } + + edges_.append({output, input}); +} + +Project *NodeViewDeleteCommand::GetRelevantProject() const +{ + if (!nodes_.isEmpty()) { + return nodes_.first().first->project(); + } + + if (!edges_.isEmpty()) { + return edges_.first().first->project(); + } + + return nullptr; +} + +void NodeViewDeleteCommand::redo() +{ + foreach (const Node::OutputConnection &edge, edges_) { + Node::DisconnectEdge(edge.first, edge.second); + } + + foreach (const NodePair &pair, nodes_) { + RemovedNode rn; + + rn.node = pair.first; + rn.context = pair.second; + rn.pos = rn.context->GetNodePositionInContext(rn.node); + + rn.context->RemoveNodeFromContext(rn.node); + + // If node is no longer in any contexts and is not connected to anything, remove it + if (rn.node->parent()->GetNumberOfContextsNodeIsIn(rn.node, true) == 0 + && rn.node->input_connections().empty() + && rn.node->output_connections().empty()) { + rn.removed_from_graph = rn.node->parent(); + rn.node->setParent(&memory_manager_); + } else { + rn.removed_from_graph = nullptr; + } + + removed_nodes_.append(rn); + } +} + +void NodeViewDeleteCommand::undo() +{ + for (auto rn=removed_nodes_.crbegin(); rn!=removed_nodes_.crend(); rn++) { + if (rn->removed_from_graph) { + rn->node->setParent(rn->removed_from_graph); + } + + rn->context->SetNodePositionInContext(rn->node, rn->pos); + } + removed_nodes_.clear(); + + for (auto edge=edges_.crbegin(); edge!=edges_.crend(); edge++) { + Node::ConnectEdge(edge->first, edge->second); + } +} + } diff --git a/app/widget/nodeview/nodeviewundo.h b/app/widget/nodeview/nodeviewundo.h index 7ed3e434e..567198b5b 100644 --- a/app/widget/nodeview/nodeviewundo.h +++ b/app/widget/nodeview/nodeviewundo.h @@ -363,6 +363,42 @@ private: }; +class NodeViewDeleteCommand : public UndoCommand +{ +public: + NodeViewDeleteCommand(); + + void AddNode(Node *node, Node *context); + + void AddEdge(Node *output, const NodeInput &input); + + virtual Project * GetRelevantProject() const override; + +protected: + virtual void redo() override; + + virtual void undo() override; + +private: + using NodePair = QPair; + + QVector nodes_; + + QVector edges_; + + struct RemovedNode { + Node *node; + Node *context; + QPointF pos; + NodeGraph *removed_from_graph; + }; + + QVector removed_nodes_; + + QObject memory_manager_; + +}; + } #endif // NODEVIEWUNDO_H From f5eaf9c6aa1cd8dd64d1b7f1fa58e1eaa2b8ac32 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 17 Nov 2021 12:30:19 -0800 Subject: [PATCH 20/94] ffmpeg: use hex for encoder error --- app/codec/ffmpeg/ffmpegencoder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 7d3a2d7e1..a6ee5c68c 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -572,7 +572,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV // Find encoder AVCodec* encoder = GetEncoder(codec); if (!encoder) { - SetError(tr("Failed to find codec for %1").arg(codec)); + SetError(tr("Failed to find codec for 0x%1").arg(codec, 16)); return false; } From 85ca5dfc826d55fbfecd8b685fd4e81c657a2a45 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 17 Nov 2021 12:30:28 -0800 Subject: [PATCH 21/94] ci: use new deps for macos --- .github/workflows/ci.yml | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34466c395..c5f7121f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -294,9 +294,6 @@ jobs: - name: Checkout Source Code uses: actions/checkout@v2 - - name: Set up Homebrew - uses: Homebrew/actions/setup-homebrew@master - - name: Automatically Generate Package Name shell: bash env: @@ -313,14 +310,10 @@ jobs: shell: bash working-directory: ${{ runner.workspace }} run: | - # HACK: Workaround for https://github.com/actions/virtual-environments/issues/4020 - rm -rf /usr/local/bin/2to3 - - brew update - brew upgrade - brew tap olive-editor/homebrew - brew install -f qt5 ffmpeg-olive openimageio-olive opencolorio opentimelineio portaudio - echo "/usr/local/opt/qt@5/bin" >> $GITHUB_PATH + $DOWNLOAD_TOOL https://github.com/olive-editor/vcpkg-registry/releases/download/continuous/olive-dep-x64-osx.zip + unzip olive-dep-x64-osx.zip + VCPKG_DIR=(vcpkg-export-*) + echo "VCPKG_ROOT=$(pwd -W)/$VCPKG_DIR" >> $GITHUB_ENV - name: Acquire Google Crashpad shell: bash @@ -344,7 +337,9 @@ jobs: run: | brew install ninja PATH=/opt/olive-editor/bin:/opt/olive-editor/crashpad:$PATH \ - cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} -G "${{ matrix.cmake-gen }}" + cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 -G "${{ matrix.cmake-gen }}" \ + -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" - name: Build working-directory: ${{ runner.workspace }}/build @@ -366,9 +361,9 @@ jobs: run: | # Use macdeployqt and macdeployqt to bundle dependencies mv app/$BUNDLE_NAME . - macdeployqt $BUNDLE_NAME -executable=$BUNDLE_NAME/Contents/MacOS/olive-crashhandler + $VCPKG_ROOT/installed/x64-osx/tools/qt5-tools/bin/macdeployqt $BUNDLE_NAME -executable=$BUNDLE_NAME/Contents/MacOS/olive-crashhandler $DOWNLOAD_TOOL https://github.com/arl/macdeployqtfix/raw/master/macdeployqtfix.py - python2 macdeployqtfix.py $BUNDLE_NAME/Contents/MacOS/Olive $(dirname $(which qmake)) + python2 macdeployqtfix.py $BUNDLE_NAME/Contents/MacOS/Olive $VCPKG_ROOT/installed/x64-osx # Manual corrections for f in $BUNDLE_NAME/Contents/Frameworks/*.dylib From 0afbad6c9319bbf7f86243c03a3342f9b85a85cd Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 17 Nov 2021 16:14:45 -0800 Subject: [PATCH 22/94] ci: just use pwd --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c5f7121f0..be1aa890a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -313,7 +313,7 @@ jobs: $DOWNLOAD_TOOL https://github.com/olive-editor/vcpkg-registry/releases/download/continuous/olive-dep-x64-osx.zip unzip olive-dep-x64-osx.zip VCPKG_DIR=(vcpkg-export-*) - echo "VCPKG_ROOT=$(pwd -W)/$VCPKG_DIR" >> $GITHUB_ENV + echo "VCPKG_ROOT=$(pwd)/$VCPKG_DIR" >> $GITHUB_ENV - name: Acquire Google Crashpad shell: bash From 40783bcc6d55ff12349a05bb5440e727b6bb633e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 19 Nov 2021 15:47:31 -0800 Subject: [PATCH 23/94] reimplemented adding/removing/connecting/disconnecting --- app/widget/nodeview/nodeview.cpp | 322 ++++++++++++------------ app/widget/nodeview/nodeview.h | 47 +--- app/widget/nodeview/nodeviewcontext.cpp | 20 +- app/widget/nodeview/nodeviewcontext.h | 7 + app/widget/nodeview/nodeviewedge.cpp | 3 +- app/widget/nodeview/nodeviewitem.cpp | 15 +- app/widget/nodeview/nodeviewitem.h | 6 + app/widget/nodeview/nodeviewscene.h | 5 + 8 files changed, 233 insertions(+), 192 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 3da2c70f1..b27b0b862 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "core.h" #include "nodeviewundo.h" @@ -44,7 +45,8 @@ NodeView::NodeView(QWidget *parent) : HandMovableView(parent), drop_edge_(nullptr), create_edge_(nullptr), - create_edge_dst_(nullptr), + create_edge_output_item_(nullptr), + create_edge_input_item_(nullptr), create_edge_dst_temp_expanded_(false), paste_command_(nullptr), scale_(1.0), @@ -87,14 +89,14 @@ void NodeView::SetContexts(const QVector &nodes) // Remove contexts that are no longer in the list foreach (Node *n, contexts_) { if (!nodes.contains(n)) { - scene_.RemoveContext(n); + RemoveContext(n); } } // Add contexts that are now in the list foreach (Node *n, nodes) { if (!contexts_.contains(n)) { - scene_.AddContext(n); + AddContext(n); } } @@ -346,29 +348,56 @@ void NodeView::keyPressEvent(QKeyEvent *event) void NodeView::mousePressEvent(QMouseEvent *event) { + // Handle mouse press event if (HandPress(event)) return; + // Get the item that the user clicked on, if any QGraphicsItem* item = itemAt(event->pos()); if (event->button() == Qt::LeftButton) { // Determine if user clicked on a connector - if (NodeViewItemConnector *connector = dynamic_cast(item)) { - NodeViewItem *attached_item = static_cast(connector->parentItem()); - if (connector->IsOutput()) { - CreateNewEdge(attached_item, event->pos()); - return; - } else { - NodeViewEdge *edge_item = attached_item->GetEdgeFromInputConnector(connector); - if (edge_item) { - create_edge_src_ = edge_item->from_item(); - create_edge_ = edge_item; + NodeViewItemConnector *connector = dynamic_cast(item); + + // If the user clicked on a connector OR the user is holding Ctrl + if (connector || (event->modifiers() & Qt::ControlModifier)) { + // Get the relevant item, either the one attached to the connector or the item if Ctrl+Clicked + NodeViewItem *attached_item = connector ? static_cast(connector->parentItem()) : dynamic_cast(item); + + if (attached_item) { + if (connector && !connector->IsOutput() && (create_edge_ = attached_item->GetEdgeFromInputConnector(connector))) { + // Since inputs can only have one edge connected, we grab the existing edge, if one exists + create_edge_output_item_ = create_edge_->from_item(); create_edge_already_exists_ = true; + create_edge_from_output_ = true; + } else { + // Create a new edge from this output + create_edge_ = new NodeViewEdge(); + create_edge_->SetCurved(scene_.GetEdgesAreCurved()); + create_edge_->SetFlowDirection(scene_.GetFlowDirection()); + + // Set source and declare that we created this edge + if ((create_edge_from_output_ = (!connector || connector->IsOutput()))) { + // Edge is being created from output + create_edge_output_item_ = attached_item; + } else { + // Edge is being created from input + create_edge_input_item_ = attached_item; + create_edge_input_ = attached_item->GetInputFromInputConnector(connector); + } + create_edge_already_exists_ = false; + + // Add edge to scene + scene_.addItem(create_edge_); + + // Position edge to mouse cursor + PositionNewEdge(event->pos()); } return; } } } + // Handle selections with the right mouse button if (event->button() == Qt::RightButton) { if (!item || !item->isSelected()) { // Qt doesn't do this by default for some reason @@ -383,19 +412,16 @@ void NodeView::mousePressEvent(QMouseEvent *event) } } - if (event->modifiers() & Qt::ControlModifier) { - NodeViewItem* node_item = dynamic_cast(item); - if (node_item) { - CreateNewEdge(node_item, event->pos()); - return; - } - } - + // Default QGraphicsView functionality (selecting, dragging, etc.) super::mousePressEvent(event); + // For any selected item, store its position in case the user is dragging it somewhere else auto selected_items = scene_.GetSelectedItems(); foreach (NodeViewItem *i, selected_items) { - dragging_nodes_.insert(i, i->GetNodePosition()); + // Ignore items attached to the cursor + if (!IsItemAttachedToCursor(i)) { + dragging_nodes_.insert(i, i->GetNodePosition()); + } } } @@ -487,23 +513,17 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) if (HandRelease(event)) return; if (create_edge_) { - // We are creating a new edge or moving an existing one + // Check if the edge was reconnected to the same place as before MultiUndoCommand* command = new MultiUndoCommand(); - Node::OutputConnections removed_edges; - Node::OutputConnection added_edge; - bool reconnected_to_itself = false; if (create_edge_already_exists_) { - if (create_edge_dst_input_ == create_edge_->input()) { + if (create_edge_output_item_ == create_edge_->from_item() && create_edge_->input() == create_edge_input_) { reconnected_to_itself = true; } else { // We are moving (or removing) an existing edge command->add_child(new NodeEdgeRemoveCommand(create_edge_->output(), create_edge_->input())); - - // Update contexts for edge removal - removed_edges.push_back({create_edge_->output(), create_edge_->input()}); } } else { // We're creating a new edge, which means this UI object is only temporary @@ -512,36 +532,35 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) create_edge_ = nullptr; - if (create_edge_dst_) { - // Clear highlight - create_edge_dst_->SetHighlightedIndex(-1); + if (create_edge_output_item_ && create_edge_input_item_) { + // Clear highlight if we set one + create_edge_input_item_->SetHighlightedIndex(-1); // Collapse if we expanded it if (create_edge_dst_temp_expanded_) { - create_edge_dst_->SetExpanded(false); - create_edge_dst_->setZValue(0); + create_edge_input_item_->SetExpanded(false); + create_edge_input_item_->setZValue(0); } - NodeInput &creating_input = create_edge_dst_input_; + NodeInput &creating_input = create_edge_input_; if (creating_input.IsValid()) { // Make connection if (!reconnected_to_itself) { - Node *creating_output = create_edge_src_->GetNode(); + Node *creating_output = create_edge_output_item_->GetNode(); if (creating_input.IsConnected()) { Node::OutputConnection existing_edge_to_remove = {creating_input.GetConnectedOutput(), creating_input}; command->add_child(new NodeEdgeRemoveCommand(existing_edge_to_remove.first, existing_edge_to_remove.second)); - removed_edges.push_back(existing_edge_to_remove); } command->add_child(new NodeEdgeAddCommand(creating_output, creating_input)); - added_edge = {creating_output, creating_input}; } creating_input.Reset(); } - create_edge_dst_ = nullptr; + create_edge_output_item_ = nullptr; + create_edge_input_item_ = nullptr; } Core::instance()->undo_stack()->pushIfHasChildren(command); @@ -551,6 +570,21 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) MultiUndoCommand* command = new MultiUndoCommand(); if (!attached_items_.isEmpty()) { + Node *context = nullptr; + + QList items_at_cursor = this->items(event->pos()); + foreach (QGraphicsItem *i, items_at_cursor) { + if (NodeViewContext *context_item = dynamic_cast(i)) { + context = context_item->GetContext(); + break; + } + } + + if (!context) { + QToolTip::showText(QCursor::pos(), tr("Nodes must be placed inside a context.")); + return; + } + if (paste_command_) { // We've already "done" this command, but MultiUndoCommand prevents "redoing" twice, so we // add it to this command (which may have extra commands added too) so that it all gets undone @@ -558,9 +592,26 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) command->add_child(paste_command_); paste_command_ = nullptr; } - } - if (!attached_items_.isEmpty()) { + { + MultiUndoCommand *add_command = new MultiUndoCommand(); + + foreach (const AttachedItem &ai, attached_items_) { + // Add node to the same graph that the context is in + add_command->add_child(new NodeAddCommand(context->parent(), ai.item->GetNode())); + + // Add node to the context + add_command->add_child(new NodeSetPositionCommand(ai.item->GetNode(), context, scene_.context_map().value(context)->MapScenePosToNodePosInContext(ai.item->pos()))); + } + + if (add_command->child_count()) { + add_command->redo_now(); + command->add_child(add_command); + } else { + delete add_command; + } + } + { // Dropped attached item onto an edge, connect it between them MultiUndoCommand *drop_edge_command = new MultiUndoCommand(); @@ -586,44 +637,6 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } } - { - // Remove from context any nodes that don't specifically output to said context - MultiUndoCommand *remove_pos_command = new MultiUndoCommand(); - - for (const AttachedItem &attached : qAsConst(attached_items_)) { - MultiUndoCommand *remove_pos_subcommand = new MultiUndoCommand(); - Node *attached_node = scene_.item_map().key(attached.item); - - bool removed = false; - QVector relevant_contexts; - for (Node *context : qAsConst(contexts_)) { - if (attached_node->OutputsTo(context, true)) { - relevant_contexts.append(context); - } else { - remove_pos_subcommand->add_child(new NodeRemovePositionFromContextCommand(attached_node, context)); - removed = true; - } - } - - if (removed && !relevant_contexts.isEmpty()) { - for (Node *relevant : qAsConst(relevant_contexts)) { - remove_pos_subcommand->add_child(new NodeSetPositionCommand(attached_node, relevant, GetEstimatedPositionForContext(attached.item, relevant))); - } - - remove_pos_command->add_child(remove_pos_subcommand); - } else { - delete remove_pos_subcommand; - } - } - - if (remove_pos_command->child_count()) { - remove_pos_command->redo_now(); - command->add_child(remove_pos_command); - } else { - delete remove_pos_command; - } - } - DetachItemsFromCursor(); } @@ -777,20 +790,14 @@ void NodeView::ShowContextMenu(const QPoint &pos) void NodeView::CreateNodeSlot(QAction *action) { - qDebug() << "STUB!"; - /*Node* new_node = NodeFactory::CreateFromMenuAction(action); + Node* new_node = NodeFactory::CreateFromMenuAction(action); if (new_node) { - paste_command_ = new MultiUndoCommand(); - paste_command_->add_child(new NodeAddCommand(graph_, new_node)); - for (Node *context : qAsConst(contexts_)) { - paste_command_->add_child(new NodeSetPositionCommand(new_node, context, QPointF(0, 0), false)); - } - paste_command_->add_child(new NodeViewAttachNodesToCursor(this, {new_node})); - paste_command_->redo_now(); - - this->setFocus(); - }*/ + NodeViewItem *new_item = new NodeViewItem(new_node, nullptr); + new_item->SetFlowDirection(scene_.GetFlowDirection()); + scene_.addItem(new_item); + AttachItemsToCursor({new_item}); + } } void NodeView::ContextMenuSetDirection(QAction *action) @@ -859,6 +866,15 @@ void NodeView::MoveToScenePoint(const QPointF &pos) centerOn(pos); } +void NodeView::NodeRemovedFromGraph() +{ + Node *context = static_cast(sender()); + + RemoveContext(context); + + contexts_.removeOne(context); +} + void NodeView::AttachNodesToCursor(const QVector &nodes) { QVector items(nodes.size()); @@ -885,6 +901,10 @@ void NodeView::AttachItemsToCursor(const QVector& items) void NodeView::DetachItemsFromCursor() { + foreach (const AttachedItem &ai, attached_items_) { + delete ai.item; + } + attached_items_.clear(); } @@ -1037,20 +1057,6 @@ Menu *NodeView::CreateAddMenu(Menu *parent) return add_menu; } -void NodeView::CreateNewEdge(NodeViewItem *output_item, const QPoint &mouse_pos) -{ - create_edge_ = new NodeViewEdge(); - create_edge_src_ = output_item; - create_edge_already_exists_ = false; - - create_edge_->SetCurved(scene_.GetEdgesAreCurved()); - create_edge_->SetFlowDirection(scene_.GetFlowDirection()); - - scene_.addItem(create_edge_); - - PositionNewEdge(mouse_pos); -} - void NodeView::PositionNewEdge(const QPoint &pos) { // Determine scene coordinate @@ -1059,62 +1065,66 @@ void NodeView::PositionNewEdge(const QPoint &pos) // Find if the cursor is currently inside an item NodeViewItem* item_at_cursor = dynamic_cast(itemAt(pos)); + NodeViewItem *source_item = create_edge_from_output_ ? create_edge_output_item_ : create_edge_input_item_; + NodeViewItem *&opposing_item = create_edge_from_output_ ? create_edge_input_item_ : create_edge_output_item_; + // Filter out connecting to self - if (item_at_cursor == create_edge_src_) { + if (item_at_cursor == source_item) { item_at_cursor = nullptr; } // Filter out connecting to a node that connects to us - if (item_at_cursor && item_at_cursor->GetNode()->OutputsTo(create_edge_src_->GetNode(), true)) { + if (item_at_cursor + && ((create_edge_from_output_ && item_at_cursor->GetNode()->OutputsTo(source_item->GetNode(), true)) + || (!create_edge_from_output_ && item_at_cursor->GetNode()->InputsFrom(source_item->GetNode(), true)))) { item_at_cursor = nullptr; } // If the item has changed - if (item_at_cursor != create_edge_dst_) { + if (item_at_cursor != opposing_item) { // If we had a destination active, disconnect from it since the item has changed - if (create_edge_dst_) { - create_edge_dst_->SetHighlightedIndex(-1); + if (opposing_item) { + opposing_item->SetHighlightedIndex(-1); if (create_edge_dst_temp_expanded_) { // We expanded this item, so we can un-expand it - create_edge_dst_->SetExpanded(false); - create_edge_dst_->setZValue(0); + opposing_item->SetExpanded(false); + opposing_item->setZValue(0); } } // Set destination - create_edge_dst_ = item_at_cursor; + opposing_item = item_at_cursor; // If our destination is an item, ensure it's expanded - if (create_edge_dst_) { - if ((create_edge_dst_temp_expanded_ = (!create_edge_dst_->IsExpanded()))) { - create_edge_dst_->SetExpanded(true, true); - create_edge_dst_->setZValue(100); // Ensure item is in front + if (opposing_item) { + if (create_edge_from_output_ && (create_edge_dst_temp_expanded_ = (!create_edge_input_item_->IsExpanded()))) { + create_edge_input_item_->SetExpanded(true, true); + create_edge_input_item_->setZValue(100); // Ensure item is in front } } } // If we have a destination, highlight the appropriate input - int highlight_index = -1; - if (create_edge_dst_) { - highlight_index = create_edge_dst_->GetIndexAt(scene_pt); - create_edge_dst_->SetHighlightedIndex(highlight_index); + if (create_edge_from_output_) { + int highlight_index = -1; + if (create_edge_input_item_) { + highlight_index = create_edge_input_item_->GetIndexAt(scene_pt); + create_edge_input_item_->SetHighlightedIndex(highlight_index); + } + + if (highlight_index >= 0) { + create_edge_input_ = create_edge_input_item_->GetInputAtIndex(highlight_index); + } else { + create_edge_input_.Reset(); + } } - if (highlight_index >= 0) { - create_edge_dst_input_ = create_edge_dst_->GetInputAtIndex(highlight_index); - create_edge_->SetPoints(create_edge_src_->GetOutputPoint(), - create_edge_dst_->GetInputPoint(create_edge_dst_input_.input(), create_edge_dst_input_.element()), - true); - } else { - create_edge_dst_input_.Reset(); - create_edge_->SetPoints(create_edge_src_->GetOutputPoint(), - scene_pt, - false); - } + QPointF output_point = create_edge_output_item_ ? create_edge_output_item_->GetOutputPoint() : scene_pt; + QPointF input_point = create_edge_input_.IsValid() ? create_edge_input_item_->GetInputPoint(create_edge_input_.input(), create_edge_input_.element()) : scene_pt; - // Set connected to whether we have a valid input destination - create_edge_->SetConnected(create_edge_dst_input_.IsValid()); + create_edge_->SetPoints(output_point, input_point, create_edge_input_item_ && create_edge_input_item_->IsExpanded()); + create_edge_->SetConnected(create_edge_output_item_ && create_edge_input_.IsValid()); } void NodeView::GroupNodes() @@ -1177,6 +1187,29 @@ void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) */ } +void NodeView::AddContext(Node *n) +{ + scene_.AddContext(n); + connect(n, &Node::RemovedFromGraph, this, &NodeView::NodeRemovedFromGraph); +} + +void NodeView::RemoveContext(Node *n) +{ + scene_.RemoveContext(n); + disconnect(n, &Node::RemovedFromGraph, this, &NodeView::NodeRemovedFromGraph); +} + +bool NodeView::IsItemAttachedToCursor(NodeViewItem *item) const +{ + foreach (const AttachedItem &ai, attached_items_) { + if (ai.item == item) { + return true; + } + } + + return false; +} + NodeView::NodeViewAttachNodesToCursor::NodeViewAttachNodesToCursor(NodeView *view, const QVector &nodes) : view_(view), nodes_(nodes) @@ -1198,23 +1231,4 @@ Project *NodeView::NodeViewAttachNodesToCursor::GetRelevantProject() const return nullptr; } -void NodeView::NodeViewItemPreventRemovingCommand::redo() -{ - NodeViewItem *item = view_->scene_.item_map().value(node_); - - if (item) { - old_prevent_removing_ = item->GetPreventRemoving(); - item->SetPreventRemoving(new_prevent_removing_); - } -} - -void NodeView::NodeViewItemPreventRemovingCommand::undo() -{ - NodeViewItem *item = view_->scene_.item_map().value(node_); - - if (item) { - item->SetPreventRemoving(old_prevent_removing_); - } -} - } diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index c9d7e0e43..c2259a780 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -139,12 +139,16 @@ private: Menu *CreateAddMenu(Menu *parent); - void CreateNewEdge(NodeViewItem *output_item, const QPoint &mouse_pos); - void PositionNewEdge(const QPoint &pos); void PasteNodesInternal(const QVector &duplicate_nodes = QVector()); + void AddContext(Node *n); + + void RemoveContext(Node *n); + + bool IsItemAttachedToCursor(NodeViewItem *item) const; + class NodeViewAttachNodesToCursor : public UndoCommand { public: @@ -171,43 +175,18 @@ private: QPointF original_pos; }; - class NodeViewItemPreventRemovingCommand : public UndoCommand - { - public: - NodeViewItemPreventRemovingCommand(NodeView *view, Node *node, bool prevent_removing) : - view_(view), - node_(node), - new_prevent_removing_(prevent_removing) - {} - - virtual Project * GetRelevantProject() const override - { - return node_->project(); - } - - protected: - virtual void redo() override; - - virtual void undo() override; - - private: - NodeView *view_; - Node *node_; - bool new_prevent_removing_; - bool old_prevent_removing_; - - }; - QList attached_items_; NodeViewEdge* drop_edge_; NodeInput drop_input_; NodeViewEdge* create_edge_; - NodeViewItem* create_edge_src_; - NodeViewItem* create_edge_dst_; - NodeInput create_edge_dst_input_; + NodeViewItem* create_edge_output_item_; + NodeViewItem* create_edge_input_item_; + NodeInput create_edge_input_; bool create_edge_dst_temp_expanded_; + bool create_edge_already_exists_; + bool create_edge_from_output_; NodeViewScene scene_; @@ -223,8 +202,6 @@ private: double scale_; - bool create_edge_already_exists_; - bool first_show_; static const double kMinimumScale; @@ -263,6 +240,8 @@ private slots: void MoveToScenePoint(const QPointF &pos); + void NodeRemovedFromGraph(); + void GroupNodes(); void UngroupNodes(); diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index 564e6552a..947762ee1 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -91,7 +91,16 @@ void NodeViewContext::RemoveChild(Node *node) disconnect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); disconnect(node, &Node::InputDisconnected, this, &NodeViewContext::ChildInputDisconnected); - delete item_map_.take(node); + NodeViewItem *item = item_map_.take(node); + + // Delete edges first because the edge destructor will try to reference item (maybe that should + // be changed...) + QVector edges_to_remove = item->edges(); + foreach (NodeViewEdge *edge, edges_to_remove) { + ChildInputDisconnected(edge->output(), edge->input()); + } + + delete item; } void NodeViewContext::ChildInputConnected(Node *output, const NodeInput &input) @@ -190,6 +199,15 @@ QVector NodeViewContext::GetSelectedItems() const return items; } +QPointF NodeViewContext::MapScenePosToNodePosInContext(const QPointF &pos) const +{ + for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) { + QPointF pos_inside_parent = it.value()->mapToParent(it.value()->mapFromScene(pos)); + return NodeViewItem::ScreenToNodePoint(pos_inside_parent, flow_dir_); + } + return QPointF(0, 0); +} + void NodeViewContext::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { // Set pen and brush diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index 8a84751de..62be15974 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -17,6 +17,11 @@ class NodeViewContext : public QObject, public QGraphicsRectItem public: NodeViewContext(Node *context, QGraphicsItem *item = nullptr); + Node *GetContext() const + { + return context_; + } + void UpdateRect(); void SetFlowDirection(NodeViewCommon::FlowDirection dir); @@ -27,6 +32,8 @@ public: QVector GetSelectedItems() const; + QPointF MapScenePosToNodePosInContext(const QPointF &pos) const; + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; public slots: diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index caf629007..794c98c52 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -162,7 +162,6 @@ void NodeViewEdge::UpdateCurve() { const QPointF &start = cached_start_; const QPointF &end = cached_end_; - const bool input_is_expanded = cached_input_is_expanded_; QPainterPath path; path.moveTo(start); @@ -182,7 +181,7 @@ void NodeViewEdge::UpdateCurve() cp1 = QPointF(start.x(), half_y); } - if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal || input_is_expanded) { + if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal || cached_input_is_expanded_) { cp2 = QPointF(half_x, end.y()); } else { cp2 = QPointF(end.x(), half_y); diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 009ea45ac..57f67ddaf 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -84,7 +84,9 @@ NodeViewItem::NodeViewItem(Node* n, Node *context, QGraphicsItem *parent) : connect(node_, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); connect(node_, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); - SetNodePosition(context_->GetNodePositionInContext(node_)); + if (context_) { + SetNodePosition(context_->GetNodePositionInContext(node_)); + } SetExpanded(node_->property("expanded").toBool()); } @@ -493,6 +495,17 @@ void NodeViewItem::SetLabelAsOutput(bool e) update(); } +NodeInput NodeViewItem::GetInputFromInputConnector(NodeViewItemConnector *connector) +{ + for (int i=0; i &edges() const + { + return edges_; + } + /** * @brief Set expanded state */ @@ -129,6 +134,7 @@ public: void SetLabelAsOutput(bool e); + NodeInput GetInputFromInputConnector(NodeViewItemConnector *connector); NodeViewEdge *GetEdgeFromInputConnector(NodeViewItemConnector *connector); protected: diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index 0ef1adfab..b9949d59c 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -60,6 +60,11 @@ public: QVector GetSelectedNodes() const; QVector GetSelectedItems() const; + const QHash &context_map() const + { + return context_map_; + } + const QHash& item_map() const { return item_map_; From a391e6325268f46e629a2526a6b286f50d38f149 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 19 Nov 2021 16:46:39 -0800 Subject: [PATCH 24/94] cleaned up some no longer used functions --- app/widget/nodeview/nodeview.cpp | 160 +++++++----------------- app/widget/nodeview/nodeview.h | 25 +--- app/widget/nodeview/nodeviewcontext.cpp | 7 ++ app/widget/nodeview/nodeviewcontext.h | 2 + app/widget/nodeview/nodeviewitem.cpp | 1 - app/widget/nodeview/nodeviewitem.h | 12 -- app/widget/nodeview/nodeviewscene.cpp | 64 +--------- app/widget/nodeview/nodeviewscene.h | 31 +---- 8 files changed, 65 insertions(+), 237 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index b27b0b862..5cb1d767c 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -140,20 +140,7 @@ void NodeView::SelectAll() ConnectSelectionChangedSignal(); - // Determine which nodes aren't selected and add them to a separate vector - QVector new_selection; - for (auto it=scene_.item_map().cbegin(); it!=scene_.item_map().cend(); it++) { - Node *n = it.key(); - if (!selected_nodes_.contains(n)) { - new_selection.append(n); - } - } - - // Add this vector to our total selection vector - selected_nodes_.append(new_selection); - - // Signal new nodes - emit NodesSelected(new_selection); + UpdateSelectionCache(); } void NodeView::DeselectAll() @@ -175,7 +162,7 @@ void NodeView::DeselectAll() selected_nodes_.clear(); } -void NodeView::Select(QVector nodes, bool center_view_on_item) +void NodeView::Select(const QVector &nodes, bool center_view_on_item) { // Optimization: rather than respond to every single item being selected, ignore the signal and // then handle them all at the end. @@ -186,54 +173,21 @@ void NodeView::Select(QVector nodes, bool center_view_on_item) scene_.DeselectAll(); - // Remove any duplicates - QVector processed; - - NodeViewItem *first_item = nullptr; - - for (Node* n : qAsConst(nodes)) { - if (processed.contains(n)) { - continue; - } - - processed.append(n); - - NodeViewItem* item = scene_.NodeToUIObject(n); - - if (item) { - item->setSelected(true); - - if (!first_item) { - first_item = item; - } - - if (deselections.contains(n)) { - deselections.removeOne(n); - } else { - new_selections.append(n); - } - } + foreach (NodeViewContext *context, scene_.context_map()) { + context->Select(nodes); } + /* // Center on something + Node *first_item = nodes.isEmpty() ? nullptr : nodes.first(); if (center_view_on_item && first_item) { centerOn(first_item); } + */ ConnectSelectionChangedSignal(); - // Emit deselect signal for any nodes that weren't in the list - if (!deselections.isEmpty()) { - emit NodesDeselected(deselections); - } - - // Emit select signal for any nodes that weren't in the list - if (!new_selections.isEmpty()) { - emit NodesSelected(new_selections); - } - - // Update selected list to the list we received - selected_nodes_ = nodes; + UpdateSelectionCache(); } void NodeView::CopySelected(bool cut) @@ -256,7 +210,7 @@ void NodeView::Paste() void NodeView::Duplicate() { - PasteNodesInternal(scene_.GetSelectedNodes()); + PasteNodesInternal(selected_nodes_); } void NodeView::SetColorLabel(int index) @@ -292,7 +246,7 @@ void NodeView::keyPressEvent(QKeyEvent *event) for (Node *n : qAsConst(selected_nodes_)) { for (Node *context : qAsConst(contexts_)) { if (context->ContextContainsNode(n)) { - QPointF old_pos = context->GetNodePositionInContext(n); + Node::Position old_pos = context->GetNodePositionInContext(n); // Determine one pixel in scene units double movement_amt = 1.0 / scale_; @@ -420,7 +374,7 @@ void NodeView::mousePressEvent(QMouseEvent *event) foreach (NodeViewItem *i, selected_items) { // Ignore items attached to the cursor if (!IsItemAttachedToCursor(i)) { - dragging_nodes_.insert(i, i->GetNodePosition()); + dragging_items_.insert(i, i->GetNodePosition()); } } } @@ -554,6 +508,11 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } command->add_child(new NodeEdgeAddCommand(creating_output, creating_input)); + + // If the output is not in the input's context, add it now + if (!create_edge_input_item_->GetContext()->ContextContainsNode(creating_output)) { + command->add_child(new NodeSetPositionCommand(creating_output, create_edge_input_item_->GetContext(), scene_.context_map().value(create_edge_input_item_->GetContext())->MapScenePosToNodePosInContext(create_edge_output_item_->scenePos()))); + } } creating_input.Reset(); @@ -640,14 +599,14 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) DetachItemsFromCursor(); } - for (auto it=dragging_nodes_.cbegin(); it!=dragging_nodes_.cend(); it++) { + for (auto it=dragging_items_.cbegin(); it!=dragging_items_.cend(); it++) { NodeViewItem *i = it.key(); QPointF current_pos = i->GetNodePosition(); if (it.value() != current_pos) { command->add_child(new NodeSetPositionCommand(i->GetNode(), i->GetContext(), current_pos)); } } - dragging_nodes_.clear(); + dragging_items_.clear(); Core::instance()->undo_stack()->pushIfHasChildren(command); @@ -663,37 +622,42 @@ void NodeView::resizeEvent(QResizeEvent *event) void NodeView::UpdateSelectionCache() { - QVector current_selection = scene_.GetSelectedNodes(); + QVector current_selection = scene_.GetSelectedItems(); QVector selected; QVector deselected; // Determine which nodes are newly selected - if (selected_nodes_.isEmpty()) { - // All nodes in the current selection have just been selected - selected = current_selection; - } else { - for (Node* n : qAsConst(current_selection)) { - if (!selected_nodes_.contains(n)) { - selected.append(n); - } + foreach (NodeViewItem* i, current_selection) { + Node *n = i->GetNode(); + if (!selected_nodes_.contains(n)) { + selected.append(n); + selected_nodes_.append(n); } } // Determine which nodes are newly deselected if (current_selection.isEmpty()) { - // All nodes that were selected have been deselected + // All nodes that were selected have been deselected, so we'll just set them all to `deselected` deselected = selected_nodes_; } else { - for (Node* n : qAsConst(selected_nodes_)) { - if (!current_selection.contains(n)) { + foreach (Node* n, selected_nodes_) { + bool still_selected = false; + + foreach (NodeViewItem *i, current_selection) { + if (i->GetNode() == n) { + still_selected = true; + break; + } + } + + if (still_selected) { deselected.append(n); + selected_nodes_.removeOne(n); } } } - selected_nodes_ = current_selection; - if (!selected.isEmpty()) { emit NodesSelected(selected); } @@ -722,7 +686,7 @@ void NodeView::ShowContextMenu(const QPoint &pos) // Label node action QAction* label_action = m.addAction(tr("Label")); connect(label_action, &QAction::triggered, this, [this](){ - Core::instance()->LabelNodes(scene_.GetSelectedNodes()); + Core::instance()->LabelNodes(selected_nodes_); }); // Grouping @@ -807,11 +771,12 @@ void NodeView::ContextMenuSetDirection(QAction *action) void NodeView::OpenSelectedNodeInViewer() { - QVector selected = scene_.GetSelectedNodes(); - ViewerOutput* viewer = selected.isEmpty() ? nullptr : dynamic_cast(selected.first()); - - if (viewer) { - Core::instance()->OpenNodeInViewer(viewer); + // Find first viewer in list of selected nodes and open it + foreach (Node *n, selected_nodes_) { + if (ViewerOutput* viewer = dynamic_cast(n)) { + Core::instance()->OpenNodeInViewer(viewer); + break; + } } } @@ -875,17 +840,6 @@ void NodeView::NodeRemovedFromGraph() contexts_.removeOne(context); } -void NodeView::AttachNodesToCursor(const QVector &nodes) -{ - QVector items(nodes.size()); - - for (int i=0; i& items) { DetachItemsFromCursor(); @@ -974,7 +928,8 @@ bool NodeView::eventFilter(QObject *object, QEvent *event) void NodeView::CopyNodesToClipboardInternal(QXmlStreamWriter *writer, const QVector &nodes, void *userdata) { - writer->writeStartElement(QStringLiteral("pos")); + qDebug() << "STUB!"; + /*writer->writeStartElement(QStringLiteral("pos")); for (Node *n : nodes) { NodeViewItem *item = scene_.item_map().value(n); @@ -987,7 +942,7 @@ void NodeView::CopyNodesToClipboardInternal(QXmlStreamWriter *writer, const QVec writer->writeEndElement(); // node } - writer->writeEndElement(); // pos + writer->writeEndElement(); // pos*/ } void NodeView::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, void *userdata) @@ -1210,25 +1165,4 @@ bool NodeView::IsItemAttachedToCursor(NodeViewItem *item) const return false; } -NodeView::NodeViewAttachNodesToCursor::NodeViewAttachNodesToCursor(NodeView *view, const QVector &nodes) : - view_(view), - nodes_(nodes) -{ -} - -void NodeView::NodeViewAttachNodesToCursor::redo() -{ - view_->AttachNodesToCursor(nodes_); -} - -void NodeView::NodeViewAttachNodesToCursor::undo() -{ - view_->DetachItemsFromCursor(); -} - -Project *NodeView::NodeViewAttachNodesToCursor::GetRelevantProject() const -{ - return nullptr; -} - } diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index c2259a780..fd5e2c0c9 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -63,7 +63,7 @@ public: void SelectAll(); void DeselectAll(); - void Select(QVector nodes, bool center_view_on_item); + void Select(const QVector &nodes, bool center_view_on_item); void CopySelected(bool cut); void Paste(); @@ -120,8 +120,6 @@ protected: virtual void PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, void* userdata) override; private: - void AttachNodesToCursor(const QVector &nodes); - void AttachItemsToCursor(const QVector &items); void DetachItemsFromCursor(); @@ -149,25 +147,6 @@ private: bool IsItemAttachedToCursor(NodeViewItem *item) const; - class NodeViewAttachNodesToCursor : public UndoCommand - { - public: - NodeViewAttachNodesToCursor(NodeView* view, const QVector& nodes); - - virtual Project * GetRelevantProject() const override; - - protected: - virtual void redo() override; - - virtual void undo() override; - - private: - NodeView* view_; - - QVector nodes_; - - }; - NodeViewMiniMap *minimap_; struct AttachedItem { @@ -198,7 +177,7 @@ private: QVector last_set_filter_nodes_; QMap context_offsets_; - QMap dragging_nodes_; + QMap dragging_items_; double scale_; diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index 947762ee1..b608ee58b 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -186,6 +186,13 @@ void NodeViewContext::DeleteSelected(NodeViewDeleteCommand *command) } } +void NodeViewContext::Select(const QVector &nodes) +{ + foreach (Node *n, nodes) { + item_map_.value(n)->setSelected(true); + } +} + QVector NodeViewContext::GetSelectedItems() const { QVector items; diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index 62be15974..cf49b332d 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -30,6 +30,8 @@ public: void DeleteSelected(NodeViewDeleteCommand *command); + void Select(const QVector &nodes); + QVector GetSelectedItems() const; QPointF MapScenePosToNodePosInContext(const QPointF &pos) const; diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 57f67ddaf..1c645601e 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -47,7 +47,6 @@ NodeViewItem::NodeViewItem(Node* n, Node *context, QGraphicsItem *parent) : hide_titlebar_(false), highlighted_index_(-1), flow_dir_(NodeViewCommon::kLeftToRight), - prevent_removing_(false), label_as_output_(false) { // Set flags for this widget diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 974cb1b3e..60d1f3874 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -122,16 +122,6 @@ public: void SetHighlightedIndex(int index); - void SetPreventRemoving(bool e) - { - prevent_removing_ = e; - } - - bool GetPreventRemoving() const - { - return prevent_removing_; - } - void SetLabelAsOutput(bool e); NodeInput GetInputFromInputConnector(NodeViewItemConnector *connector); @@ -210,8 +200,6 @@ private: QPointF cached_node_pos_; - bool prevent_removing_; - std::vector > input_connectors_; NodeViewItemConnector *output_connector_; diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index 2e422c24b..37a226710 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -44,37 +44,16 @@ void NodeViewScene::SetFlowDirection(NodeViewCommon::FlowDirection direction) } } -void NodeViewScene::clear() -{ - // Deselect everything (prevents signals that a selection has changed after deleting an object) - DeselectAll(); - - // HACK: QGraphicsScene contains some sort of internal caching of the selected items which doesn't update unless - // we call a function like this. That means even though we deselect all items above, QGraphicsScene will - // continue to incorrectly signal selectionChanged() when items that were selected (but are now not) get - // deleted. Calling this function appears to update the internal cache and prevent this. - selectedItems(); - - for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) { - delete it.value(); - } - item_map_.clear(); -} - void NodeViewScene::SelectAll() { - QList all_items = this->items(); - - foreach (QGraphicsItem* i, all_items) { + foreach (QGraphicsItem* i, items()) { i->setSelected(true); } } void NodeViewScene::DeselectAll() { - QList selected_items = this->selectedItems(); - - foreach (QGraphicsItem* i, selected_items) { + foreach (QGraphicsItem* i, items()) { i->setSelected(false); } } @@ -90,25 +69,6 @@ void NodeViewScene::DeleteSelected() Core::instance()->undo_stack()->push(command); } -NodeViewItem *NodeViewScene::NodeToUIObject(Node *n) -{ - return item_map_.value(n); -} - -QVector NodeViewScene::GetSelectedNodes() const -{ - QHash::const_iterator iterator; - QVector selected; - - for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) { - if (iterator.value()->isSelected()) { - selected.append(iterator.key()); - } - } - - return selected; -} - QVector NodeViewScene::GetSelectedItems() const { QVector items; @@ -151,31 +111,11 @@ void NodeViewScene::RemoveContext(Node *node) delete context_map_.take(node); } -int NodeViewScene::DetermineWeight(Node *n) -{ - QVector inputs = n->GetImmediateDependencies(); - - int weight = 0; - - foreach (Node* i, inputs) { - if (i->GetNumberOfRoutesTo(n) == 1) { - weight += DetermineWeight(i); - } - } - - return qMax(1, weight); -} - Qt::Orientation NodeViewScene::GetFlowOrientation() const { return NodeViewCommon::GetFlowOrientation(direction_); } -NodeViewCommon::FlowDirection NodeViewScene::GetFlowDirection() const -{ - return direction_; -} - void NodeViewScene::SetEdgesAreCurved(bool curved) { if (curved_edges_ != curved) { diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index b9949d59c..467109923 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -37,27 +37,11 @@ class NodeViewScene : public QGraphicsScene public: NodeViewScene(QObject *parent = nullptr); - void clear(); - void SelectAll(); void DeselectAll(); void DeleteSelected(); - /** - * @brief Retrieve the graphical widget corresponding to a specific Node - * - * In situations where you know what Node you're working with but need the UI object (e.g. for positioning), this - * static function will retrieve the NodeViewItem (Node UI representation) connected to this Node in a certain - * QGraphicsScene. This can be called from any other UI object, since it'll have a reference to the QGraphicsScene - * through QGraphicsItem::scene(). - * - * If the scene does not contain a widget for this node (usually meaning the node's graph is not the active graph - * in this view/scene), this function returns nullptr. - */ - NodeViewItem* NodeToUIObject(Node* n); - - QVector GetSelectedNodes() const; QVector GetSelectedItems() const; const QHash &context_map() const @@ -65,14 +49,13 @@ public: return context_map_; } - const QHash& item_map() const - { - return item_map_; - } - Qt::Orientation GetFlowOrientation() const; - NodeViewCommon::FlowDirection GetFlowDirection() const; + NodeViewCommon::FlowDirection GetFlowDirection() const + { + return direction_; + } + void SetFlowDirection(NodeViewCommon::FlowDirection direction); bool GetEdgesAreCurved() const @@ -90,12 +73,8 @@ public slots: void SetEdgesAreCurved(bool curved); private: - static int DetermineWeight(Node* n); - QHash context_map_; - QHash item_map_; - NodeGraph* graph_; NodeViewCommon::FlowDirection direction_; From a94c7169f2f81d2a7a9ca96614dc8499e602ddb2 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 19 Nov 2021 17:14:56 -0800 Subject: [PATCH 25/94] store expanded status in context --- app/core.cpp | 2 +- app/node/node.cpp | 18 +++-- app/node/node.h | 71 ++++++++++++++++--- app/node/nodecopypaste.cpp | 8 +-- app/node/project/project.cpp | 17 +++-- app/node/project/project.h | 4 +- app/widget/nodeview/nodeview.cpp | 100 +++++++++++++-------------- app/widget/nodeview/nodeviewitem.cpp | 8 ++- 8 files changed, 143 insertions(+), 85 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index 82a6b98fc..1db15b4fa 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -437,7 +437,7 @@ void Core::CreateNewSequence() command->add_child(new NodeAddCommand(active_project, new_sequence)); command->add_child(new FolderAddChild(GetSelectedFolderInActiveProject(), new_sequence)); - command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0))); + command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, Node::Position())); // Create and connect default nodes to new sequence new_sequence->add_default_nodes(command); diff --git a/app/node/node.cpp b/app/node/node.cpp index b1b45ccf6..ddabc5a61 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -243,12 +243,16 @@ QIcon Node::icon() const return icon::New; } -QPointF Node::GetNodePositionInContext(Node *node) +bool Node::SetNodePositionInContext(Node *node, const QPointF &pos) { - return context_positions_.value(node); + Position p = context_positions_.value(node); + + p.position = pos; + + return SetNodePositionInContext(node, p); } -bool Node::SetNodePositionInContext(Node *node, const QPointF &pos) +bool Node::SetNodePositionInContext(Node *node, const Position &pos) { bool added = !ContextContainsNode(node); context_positions_.insert(node, pos); @@ -257,7 +261,7 @@ bool Node::SetNodePositionInContext(Node *node, const QPointF &pos) emit NodeAddedToContext(node); } - emit NodePositionInContextChanged(node, pos); + emit NodePositionInContextChanged(node, pos.position); return added; } @@ -2350,13 +2354,13 @@ Project *Node::ArrayResizeCommand::GetRelevantProject() const void NodeSetPositionCommand::redo() { if (!(added_ = !context_->ContextContainsNode(node_))) { - old_pos_ = context_->GetNodePositionInContext(node_); + old_pos_ = context_->GetNodePositionDataInContext(node_); } if (added_) { context_->SetNodePositionInContext(node_, pos_); } else { - move(context_, node_, pos_ - old_pos_, move_deps_); + move(context_, node_, pos_.position - old_pos_.position, move_deps_); } } @@ -2365,7 +2369,7 @@ void NodeSetPositionCommand::undo() if (added_) { context_->RemoveNodeFromContext(node_); } else { - move(context_, node_, old_pos_ - pos_, move_deps_); + move(context_, node_, old_pos_.position - pos_.position, move_deps_); } } diff --git a/app/node/node.h b/app/node/node.h index fceb08d1c..ca3f7f43c 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -208,21 +208,77 @@ public: return HasInputWithID(id); } - using PositionMap = QHash; + struct Position + { + Position(const QPointF &p = QPointF(0, 0), bool e = false) + { + position = p; + expanded = e; + } + + QPointF position; + bool expanded; + + inline Position &operator+=(const Position &p) + { + position += p.position; + return *this; + } + + inline Position &operator-=(const Position &p) + { + position -= p.position; + return *this; + } + + friend inline const Position operator+(Position a, const Position &b) + { + a += b; + return a; + } + + friend inline const Position operator-(Position a, const Position &b) + { + a -= b; + return a; + } + }; + + using PositionMap = QHash; const PositionMap &GetContextPositions() const { return context_positions_; } + bool IsNodeExpandedInContext(Node *node) const + { + return context_positions_.value(node).expanded; + } + bool ContextContainsNode(Node *node) const { return context_positions_.contains(node); } - QPointF GetNodePositionInContext(Node *node); + Position GetNodePositionDataInContext(Node *node) + { + return context_positions_.value(node); + } + + QPointF GetNodePositionInContext(Node *node) + { + return GetNodePositionDataInContext(node).position; + } bool SetNodePositionInContext(Node *node, const QPointF &pos); + bool SetNodePositionInContext(Node *node, const Position &pos); + + void SetNodeExpandedInContext(Node *node, bool e) + { + context_positions_[node].expanded = e; + } + bool RemoveNodeFromContext(Node *node); /** @@ -1006,11 +1062,6 @@ protected: } signals: - /** - * @brief Signal emitted whenever the position is set through SetPosition() - */ - void PositionChanged(const QPointF& pos); - /** * @brief Signal emitted when SetLabel() is called */ @@ -1394,7 +1445,7 @@ using NodePtr = std::shared_ptr; class NodeSetPositionCommand : public UndoCommand { public: - NodeSetPositionCommand(Node* node, Node* context, const QPointF& pos, bool move_dependencies_relatively = false) + NodeSetPositionCommand(Node* node, Node* context, const Node::Position& pos, bool move_dependencies_relatively = false) { node_ = node; context_ = context; @@ -1417,8 +1468,8 @@ private: Node* node_; Node* context_; - QPointF pos_; - QPointF old_pos_; + Node::Position pos_; + Node::Position old_pos_; bool added_; bool move_deps_; diff --git a/app/node/nodecopypaste.cpp b/app/node/nodecopypaste.cpp index 0de513820..e9c0a4d0e 100644 --- a/app/node/nodecopypaste.cpp +++ b/app/node/nodecopypaste.cpp @@ -92,7 +92,7 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, QVector pasted_nodes; XMLNodeData xml_node_data; - QMap > pasted_contexts; + QMap > pasted_contexts; while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("olive")) { @@ -131,7 +131,7 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("context")) { // Get context ptr - QMap map; + QMap map; quintptr context_ptr = 0; XMLAttributeLoop((&reader), attr) { if (attr.name() == QStringLiteral("ptr")) { @@ -144,7 +144,7 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("node")) { quintptr node_ptr; - QPointF node_pos; + Node::Position node_pos; if (Project::LoadPosition(&reader, &node_ptr, &node_pos)) { map.insert(node_ptr, node_pos); @@ -216,7 +216,7 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, for (auto it=pasted_contexts.cbegin(); it!=pasted_contexts.cend(); it++) { Node *context = xml_node_data.node_ptrs.value(it.key()); if (context) { - const QMap &map = it.value(); + auto map = it.value(); for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { Node *subnode = xml_node_data.node_ptrs.value(jt.key()); if (subnode) { diff --git a/app/node/project/project.cpp b/app/node/project/project.cpp index 64d7cb107..ba118ba50 100644 --- a/app/node/project/project.cpp +++ b/app/node/project/project.cpp @@ -158,7 +158,7 @@ void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, uint while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("node")) { quintptr node_ptr; - QPointF node_pos; + Node::Position node_pos; if (LoadPosition(reader, &node_ptr, &node_pos)) { Node *node = xml_node_data.node_ptrs.value(node_ptr); @@ -350,7 +350,7 @@ void Project::RegenerateUuid() uuid_ = QUuid::createUuid(); } -bool Project::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, QPointF *pos) +bool Project::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos) { bool got_node_ptr = false; bool got_pos_x = false; @@ -366,11 +366,13 @@ bool Project::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, QPointF while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("x")) { - pos->setX(reader->readElementText().toDouble()); + pos->position.setX(reader->readElementText().toDouble()); got_pos_x = true; } else if (reader->name() == QStringLiteral("y")) { - pos->setY(reader->readElementText().toDouble()); + pos->position.setY(reader->readElementText().toDouble()); got_pos_y = true; + } else if (reader->name() == QStringLiteral("expanded")) { + pos->expanded = reader->readElementText().toInt(); } else { reader->skipCurrentElement(); } @@ -379,12 +381,13 @@ bool Project::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, QPointF return got_node_ptr && got_pos_x && got_pos_y; } -void Project::SavePosition(QXmlStreamWriter *writer, Node *node, const QPointF &pos) +void Project::SavePosition(QXmlStreamWriter *writer, Node *node, const Node::Position &pos) { writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(node))); - writer->writeTextElement(QStringLiteral("x"), QString::number(pos.x())); - writer->writeTextElement(QStringLiteral("y"), QString::number(pos.y())); + writer->writeTextElement(QStringLiteral("x"), QString::number(pos.position.x())); + writer->writeTextElement(QStringLiteral("y"), QString::number(pos.position.y())); + writer->writeTextElement(QStringLiteral("expanded"), QString::number(pos.expanded)); } void Project::ColorManagerValueChanged(const NodeInput &input, const TimeRange &range) diff --git a/app/node/project/project.h b/app/node/project/project.h index 3dbceb2bf..ef2e3e192 100644 --- a/app/node/project/project.h +++ b/app/node/project/project.h @@ -82,8 +82,8 @@ public: void RegenerateUuid(); - static bool LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, QPointF *pos); - static void SavePosition(QXmlStreamWriter *writer, Node *node, const QPointF &pos); + static bool LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos); + static void SavePosition(QXmlStreamWriter *writer, Node *node, const Node::Position &pos); signals: void NameChanged(); diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 5cb1d767c..e805d7eda 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -523,7 +523,6 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } Core::instance()->undo_stack()->pushIfHasChildren(command); - return; } MultiUndoCommand* command = new MultiUndoCommand(); @@ -539,64 +538,63 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } } - if (!context) { - QToolTip::showText(QCursor::pos(), tr("Nodes must be placed inside a context.")); - return; - } - - if (paste_command_) { - // We've already "done" this command, but MultiUndoCommand prevents "redoing" twice, so we - // add it to this command (which may have extra commands added too) so that it all gets undone - // in the same action - command->add_child(paste_command_); - paste_command_ = nullptr; - } - - { - MultiUndoCommand *add_command = new MultiUndoCommand(); - - foreach (const AttachedItem &ai, attached_items_) { - // Add node to the same graph that the context is in - add_command->add_child(new NodeAddCommand(context->parent(), ai.item->GetNode())); - - // Add node to the context - add_command->add_child(new NodeSetPositionCommand(ai.item->GetNode(), context, scene_.context_map().value(context)->MapScenePosToNodePosInContext(ai.item->pos()))); + if (context) { + if (paste_command_) { + // We've already "done" this command, but MultiUndoCommand prevents "redoing" twice, so we + // add it to this command (which may have extra commands added too) so that it all gets undone + // in the same action + command->add_child(paste_command_); + paste_command_ = nullptr; } - if (add_command->child_count()) { - add_command->redo_now(); - command->add_child(add_command); - } else { - delete add_command; - } - } + { + MultiUndoCommand *add_command = new MultiUndoCommand(); - { - // Dropped attached item onto an edge, connect it between them - MultiUndoCommand *drop_edge_command = new MultiUndoCommand(); - if (attached_items_.size() == 1) { - Node* dropping_node = attached_items_.first().item->GetNode(); + foreach (const AttachedItem &ai, attached_items_) { + // Add node to the same graph that the context is in + add_command->add_child(new NodeAddCommand(context->parent(), ai.item->GetNode())); - if (drop_edge_) { - // Remove old edge - drop_edge_command->add_child(new NodeEdgeRemoveCommand(drop_edge_->output(), drop_edge_->input())); - - // Place new edges - drop_edge_command->add_child(new NodeEdgeAddCommand(drop_edge_->output(), drop_input_)); - drop_edge_command->add_child(new NodeEdgeAddCommand(dropping_node, drop_edge_->input())); + // Add node to the context + add_command->add_child(new NodeSetPositionCommand(ai.item->GetNode(), context, scene_.context_map().value(context)->MapScenePosToNodePosInContext(ai.item->pos()))); } - drop_edge_ = nullptr; + if (add_command->child_count()) { + add_command->redo_now(); + command->add_child(add_command); + } else { + delete add_command; + } } - if (drop_edge_command->child_count()) { - drop_edge_command->redo_now(); - command->add_child(drop_edge_command); - } else { - delete drop_edge_command; - } - } - DetachItemsFromCursor(); + { + // Dropped attached item onto an edge, connect it between them + MultiUndoCommand *drop_edge_command = new MultiUndoCommand(); + if (attached_items_.size() == 1) { + Node* dropping_node = attached_items_.first().item->GetNode(); + + if (drop_edge_) { + // Remove old edge + drop_edge_command->add_child(new NodeEdgeRemoveCommand(drop_edge_->output(), drop_edge_->input())); + + // Place new edges + drop_edge_command->add_child(new NodeEdgeAddCommand(drop_edge_->output(), drop_input_)); + drop_edge_command->add_child(new NodeEdgeAddCommand(dropping_node, drop_edge_->input())); + } + + drop_edge_ = nullptr; + } + if (drop_edge_command->child_count()) { + drop_edge_command->redo_now(); + command->add_child(drop_edge_command); + } else { + delete drop_edge_command; + } + } + + DetachItemsFromCursor(); + } else { + QToolTip::showText(QCursor::pos(), tr("Nodes must be placed inside a context.")); + } } for (auto it=dragging_items_.cbegin(); it!=dragging_items_.cend(); it++) { diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 1c645601e..3ea29ff5a 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -85,9 +85,8 @@ NodeViewItem::NodeViewItem(Node* n, Node *context, QGraphicsItem *parent) : if (context_) { SetNodePosition(context_->GetNodePositionInContext(node_)); + SetExpanded(context_->IsNodeExpandedInContext(node_)); } - - SetExpanded(node_->property("expanded").toBool()); } QPointF NodeViewItem::GetNodePosition() const @@ -236,7 +235,10 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) expanded_ = e; hide_titlebar_ = hide_titlebar; - node_->setProperty("expanded", e); + + if (context_) { + context_->SetNodeExpandedInContext(node_, e); + } if (expanded_ && !node_inputs_.isEmpty()) { // Create new rect From 495b07fc5578978e9e462038c730286f40952e8c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:58:48 -0800 Subject: [PATCH 26/94] implemented node groups --- app/dialog/CMakeLists.txt | 1 + app/dialog/nodegroup/CMakeLists.txt | 22 ++ app/dialog/nodegroup/nodegroupdialog.cpp | 99 ++++++++ .../nodegroup/nodegroupdialog.h} | 31 ++- .../nodeproperties/nodepropertiesdialog.cpp | 2 +- app/node/CMakeLists.txt | 3 +- app/node/group.cpp | 30 --- app/node/group/CMakeLists.txt | 22 ++ app/node/group/group.cpp | 177 +++++++++++++++ app/node/group/group.h | 176 ++++++++++++++ app/node/node.cpp | 19 +- app/node/node.h | 41 +--- app/node/output/viewer/viewer.h | 2 - app/node/param.cpp | 18 ++ app/node/param.h | 37 ++- app/render/previewautocacher.cpp | 3 + app/render/previewautocacher.h | 4 +- app/widget/nodeparamview/nodeparamview.cpp | 214 +++++++++++------- app/widget/nodeparamview/nodeparamview.h | 29 ++- .../nodeparamview/nodeparamviewdockarea.h | 2 + .../nodeparamview/nodeparamviewitem.cpp | 68 +++++- app/widget/nodeparamview/nodeparamviewitem.h | 29 ++- app/widget/nodeview/nodeview.cpp | 96 +++++++- app/widget/nodeview/nodeview.h | 2 +- app/widget/nodeview/nodeviewcontext.cpp | 4 +- app/widget/nodeview/nodeviewcontext.h | 5 + app/widget/nodeview/nodeviewitem.cpp | 2 +- app/window/mainwindow/mainwindow.cpp | 10 - app/window/mainwindow/mainwindow.h | 2 - 29 files changed, 947 insertions(+), 203 deletions(-) create mode 100644 app/dialog/nodegroup/CMakeLists.txt create mode 100644 app/dialog/nodegroup/nodegroupdialog.cpp rename app/{node/group.h => dialog/nodegroup/nodegroupdialog.h} (61%) delete mode 100644 app/node/group.cpp create mode 100644 app/node/group/CMakeLists.txt create mode 100644 app/node/group/group.cpp create mode 100644 app/node/group/group.h diff --git a/app/dialog/CMakeLists.txt b/app/dialog/CMakeLists.txt index ff82a6b2c..01bbfd5ec 100644 --- a/app/dialog/CMakeLists.txt +++ b/app/dialog/CMakeLists.txt @@ -23,6 +23,7 @@ add_subdirectory(diskcache) add_subdirectory(export) add_subdirectory(footagerelink) add_subdirectory(keyframeproperties) +add_subdirectory(nodegroup) add_subdirectory(nodeproperties) add_subdirectory(preferences) add_subdirectory(progress) diff --git a/app/dialog/nodegroup/CMakeLists.txt b/app/dialog/nodegroup/CMakeLists.txt new file mode 100644 index 000000000..0f48edfc9 --- /dev/null +++ b/app/dialog/nodegroup/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2021 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + dialog/nodegroup/nodegroupdialog.cpp + dialog/nodegroup/nodegroupdialog.h + PARENT_SCOPE +) diff --git a/app/dialog/nodegroup/nodegroupdialog.cpp b/app/dialog/nodegroup/nodegroupdialog.cpp new file mode 100644 index 000000000..2d07a4338 --- /dev/null +++ b/app/dialog/nodegroup/nodegroupdialog.cpp @@ -0,0 +1,99 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "nodegroupdialog.h" + +#include +#include +#include +#include + +#include "widget/nodeparamview/nodeparamview.h" +#include "widget/nodeview/nodeview.h" + +namespace olive { + +#define super QDialog + +NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : + super(parent), + group_(group), + parent_undo_(nullptr) +{ + QGridLayout *layout = new QGridLayout(this); + + int row = 0; + + layout->addWidget(new QLabel(tr("Name:")), row, 0); + + name_edit_ = new QLineEdit(); + layout->addWidget(name_edit_, row, 1); + + row++; + + QSplitter *splitter = new QSplitter(Qt::Horizontal); + layout->addWidget(splitter, row, 0, 1, 2); + + NodeParamView *param_view = new NodeParamView(false); + param_view->SetCreateCheckBoxes(kCheckBoxesOnNonConnected); + splitter->addWidget(param_view); + + NodeView *node_view = new NodeView(); + node_view->SetContexts({group}); + QMetaObject::invokeMethod(node_view, &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); + splitter->addWidget(node_view); + + for (auto it=group->GetInputPassthroughs().cbegin(); it!=group->GetInputPassthroughs().cend(); it++) { + param_view->SetInputChecked(it.value(), true); + } + + connect(node_view, &NodeView::NodesSelected, param_view, &NodeParamView::SelectNodes); + connect(node_view, &NodeView::NodesDeselected, param_view, &NodeParamView::DeselectNodes); + node_view->SelectAll(); + + row++; + + QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + btns->setCenterButtons(true); + connect(btns, &QDialogButtonBox::accepted, this, &NodeGroupDialog::accept); + connect(btns, &QDialogButtonBox::rejected, this, &NodeGroupDialog::reject); + layout->addWidget(btns, row, 0, 1, 2); + + setWindowTitle(tr("Group Editor")); +} + +void NodeGroupDialog::accept() +{ + MultiUndoCommand *command = new MultiUndoCommand(); + + if (name_edit_->text() != group_->GetCustomName()) { + command->add_child(new NodeGroupSetCustomNameCommand(group_, name_edit_->text())); + } + + if (parent_undo_) { + parent_undo_->add_child(command); + } else { + Core::instance()->undo_stack()->push(command); + } + + super::accept(); +} + +} diff --git a/app/node/group.h b/app/dialog/nodegroup/nodegroupdialog.h similarity index 61% rename from app/node/group.h rename to app/dialog/nodegroup/nodegroupdialog.h index 2f15bbb71..741f0ac55 100644 --- a/app/node/group.h +++ b/app/dialog/nodegroup/nodegroupdialog.h @@ -18,26 +18,41 @@ ***/ -#ifndef NODEGROUP_H -#define NODEGROUP_H +#ifndef NODEGROUPDIALOG_H +#define NODEGROUPDIALOG_H -#include "node.h" +#include +#include + +#include "node/group/group.h" namespace olive { -class NodeGroup : public Node +class NodeGroupDialog : public QDialog { Q_OBJECT public: - NodeGroup(); + explicit NodeGroupDialog(NodeGroup *group, QWidget *parent = nullptr); - void SetNodes(Node *nodes); + void SetParentUndoCommand(MultiUndoCommand *c) + { + parent_undo_ = c; + } + +public slots: + virtual void accept() override; + +signals: private: - QVector nodes_; + NodeGroup *group_; + + QLineEdit *name_edit_; + + MultiUndoCommand *parent_undo_; }; } -#endif // NODEGROUP_H +#endif // NODEGROUPDIALOG_H diff --git a/app/dialog/nodeproperties/nodepropertiesdialog.cpp b/app/dialog/nodeproperties/nodepropertiesdialog.cpp index 123b6c69b..fca1935ba 100644 --- a/app/dialog/nodeproperties/nodepropertiesdialog.cpp +++ b/app/dialog/nodeproperties/nodepropertiesdialog.cpp @@ -46,7 +46,7 @@ NodePropertiesDialog::NodePropertiesDialog(Node *node, const rational &timebase, label_edit_->setText(node->GetLabel()); label_layout->addWidget(label_edit_); - NodeParamViewItem *item = new NodeParamViewItem(node); + NodeParamViewItem *item = new NodeParamViewItem(node, kNoCheckBoxes); item->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); item->SetTimebase(timebase); item->setTitleBarWidget(new QWidget()); diff --git a/app/node/CMakeLists.txt b/app/node/CMakeLists.txt index 6cd34ca5b..508bf00bf 100644 --- a/app/node/CMakeLists.txt +++ b/app/node/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory(color) add_subdirectory(distort) add_subdirectory(filter) add_subdirectory(generator) +add_subdirectory(group) add_subdirectory(input) add_subdirectory(math) add_subdirectory(output) @@ -34,8 +35,6 @@ set(OLIVE_SOURCES node/globals.h node/graph.cpp node/graph.h - node/group.cpp - node/group.h node/hashtraverser.cpp node/hashtraverser.h node/inputdragger.cpp diff --git a/app/node/group.cpp b/app/node/group.cpp deleted file mode 100644 index 7b7ab0c87..000000000 --- a/app/node/group.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "group.h" - -namespace olive { - -NodeGroup::NodeGroup() -{ - -} - -} diff --git a/app/node/group/CMakeLists.txt b/app/node/group/CMakeLists.txt new file mode 100644 index 000000000..3c0bce4d6 --- /dev/null +++ b/app/node/group/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2021 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/group/group.cpp + node/group/group.h + PARENT_SCOPE +) diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp new file mode 100644 index 000000000..fbe785bd9 --- /dev/null +++ b/app/node/group/group.cpp @@ -0,0 +1,177 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "group.h" + +#include "node/graph.h" + +namespace olive { + +NodeGroup::NodeGroup() : + output_passthrough_(nullptr) +{ + graph_ = new NodeGraph(); + graph_->setParent(this); +} + +QString NodeGroup::Name() const +{ + if (custom_name_.isEmpty()) { + return tr("Group"); + } else { + return custom_name_; + } +} + +QString NodeGroup::id() const +{ + return QStringLiteral("org.olivevideoeditor.Olive.group"); +} + +QVector NodeGroup::Category() const +{ + return {kCategoryGeneral}; +} + +QString NodeGroup::Description() const +{ + return tr("A group of nodes that is represented as a single node."); +} + +void NodeGroup::Retranslate() +{ + foreach (Node *n, graph_->nodes()) { + n->Retranslate(); + } +} + +void NodeGroup::AddNode(Node *node) +{ + node->setParent(graph_); +} + +void NodeGroup::RemoveNode(Node *node, QObject *new_parent) +{ + if (node->parent() == graph_) { + node->setParent(new_parent); + } +} + +void NodeGroup::AddInputPassthrough(const NodeInput &input) +{ + Q_ASSERT(graph_->nodes().contains(input.node())); + + for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) { + if (it.value() == input) { + // Already passing this input through + return; + } + } + + // Add input + QString id = GetGroupInputIDFromInput(input); + + AddInput(id, input.GetDataType(), input.GetDefaultValue(), input.GetFlags()); + + input_passthroughs_.insert(id, input); +} + +void NodeGroup::RemoveInputPassthrough(const NodeInput &input) +{ + for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) { + if (it.value() == input) { + RemoveInput(it.key()); + input_passthroughs_.erase(it); + break; + } + } +} + +void NodeGroup::SetOutputPassthrough(Node *node) +{ + Q_ASSERT(graph_->nodes().contains(node)); + + output_passthrough_ = node; +} + +QString NodeGroup::GetGroupInputIDFromInput(const NodeInput &input) +{ + QCryptographicHash hash(QCryptographicHash::Sha1); + + hash.addData(input.node()->GetUUID().toByteArray()); + + hash.addData(input.input().toUtf8()); + + hash.addData((const char*) &input.element(), sizeof(input.element())); + + return QString::fromLatin1(hash.result().toHex()); +} + +bool NodeGroup::ContainsInputPassthrough(const NodeInput &input) const +{ + for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) { + if (it.value() == input) { + return true; + } + } + + return false; +} + +void NodeAddToGroupCommand::redo() +{ + previous_parent_ = node_->parent(); + group_->AddNode(node_); +} + +void NodeAddToGroupCommand::undo() +{ + group_->RemoveNode(node_, previous_parent_); +} + +void NodeGroupSetCustomNameCommand::redo() +{ + old_name_ = group_->GetCustomName(); + group_->SetCustomName(new_name_); +} + +void NodeGroupSetCustomNameCommand::undo() +{ + group_->SetCustomName(old_name_); +} + +void NodeGroupAddInputPassthrough::redo() +{ + if (!group_->ContainsInputPassthrough(input_)) { + group_->AddInputPassthrough(input_); + actually_added_ = true; + } else { + actually_added_ = false; + } +} + +void NodeGroupAddInputPassthrough::undo() +{ + if (actually_added_) { + group_->RemoveInputPassthrough(input_); + } +} + +} diff --git a/app/node/group/group.h b/app/node/group/group.h new file mode 100644 index 000000000..a0aa9cee3 --- /dev/null +++ b/app/node/group/group.h @@ -0,0 +1,176 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef NODEGROUP_H +#define NODEGROUP_H + +#include "node/node.h" + +namespace olive { + +class NodeGroup : public Node +{ + Q_OBJECT +public: + NodeGroup(); + + NODE_DEFAULT_DESTRUCTOR(NodeGroup) + NODE_COPY_FUNCTION(NodeGroup) + + virtual QString Name() const override; + virtual QString id() const override; + virtual QVector Category() const override; + virtual QString Description() const override; + + virtual void Retranslate() override; + + void AddNode(Node *node); + + void RemoveNode(Node *node, QObject *new_parent = nullptr); + + void AddInputPassthrough(const NodeInput &input); + + void RemoveInputPassthrough(const NodeInput &input); + + void SetOutputPassthrough(Node *node); + + const QString &GetCustomName() const + { + return custom_name_; + } + + void SetCustomName(const QString &name) + { + custom_name_ = name; + + // NOTE: Not technically the right signal, but should achieve the right goal + emit LabelChanged(custom_name_); + } + + void ClearCustomName() + { + custom_name_.clear(); + } + + static QString GetGroupInputIDFromInput(const NodeInput &input); + + const QHash &GetInputPassthroughs() const + { + return input_passthroughs_; + } + + bool ContainsInputPassthrough(const NodeInput &input) const; + +private: + NodeGraph *graph_; + + QHash input_passthroughs_; + + Node *output_passthrough_; + + QString custom_name_; + +}; + +class NodeAddToGroupCommand : public UndoCommand +{ +public: + NodeAddToGroupCommand(Node *node, NodeGroup *group) : + node_(node), + group_(group) + {} + + virtual Project * GetRelevantProject() const override + { + return node_->project(); + } + +protected: + virtual void redo() override; + + virtual void undo() override; + +private: + Node *node_; + + NodeGroup *group_; + + QObject *previous_parent_; + +}; + +class NodeGroupSetCustomNameCommand : public UndoCommand +{ +public: + NodeGroupSetCustomNameCommand(NodeGroup *group, const QString &name) : + group_(group), + new_name_(name) + {} + + virtual Project * GetRelevantProject() const override + { + return group_->project(); + } + +protected: + virtual void redo() override; + + virtual void undo() override; + +private: + NodeGroup *group_; + + QString old_name_; + + QString new_name_; + +}; + +class NodeGroupAddInputPassthrough : public UndoCommand +{ +public: + NodeGroupAddInputPassthrough(NodeGroup *group, const NodeInput &input) : + group_(group), + input_(input), + actually_added_(false) + {} + + virtual Project * GetRelevantProject() const override + { + return group_->project(); + } + +protected: + virtual void redo() override; + + virtual void undo() override; + +private: + NodeGroup *group_; + + NodeInput input_; + + bool actually_added_; + +}; + +} + +#endif // NODEGROUP_H diff --git a/app/node/node.cpp b/app/node/node.cpp index ddabc5a61..1d0316e8b 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -49,6 +49,7 @@ Node::Node() : operation_stack_(0), cache_result_(false) { + uuid_ = QUuid::createUuid(); } Node::~Node() @@ -88,6 +89,8 @@ void Node::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, uint versi xml_node_data.node_ptrs.insert(reader->readElementText().toULongLong(), this); } else if (reader->name() == QStringLiteral("label")) { SetLabel(reader->readElementText()); + } else if (reader->name() == QStringLiteral("uuid")) { + SetUUID(QUuid::fromString(reader->readElementText())); } else if (reader->name() == QStringLiteral("color")) { override_color_ = reader->readElementText().toInt(); } else if (reader->name() == QStringLiteral("links")) { @@ -169,6 +172,7 @@ void Node::Save(QXmlStreamWriter *writer) const { writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast(this))); + writer->writeTextElement(QStringLiteral("uuid"), uuid_.toString()); writer->writeTextElement(QStringLiteral("label"), GetLabel()); writer->writeTextElement(QStringLiteral("color"), QString::number(override_color_)); @@ -219,7 +223,16 @@ void Node::Save(QXmlStreamWriter *writer) const Project* Node::project() const { - return dynamic_cast(parent()); + QObject *t = this->parent(); + + while (t) { + if (Project *p = dynamic_cast(t)) { + return p; + } + t = t->parent(); + } + + return nullptr; } QString Node::ShortName() const @@ -1089,7 +1102,7 @@ NodeInputImmediate *Node::GetImmediate(const QString &input, int element) const return nullptr; } -Node::InputFlags Node::GetInputFlags(const QString &input) const +InputFlags Node::GetInputFlags(const QString &input) const { const Input* i = GetInternalInputData(input); @@ -1343,7 +1356,7 @@ void Node::HashAddNodeSignature(QCryptographicHash &hash) const hash.addData(id().toUtf8()); } -void Node::InsertInput(const QString &id, NodeValue::Type type, const QVariant &default_value, Node::InputFlags flags, int index) +void Node::InsertInput(const QString &id, NodeValue::Type type, const QVariant &default_value, InputFlags flags, int index) { if (id.isEmpty()) { qWarning() << "Rejected adding input with an empty ID on node" << this->id(); diff --git a/app/node/node.h b/app/node/node.h index ca3f7f43c..3a0fa5130 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "codec/frame.h" @@ -112,6 +113,9 @@ public: Project* project() const; + const QUuid &GetUUID() const {return uuid_;} + void SetUUID(const QUuid &uuid) {uuid_ = uuid;} + /** * @brief Clear current node variables and replace them with */ @@ -935,38 +939,9 @@ public: }; + InputFlags GetInputFlags(const QString& input) const; + protected: - enum InputFlag { - /// By default, inputs are keyframable, connectable, and NOT arrays - kInputFlagNormal = 0x0, - kInputFlagArray = 0x1, - kInputFlagNotKeyframable = 0x2, - kInputFlagNotConnectable = 0x4, - kInputFlagHidden = 0x8 - }; - - class InputFlags { - public: - explicit InputFlags() - { - f_ = kInputFlagNormal; - } - - explicit InputFlags(uint64_t flags) - { - f_ = flags; - } - - bool operator&(const InputFlag& f) const - { - return f_ & f; - } - - private: - uint64_t f_; - - }; - virtual void Hash(QCryptographicHash& hash, const NodeGlobals &globals, const VideoParams& video_params) const; void HashAddNodeSignature(QCryptographicHash &hash) const; @@ -1216,8 +1191,6 @@ private: return input_ids_.indexOf(input); } - InputFlags GetInputFlags(const QString& input) const; - Input* GetInternalInputData(const QString& input) { int i = GetInternalInputIndex(input); @@ -1336,6 +1309,8 @@ private: PositionMap context_positions_; + QUuid uuid_; + private slots: /** * @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 56a89908c..5c516000c 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -254,8 +254,6 @@ private: AudioPlaybackCache audio_playback_cache_; - int operation_stack_; - VideoParams cached_video_params_; AudioParams cached_audio_params_; diff --git a/app/node/param.cpp b/app/node/param.cpp index f0a685abe..2e579f613 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -69,6 +69,15 @@ bool NodeInput::IsArray() const } } +InputFlags NodeInput::GetFlags() const +{ + if (IsValid()) { + return node_->GetInputFlags(input_); + } else { + return InputFlags(kInputFlagNormal); + } +} + Node *NodeInput::GetConnectedOutput() const { if (IsValid()) { @@ -87,6 +96,15 @@ NodeValue::Type NodeInput::GetDataType() const } } +QVariant NodeInput::GetDefaultValue() const +{ + if (IsValid()) { + return node_->GetDefaultValue(input_); + } else { + return QVariant(); + } +} + QStringList NodeInput::GetComboBoxStrings() const { if (IsValid()) { diff --git a/app/node/param.h b/app/node/param.h index 3de4b6727..2e2276187 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -31,6 +31,37 @@ namespace olive { class Node; class NodeKeyframe; +enum InputFlag { + /// By default, inputs are keyframable, connectable, and NOT arrays + kInputFlagNormal = 0x0, + kInputFlagArray = 0x1, + kInputFlagNotKeyframable = 0x2, + kInputFlagNotConnectable = 0x4, + kInputFlagHidden = 0x8 +}; + +class InputFlags { +public: + explicit InputFlags() + { + f_ = kInputFlagNormal; + } + + explicit InputFlags(uint64_t flags) + { + f_ = flags; + } + + bool operator&(const InputFlag& f) const + { + return f_ & f; + } + +private: + uint64_t f_; + +}; + struct NodeInputPair { bool operator==(const NodeInputPair& rhs) const { @@ -98,7 +129,7 @@ public: return input_; } - int element() const + const int &element() const { return element_; } @@ -123,10 +154,14 @@ public: bool IsArray() const; + InputFlags GetFlags() const; + Node *GetConnectedOutput() const; NodeValue::Type GetDataType() const; + QVariant GetDefaultValue() const; + QStringList GetComboBoxStrings() const; QVariant GetProperty(const QString& key) const; diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 2c121745d..75b78c3c5 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -373,6 +373,9 @@ void PreviewAutoCacher::AddNode(Node *node) // Add to project copy->setParent(&copied_project_); + // Copy UUID + copy->SetUUID(node->GetUUID()); + // Insert into map InsertIntoCopyMap(node, copy); diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index ad4665da3..f770c776f 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -24,8 +24,9 @@ #include #include "config/config.h" -#include "node/graph.h" #include "node/color/colormanager/colormanager.h" +#include "node/graph.h" +#include "node/group/group.h" #include "node/node.h" #include "node/output/viewer/viewer.h" #include "node/project/project.h" @@ -172,6 +173,7 @@ private: QVector graph_update_queue_; QHash copy_map_; + QHash graph_map_; ViewerOutput* copied_viewer_node_; ColorManager* copied_color_manager_; QVector created_nodes_; diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 9d3e77000..52b4407fc 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -32,10 +32,12 @@ namespace olive { #define super TimeBasedWidget -NodeParamView::NodeParamView(QWidget *parent) : +NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : super(true, false, parent), last_scroll_val_(0), - focused_node_(nullptr) + focused_node_(nullptr), + create_checkboxes_(kNoCheckBoxes), + time_target_(nullptr) { // Create horizontal layout to place scroll area in (and keyframe editing eventually) QHBoxLayout* layout = new QHBoxLayout(this); @@ -46,16 +48,16 @@ NodeParamView::NodeParamView(QWidget *parent) : layout->addWidget(splitter); // Set up scroll area for params - QScrollArea* scroll_area = new QScrollArea(); - scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); - scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scroll_area->setWidgetResizable(true); - splitter->addWidget(scroll_area); + param_scroll_area_ = new QScrollArea(); + param_scroll_area_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + param_scroll_area_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + param_scroll_area_->setWidgetResizable(true); + splitter->addWidget(param_scroll_area_); // Param widget param_widget_container_ = new NodeParamViewParamContainer(); connect(param_widget_container_, &NodeParamViewParamContainer::Resized, this, &NodeParamView::UpdateGlobalScrollBar); - scroll_area->setWidget(param_widget_container_); + param_scroll_area_->setWidget(param_widget_container_); param_widget_area_ = new NodeParamViewDockArea(); @@ -74,56 +76,63 @@ NodeParamView::NodeParamView(QWidget *parent) : param_widget_container_layout->addStretch(INT_MAX); - // Set up keyframe view - QWidget* keyframe_area = new QWidget(); - QVBoxLayout* keyframe_area_layout = new QVBoxLayout(keyframe_area); - keyframe_area_layout->setSpacing(0); - keyframe_area_layout->setMargin(0); - - // Create ruler object - keyframe_area_layout->addWidget(ruler()); - - // Create keyframe view - keyframe_view_ = new KeyframeView(); - keyframe_view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ConnectTimelineView(keyframe_view_); - keyframe_area_layout->addWidget(keyframe_view_); - - // Connect ruler and keyframe view together - connect(ruler(), &TimeRuler::TimeChanged, keyframe_view_, &KeyframeView::SetTime); - connect(keyframe_view_, &KeyframeView::TimeChanged, ruler(), &TimeRuler::SetTime); - connect(keyframe_view_, &KeyframeView::TimeChanged, this, &NodeParamView::SetTime); - connect(keyframe_view_, &KeyframeView::Dragged, this, &NodeParamView::KeyframeViewDragged); - - // Connect keyframe view scaling to this - connect(keyframe_view_, &KeyframeView::ScaleChanged, this, &NodeParamView::SetScale); - - splitter->addWidget(keyframe_area); - - // Set both widgets to 50/50 - splitter->setSizes({INT_MAX, INT_MAX}); - // Disable collapsing param view (but collapsing keyframe view is permitted) splitter->setCollapsible(0, false); + if (create_keyframe_view) { + // Set up keyframe view + QWidget* keyframe_area = new QWidget(); + QVBoxLayout* keyframe_area_layout = new QVBoxLayout(keyframe_area); + keyframe_area_layout->setSpacing(0); + keyframe_area_layout->setMargin(0); + + // Create ruler object + keyframe_area_layout->addWidget(ruler()); + + // Create keyframe view + keyframe_view_ = new KeyframeView(); + keyframe_view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ConnectTimelineView(keyframe_view_); + keyframe_area_layout->addWidget(keyframe_view_); + + // Connect ruler and keyframe view together + connect(ruler(), &TimeRuler::TimeChanged, keyframe_view_, &KeyframeView::SetTime); + connect(keyframe_view_, &KeyframeView::TimeChanged, ruler(), &TimeRuler::SetTime); + connect(keyframe_view_, &KeyframeView::TimeChanged, this, &NodeParamView::SetTime); + connect(keyframe_view_, &KeyframeView::Dragged, this, &NodeParamView::KeyframeViewDragged); + + // Connect keyframe view scaling to this + connect(keyframe_view_, &KeyframeView::ScaleChanged, this, &NodeParamView::SetScale); + + splitter->addWidget(keyframe_area); + + // Set both widgets to 50/50 + splitter->setSizes({INT_MAX, INT_MAX}); + } else { + keyframe_view_ = nullptr; + } + // Create global vertical scrollbar on the right vertical_scrollbar_ = new QScrollBar(); vertical_scrollbar_->setMaximum(0); layout->addWidget(vertical_scrollbar_); // Connect scrollbars together - connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); - connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, scroll_area->verticalScrollBar(), &QScrollBar::setValue); - connect(scroll_area->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); - connect(scroll_area->verticalScrollBar(), &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue); - connect(vertical_scrollbar_, &QScrollBar::valueChanged, scroll_area->verticalScrollBar(), &QScrollBar::setValue); - connect(vertical_scrollbar_, &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue); + connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); + connect(vertical_scrollbar_, &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue); - // TimeBasedWidget's scrollbar has extra functionality that we can take advantage of - keyframe_view_->setHorizontalScrollBar(scrollbar()); - keyframe_view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + if (keyframe_view_) { + connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); + connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue); + connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue); + connect(vertical_scrollbar_, &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue); - connect(keyframe_view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); + // TimeBasedWidget's scrollbar has extra functionality that we can take advantage of + keyframe_view_->setHorizontalScrollBar(scrollbar()); + keyframe_view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + + connect(keyframe_view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); + } // Set a default scale - FIXME: Hardcoded SetScale(120); @@ -192,6 +201,14 @@ void NodeParamView::DeselectNodes(const QVector &nodes) } } +void NodeParamView::SetInputChecked(const NodeInput &input, bool e) +{ + input_checked_.insert(input, e); + if (NodeParamViewItem *item = items_.value(input.node())) { + item->SetInputChecked(input, e); + } +} + void NodeParamView::resizeEvent(QResizeEvent *event) { super::resizeEvent(event); @@ -205,17 +222,21 @@ void NodeParamView::ScaleChangedEvent(const double &scale) { super::ScaleChangedEvent(scale); - keyframe_view_->SetScale(scale); + if (keyframe_view_) { + keyframe_view_->SetScale(scale); + } } void NodeParamView::TimebaseChangedEvent(const rational &timebase) { super::TimebaseChangedEvent(timebase); - keyframe_view_->SetTimebase(timebase); + if (keyframe_view_) { + keyframe_view_->SetTimebase(timebase); + } foreach (NodeParamViewItem* item, items_) { - item->SetTimebase(timebase); + item->SetTimebase(timebase); } UpdateItemTime(GetTime()); @@ -225,29 +246,37 @@ void NodeParamView::TimeChangedEvent(const rational &time) { super::TimeChangedEvent(time); - keyframe_view_->SetTime(time); + if (keyframe_view_) { + keyframe_view_->SetTime(time); + } UpdateItemTime(time); } void NodeParamView::ConnectedNodeChangeEvent(ViewerOutput *n) { - // Set viewer as a time target - keyframe_view_->SetTimeTarget(n); + if (keyframe_view_) { + // Set viewer as a time target + keyframe_view_->SetTimeTarget(n); + } foreach (NodeParamViewItem* item, items_) { item->SetTimeTarget(n); } + + time_target_ = n; } Node *NodeParamView::GetTimeTarget() const { - return keyframe_view_->GetTimeTarget(); + return time_target_; } void NodeParamView::DeleteSelected() { - keyframe_view_->DeleteSelected(); + if (keyframe_view_) { + keyframe_view_->DeleteSelected(); + } } void NodeParamView::UpdateItemTime(const rational &time) @@ -293,14 +322,16 @@ void NodeParamView::SignalNodeOrder() void NodeParamView::AddNode(Node *n) { - NodeParamViewItem* item = new NodeParamViewItem(n, param_widget_area_); + NodeParamViewItem* item = new NodeParamViewItem(n, create_checkboxes_, param_widget_area_); item->setAllowedAreas(Qt::LeftDockWidgetArea); item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable); item->SetExpanded(node_expanded_state_.value(n, true)); - connect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); - connect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); + if (keyframe_view_) { + connect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); + connect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); + } connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::SetTimeAndSignal); connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode); @@ -310,6 +341,15 @@ void NodeParamView::AddNode(Node *n) connect(item, &NodeParamViewItem::ArrayExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate); connect(item, &NodeParamViewItem::ExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate); connect(item, &NodeParamViewItem::Moved, this, &NodeParamView::QueueKeyframePositionUpdate); + connect(item, &NodeParamViewItem::InputCheckedChanged, this, &NodeParamView::SetInputChecked); + + if (create_checkboxes_) { + for (auto it=input_checked_.cbegin(); it!=input_checked_.cend(); it++) { + if (it.key().node() == n) { + item->SetInputChecked(it.key(), it.value()); + } + } + } // Set time target item->SetTimeTarget(GetTimeTarget()); @@ -327,15 +367,19 @@ void NodeParamView::AddNode(Node *n) emit FocusedNodeChanged(focused_node_); } - keyframe_view_->AddKeyframesOfNode(n); + if (keyframe_view_) { + keyframe_view_->AddKeyframesOfNode(n); + } } void NodeParamView::RemoveNode(Node *n) { - keyframe_view_->RemoveKeyframesOfNode(n); + if (keyframe_view_) { + keyframe_view_->RemoveKeyframesOfNode(n); - disconnect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); - disconnect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); + disconnect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); + disconnect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); + } delete items_.take(n); @@ -358,8 +402,10 @@ void NodeParamView::UpdateGlobalScrollBar() { int height_offscreen = param_widget_container_->height() - ruler()->height() + scrollbar()->height(); - keyframe_view_->SetMaxScroll(height_offscreen); - vertical_scrollbar_->setRange(0, height_offscreen - keyframe_view_->height()); + if (keyframe_view_) { + keyframe_view_->SetMaxScroll(height_offscreen); + } + vertical_scrollbar_->setRange(0, height_offscreen - param_scroll_area_->height()); } void NodeParamView::PinNode(bool pin) @@ -390,18 +436,20 @@ void NodeParamView::FocusChanged(QWidget* old, QWidget* now) item = dynamic_cast(parent); if (item) { - // Found it! - if (item->GetNode() != focused_node_) { - if (focused_node_) { - // De-focus current node - items_.value(focused_node_)->SetHighlighted(false); + if (item->parent() == param_widget_area_) { + // Found it! + if (item->GetNode() != focused_node_) { + if (focused_node_) { + // De-focus current node + items_.value(focused_node_)->SetHighlighted(false); + } + + focused_node_ = item->GetNode(); + + item->SetHighlighted(true); + + emit FocusedNodeChanged(focused_node_); } - - focused_node_ = item->GetNode(); - - item->SetHighlighted(true); - - emit FocusedNodeChanged(focused_node_); } break; @@ -421,15 +469,17 @@ void NodeParamView::KeyframeViewDragged(int x, int y) void NodeParamView::UpdateElementY() { - for (auto it=items_.cbegin(); it!=items_.cend(); it++) { - foreach (const QString& input, it.key()->inputs()) { - int arr_sz = it.key()->InputArraySize(input); + if (keyframe_view_) { + for (auto it=items_.cbegin(); it!=items_.cend(); it++) { + foreach (const QString& input, it.key()->inputs()) { + int arr_sz = it.key()->InputArraySize(input); - for (int i=-1; iGetElementY(ic); - keyframe_view_->SetElementY(ic, y); + int y = it.value()->GetElementY(ic); + keyframe_view_->SetElementY(ic, y); + } } } } diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 1718f445e..677c4dce5 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -58,11 +58,25 @@ class NodeParamView : public TimeBasedWidget { Q_OBJECT public: - NodeParamView(QWidget* parent = nullptr); + NodeParamView(bool create_keyframe_view, QWidget* parent = nullptr); + NodeParamView(QWidget* parent = nullptr) : + NodeParamView(true, parent) + { + } void SelectNodes(const QVector &nodes); void DeselectNodes(const QVector& nodes); + void SetCreateCheckBoxes(NodeParamViewCheckBoxBehavior e) + { + create_checkboxes_ = e; + } + + bool IsInputChecked(const NodeInput &input) const + { + return input_checked_.value(input); + } + const QMap& GetItemMap() const { return items_; @@ -82,6 +96,9 @@ public: keyframe_view_->DeselectAll(); } +public slots: + void SetInputChecked(const NodeInput &input, bool e); + signals: void RequestSelectNode(const QVector& target); @@ -117,10 +134,10 @@ private: int last_scroll_val_; + QScrollArea* param_scroll_area_; + NodeParamViewParamContainer* param_widget_container_; - // This may look weird, but QMainWindow is just a QWidget with a fancy layout that allows - // docking windows NodeParamViewDockArea* param_widget_area_; QVector pinned_nodes_; @@ -131,6 +148,12 @@ private: Node* focused_node_; + NodeParamViewCheckBoxBehavior create_checkboxes_; + + Node *time_target_; + + QHash input_checked_; + private slots: void UpdateGlobalScrollBar(); diff --git a/app/widget/nodeparamview/nodeparamviewdockarea.h b/app/widget/nodeparamview/nodeparamviewdockarea.h index 90a00c1a0..c313236cd 100644 --- a/app/widget/nodeparamview/nodeparamviewdockarea.h +++ b/app/widget/nodeparamview/nodeparamviewdockarea.h @@ -25,6 +25,8 @@ namespace olive { +// This may look weird, but QMainWindow is just a QWidget with a fancy layout that allows +// for docking QDockWidgets class NodeParamViewDockArea : public QMainWindow { Q_OBJECT diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 3c2d5bbb0..3347551ef 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -38,12 +38,14 @@ const int NodeParamViewItemBody::kArrayInsertColumn = kKeyControlColumn-1; const int NodeParamViewItemBody::kArrayRemoveColumn = kArrayInsertColumn-1; const int NodeParamViewItemBody::kExtraButtonColumn = kKeyControlColumn-1; -// 0 is for the array collapse button, 1 is for the main label, widgets start at 2 -const int NodeParamViewItemBody::kWidgetStartColumn = 2; +const int NodeParamViewItemBody::kOptionalCheckBox = 0; +const int NodeParamViewItemBody::kArrayCollapseBtnColumn = 1; +const int NodeParamViewItemBody::kLabelColumn = 2; +const int NodeParamViewItemBody::kWidgetStartColumn = 3; #define super QDockWidget -NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) : +NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) : super(parent), node_(node), highlighted_(false) @@ -55,10 +57,11 @@ NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) : this->setTitleBarWidget(title_bar_); // Create and add contents widget - body_ = new NodeParamViewItemBody(node_); + body_ = new NodeParamViewItemBody(node_, create_checkboxes); connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode); connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged); + connect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged); connect(title_bar_, &NodeParamViewItemTitleBar::ExpandedStateChanged, this, &NodeParamViewItem::SetExpanded); connect(title_bar_, &NodeParamViewItemTitleBar::PinToggled, this, &NodeParamViewItem::PinToggled); @@ -165,6 +168,11 @@ int NodeParamViewItem::GetElementY(const NodeInput &c) const } } +void NodeParamViewItem::SetInputChecked(const NodeInput &input, bool e) +{ + body_->SetInputChecked(input, e); +} + void NodeParamViewItem::ToggleExpanded() { SetExpanded(!IsExpanded()); @@ -222,9 +230,10 @@ void NodeParamViewItemTitleBar::mouseDoubleClickEvent(QMouseEvent *event) collapse_btn_->click(); } -NodeParamViewItemBody::NodeParamViewItemBody(Node* node, QWidget *parent) : +NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) : QWidget(parent), - node_(node) + node_(node), + create_checkboxes_(create_checkboxes) { QGridLayout* root_layout = new QGridLayout(this); @@ -277,11 +286,22 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const ui_objects.layout = layout; ui_objects.row = row; + // Create optional checkbox if requested + if (create_checkboxes_) { + ui_objects.optional_checkbox = new QCheckBox(); + connect(ui_objects.optional_checkbox, &QCheckBox::clicked, this, &NodeParamViewItemBody::OptionalCheckBoxClicked); + layout->addWidget(ui_objects.optional_checkbox, row, kOptionalCheckBox); + + if (create_checkboxes_ == kCheckBoxesOnNonConnected && input_ref.IsConnected()) { + ui_objects.optional_checkbox->setVisible(false); + } + } + // Add descriptor label ui_objects.main_label = new QLabel(); - // Label always goes into column 1 (array collapse button goes into 0 if applicable) - layout->addWidget(ui_objects.main_label, row, 1); + // Create input label + layout->addWidget(ui_objects.main_label, row, kLabelColumn); if (node->InputIsArray(input)) { if (element == -1) { @@ -292,8 +312,8 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const // Default to collapsed array_collapse_btn->setChecked(false); - // Collapse button always goes into column 0 - layout->addWidget(array_collapse_btn, row, 0); + // Add collapse button to layout + layout->addWidget(array_collapse_btn, row, kArrayCollapseBtnColumn); // Connect signal to show/hide array params when toggled connect(array_collapse_btn, &CollapseButton::toggled, this, &NodeParamViewItemBody::ArrayCollapseBtnPressed); @@ -448,6 +468,11 @@ void NodeParamViewItemBody::UpdateUIForEdgeConnection(const NodeInput& input) if (ui_objects.key_control) { ui_objects.key_control->setVisible(!input.IsConnected()); } + + // Show/hide optional checkbox if requested + if (create_checkboxes_ == kCheckBoxesOnNonConnected) { + ui_objects.optional_checkbox->setVisible(!input.IsConnected()); + } } } @@ -575,6 +600,16 @@ void NodeParamViewItemBody::SetTimebase(const rational& timebase) } } +void NodeParamViewItemBody::SetInputChecked(const NodeInput &input, bool e) +{ + if (input_ui_map_.contains(input)) { + QCheckBox *cb = input_ui_map_.value(input).optional_checkbox; + if (cb) { + cb->setChecked(e); + } + } +} + void NodeParamViewItemBody::ReplaceWidgets(const NodeInput &input) { InputUI ui = input_ui_map_.value(input); @@ -588,12 +623,25 @@ void NodeParamViewItemBody::ShowSpeedDurationDialogForNode() sdd.exec(); } +void NodeParamViewItemBody::OptionalCheckBoxClicked(bool e) +{ + QCheckBox *cb = static_cast(sender()); + + for (auto it=input_ui_map_.cbegin(); it!=input_ui_map_.cend(); it++) { + if (it.value().optional_checkbox == cb) { + emit InputCheckedChanged(it.key(), e); + break; + } + } +} + NodeParamViewItemBody::InputUI::InputUI() : main_label(nullptr), widget_bridge(nullptr), connected_label(nullptr), key_control(nullptr), extra_btn(nullptr), + optional_checkbox(nullptr), array_insert_btn(nullptr), array_remove_btn(nullptr) { diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index bbd31c9f4..55a0a3174 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -21,6 +21,7 @@ #ifndef NODEPARAMVIEWITEM_H #define NODEPARAMVIEWITEM_H +#include #include #include #include @@ -38,6 +39,12 @@ namespace olive { +enum NodeParamViewCheckBoxBehavior { + kNoCheckBoxes, + kCheckBoxesOn, + kCheckBoxesOnNonConnected +}; + class NodeParamViewItemTitleBar : public QWidget { Q_OBJECT @@ -75,7 +82,7 @@ private: class NodeParamViewItemBody : public QWidget { Q_OBJECT public: - NodeParamViewItemBody(Node* node, QWidget* parent = nullptr); + NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget* parent = nullptr); void SetTimeTarget(Node* target); @@ -86,7 +93,9 @@ public: int GetElementY(NodeInput c) const; // Set the timebase of any timebased widgets contained here - void SetTimebase(const rational& timebase); + void SetTimebase(const rational& timebase); + + void SetInputChecked(const NodeInput &input, bool e); signals: void RequestSetTime(const rational& time); @@ -95,6 +104,8 @@ signals: void ArrayExpandedChanged(bool e); + void InputCheckedChanged(const NodeInput &input, bool e); + private: void CreateWidgets(QGridLayout *layout, Node* node, const QString& input, int element, int row_index); @@ -114,6 +125,7 @@ private: QGridLayout* layout; int row; QPushButton *extra_btn; + QCheckBox *optional_checkbox; NodeParamViewArrayButton* array_insert_btn; NodeParamViewArrayButton* array_remove_btn; @@ -135,6 +147,8 @@ private: rational timebase_; + NodeParamViewCheckBoxBehavior create_checkboxes_; + /** * @brief The column to place the keyframe controls in * @@ -147,6 +161,9 @@ private: static const int kArrayRemoveColumn; static const int kExtraButtonColumn; + static const int kOptionalCheckBox; + static const int kArrayCollapseBtnColumn; + static const int kLabelColumn; static const int kWidgetStartColumn; private slots: @@ -168,13 +185,15 @@ private slots: void ShowSpeedDurationDialogForNode(); + void OptionalCheckBoxClicked(bool e); + }; class NodeParamViewItem : public QDockWidget { Q_OBJECT public: - NodeParamViewItem(Node* node, QWidget* parent = nullptr); + NodeParamViewItem(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget* parent = nullptr); void SetTimeTarget(Node* target); @@ -196,6 +215,8 @@ public: int GetElementY(const NodeInput& c) const; + void SetInputChecked(const NodeInput &input, bool e); + public slots: void SetExpanded(bool e); @@ -214,6 +235,8 @@ signals: void Moved(); + void InputCheckedChanged(const NodeInput &input, bool e); + protected: virtual void changeEvent(QEvent *e) override; diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index e805d7eda..9b3619e0e 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -21,16 +21,18 @@ #include "nodeview.h" #include +#include #include #include #include #include "core.h" +#include "dialog/nodegroup/nodegroupdialog.h" #include "nodeviewundo.h" #include "node/audio/volume/volume.h" #include "node/distort/transform/transformdistortnode.h" #include "node/factory.h" -#include "node/group.h" +#include "node/group/group.h" #include "node/traverser.h" #include "widget/menu/menushared.h" #include "widget/timebased/timebasedview.h" @@ -638,6 +640,7 @@ void NodeView::UpdateSelectionCache() if (current_selection.isEmpty()) { // All nodes that were selected have been deselected, so we'll just set them all to `deselected` deselected = selected_nodes_; + selected_nodes_.clear(); } else { foreach (Node* n, selected_nodes_) { bool still_selected = false; @@ -649,20 +652,20 @@ void NodeView::UpdateSelectionCache() } } - if (still_selected) { + if (!still_selected) { deselected.append(n); selected_nodes_.removeOne(n); } } } - if (!selected.isEmpty()) { - emit NodesSelected(selected); - } - if (!deselected.isEmpty()) { emit NodesDeselected(deselected); } + + if (!selected.isEmpty()) { + emit NodesSelected(selected); + } } void NodeView::ShowContextMenu(const QPoint &pos) @@ -1082,13 +1085,88 @@ void NodeView::PositionNewEdge(const QPoint &pos) void NodeView::GroupNodes() { - /*NodeGroup *group = new NodeGroup(); - selected_nodes_*/ + // Get items + QVector items = scene_.GetSelectedItems(); + if (items.isEmpty()) { + return; + } + + // Get node context + Node *context = items.first()->GetContext(); + QPointF avg_pos = items.first()->GetNodePosition(); + for (int i=1; iGetContext() != context) { + QMessageBox::critical(this, tr("Failed to group nodes"), tr("Nodes can only be grouped if they're in the same context.")); + return; + } + + avg_pos += items.at(i)->GetNodePosition(); + } + avg_pos /= items.size(); + + // Create group + NodeGroup *group = new NodeGroup(); + + // Add group to graph and context + MultiUndoCommand *command = new MultiUndoCommand(); + + command->add_child(new NodeAddCommand(context->parent(), group)); + command->add_child(new NodeSetPositionCommand(group, context, avg_pos)); + + // Add nodes to group + QVector nodes_to_group = selected_nodes_; + DeselectAll(); + foreach (Node *n, nodes_to_group) { + for (auto it=n->input_connections().cbegin(); it!=n->input_connections().cend(); it++) { + Node *output = it->second; + const NodeInput &input = it->first; + + if (!nodes_to_group.contains(output)) { + command->add_child(new NodeEdgeRemoveCommand(output, input)); + command->add_child(new NodeEdgeAddCommand(output, NodeInput(group, input.input(), input.element()))); + } + } + + for (auto it=n->output_connections().cbegin(); it!=n->output_connections().cend(); it++) { + Node *output = it->first; + const NodeInput &input = it->second; + + if (!nodes_to_group.contains(input.node())) { + command->add_child(new NodeEdgeRemoveCommand(output, input)); + command->add_child(new NodeEdgeAddCommand(group, input)); + } + } + + command->add_child(new NodeRemovePositionFromContextCommand(n, context)); + command->add_child(new NodeAddToGroupCommand(n, group)); + command->add_child(new NodeSetPositionCommand(n, group, scene_.context_map().value(context)->GetItemFromMap(n)->GetNodePosition())); + + for (auto it=n->inputs().cbegin(); it!=n->inputs().cend(); it++) { + NodeInput input(n, *it, -1); + + if (!input.IsConnected() || !nodes_to_group.contains(input.GetConnectedOutput())) { + command->add_child(new NodeGroupAddInputPassthrough(group, input)); + } + } + } + + // Do command + command->redo_now(); + + NodeGroupDialog ngd(group, this); + if (ngd.exec() == QDialog::Accepted) { + // Push to stack so it can be undone (MultiUndoCommand will ignore the request to redo again) + Core::instance()->undo_stack()->push(command); + } else { + // Undo command and delete + command->undo_now(); + delete command; + } } void NodeView::UngroupNodes() { - //static_cast(selected_nodes_.first()); + //NodeGroup *group = static_cast(selected_nodes_.first()); } void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index fd5e2c0c9..5f7c802bb 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -45,7 +45,7 @@ class NodeView : public HandMovableView, public NodeCopyPasteService { Q_OBJECT public: - NodeView(QWidget* parent); + NodeView(QWidget* parent = nullptr); virtual ~NodeView() override; diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index b608ee58b..c2c516c60 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -189,7 +189,9 @@ void NodeViewContext::DeleteSelected(NodeViewDeleteCommand *command) void NodeViewContext::Select(const QVector &nodes) { foreach (Node *n, nodes) { - item_map_.value(n)->setSelected(true); + if (NodeViewItem *item = item_map_.value(n)) { + item->setSelected(true); + } } } diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index cf49b332d..7cdc638a7 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -36,6 +36,11 @@ public: QPointF MapScenePosToNodePosInContext(const QPointF &pos) const; + NodeViewItem *GetItemFromMap(Node *node) const + { + return item_map_.value(node); + } + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; public slots: diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 3ea29ff5a..1a9b6c516 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -544,7 +544,7 @@ QPointF NodeViewItem::GetInputPoint(const QString &input, int element) const int index = node_inputs_.indexOf(input); if (index < 0 || index >= int(input_connectors_.size())) { - return QPointF(); + return pos(); } return input_connectors_[index]->scenePos(); diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 47c23c3cb..db245f005 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -467,15 +467,6 @@ void MainWindow::TimelinePanelSelectionChanged(const QVector &blocks) } } -void MainWindow::ProjectPanelSelectionChanged(const QVector &nodes) -{ - ProjectPanel *panel = static_cast(sender()); - - if (PanelManager::instance()->CurrentlyFocused(false) == panel) { - node_panel_->Select(nodes, true); - } -} - void MainWindow::ShowWelcomeDialog() { if (Config::Current()[QStringLiteral("ShowWelcomeDialog")].toBool()) { @@ -572,7 +563,6 @@ ProjectPanel *MainWindow::AppendProjectPanel() connect(panel, &PanelWidget::CloseRequested, this, &MainWindow::ProjectCloseRequested); connect(panel, &ProjectPanel::ProjectNameChanged, this, &MainWindow::UpdateTitle); - connect(panel, &ProjectPanel::SelectionChanged, this, &MainWindow::ProjectPanelSelectionChanged); return panel; } diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index f8cf36d98..719652200 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -193,8 +193,6 @@ private slots: void TimelinePanelSelectionChanged(const QVector &blocks); - void ProjectPanelSelectionChanged(const QVector &nodes); - void ShowWelcomeDialog(); }; From cb0ebb24a43193ebc14d150df4f056d7fbed80d2 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Thu, 25 Nov 2021 13:18:01 +0000 Subject: [PATCH 27/94] saveotio: Fix external reference available media --- app/task/project/saveotio/saveotio.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/app/task/project/saveotio/saveotio.cpp b/app/task/project/saveotio/saveotio.cpp index 92af6753b..107e36ec0 100644 --- a/app/task/project/saveotio/saveotio.cpp +++ b/app/task/project/saveotio/saveotio.cpp @@ -135,12 +135,33 @@ OTIO::Track *SaveOTIOTask::SerializeTrack(Track *track) if (dynamic_cast(block)) { auto otio_clip = new OTIO::Clip(block->GetLabel().toStdString()); + auto rate = 0.0; + rational frame_rate = static_cast(block)->connected_viewer()->GetVideoParams().frame_rate(); + if (frame_rate.denominator() == 1) { + rate = frame_rate.numerator(); + } else { + // Handle strange rates + rate = static_cast(frame_rate.numerator()) / + static_cast(frame_rate.denominator()); + } + otio_clip->set_source_range(OTIO::TimeRange(block->in().toRationalTime(), - block->length().toRationalTime())); + block->length().toRationalTime(rate))); QVector media_nodes = block->FindInputNodes(); if (!media_nodes.isEmpty()) { - auto media_ref = new OTIO::ExternalReference(media_nodes.first()->filename().toStdString()); + + OTIO::TimeRange range; + if (otio_track->kind().compare("Video") == 0) { + range = OTIO::TimeRange(OTIO::RationalTime(0, rate), + OTIO::RationalTime(media_nodes.first()->GetVideoParams().duration(), + rate)); + } else if (otio_track->kind().compare("Audio") == 0) { + range = OTIO::TimeRange(OTIO::RationalTime(0, media_nodes.first()->GetAudioParams().sample_rate()), + OTIO::RationalTime(media_nodes.first()->GetAudioParams().duration(), + media_nodes.first()->GetAudioParams().sample_rate())); + } + auto media_ref = new OTIO::ExternalReference(media_nodes.first()->filename().toStdString(), range); otio_clip->set_media_reference(media_ref); } From 24a11e2dad5a01ddd34dc1c15158b4460fa23e1e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 25 Nov 2021 15:12:44 -0800 Subject: [PATCH 28/94] fixed mistakes in export task --- app/task/export/export.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/task/export/export.cpp b/app/task/export/export.cpp index 93c7d251c..ae7446b1c 100644 --- a/app/task/export/export.cpp +++ b/app/task/export/export.cpp @@ -87,15 +87,14 @@ bool ExportTask::Run() if (params_.video_enabled()) { // If a transformation matrix is applied to this video, create it here - VideoParams vp = viewer()->GetVideoParams(); - if (vp.width() != params_.video_params().width() - || params_.video_params().height() != params_.video_params().height()) { + if (video_params().width() != params_.video_params().width() + || video_params().height() != params_.video_params().height()) { video_force_size = QSize(params_.video_params().width(), params_.video_params().height()); if (params_.video_scaling_method() != ExportParams::kStretch) { video_force_matrix = ExportParams::GenerateMatrix(params_.video_scaling_method(), - vp.width(), - vp.height(), + video_params().width(), + video_params().height(), params_.video_params().width(), params_.video_params().height()); } From 83565cc7deb823db444ba2e47e33467a0dcbe3f6 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 25 Nov 2021 15:13:56 -0800 Subject: [PATCH 29/94] fixed widget bridge issue with comboboxes --- .../nodeparamview/nodeparamviewwidgetbridge.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index e54350ef4..ca983c581 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -520,13 +520,8 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() QComboBox* cb = static_cast(widgets_.first()); cb->blockSignals(true); int index = input_.GetValueAtTime(node_time).toInt(); - int real_row = -1; for (int i=0; icount(); i++) { - if (!cb->itemText(i).isEmpty()) { - real_row++; - } - - if (real_row == index) { + if (cb->itemData(i).toInt() == index) { cb->setCurrentIndex(i); } } @@ -728,11 +723,14 @@ void NodeParamViewWidgetBridge::PropertyChanged(const QString& input, const QStr cb->clear(); QStringList items = input_.GetComboBoxStrings(); + int index = 0; foreach (const QString& s, items) { if (s.isEmpty()) { cb->insertSeparator(cb->count()); + cb->setItemData(cb->count()-1, -1); } else { - cb->addItem(s); + cb->addItem(s, index); + index++; } } From 451e513a25367921c0a67d27c52a72665ba89ffa Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 25 Nov 2021 15:23:21 -0800 Subject: [PATCH 30/94] match export alpha behavior with preview --- app/render/renderprocessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 3b68001f8..267ee723b 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -96,7 +96,7 @@ FramePtr RenderProcessor::GenerateFrame(TexturePtr texture, const rational& time if (output_color_transform) { // Yes color transform, blit color managed - render_ctx_->BlitColorManaged(output_color_transform, texture, Renderer::kAlphaAssociated, blit_tex.get(), true, matrix); + render_ctx_->BlitColorManaged(output_color_transform, texture, Renderer::kAlphaNone, blit_tex.get(), true, matrix); } else { // No color transform, just blit ShaderJob job; From 239a019690bdfd807181ed69a39ac522cf3a40e3 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 29 Nov 2021 21:22:49 -0800 Subject: [PATCH 31/94] many improvements and reimplementations --- app/dialog/nodegroup/nodegroupdialog.cpp | 2 + app/node/graph.cpp | 16 + app/node/graph.h | 7 +- app/node/group/group.cpp | 52 +- app/node/group/group.h | 83 ++- app/node/node.cpp | 67 ++- app/node/node.h | 40 +- app/node/param.cpp | 9 + app/node/param.h | 12 + app/widget/nodeview/nodeview.cpp | 268 ++++++---- app/widget/nodeview/nodeview.h | 10 +- app/widget/nodeview/nodeviewcommon.h | 1 + app/widget/nodeview/nodeviewcontext.cpp | 116 ++-- app/widget/nodeview/nodeviewcontext.h | 9 +- app/widget/nodeview/nodeviewedge.cpp | 64 ++- app/widget/nodeview/nodeviewedge.h | 14 +- app/widget/nodeview/nodeviewitem.cpp | 503 ++++++++++-------- app/widget/nodeview/nodeviewitem.h | 90 ++-- app/widget/nodeview/nodeviewitemconnector.cpp | 2 + .../undo/timelineundogeneral.cpp | 2 +- app/window/mainwindow/mainwindow.cpp | 2 +- 21 files changed, 874 insertions(+), 495 deletions(-) diff --git a/app/dialog/nodegroup/nodegroupdialog.cpp b/app/dialog/nodegroup/nodegroupdialog.cpp index 2d07a4338..9fd1fa4af 100644 --- a/app/dialog/nodegroup/nodegroupdialog.cpp +++ b/app/dialog/nodegroup/nodegroupdialog.cpp @@ -60,6 +60,8 @@ NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : QMetaObject::invokeMethod(node_view, &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); splitter->addWidget(node_view); + splitter->setSizes({splitter->width() / 4, splitter->width() / 4 * 3}); + for (auto it=group->GetInputPassthroughs().cbegin(); it!=group->GetInputPassthroughs().cend(); it++) { param_view->SetInputChecked(it.value(), true); } diff --git a/app/node/graph.cpp b/app/node/graph.cpp index bdae43570..024cebf98 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -74,6 +74,12 @@ void NodeGraph::childEvent(QChildEvent *event) connect(node, &Node::ValueChanged, this, &NodeGraph::ValueChanged, Qt::DirectConnection); connect(node, &Node::InputValueHintChanged, this, &NodeGraph::InputValueHintChanged, Qt::DirectConnection); + if (NodeGroup *group = dynamic_cast(node)) { + connect(group, &NodeGroup::InputPassthroughAdded, this, &NodeGraph::GroupAddedInputPassthrough, Qt::DirectConnection); + connect(group, &NodeGroup::InputPassthroughRemoved, this, &NodeGraph::GroupRemovedInputPassthrough, Qt::DirectConnection); + connect(group, &NodeGroup::OutputPassthroughChanged, this, &NodeGraph::GroupChangedOutputPassthrough, Qt::DirectConnection); + } + emit NodeAdded(node); emit node->AddedToGraph(this); @@ -87,12 +93,22 @@ void NodeGraph::childEvent(QChildEvent *event) disconnect(node, &Node::ValueChanged, this, &NodeGraph::ValueChanged); disconnect(node, &Node::InputValueHintChanged, this, &NodeGraph::InputValueHintChanged); + if (NodeGroup *group = dynamic_cast(node)) { + disconnect(group, &NodeGroup::InputPassthroughAdded, this, &NodeGraph::GroupAddedInputPassthrough); + disconnect(group, &NodeGroup::InputPassthroughRemoved, this, &NodeGraph::GroupRemovedInputPassthrough); + disconnect(group, &NodeGroup::OutputPassthroughChanged, this, &NodeGraph::GroupChangedOutputPassthrough); + } + emit NodeRemoved(node); emit node->RemovedFromGraph(this); // Remove from any contexts foreach (Node *context, node_children_) { context->RemoveNodeFromContext(node); + + if (NodeGroup *group = dynamic_cast(context)) { + group->RemoveNode(node); + } } } } diff --git a/app/node/graph.h b/app/node/graph.h index fdf923b56..443e20748 100644 --- a/app/node/graph.h +++ b/app/node/graph.h @@ -21,6 +21,7 @@ #ifndef NODEGRAPH_H #define NODEGRAPH_H +#include "node/group/group.h" #include "node/node.h" namespace olive { @@ -84,9 +85,11 @@ signals: void InputValueHintChanged(const NodeInput& input); - void NodePositionAdded(Node *node, Node *relative, const QPointF &position); + void GroupAddedInputPassthrough(NodeGroup *group, const NodeInput &input); - void NodePositionRemoved(Node *node, Node *relative); + void GroupRemovedInputPassthrough(NodeGroup *group, const NodeInput &input); + + void GroupChangedOutputPassthrough(NodeGroup *group, Node *output); protected: void AddDefaultNode(Node* n) diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp index fbe785bd9..f8e44790f 100644 --- a/app/node/group/group.cpp +++ b/app/node/group/group.cpp @@ -27,8 +27,6 @@ namespace olive { NodeGroup::NodeGroup() : output_passthrough_(nullptr) { - graph_ = new NodeGraph(); - graph_->setParent(this); } QString NodeGroup::Name() const @@ -57,26 +55,28 @@ QString NodeGroup::Description() const void NodeGroup::Retranslate() { - foreach (Node *n, graph_->nodes()) { + foreach (Node *n, nodes_) { n->Retranslate(); } } void NodeGroup::AddNode(Node *node) { - node->setParent(graph_); + nodes_.append(node); + + emit NodeAddedToGroup(node); } -void NodeGroup::RemoveNode(Node *node, QObject *new_parent) +void NodeGroup::RemoveNode(Node *node) { - if (node->parent() == graph_) { - node->setParent(new_parent); + if (nodes_.removeOne(node)) { + emit NodeRemovedFromGroup(node); } } void NodeGroup::AddInputPassthrough(const NodeInput &input) { - Q_ASSERT(graph_->nodes().contains(input.node())); + Q_ASSERT(nodes_.contains(input.node())); for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) { if (it.value() == input) { @@ -91,6 +91,8 @@ void NodeGroup::AddInputPassthrough(const NodeInput &input) AddInput(id, input.GetDataType(), input.GetDefaultValue(), input.GetFlags()); input_passthroughs_.insert(id, input); + + emit InputPassthroughAdded(this, input); } void NodeGroup::RemoveInputPassthrough(const NodeInput &input) @@ -99,6 +101,7 @@ void NodeGroup::RemoveInputPassthrough(const NodeInput &input) if (it.value() == input) { RemoveInput(it.key()); input_passthroughs_.erase(it); + emit InputPassthroughRemoved(this, it.value()); break; } } @@ -106,9 +109,11 @@ void NodeGroup::RemoveInputPassthrough(const NodeInput &input) void NodeGroup::SetOutputPassthrough(Node *node) { - Q_ASSERT(graph_->nodes().contains(node)); + Q_ASSERT(!node || nodes_.contains(node)); output_passthrough_ = node; + + emit OutputPassthroughChanged(this, output_passthrough_); } QString NodeGroup::GetGroupInputIDFromInput(const NodeInput &input) @@ -135,15 +140,19 @@ bool NodeGroup::ContainsInputPassthrough(const NodeInput &input) const return false; } +QString NodeGroup::GetInputName(const QString &id) const +{ + return input_passthroughs_.value(id).name(); +} + void NodeAddToGroupCommand::redo() { - previous_parent_ = node_->parent(); group_->AddNode(node_); } void NodeAddToGroupCommand::undo() { - group_->RemoveNode(node_, previous_parent_); + group_->RemoveNode(node_); } void NodeGroupSetCustomNameCommand::redo() @@ -174,4 +183,25 @@ void NodeGroupAddInputPassthrough::undo() } } +void NodeRemoveFromGroupCommand::redo() +{ + group_->RemoveNode(node_); +} + +void NodeRemoveFromGroupCommand::undo() +{ + group_->AddNode(node_); +} + +void NodeGroupSetOutputPassthrough::redo() +{ + old_output_ = group_->GetOutputPassthrough(); + group_->SetOutputPassthrough(new_output_); +} + +void NodeGroupSetOutputPassthrough::undo() +{ + group_->SetOutputPassthrough(old_output_); +} + } diff --git a/app/node/group/group.h b/app/node/group/group.h index a0aa9cee3..31e07ab07 100644 --- a/app/node/group/group.h +++ b/app/node/group/group.h @@ -43,12 +43,27 @@ public: void AddNode(Node *node); - void RemoveNode(Node *node, QObject *new_parent = nullptr); + void RemoveNode(Node *node); + + bool ContainsNode(Node *node) const + { + return nodes_.contains(node); + } + + const QVector &GetNodes() const + { + return nodes_; + } void AddInputPassthrough(const NodeInput &input); void RemoveInputPassthrough(const NodeInput &input); + Node *GetOutputPassthrough() const + { + return output_passthrough_; + } + void SetOutputPassthrough(Node *node); const QString &GetCustomName() const @@ -78,8 +93,21 @@ public: bool ContainsInputPassthrough(const NodeInput &input) const; + virtual QString GetInputName(const QString& id) const override; + +signals: + void NodeAddedToGroup(Node *node); + + void NodeRemovedFromGroup(Node *node); + + void InputPassthroughAdded(NodeGroup *group, const NodeInput &input); + + void InputPassthroughRemoved(NodeGroup *group, const NodeInput &input); + + void OutputPassthroughChanged(NodeGroup *group, Node *output); + private: - NodeGraph *graph_; + QVector nodes_; QHash input_passthroughs_; @@ -112,7 +140,30 @@ private: NodeGroup *group_; - QObject *previous_parent_; +}; + +class NodeRemoveFromGroupCommand : public UndoCommand +{ +public: + NodeRemoveFromGroupCommand(Node *node, NodeGroup *group) : + node_(node), + group_(group) + {} + + virtual Project * GetRelevantProject() const override + { + return node_->project(); + } + +protected: + virtual void redo() override; + + virtual void undo() override; + +private: + Node *node_; + + NodeGroup *group_; }; @@ -171,6 +222,32 @@ private: }; +class NodeGroupSetOutputPassthrough : public UndoCommand +{ +public: + NodeGroupSetOutputPassthrough(NodeGroup *group, Node *output) : + group_(group), + new_output_(output) + {} + + virtual Project * GetRelevantProject() const override + { + return group_->project(); + } + +protected: + virtual void redo() override; + + virtual void undo() override; + +private: + NodeGroup *group_; + + Node *new_output_; + Node *old_output_; + +}; + } #endif // NODEGROUP_H diff --git a/app/node/node.cpp b/app/node/node.cpp index 1d0316e8b..4e850e821 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -1610,7 +1610,9 @@ void Node::CopyValuesOfElement(const Node *src, Node *dst, const QString &input, dst->SetSplitStandardValue(input, src->GetSplitStandardValue(input, src_element), dst_element); // Copy keyframes - dst->GetImmediate(input, dst_element)->delete_all_keyframes(); + if (NodeInputImmediate *immediate = dst->GetImmediate(input, dst_element)) { + immediate->delete_all_keyframes(); + } foreach (const NodeKeyframeTrack& track, src->GetImmediate(input, src_element)->keyframe_tracks()) { foreach (NodeKeyframe* key, track) { key->copy(dst_element, dst); @@ -2366,15 +2368,13 @@ Project *Node::ArrayResizeCommand::GetRelevantProject() const void NodeSetPositionCommand::redo() { - if (!(added_ = !context_->ContextContainsNode(node_))) { + added_ = !context_->ContextContainsNode(node_); + + if (!added_) { old_pos_ = context_->GetNodePositionDataInContext(node_); } - if (added_) { - context_->SetNodePositionInContext(node_, pos_); - } else { - move(context_, node_, pos_.position - old_pos_.position, move_deps_); - } + context_->SetNodePositionInContext(node_, pos_); } void NodeSetPositionCommand::undo() @@ -2382,23 +2382,7 @@ void NodeSetPositionCommand::undo() if (added_) { context_->RemoveNodeFromContext(node_); } else { - move(context_, node_, old_pos_.position - pos_.position, move_deps_); - } -} - -void NodeSetPositionCommand::move(Node *context, Node *node, const QPointF &diff, bool recursive) -{ - QPointF p = context->GetNodePositionInContext(node); - p += diff; - context->SetNodePositionInContext(node, p); - - if (recursive) { - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - Node *output = it->second; - if (context->ContextContainsNode(output)) { - move(context, output, diff, recursive); - } - } + context_->SetNodePositionInContext(node_, old_pos_); } } @@ -2407,7 +2391,7 @@ void NodeRemovePositionFromContextCommand::redo() contained_ = context_->ContextContainsNode(node_); if (contained_) { - old_pos_ = context_->GetNodePositionInContext(node_); + old_pos_ = context_->GetNodePositionDataInContext(node_); context_->RemoveNodeFromContext(node_); } } @@ -2486,4 +2470,37 @@ void Node::ValueHint::Save(QXmlStreamWriter *writer) const writer->writeTextElement(QStringLiteral("tag"), tag_); } +void NodeSetPositionAndDependenciesRecursivelyCommand::prepare() +{ + move_recursively(node_, pos_.position - context_->GetNodePositionDataInContext(node_).position); +} + +void NodeSetPositionAndDependenciesRecursivelyCommand::redo() +{ + for (auto it=commands_.cbegin(); it!=commands_.cend(); it++) { + (*it)->redo_now(); + } +} + +void NodeSetPositionAndDependenciesRecursivelyCommand::undo() +{ + for (auto it=commands_.crbegin(); it!=commands_.crend(); it++) { + (*it)->undo_now(); + } +} + +void NodeSetPositionAndDependenciesRecursivelyCommand::move_recursively(Node *node, const QPointF &diff) +{ + Node::Position pos = context_->GetNodePositionDataInContext(node); + pos += diff; + commands_.append(new NodeSetPositionCommand(node_, context_, pos)); + + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + Node *output = it->second; + if (context_->ContextContainsNode(output)) { + move_recursively(output, diff); + } + } +} + } diff --git a/app/node/node.h b/app/node/node.h index 3a0fa5130..f38835ce9 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -320,7 +320,7 @@ public: static void DisconnectEdge(Node *output, const NodeInput& input); - QString GetInputName(const QString& id) const; + virtual QString GetInputName(const QString& id) const; void LoadInput(QXmlStreamReader* reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled); void SaveInput(QXmlStreamWriter* writer, const QString& id) const; @@ -1420,12 +1420,11 @@ using NodePtr = std::shared_ptr; class NodeSetPositionCommand : public UndoCommand { public: - NodeSetPositionCommand(Node* node, Node* context, const Node::Position& pos, bool move_dependencies_relatively = false) + NodeSetPositionCommand(Node* node, Node* context, const Node::Position& pos) { node_ = node; context_ = context; pos_ = pos; - move_deps_ = move_dependencies_relatively; } virtual Project* GetRelevantProject() const override @@ -1439,14 +1438,41 @@ protected: virtual void undo() override; private: - static void move(Node *context, Node *node, const QPointF &diff, bool recursive); - Node* node_; Node* context_; Node::Position pos_; Node::Position old_pos_; bool added_; - bool move_deps_; + +}; + +class NodeSetPositionAndDependenciesRecursivelyCommand : public UndoCommand{ +public: + NodeSetPositionAndDependenciesRecursivelyCommand(Node* node, Node* context, const Node::Position& pos) : + node_(node), + context_(context), + pos_(pos) + {} + + virtual Project* GetRelevantProject() const override + { + return node_->project(); + } + +protected: + virtual void prepare() override; + + virtual void redo() override; + + virtual void undo() override; + +private: + void move_recursively(Node *node, const QPointF &diff); + + Node* node_; + Node* context_; + Node::Position pos_; + QVector commands_; }; @@ -1474,7 +1500,7 @@ private: Node *context_; - QPointF old_pos_; + Node::Position old_pos_; bool contained_; diff --git a/app/node/param.cpp b/app/node/param.cpp index 2e579f613..d7676c615 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -150,6 +150,15 @@ QVariant NodeInput::GetSplitDefaultValueForTrack(int track) const } } +int NodeInput::GetArraySize() const +{ + if (IsValid() && element_ == -1) { + return node_->InputArraySize(input_); + } else { + return 0; + } +} + uint qHash(const NodeInput &i) { return qHash(i.node()) ^ qHash(i.input()) ^ qHash(i.element()); diff --git a/app/node/param.h b/app/node/param.h index 2e2276187..77f8e63f6 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -134,6 +134,16 @@ public: return element_; } + void set_node(Node *node) + { + node_ = node; + } + + void set_input(const QString &input) + { + input_ = input; + } + void set_element(int e) { element_ = e; @@ -172,6 +182,8 @@ public: QVariant GetSplitDefaultValueForTrack(int track) const; + int GetArraySize() const; + void Reset() { *this = NodeInput(); diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 9b3619e0e..2dab4cb29 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -49,10 +49,9 @@ NodeView::NodeView(QWidget *parent) : create_edge_(nullptr), create_edge_output_item_(nullptr), create_edge_input_item_(nullptr), - create_edge_dst_temp_expanded_(false), + create_edge_expand_item_(nullptr), paste_command_(nullptr), - scale_(1.0), - first_show_(true) + scale_(1.0) { setScene(&scene_); SetDefaultDragMode(RubberBandDrag); @@ -311,46 +310,55 @@ void NodeView::mousePressEvent(QMouseEvent *event) QGraphicsItem* item = itemAt(event->pos()); if (event->button() == Qt::LeftButton) { - // Determine if user clicked on a connector - NodeViewItemConnector *connector = dynamic_cast(item); + // Sane defaults + create_edge_output_item_ = nullptr; + create_edge_input_item_ = nullptr; + create_edge_already_exists_ = false; + create_edge_from_output_ = true; - // If the user clicked on a connector OR the user is holding Ctrl - if (connector || (event->modifiers() & Qt::ControlModifier)) { - // Get the relevant item, either the one attached to the connector or the item if Ctrl+Clicked - NodeViewItem *attached_item = connector ? static_cast(connector->parentItem()) : dynamic_cast(item); - - if (attached_item) { - if (connector && !connector->IsOutput() && (create_edge_ = attached_item->GetEdgeFromInputConnector(connector))) { - // Since inputs can only have one edge connected, we grab the existing edge, if one exists - create_edge_output_item_ = create_edge_->from_item(); - create_edge_already_exists_ = true; - create_edge_from_output_ = true; - } else { - // Create a new edge from this output - create_edge_ = new NodeViewEdge(); - create_edge_->SetCurved(scene_.GetEdgesAreCurved()); - create_edge_->SetFlowDirection(scene_.GetFlowDirection()); - - // Set source and declare that we created this edge - if ((create_edge_from_output_ = (!connector || connector->IsOutput()))) { - // Edge is being created from output - create_edge_output_item_ = attached_item; - } else { - // Edge is being created from input - create_edge_input_item_ = attached_item; - create_edge_input_ = attached_item->GetInputFromInputConnector(connector); - } - create_edge_already_exists_ = false; - - // Add edge to scene - scene_.addItem(create_edge_); - - // Position edge to mouse cursor - PositionNewEdge(event->pos()); - } - return; + if (event->modifiers() & Qt::ControlModifier) { + create_edge_output_item_ = dynamic_cast(item); + if (create_edge_output_item_ && !create_edge_output_item_->IsOutputItem()) { + create_edge_output_item_ = nullptr; } } + + if (!create_edge_output_item_) { + // Determine if user clicked on a connector + if (NodeViewItemConnector *connector = dynamic_cast(item)) { + NodeViewItem *attached = static_cast(connector->parentItem()); + + if (connector->IsOutput()) { + create_edge_output_item_ = attached; + } else { + create_edge_input_item_ = attached; + + if (!create_edge_input_item_->edges().isEmpty()) { + // Drag existing edge instead + create_edge_ = create_edge_input_item_->edges().first(); + create_edge_input_item_ = nullptr; + create_edge_output_item_ = create_edge_->from_item(); + create_edge_already_exists_ = true; + } else { + create_edge_from_output_ = false; + create_edge_input_ = create_edge_input_item_->GetInput(); + } + } + } + } + + if ((create_edge_output_item_ || create_edge_input_item_) && !create_edge_already_exists_) { + // Create a new edge from this output + create_edge_ = new NodeViewEdge(); + create_edge_->SetCurved(scene_.GetEdgesAreCurved()); + + // Add edge to scene + scene_.addItem(create_edge_); + + // Position edge to mouse cursor + PositionNewEdge(event->pos()); + return; + } } // Handle selections with the right mouse button @@ -490,12 +498,12 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) if (create_edge_output_item_ && create_edge_input_item_) { // Clear highlight if we set one - create_edge_input_item_->SetHighlightedIndex(-1); + create_edge_input_item_->SetHighlighted(false); // Collapse if we expanded it - if (create_edge_dst_temp_expanded_) { - create_edge_input_item_->SetExpanded(false); - create_edge_input_item_->setZValue(0); + if (create_edge_expand_item_) { + create_edge_expand_item_->SetExpanded(false); + create_edge_expand_item_->setZValue(0); } NodeInput &creating_input = create_edge_input_; @@ -504,17 +512,20 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) if (!reconnected_to_itself) { Node *creating_output = create_edge_output_item_->GetNode(); + if (NodeGroup *output_group = dynamic_cast(creating_output)) { + creating_output = output_group->GetOutputPassthrough(); + } + + if (NodeGroup *input_group = dynamic_cast(creating_input.node())) { + creating_input = input_group->GetInputPassthroughs().value(creating_input.input()); + } + if (creating_input.IsConnected()) { Node::OutputConnection existing_edge_to_remove = {creating_input.GetConnectedOutput(), creating_input}; command->add_child(new NodeEdgeRemoveCommand(existing_edge_to_remove.first, existing_edge_to_remove.second)); } command->add_child(new NodeEdgeAddCommand(creating_output, creating_input)); - - // If the output is not in the input's context, add it now - if (!create_edge_input_item_->GetContext()->ContextContainsNode(creating_output)) { - command->add_child(new NodeSetPositionCommand(creating_output, create_edge_input_item_->GetContext(), scene_.context_map().value(create_edge_input_item_->GetContext())->MapScenePosToNodePosInContext(create_edge_output_item_->scenePos()))); - } } creating_input.Reset(); @@ -686,9 +697,7 @@ void NodeView::ShowContextMenu(const QPoint &pos) // Label node action QAction* label_action = m.addAction(tr("Label")); - connect(label_action, &QAction::triggered, this, [this](){ - Core::instance()->LabelNodes(selected_nodes_); - }); + connect(label_action, &QAction::triggered, this, &NodeView::LabelSelectedNodes); // Grouping if (selected.size() == 1 && dynamic_cast(selected.first()->GetNode())) { @@ -702,13 +711,19 @@ void NodeView::ShowContextMenu(const QPoint &pos) // Color menu MenuShared::instance()->AddColorCodingMenu(&m); - ViewerOutput* viewer = dynamic_cast(selected.first()->GetNode()); - if (viewer) { + // Show in Viewer option for nodes based on Viewer + if (ViewerOutput* viewer = dynamic_cast(selected.first()->GetNode())) { m.addSeparator(); QAction* open_in_viewer_action = m.addAction(tr("Open in Viewer")); connect(open_in_viewer_action, &QAction::triggered, this, &NodeView::OpenSelectedNodeInViewer); } + m.addSeparator(); + + // Properties + QAction *properties_action = m.addAction(tr("P&roperties")); + connect(properties_action, &QAction::triggered, this, &NodeView::ShowNodeProperties); + } else { QAction* curved_action = m.addAction(tr("Smooth Edges")); @@ -988,6 +1003,13 @@ void NodeView::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNode }*/ } +void NodeView::changeEvent(QEvent *e) +{ + // Add translation code + + super::changeEvent(e); +} + void NodeView::ZoomFromKeyboard(double multiplier) { QPoint cursor_pos = mapFromGlobal(QCursor::pos()); @@ -1029,10 +1051,11 @@ void NodeView::PositionNewEdge(const QPoint &pos) item_at_cursor = nullptr; } - // Filter out connecting to a node that connects to us + // Filter out connecting to a node that connects to us or an item of the same type if (item_at_cursor && ((create_edge_from_output_ && item_at_cursor->GetNode()->OutputsTo(source_item->GetNode(), true)) - || (!create_edge_from_output_ && item_at_cursor->GetNode()->InputsFrom(source_item->GetNode(), true)))) { + || (!create_edge_from_output_ && item_at_cursor->GetNode()->InputsFrom(source_item->GetNode(), true)) + || (create_edge_from_output_ == item_at_cursor->IsOutputItem()))) { item_at_cursor = nullptr; } @@ -1040,46 +1063,32 @@ void NodeView::PositionNewEdge(const QPoint &pos) if (item_at_cursor != opposing_item) { // If we had a destination active, disconnect from it since the item has changed if (opposing_item) { - opposing_item->SetHighlightedIndex(-1); + opposing_item->SetHighlighted(false); + opposing_item = nullptr; + } - if (create_edge_dst_temp_expanded_) { - // We expanded this item, so we can un-expand it - opposing_item->SetExpanded(false); - opposing_item->setZValue(0); + // Clear cached input + if (create_edge_from_output_) { + if (create_edge_input_.IsValid()) { + create_edge_input_.Reset(); } } - // Set destination + // If this is an input and we're opposing_item = item_at_cursor; - // If our destination is an item, ensure it's expanded if (opposing_item) { - if (create_edge_from_output_ && (create_edge_dst_temp_expanded_ = (!create_edge_input_item_->IsExpanded()))) { - create_edge_input_item_->SetExpanded(true, true); - create_edge_input_item_->setZValue(100); // Ensure item is in front + opposing_item->SetHighlighted(true); + if (!opposing_item->IsOutputItem()) { + create_edge_input_ = opposing_item->GetInput(); } } } - // If we have a destination, highlight the appropriate input - if (create_edge_from_output_) { - int highlight_index = -1; - if (create_edge_input_item_) { - highlight_index = create_edge_input_item_->GetIndexAt(scene_pt); - create_edge_input_item_->SetHighlightedIndex(highlight_index); - } - - if (highlight_index >= 0) { - create_edge_input_ = create_edge_input_item_->GetInputAtIndex(highlight_index); - } else { - create_edge_input_.Reset(); - } - } - QPointF output_point = create_edge_output_item_ ? create_edge_output_item_->GetOutputPoint() : scene_pt; - QPointF input_point = create_edge_input_.IsValid() ? create_edge_input_item_->GetInputPoint(create_edge_input_.input(), create_edge_input_.element()) : scene_pt; + QPointF input_point = create_edge_input_.IsValid() ? create_edge_input_item_->GetInputPoint() : scene_pt; - create_edge_->SetPoints(output_point, input_point, create_edge_input_item_ && create_edge_input_item_->IsExpanded()); + create_edge_->SetPoints(output_point, input_point); create_edge_->SetConnected(create_edge_output_item_ && create_edge_input_.IsValid()); } @@ -1110,36 +1119,14 @@ void NodeView::GroupNodes() // Add group to graph and context MultiUndoCommand *command = new MultiUndoCommand(); - command->add_child(new NodeAddCommand(context->parent(), group)); - command->add_child(new NodeSetPositionCommand(group, context, avg_pos)); - // Add nodes to group + Node *output_passthrough = nullptr; QVector nodes_to_group = selected_nodes_; DeselectAll(); foreach (Node *n, nodes_to_group) { - for (auto it=n->input_connections().cbegin(); it!=n->input_connections().cend(); it++) { - Node *output = it->second; - const NodeInput &input = it->first; - - if (!nodes_to_group.contains(output)) { - command->add_child(new NodeEdgeRemoveCommand(output, input)); - command->add_child(new NodeEdgeAddCommand(output, NodeInput(group, input.input(), input.element()))); - } - } - - for (auto it=n->output_connections().cbegin(); it!=n->output_connections().cend(); it++) { - Node *output = it->first; - const NodeInput &input = it->second; - - if (!nodes_to_group.contains(input.node())) { - command->add_child(new NodeEdgeRemoveCommand(output, input)); - command->add_child(new NodeEdgeAddCommand(group, input)); - } - } - command->add_child(new NodeRemovePositionFromContextCommand(n, context)); command->add_child(new NodeAddToGroupCommand(n, group)); - command->add_child(new NodeSetPositionCommand(n, group, scene_.context_map().value(context)->GetItemFromMap(n)->GetNodePosition())); + command->add_child(new NodeSetPositionCommand(n, group, context->GetNodePositionDataInContext(n))); for (auto it=n->inputs().cbegin(); it!=n->inputs().cend(); it++) { NodeInput input(n, *it, -1); @@ -1148,8 +1135,25 @@ void NodeView::GroupNodes() command->add_child(new NodeGroupAddInputPassthrough(group, input)); } } + + if (!output_passthrough) { + // Default to the first node we find that doesn't output to a node inside the group + foreach (Node *potential_in, nodes_to_group) { + if (potential_in != n && !n->OutputsTo(potential_in, false)) { + output_passthrough = n; + break; + } + } + } } + // Set output passthrough + command->add_child(new NodeGroupSetOutputPassthrough(group, output_passthrough)); + + // Add group to graph + command->add_child(new NodeAddCommand(context->parent(), group)); + command->add_child(new NodeSetPositionCommand(group, context, avg_pos)); + // Do command command->redo_now(); @@ -1166,7 +1170,55 @@ void NodeView::GroupNodes() void NodeView::UngroupNodes() { - //NodeGroup *group = static_cast(selected_nodes_.first()); + NodeViewItem *group_item = nullptr; + QVector items = scene_.GetSelectedItems(); + if (items.isEmpty()) { + return; + } + + NodeGroup *group; + foreach (NodeViewItem *i, items) { + if ((group = dynamic_cast(i->GetNode()))) { + group_item = i; + break; + } + } + + if (!group_item) { + return; + } + + MultiUndoCommand *command = new MultiUndoCommand(); + + Node *context = group_item->GetContext(); + + command->add_child(new NodeRemovePositionFromContextCommand(group, context)); + command->add_child(new NodeRemoveAndDisconnectCommand(group)); + + foreach (Node *n, group->GetNodes()) { + command->add_child(new NodeRemovePositionFromContextCommand(n, group)); + command->add_child(new NodeRemoveFromGroupCommand(n, group)); + command->add_child(new NodeSetPositionCommand(n, context, group->GetNodePositionDataInContext(n))); + } + + Core::instance()->undo_stack()->push(command); +} + +void NodeView::ShowNodeProperties() +{ + Node *first_node = selected_nodes_.first(); + + if (NodeGroup *group = dynamic_cast(first_node)) { + NodeGroupDialog ngd(group, this); + ngd.exec(); + } else { + LabelSelectedNodes(); + } +} + +void NodeView::LabelSelectedNodes() +{ + Core::instance()->LabelNodes(selected_nodes_); } void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 5f7c802bb..4b3a0417f 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -119,6 +119,8 @@ protected: virtual void CopyNodesToClipboardInternal(QXmlStreamWriter *writer, const QVector &nodes, void* userdata) override; virtual void PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, void* userdata) override; + virtual void changeEvent(QEvent *e) override; + private: void AttachItemsToCursor(const QVector &items); @@ -162,8 +164,8 @@ private: NodeViewEdge* create_edge_; NodeViewItem* create_edge_output_item_; NodeViewItem* create_edge_input_item_; + NodeViewItem *create_edge_expand_item_; NodeInput create_edge_input_; - bool create_edge_dst_temp_expanded_; bool create_edge_already_exists_; bool create_edge_from_output_; @@ -181,8 +183,6 @@ private: double scale_; - bool first_show_; - static const double kMinimumScale; private slots: @@ -225,6 +225,10 @@ private slots: void UngroupNodes(); + void ShowNodeProperties(); + + void LabelSelectedNodes(); + }; } diff --git a/app/widget/nodeview/nodeviewcommon.h b/app/widget/nodeview/nodeviewcommon.h index d0541fba4..f8ffa4583 100644 --- a/app/widget/nodeview/nodeviewcommon.h +++ b/app/widget/nodeview/nodeviewcommon.h @@ -30,6 +30,7 @@ namespace olive { class NodeViewCommon { public: enum FlowDirection { + kInvalidDirection = -1, kTopToBottom, kBottomToTop, kLeftToRight, diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index c2c516c60..59c640634 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -53,29 +53,16 @@ void NodeViewContext::AddChild(Node *node) NodeViewItem *item = new NodeViewItem(node, context_, this); item->SetFlowDirection(flow_dir_); - connect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); - connect(node, &Node::InputDisconnected, this, &NodeViewContext::ChildInputDisconnected); + AddNodeInternal(node, item); - item_map_.insert(node, item); - - if (node == context_) { - item->SetLabelAsOutput(true); - } - - for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) { - if (!it->second.IsHidden()) { - if (NodeViewItem *other_item = item_map_.value(it->second.node())) { - AddEdgeInternal(node, it->second, item, other_item); - } + if (NodeGroup *group = dynamic_cast(node)) { + foreach (Node *n, group->GetNodes()) { + // Use this item as the representative for all of these nodes too + AddNodeInternal(n, item); } - } - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - if (!it->first.IsHidden()) { - if (NodeViewItem *other_item = item_map_.value(it->second)) { - AddEdgeInternal(it->second, it->first, other_item, item); - } - } + connect(group, &NodeGroup::NodeAddedToGroup, this, &NodeViewContext::GroupAddedNode); + connect(group, &NodeGroup::NodeRemovedFromGroup, this, &NodeViewContext::GroupRemovedNode); } UpdateRect(); @@ -95,12 +82,28 @@ void NodeViewContext::RemoveChild(Node *node) // Delete edges first because the edge destructor will try to reference item (maybe that should // be changed...) - QVector edges_to_remove = item->edges(); + QVector edges_to_remove = item->GetAllEdgesRecursively(); foreach (NodeViewEdge *edge, edges_to_remove) { - ChildInputDisconnected(edge->output(), edge->input()); + if (node == item->GetNode() || edge->output() == node || edge->input().node() == node) { + ChildInputDisconnected(edge->output(), edge->input()); + } } - delete item; + // Check if this item is specifically for this node and the node is a group. If so, remove it for + // all other entries in the map. + if (item->GetNode() == node) { + if (dynamic_cast(item->GetNode())) { + for (auto it=item_map_.begin(); it!=item_map_.end(); ) { + if (it.value() == item) { + it = item_map_.erase(it); + } else { + it++; + } + } + } + + delete item; + } } void NodeViewContext::ChildInputConnected(Node *output, const NodeInput &input) @@ -108,7 +111,7 @@ void NodeViewContext::ChildInputConnected(Node *output, const NodeInput &input) // Add edge if (!input.IsHidden()) { if (NodeViewItem* output_item = item_map_.value(output)) { - AddEdgeInternal(output, input, output_item, item_map_.value(input.node())); + AddEdgeInternal(output, input, output_item, item_map_.value(input.node())->GetItemForInput(input)); } } } @@ -117,8 +120,9 @@ bool NodeViewContext::ChildInputDisconnected(Node *output, const NodeInput &inpu { // Remove edge for (int i=0; ioutput() == output && edges_.at(i)->input() == input) { - delete edges_.at(i); + NodeViewEdge *e = edges_.at(i); + if (e->output() == output && e->input() == input) { + delete e; edges_.removeAt(i); return true; } @@ -154,10 +158,6 @@ void NodeViewContext::SetFlowDirection(NodeViewCommon::FlowDirection dir) foreach (NodeViewItem *item, item_map_) { item->SetFlowDirection(dir); } - - foreach (NodeViewEdge *edge, edges_) { - edge->SetFlowDirection(dir); - } } void NodeViewContext::SetCurvedEdges(bool e) @@ -201,7 +201,9 @@ QVector NodeViewContext::GetSelectedItems() const for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) { if (it.value()->isSelected()) { - items.append(it.value()); + if (!items.contains(it.value())) { + items.append(it.value()); + } } } @@ -269,16 +271,62 @@ void NodeViewContext::mousePressEvent(QGraphicsSceneMouseEvent *event) super::mousePressEvent(event); } -NodeViewEdge* NodeViewContext::AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to) +void NodeViewContext::AddNodeInternal(Node *node, NodeViewItem *item) { + connect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); + connect(node, &Node::InputDisconnected, this, &NodeViewContext::ChildInputDisconnected); + + item_map_.insert(node, item); + + if (node == context_) { + item->SetLabelAsOutput(true); + } + + for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) { + if (!it->second.IsHidden()) { + if (NodeViewItem *other_item = item_map_.value(it->second.node())) { + AddEdgeInternal(node, it->second, item, other_item->GetItemForInput(it->second)); + } + } + } + + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + if (!it->first.IsHidden()) { + if (NodeViewItem *other_item = item_map_.value(it->second)) { + AddEdgeInternal(it->second, it->first, other_item, item->GetItemForInput(it->first)); + } + } + } +} + +void NodeViewContext::AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to) +{ + if (from == to) { + return; + } + NodeViewEdge* edge_ui = new NodeViewEdge(output, input, from, to, this); - edge_ui->SetFlowDirection(flow_dir_); + edge_ui->Adjust(); edge_ui->SetCurved(curved_edges_); edges_.append(edge_ui); +} - return edge_ui; +void NodeViewContext::GroupAddedNode(Node *node) +{ + NodeGroup *group = static_cast(sender()); + + AddNodeInternal(node, item_map_.value(group)); +} + +void NodeViewContext::GroupRemovedNode(Node *node) +{ + NodeGroup *group = static_cast(sender()); + + if (item_map_.value(node) == item_map_.value(group)) { + item_map_.remove(node); + } } } diff --git a/app/widget/nodeview/nodeviewcontext.h b/app/widget/nodeview/nodeviewcontext.h index 7cdc638a7..6d191d916 100644 --- a/app/widget/nodeview/nodeviewcontext.h +++ b/app/widget/nodeview/nodeviewcontext.h @@ -60,7 +60,9 @@ protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; private: - NodeViewEdge *AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to); + void AddNodeInternal(Node *node, NodeViewItem *item); + + void AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to); Node *context_; @@ -76,6 +78,11 @@ private: QVector edges_; +private slots: + void GroupAddedNode(Node *node); + + void GroupRemovedNode(Node *node); + }; } diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index 794c98c52..0cf408ea1 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -70,12 +70,40 @@ NodeViewEdge::~NodeViewEdge() } } +void NodeViewEdge::set_from_item(NodeViewItem *i) +{ + if (from_item_) { + from_item_->RemoveEdge(this); + } + + from_item_ = i; + + if (from_item_) { + from_item_->AddEdge(this); + } + + Adjust(); +} + +void NodeViewEdge::set_to_item(NodeViewItem *i) +{ + if (to_item_) { + to_item_->RemoveEdge(this); + } + + to_item_ = i; + + if (to_item_) { + to_item_->AddEdge(this); + } + + Adjust(); +} + void NodeViewEdge::Adjust() { // Draw a line between the two - SetPoints(from_item()->GetOutputPoint(), - to_item()->GetInputPoint(input_.input(), input_.element()), - to_item()->IsExpanded()); + SetPoints(from_item()->GetOutputPoint(), to_item()->GetInputPoint()); } void NodeViewEdge::SetConnected(bool c) @@ -92,24 +120,14 @@ void NodeViewEdge::SetHighlighted(bool e) update(); } -void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end, bool input_is_expanded) +void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end) { cached_start_ = start; cached_end_ = end; - cached_input_is_expanded_ = input_is_expanded; UpdateCurve(); } -void NodeViewEdge::SetFlowDirection(NodeViewCommon::FlowDirection dir) -{ - flow_dir_ = dir; - - if (from_item_ && to_item_) { - Adjust(); - } -} - void NodeViewEdge::SetCurved(bool e) { curved_ = e; @@ -146,7 +164,6 @@ void NodeViewEdge::Init() { connected_ = false; highlighted_ = false; - flow_dir_ = NodeViewCommon::kLeftToRight; curved_ = true; setFlag(QGraphicsItem::ItemIsSelectable); @@ -175,13 +192,26 @@ void NodeViewEdge::UpdateCurve() QPointF cp1, cp2; - if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) { + NodeViewCommon::FlowDirection from_flow = from_item_ ? from_item_->GetFlowDirection() : NodeViewCommon::kInvalidDirection; + NodeViewCommon::FlowDirection to_flow = to_item_ ? to_item_->GetFlowDirection() : NodeViewCommon::kInvalidDirection; + + if (from_flow == NodeViewCommon::kInvalidDirection && to_flow == NodeViewCommon::kInvalidDirection) { + // This is a technically unsupported scenario, but to avoid issues, we'll use a fallback + from_flow = NodeViewCommon::kLeftToRight; + to_flow = NodeViewCommon::kLeftToRight; + } else if (from_flow == NodeViewCommon::kInvalidDirection) { + from_flow = to_flow; + } else if (to_flow == NodeViewCommon::kInvalidDirection) { + to_flow = from_flow; + } + + if (NodeViewCommon::GetFlowOrientation(from_flow) == Qt::Horizontal) { cp1 = QPointF(half_x, start.y()); } else { cp1 = QPointF(start.x(), half_y); } - if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal || cached_input_is_expanded_) { + if (NodeViewCommon::GetFlowOrientation(to_flow) == Qt::Horizontal) { cp2 = QPointF(half_x, end.y()); } else { cp2 = QPointF(end.x(), half_y); diff --git a/app/widget/nodeview/nodeviewedge.h b/app/widget/nodeview/nodeviewedge.h index 31a56664e..b149b3680 100644 --- a/app/widget/nodeview/nodeviewedge.h +++ b/app/widget/nodeview/nodeviewedge.h @@ -72,6 +72,10 @@ public: return to_item_; } + void set_from_item(NodeViewItem *i); + + void set_to_item(NodeViewItem *i); + void Adjust(); /** @@ -101,12 +105,7 @@ public: /** * @brief Set points to create curve from */ - void SetPoints(const QPointF& start, const QPointF& end, bool input_is_expanded); - - /** - * @brief Sets the direction nodes are flowing - */ - void SetFlowDirection(NodeViewCommon::FlowDirection dir); + void SetPoints(const QPointF& start, const QPointF& end); /** * @brief Set whether edges should be drawn as curved or as straight lines @@ -137,13 +136,10 @@ private: bool highlighted_; - NodeViewCommon::FlowDirection flow_dir_; - bool curved_; QPointF cached_start_; QPointF cached_end_; - bool cached_input_is_expanded_; }; diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 1a9b6c516..9357a8f0b 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -39,21 +39,17 @@ namespace olive { -NodeViewItem::NodeViewItem(Node* n, Node *context, QGraphicsItem *parent) : +NodeViewItem::NodeViewItem(Node *node, const QString &input, int element, Node *context, QGraphicsItem *parent) : QGraphicsRectItem(parent), - node_(n), + node_(node), + input_(input), + element_(element), context_(context), expanded_(false), - hide_titlebar_(false), - highlighted_index_(-1), - flow_dir_(NodeViewCommon::kLeftToRight), + highlighted_(false), + flow_dir_(NodeViewCommon::kInvalidDirection), label_as_output_(false) { - // Set flags for this widget - setFlag(QGraphicsItem::ItemIsMovable); - setFlag(QGraphicsItem::ItemIsSelectable); - setFlag(QGraphicsItem::ItemSendsGeometryChanges); - // // We use font metrics to set all the UI measurements for DPI-awareness // @@ -61,32 +57,41 @@ NodeViewItem::NodeViewItem(Node* n, Node *context, QGraphicsItem *parent) : // Set border width node_border_width_ = DefaultItemBorder(); - int widget_width = DefaultItemWidth(); - int widget_height = DefaultItemHeight(); - - title_bar_rect_ = QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height); - setRect(title_bar_rect_); + // Set rect size to default + SetRectSize(); + // Create connector + input_connector_ = new NodeViewItemConnector(false, this); output_connector_ = new NodeViewItemConnector(true, this); - // Set up node - node_->Retranslate(); - - foreach (const QString& input, node_->inputs()) { - if (node_->IsInputConnectable(input) && !node_->IsInputHidden(input)) { - node_inputs_.append(input); - } - } - - UpdateInputConnectors(); - connect(node_, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); connect(node_, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); - if (context_) { - SetNodePosition(context_->GetNodePositionInContext(node_)); - SetExpanded(context_->IsNodeExpandedInContext(node_)); + if (IsOutputItem()) { + connect(node_, &Node::InputAdded, this, &NodeViewItem::RepopulateInputs); + connect(node_, &Node::InputRemoved, this, &NodeViewItem::RepopulateInputs); + RepopulateInputs(); + + // Set flags for this widget + setFlag(QGraphicsItem::ItemSendsGeometryChanges); + setFlag(QGraphicsItem::ItemIsMovable); + setFlag(QGraphicsItem::ItemIsSelectable); + + if (context_) { + SetNodePosition(context_->GetNodePositionInContext(node_)); + SetExpanded(context_->IsNodeExpandedInContext(node_)); + } + } else { + output_connector_->setVisible(false); } + + // This should be set during runtime, but just in case here's a default fallback + SetFlowDirection(NodeViewCommon::kLeftToRight); +} + +NodeViewItem::~NodeViewItem() +{ + Q_ASSERT(edges_.isEmpty()); } QPointF NodeViewItem::GetNodePosition() const @@ -101,6 +106,17 @@ void NodeViewItem::SetNodePosition(const QPointF &pos) UpdateNodePosition(); } +QVector NodeViewItem::GetAllEdgesRecursively() const +{ + QVector list = edges_; + + foreach (NodeViewItem *item, children_) { + list.append(item->GetAllEdgesRecursively()); + } + + return list; +} + int NodeViewItem::DefaultTextPadding() { return QFontMetrics(QFont()).height() / 4; @@ -139,6 +155,8 @@ QPointF NodeViewItem::NodeToScreenPoint(QPointF p, NodeViewCommon::FlowDirection // Swap X/Y and invert Y p = QPointF(p.y(), -p.x()); break; + case NodeViewCommon::kInvalidDirection: + break; } // Multiply by item sizes for this direction @@ -164,12 +182,14 @@ QPointF NodeViewItem::ScreenToNodePoint(QPointF p, NodeViewCommon::FlowDirection break; case NodeViewCommon::kTopToBottom: // Swap X/Y - p = QPointF(p.y(), p.x()); + p = QPointF(p.y(), p.x()); break; case NodeViewCommon::kBottomToTop: // Swap X/Y and invert Y p = QPointF(-p.y(), p.x()); break; + case NodeViewCommon::kInvalidDirection: + break; } return p; @@ -213,51 +233,79 @@ void NodeViewItem::RemoveEdge(NodeViewEdge *edge) edges_.removeOne(edge); } -int NodeViewItem::GetIndexAt(QPointF pt) const -{ - pt -= this->scenePos(); - - for (int i=0; iGetInputFlags(input_) & kInputFlagArray)) + || (expanded_ == e)) { return; } expanded_ = e; - hide_titlebar_ = hide_titlebar; if (context_) { context_->SetNodeExpandedInContext(node_, e); } - if (expanded_ && !node_inputs_.isEmpty()) { - // Create new rect - QRectF new_rect = title_bar_rect_; - - if (hide_titlebar_) { - new_rect.setHeight(new_rect.height() * node_inputs_.size()); - } else { - new_rect.setHeight(new_rect.height() * (node_inputs_.size() + 1)); - } - - setRect(new_rect); - } else { - setRect(title_bar_rect_); + if (IsOutputItem()) { + // We don't have to check has_connectable_inputs_ here because we did it at the top + input_connector_->setVisible(!expanded_); } - UpdateInputConnectorFlowDirections(); + if (expanded_) { + if (IsOutputItem()) { + // Create items for each input of the node + int i = 1; + foreach (const QString &input, node_->inputs()) { + if (IsInputValid(input)) { + NodeViewItem *item = new NodeViewItem(node_, input, -1, context_, this); + item->setPos(QPointF(0, i * item->rect().height())); + children_.append(item); + i++; + } + } - UpdateConnectorPositions(); + QVector edges = edges_; + for (auto it=edges.cbegin(); it!=edges.cend(); it++) { + if ((*it)->to_item() == this) { + (*it)->set_to_item(GetItemForInput((*it)->input())); + } + } + + SetRectSize(i); + } else { + // Create items for each element of the input array + int arr_sz = node_->InputArraySize(input_); + children_.resize(arr_sz); + for (int i=0; isetPos(pos() + QPointF(0, (i+1) * item->rect().height())); + children_[i] = item; + } + + QVector edges = edges_; + for (auto it=edges.cbegin(); it!=edges.cend(); it++) { + if ((*it)->to_item() == this) { + (*it)->set_to_item(GetItemForInput((*it)->input())); + } + } + } + } else { + foreach (NodeViewItem *child, children_) { + QVector child_edges = child->edges(); + foreach (NodeViewEdge *edge, child_edges) { + edge->set_to_item(this); + } + delete child; + } + children_.clear(); + + SetRectSize(1); + } + + if (flow_dir_ == NodeViewCommon::kTopToBottom) { + UpdateOutputConnectorPosition(); + } ReadjustAllEdges(); @@ -277,35 +325,16 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti // has been slightly modified QPalette app_pal = Core::instance()->main_window()->palette(); - // Draw background rect if expanded - if (IsExpanded()) { - painter->setPen(Qt::NoPen); - painter->setBrush(app_pal.color(QPalette::Window)); - - painter->drawRect(rect()); - - painter->setPen(app_pal.color(QPalette::Text)); - - for (int i=0;ifillRect(input_rect, highlight_col); - } - - painter->drawText(input_rect, Qt::AlignCenter, node_->GetInputName(node_inputs_.at(i))); - } - } - // Draw the titlebar - if (!hide_titlebar_ && node_) { + if (IsOutputItem()) { + QRectF single_unit_rect = rect(); + single_unit_rect.setHeight(DefaultItemHeight()); + // Output item drawing code painter->setPen(Qt::black); - painter->setBrush(node_->brush(title_bar_rect_.top(), title_bar_rect_.bottom())); + painter->setBrush(node_->brush(single_unit_rect.top(), single_unit_rect.bottom())); - painter->drawRect(title_bar_rect_); + painter->drawRect(single_unit_rect); painter->setPen(app_pal.color(QPalette::Text)); @@ -320,40 +349,54 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti int icon_size = painter->fontMetrics().height()/2; - bool draw_arrow = !node_inputs_.isEmpty(); - if (node_label.isEmpty()) { // Draw shortname only - DrawNodeTitle(painter, node_shortname, title_bar_rect_, Qt::AlignVCenter, icon_size, draw_arrow); + DrawNodeTitle(painter, node_shortname, single_unit_rect, Qt::AlignVCenter, icon_size, has_connectable_inputs_); } else { int text_pad = DefaultTextPadding()/2; - QRectF safe_label_bounds = title_bar_rect_.adjusted(text_pad, text_pad, -text_pad, -text_pad); + QRectF safe_label_bounds = single_unit_rect.adjusted(text_pad, text_pad, -text_pad, -text_pad); QFont f; qreal font_sz = f.pointSizeF(); f.setPointSizeF(font_sz * 0.8); painter->setFont(f); - DrawNodeTitle(painter, node_label, safe_label_bounds, Qt::AlignTop, icon_size, draw_arrow); + DrawNodeTitle(painter, node_label, safe_label_bounds, Qt::AlignTop, icon_size, has_connectable_inputs_); f.setPointSizeF(font_sz * 0.6); painter->setFont(f); DrawNodeTitle(painter, node_shortname, safe_label_bounds, Qt::AlignBottom, icon_size, false); } - } + // Draw final border + QPen border_pen; + border_pen.setWidth(node_border_width_); - // Draw final border - QPen border_pen; - border_pen.setWidth(node_border_width_); + if (option->state & QStyle::State_Selected) { + border_pen.setColor(app_pal.color(QPalette::Highlight)); + } else { + border_pen.setColor(Qt::black); + } - if (option->state & QStyle::State_Selected) { - border_pen.setColor(app_pal.color(QPalette::Highlight)); + painter->setPen(border_pen); + painter->setBrush(Qt::NoBrush); + + painter->drawRect(rect()); } else { - border_pen.setColor(Qt::black); + // Input item drawing code + painter->setPen(Qt::NoPen); + painter->setBrush(app_pal.color(QPalette::Window)); + + painter->drawRect(rect()); + + if (highlighted_) { + QColor highlight_col = app_pal.color(QPalette::Text); + highlight_col.setAlpha(64); + painter->setBrush(highlight_col); + painter->drawRect(rect()); + } + + painter->setPen(app_pal.color(QPalette::Text)); + + painter->drawText(rect(), Qt::AlignCenter, node_->GetInputName(input_)); } - - painter->setPen(border_pen); - painter->setBrush(Qt::NoBrush); - - painter->drawRect(rect()); } void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) @@ -399,11 +442,16 @@ QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, cons void NodeViewItem::ReadjustAllEdges() { - UpdateInputConnectorFlowDirections(); - UpdateInputConnectorPositions(); foreach (NodeViewEdge* edge, edges_) { + if (NodeViewItem *to_item = edge->to_item()) { + static_cast(to_item->parentItem())->UpdateFlowDirectionOfInputItem(to_item); + } + edge->Adjust(); } + foreach (NodeViewItem *child, children_) { + child->ReadjustAllEdges(); + } } void NodeViewItem::UpdateContextRect() @@ -420,19 +468,19 @@ void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& painter->setRenderHint(QPainter::SmoothPixmapTransform); // Draw right or down arrow based on expanded state - int icon_padding = title_bar_rect_.height() / 2 - icon_size / 2; + int icon_padding = DefaultItemHeight() / 2 - icon_size / 2; int icon_full_size = icon_size + icon_padding * 2; if (draw_arrow) { const QIcon& expand_icon = IsExpanded() ? icon::TriDown : icon::TriRight; int icon_size_scaled = icon_size * painter->transform().m11(); - painter->drawPixmap(QRect(title_bar_rect_.x() + icon_padding, - title_bar_rect_.y() + icon_padding, + painter->drawPixmap(QRect(this->rect().x() + icon_padding, + this->rect().y() + icon_padding, icon_size, icon_size), expand_icon.pixmap(QSize(icon_size_scaled, icon_size_scaled))); } // Calculate how much space we have for text - int item_width = title_bar_rect_.width(); + int item_width = this->rect().width(); int max_text_width = item_width - DefaultTextPadding() * 2 - icon_full_size; int label_width = QtUtils::QFontMetricsWidth(fm, text); @@ -467,28 +515,6 @@ void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& text); } -NodeViewEdge *NodeViewItem::GetEdgeFromInputIndex(int index) -{ - foreach (NodeViewEdge *edge, edges_) { - if (edge->input().input() == node_inputs_.at(index)) { - return edge; - } - } - - return nullptr; -} - -void NodeViewItem::SetHighlightedIndex(int index) -{ - if (highlighted_index_ == index) { - return; - } - - highlighted_index_ = index; - - update(); -} - void NodeViewItem::SetLabelAsOutput(bool e) { label_as_output_ = e; @@ -496,58 +522,9 @@ void NodeViewItem::SetLabelAsOutput(bool e) update(); } -NodeInput NodeViewItem::GetInputFromInputConnector(NodeViewItemConnector *connector) +QPointF NodeViewItem::GetInputPoint() const { - for (int i=0; i= int(input_connectors_.size())) { - return pos(); - } - - return input_connectors_[index]->scenePos(); + return input_connector_->scenePos(); } QPointF NodeViewItem::GetOutputPoint() const @@ -576,13 +553,21 @@ QPointF NodeViewItem::GetOutputPoint() const void NodeViewItem::SetFlowDirection(NodeViewCommon::FlowDirection dir) { - flow_dir_ = dir; + if (flow_dir_ != dir) { + flow_dir_ = dir; - UpdateInputConnectorFlowDirections(); - output_connector_->SetFlowDirection(dir); + input_connector_->SetFlowDirection(dir); + output_connector_->SetFlowDirection(dir); - UpdateConnectorPositions(); - UpdateNodePosition(); + UpdateInputConnectorPosition(); + UpdateOutputConnectorPosition(); + + if (IsOutputItem()) { + UpdateNodePosition(); + } + + ReadjustAllEdges(); + } } void NodeViewItem::UpdateNodePosition() @@ -590,26 +575,47 @@ void NodeViewItem::UpdateNodePosition() setPos(NodeToScreenPoint(cached_node_pos_, flow_dir_)); } -void NodeViewItem::UpdateInputConnectors() +void NodeViewItem::UpdateInputConnectorPosition() { - int old_sz = input_connectors_.size(); + QRectF output_rect = input_connector_->boundingRect(); - input_connectors_.resize(node_inputs_.size()); - for (size_t i=old_sz; i(false, this); + NodeViewCommon::FlowDirection using_flow_dir = flow_dir_; + + if (IsExpanded() && !NodeViewCommon::IsFlowHorizontal(flow_dir_)) { + if (edges_.isEmpty() || edges_.first()->from_item()->x() < this->x()) { + using_flow_dir = NodeViewCommon::kLeftToRight; + } else { + using_flow_dir = NodeViewCommon::kRightToLeft; + } + } + + // Input connector flow directions change conditionally + switch (using_flow_dir) { + case NodeViewCommon::kLeftToRight: + input_connector_->setPos(rect().left() - output_rect.width(), 0); + break; + case NodeViewCommon::kRightToLeft: + input_connector_->setPos(rect().right() + output_rect.width(), 0); + break; + case NodeViewCommon::kTopToBottom: + input_connector_->setPos(rect().center().x(), rect().top() - output_rect.height()); + break; + case NodeViewCommon::kBottomToTop: + input_connector_->setPos(rect().center().x(), rect().bottom() + output_rect.height()); + break; + case NodeViewCommon::kInvalidDirection: + break; } } -void NodeViewItem::UpdateConnectorPositions() +void NodeViewItem::UpdateOutputConnectorPosition() { - UpdateInputConnectorPositions(); - switch (flow_dir_) { case NodeViewCommon::kLeftToRight: - output_connector_->setPos(rect().right(), title_bar_rect_.center().y()); + output_connector_->setPos(rect().right(), 0); break; case NodeViewCommon::kRightToLeft: - output_connector_->setPos(rect().left(), title_bar_rect_.center().y()); + output_connector_->setPos(rect().left(), 0); break; case NodeViewCommon::kTopToBottom: output_connector_->setPos(rect().center().x(), rect().bottom()); @@ -617,57 +623,94 @@ void NodeViewItem::UpdateConnectorPositions() case NodeViewCommon::kBottomToTop: output_connector_->setPos(rect().center().x(), rect().top()); break; + case NodeViewCommon::kInvalidDirection: + break; } } -void NodeViewItem::UpdateInputConnectorPositions() +bool NodeViewItem::IsInputValid(const QString &input) { - QRectF output_rect = output_connector_->boundingRect(); - - // Input connector flow directions change conditionally - for (size_t i=0; isetPos(rect().left() - output_rect.width(), GetInputRect(i).center().y()); - break; - case NodeViewCommon::kRightToLeft: - input_connectors_[i]->setPos(rect().right() + output_rect.width(), GetInputRect(i).center().y()); - break; - case NodeViewCommon::kTopToBottom: - input_connectors_[i]->setPos(rect().center().x(), rect().top() - output_rect.height()); - break; - case NodeViewCommon::kBottomToTop: - input_connectors_[i]->setPos(rect().center().x(), rect().bottom() + output_rect.height()); - break; - } - } + return node_->IsInputConnectable(input) && !node_->IsInputHidden(input); } -void NodeViewItem::UpdateInputConnectorFlowDirections() +void NodeViewItem::SetRectSize(int height_units) { - for (size_t i=0; iSetFlowDirection(GetFlowDirectionForInput(i)); - } + // Set rect + int widget_width = DefaultItemWidth(); + int widget_height = DefaultItemHeight(); + + setRect(QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height * height_units)); } -NodeViewCommon::FlowDirection NodeViewItem::GetFlowDirectionForInput(int index) +void NodeViewItem::UpdateFlowDirectionOfInputItem(NodeViewItem *child) { - if (!expanded_ || NodeViewCommon::IsFlowHorizontal(flow_dir_)) { - return flow_dir_; - } else { - NodeViewEdge *edge = GetEdgeFromInputIndex(index); - - if (!edge || edge->from_item()->x() < this->x()) { - return NodeViewCommon::kLeftToRight; + if (!child->IsOutputItem()) { + if (NodeViewCommon::IsFlowVertical(flow_dir_)) { + if (!child->edges().isEmpty() && child->edges().first()->from_item()->scenePos().x() > child->scenePos().x()) { + child->SetFlowDirection(NodeViewCommon::kRightToLeft); + } else { + child->SetFlowDirection(NodeViewCommon::kLeftToRight); + } } else { - return NodeViewCommon::kRightToLeft; + child->SetFlowDirection(flow_dir_); } } } +void NodeViewItem::RepopulateInputs() +{ + has_connectable_inputs_ = false; + + foreach (const QString& input, node_->inputs()) { + if (IsInputValid(input)) { + has_connectable_inputs_ = true; + break; + } + } + + input_connector_->setVisible(has_connectable_inputs_); +} + void NodeViewItem::NodeAppearanceChanged() { update(); } +void NodeViewItem::SetHighlighted(bool e) +{ + highlighted_ = e; + update(); +} + +NodeViewItem *NodeViewItem::GetItemForInput(NodeInput input) +{ + if (NodeGroup *group = dynamic_cast(node_)) { + if (input.node() != group) { + // Translate input to group input + QString id = NodeGroup::GetGroupInputIDFromInput(input); + input.set_node(group); + input.set_input(id); + } + } + + if (IsExpanded()) { + if (input_.isEmpty()) { + // Look for the input in our children + foreach (NodeViewItem *i, children_) { + if (i->input_ == input.input()) { + return i; + } + } + } else { + // Look for element in our children + if (input.element() >= 0 && input.element() < children_.size()) { + return children_.at(input.element()); + } + } + } + + // Fallback to this object + return this; +} + } diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 60d1f3874..4b2c7236f 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -32,6 +32,7 @@ namespace olive { +class NodeViewItem; class NodeViewEdge; /** @@ -45,11 +46,19 @@ class NodeViewItem : public QObject, public QGraphicsRectItem { Q_OBJECT public: - NodeViewItem(Node* n, Node *context, QGraphicsItem* parent = nullptr); + NodeViewItem(Node *node, const QString &input, int element, Node *context, QGraphicsItem* parent = nullptr); + NodeViewItem(Node *node, Node *context, QGraphicsItem* parent = nullptr) : + NodeViewItem(node, QString(), -1, context, parent) + { + } + + virtual ~NodeViewItem() override; QPointF GetNodePosition() const; void SetNodePosition(const QPointF& pos); + QVector GetAllEdgesRecursively() const; + /** * @brief Get currently attached node */ @@ -58,6 +67,11 @@ public: return node_; } + NodeInput GetInput() const + { + return NodeInput(node_, input_, element_); + } + Node *GetContext() const { return context_; @@ -82,11 +96,7 @@ public: void SetExpanded(bool e, bool hide_titlebar = false); void ToggleExpanded(); - /** - * @brief Returns GLOBAL point that edges should connect to for any NodeParam member of this object - */ - QPointF GetInputPoint(const QString& input, int element) const; - + QPointF GetInputPoint() const; QPointF GetOutputPoint() const; /** @@ -94,6 +104,11 @@ public: */ void SetFlowDirection(NodeViewCommon::FlowDirection dir); + NodeViewCommon::FlowDirection GetFlowDirection() const + { + return flow_dir_; + } + static int DefaultTextPadding(); static int DefaultItemHeight(); @@ -113,19 +128,20 @@ public: void AddEdge(NodeViewEdge* edge); void RemoveEdge(NodeViewEdge* edge); - int GetIndexAt(QPointF pt) const; - - NodeInput GetInputAtIndex(int index) const - { - return NodeInput(node_, node_inputs_.at(index)); - } - - void SetHighlightedIndex(int index); - void SetLabelAsOutput(bool e); - NodeInput GetInputFromInputConnector(NodeViewItemConnector *connector); - NodeViewEdge *GetEdgeFromInputConnector(NodeViewItemConnector *connector); + void SetHighlighted(bool e); + + NodeViewItem *GetItemForInput(NodeInput input); + + bool IsOutputItem() const + { + return input_.isEmpty(); + } + + void ReadjustAllEdges(); + + void UpdateFlowDirectionOfInputItem(NodeViewItem *child); protected: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; @@ -138,49 +154,35 @@ protected: virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; private: - void ReadjustAllEdges(); - void UpdateContextRect(); void DrawNodeTitle(QPainter *painter, QString text, const QRectF &rect, Qt::Alignment vertical_align, int icon_size, bool draw_arrow); - NodeViewEdge *GetEdgeFromInputIndex(int index); - - /** - * @brief Returns local rect of a NodeInput in array node_inputs_[index] - */ - QRectF GetInputRect(int index) const; - /** * @brief Internal update function when logical position changes */ void UpdateNodePosition(); - void UpdateInputConnectors(); + void UpdateInputConnectorPosition(); + void UpdateOutputConnectorPosition(); - void UpdateConnectorPositions(); + bool IsInputValid(const QString &input); - void UpdateInputConnectorPositions(); - void UpdateInputConnectorFlowDirections(); - - NodeViewCommon::FlowDirection GetFlowDirectionForInput(int index); + void SetRectSize(int height_units = 1); /** * @brief Reference to attached Node */ - Node* node_; + Node *node_; + QString input_; + int element_; Node *context_; /** * @brief Cached list of node inputs */ - QVector node_inputs_; - - /** - * @brief Rectangle of the Node's title bar (equal to rect() when collapsed) - */ - QRectF title_bar_rect_; + QVector children_; /// Sizing variables to use when drawing int node_border_width_; @@ -190,9 +192,7 @@ private: */ bool expanded_; - bool hide_titlebar_; - - int highlighted_index_; + bool highlighted_; NodeViewCommon::FlowDirection flow_dir_; @@ -200,14 +200,18 @@ private: QPointF cached_node_pos_; - std::vector > input_connectors_; + NodeViewItemConnector *input_connector_; NodeViewItemConnector *output_connector_; + bool has_connectable_inputs_; + bool label_as_output_; private slots: void NodeAppearanceChanged(); + void RepopulateInputs(); + }; } diff --git a/app/widget/nodeview/nodeviewitemconnector.cpp b/app/widget/nodeview/nodeviewitemconnector.cpp index 2ab919d77..032b9903f 100644 --- a/app/widget/nodeview/nodeviewitemconnector.cpp +++ b/app/widget/nodeview/nodeviewitemconnector.cpp @@ -74,6 +74,8 @@ void NodeViewItemConnector::SetFlowDirection(NodeViewCommon::FlowDirection dir) p[1] = QPointF(-triangle_sz_half, 0); p[2] = QPointF(0, triangle_sz_half); break; + case NodeViewCommon::kInvalidDirection: + break; } setPolygon(p); diff --git a/app/widget/timelinewidget/undo/timelineundogeneral.cpp b/app/widget/timelinewidget/undo/timelineundogeneral.cpp index 9c8bbae14..a64d1e342 100644 --- a/app/widget/timelinewidget/undo/timelineundogeneral.cpp +++ b/app/widget/timelinewidget/undo/timelineundogeneral.cpp @@ -153,7 +153,7 @@ void TimelineAddTrackCommand::redo() if (create_pos_command) { position_command_->add_child(new NodeSetPositionCommand(track_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, -position_factor))); position_command_->add_child(new NodeSetPositionCommand(merge_, sequence, sequence->GetNodePositionInContext(sequence))); - position_command_->add_child(new NodeSetPositionCommand(merge_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, position_factor * timeline_->GetTrackCount()), true)); + position_command_->add_child(new NodeSetPositionAndDependenciesRecursivelyCommand(merge_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, position_factor * timeline_->GetTrackCount()))); } } else if (direct_.IsValid() && !direct_.IsConnected()) { // If no merge, we have a direct connection, and nothing else is connected, connect this diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index db245f005..ddc0e6c0c 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -750,7 +750,7 @@ void MainWindow::SetDefaultLayout() node_panel_->show(); tabifyDockWidget(param_panel_, node_panel_); - footage_viewer_panel_->raise(); + param_panel_->raise(); curve_panel_->hide(); curve_panel_->setFloating(true); From 2473bccec3d8c18ebadfb87ab528a1a3cf5c506f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 1 Dec 2021 12:14:26 -0800 Subject: [PATCH 32/94] implemented arrays and reimplemented expanding --- app/widget/nodeview/nodeview.cpp | 84 ++++++++- app/widget/nodeview/nodeview.h | 10 +- app/widget/nodeview/nodeviewitem.cpp | 262 +++++++++++++++++---------- app/widget/nodeview/nodeviewitem.h | 14 +- 4 files changed, 266 insertions(+), 104 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 2dab4cb29..5f87c40b4 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -49,7 +49,6 @@ NodeView::NodeView(QWidget *parent) : create_edge_(nullptr), create_edge_output_item_(nullptr), create_edge_input_item_(nullptr), - create_edge_expand_item_(nullptr), paste_command_(nullptr), scale_(1.0) { @@ -500,11 +499,11 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) // Clear highlight if we set one create_edge_input_item_->SetHighlighted(false); - // Collapse if we expanded it - if (create_edge_expand_item_) { - create_edge_expand_item_->SetExpanded(false); - create_edge_expand_item_->setZValue(0); + // Collapse any items we expanded + for (auto it=create_edge_expanded_items_.crbegin(); it!=create_edge_expanded_items_.crend(); it++) { + CollapseItem(*it); } + create_edge_expanded_items_.clear(); NodeInput &creating_input = create_edge_input_; if (creating_input.IsValid()) { @@ -526,6 +525,13 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } command->add_child(new NodeEdgeAddCommand(creating_output, creating_input)); + + // If the output is not in the input's context, add it now. We check the item rather than + // the node itself, because sometimes a node may not be in the context but another node + // representing it will be (e.g. groups) + if (!scene_.context_map().value(create_edge_input_item_->GetContext())->GetItemFromMap(creating_output)) { + command->add_child(new NodeSetPositionCommand(creating_output, create_edge_input_item_->GetContext(), scene_.context_map().value(create_edge_input_item_->GetContext())->MapScenePosToNodePosInContext(create_edge_output_item_->scenePos()))); + } } creating_input.Reset(); @@ -624,6 +630,18 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) super::mouseReleaseEvent(event); } +void NodeView::mouseDoubleClickEvent(QMouseEvent *event) +{ + super::mouseDoubleClickEvent(event); + + if (!(event->modifiers() & Qt::ControlModifier)) { + NodeViewItem *item_at_cursor = dynamic_cast(itemAt(event->pos())); + if (item_at_cursor) { + item_at_cursor->ToggleExpanded(); + } + } +} + void NodeView::resizeEvent(QResizeEvent *event) { super::resizeEvent(event); @@ -1022,6 +1040,13 @@ void NodeView::ZoomFromKeyboard(double multiplier) ZoomIntoCursorPosition(nullptr, multiplier, cursor_pos); } +void NodeView::ClearCreateEdgeInputIfNecessary() +{ + if (create_edge_from_output_ && create_edge_input_.IsValid()) { + create_edge_input_.Reset(); + } +} + QPointF NodeView::GetEstimatedPositionForContext(NodeViewItem *item, Node *context) const { return item->GetNodePosition() - context_offsets_.value(context); @@ -1051,6 +1076,37 @@ void NodeView::PositionNewEdge(const QPoint &pos) item_at_cursor = nullptr; } + // Collapse any items that the cursor is no longer inside + int i=create_edge_expanded_items_.size() - 1; + for ( ; i>=0; i--) { + NodeViewItem* nvi = create_edge_expanded_items_.at(i); + QPointF local_pt = nvi->mapFromScene(scene_pt); + + if (nvi->contains(local_pt) || (!nvi->IsOutputItem() && nvi->parentItem()->contains(nvi->parentItem()->mapFromScene(scene_pt)) && local_pt.y() > nvi->rect().bottom())) { + break; + } else { + // Collapsing an item will destroy its children, so if the cursor item happens to be a child + // of the item we're about to collapse, set it to null + if (item_at_cursor && item_at_cursor->parentItem() == nvi) { + item_at_cursor = nullptr; + } + + if (opposing_item && opposing_item->parentItem() == nvi) { + opposing_item = nullptr; + ClearCreateEdgeInputIfNecessary(); + } + + CollapseItem(nvi); + } + } + create_edge_expanded_items_.resize(i + 1); + + // Expand item if possible + if (item_at_cursor && item_at_cursor->CanBeExpanded() && !item_at_cursor->IsExpanded()) { + ExpandItem(item_at_cursor); + create_edge_expanded_items_.append(item_at_cursor); + } + // Filter out connecting to a node that connects to us or an item of the same type if (item_at_cursor && ((create_edge_from_output_ && item_at_cursor->GetNode()->OutputsTo(source_item->GetNode(), true)) @@ -1068,11 +1124,7 @@ void NodeView::PositionNewEdge(const QPoint &pos) } // Clear cached input - if (create_edge_from_output_) { - if (create_edge_input_.IsValid()) { - create_edge_input_.Reset(); - } - } + ClearCreateEdgeInputIfNecessary(); // If this is an input and we're opposing_item = item_at_cursor; @@ -1293,4 +1345,16 @@ bool NodeView::IsItemAttachedToCursor(NodeViewItem *item) const return false; } +void NodeView::ExpandItem(NodeViewItem *item) +{ + item->SetExpanded(true); + item->setZValue(100); +} + +void NodeView::CollapseItem(NodeViewItem *item) +{ + item->SetExpanded(false); + item->setZValue(0); +} + } diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 4b3a0417f..8e34bba99 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -107,6 +107,7 @@ protected: virtual void mousePressEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override; virtual void mouseReleaseEvent(QMouseEvent* event) override; + virtual void mouseDoubleClickEvent(QMouseEvent* event) override; virtual void resizeEvent(QResizeEvent *event) override; @@ -135,6 +136,8 @@ private: void ZoomFromKeyboard(double multiplier); + void ClearCreateEdgeInputIfNecessary(); + QPointF GetEstimatedPositionForContext(NodeViewItem *item, Node *context) const; Menu *CreateAddMenu(Menu *parent); @@ -149,6 +152,10 @@ private: bool IsItemAttachedToCursor(NodeViewItem *item) const; + void ExpandItem(NodeViewItem *item); + + void CollapseItem(NodeViewItem *item); + NodeViewMiniMap *minimap_; struct AttachedItem { @@ -164,11 +171,12 @@ private: NodeViewEdge* create_edge_; NodeViewItem* create_edge_output_item_; NodeViewItem* create_edge_input_item_; - NodeViewItem *create_edge_expand_item_; NodeInput create_edge_input_; bool create_edge_already_exists_; bool create_edge_from_output_; + QVector create_edge_expanded_items_; + NodeViewScene scene_; MultiUndoCommand* paste_command_; diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 9357a8f0b..d6bb565e9 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -48,6 +48,7 @@ NodeViewItem::NodeViewItem(Node *node, const QString &input, int element, Node * expanded_(false), highlighted_(false), flow_dir_(NodeViewCommon::kInvalidDirection), + arrow_click_(false), label_as_output_(false) { // @@ -235,9 +236,7 @@ void NodeViewItem::RemoveEdge(NodeViewEdge *edge) void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) { - if ((IsOutputItem() && !has_connectable_inputs_) - || (!IsOutputItem() && !(node_->GetInputFlags(input_) & kInputFlagArray)) - || (expanded_ == e)) { + if (!CanBeExpanded() || (expanded_ == e)) { return; } @@ -253,13 +252,14 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) } if (expanded_) { + node_->Retranslate(); + if (IsOutputItem()) { // Create items for each input of the node int i = 1; foreach (const QString &input, node_->inputs()) { if (IsInputValid(input)) { NodeViewItem *item = new NodeViewItem(node_, input, -1, context_, this); - item->setPos(QPointF(0, i * item->rect().height())); children_.append(item); i++; } @@ -271,15 +271,12 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) (*it)->set_to_item(GetItemForInput((*it)->input())); } } - - SetRectSize(i); } else { // Create items for each element of the input array int arr_sz = node_->InputArraySize(input_); children_.resize(arr_sz); for (int i=0; isetPos(pos() + QPointF(0, (i+1) * item->rect().height())); children_[i] = item; } @@ -299,10 +296,10 @@ void NodeViewItem::SetExpanded(bool e, bool hide_titlebar) delete child; } children_.clear(); - - SetRectSize(1); } + UpdateChildrenPositions(); + if (flow_dir_ == NodeViewCommon::kTopToBottom) { UpdateOutputConnectorPosition(); } @@ -325,47 +322,81 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti // has been slightly modified QPalette app_pal = Core::instance()->main_window()->palette(); - // Draw the titlebar - if (IsOutputItem()) { - QRectF single_unit_rect = rect(); - single_unit_rect.setHeight(DefaultItemHeight()); + // We only draw a single unit's worth + QRectF single_unit_rect = rect(); + single_unit_rect.setHeight(DefaultItemHeight()); - // Output item drawing code + if (IsOutputItem()) { + // Set output item colors painter->setPen(Qt::black); painter->setBrush(node_->brush(single_unit_rect.top(), single_unit_rect.bottom())); + } else { + // Set input item colors + painter->setPen(Qt::NoPen); + painter->setBrush(element_ == -1 ? app_pal.color(QPalette::Window) : app_pal.color(QPalette::Base)); + } - painter->drawRect(single_unit_rect); + painter->drawRect(single_unit_rect); - painter->setPen(app_pal.color(QPalette::Text)); + // Draw highlight if applicable + if (highlighted_) { + QColor highlight_col = app_pal.color(QPalette::Text); + highlight_col.setAlpha(64); + painter->setBrush(highlight_col); + painter->drawRect(rect()); + } - QString node_label, node_shortname; + // Determine what text to draw and whether to draw an arrow + QString node_label, node_name; + if (IsOutputItem()) { if (label_as_output_) { - node_shortname = QCoreApplication::translate("NodeViewItem", "Output"); + node_name = QCoreApplication::translate("NodeViewItem", "Output"); } else { node_label = node_->GetLabel(); - node_shortname = node_->ShortName(); + node_name = node_->ShortName(); } - - int icon_size = painter->fontMetrics().height()/2; - - if (node_label.isEmpty()) { - // Draw shortname only - DrawNodeTitle(painter, node_shortname, single_unit_rect, Qt::AlignVCenter, icon_size, has_connectable_inputs_); + } else { + if (element_ == -1) { + node_name = node_->GetInputName(input_); } else { - int text_pad = DefaultTextPadding()/2; - QRectF safe_label_bounds = single_unit_rect.adjusted(text_pad, text_pad, -text_pad, -text_pad); - QFont f; - qreal font_sz = f.pointSizeF(); - f.setPointSizeF(font_sz * 0.8); - painter->setFont(f); - DrawNodeTitle(painter, node_label, safe_label_bounds, Qt::AlignTop, icon_size, has_connectable_inputs_); - f.setPointSizeF(font_sz * 0.6); - painter->setFont(f); - DrawNodeTitle(painter, node_shortname, safe_label_bounds, Qt::AlignBottom, icon_size, false); + node_name = QString::number(element_); } + } - // Draw final border + // Draw arrow if necessary + int arrow_size = CanBeExpanded() ? DrawExpandArrow(painter) : 0; + + if (IsOutputItem()) { + // Determine the text color (automatically calculate from node background color) + painter->setPen(ColorCoding::GetUISelectorColor(node_->color())); + } else { + // Just use text item + painter->setPen(app_pal.text().color()); + } + + if (node_label.isEmpty()) { + // Draw name only + DrawNodeTitle(painter, node_name, single_unit_rect, Qt::AlignVCenter, arrow_size); + } else { + int text_pad = DefaultTextPadding()/2; + QRectF safe_label_bounds = single_unit_rect.adjusted(text_pad, text_pad, -text_pad, -text_pad); + QFont f; + qreal font_sz = f.pointSizeF(); + + // Draw label as larger/upper text + f.setPointSizeF(font_sz * 0.8); + painter->setFont(f); + DrawNodeTitle(painter, node_label, safe_label_bounds, Qt::AlignTop, arrow_size); + + // Draw node name as smaller/lower text + f.setPointSizeF(font_sz * 0.6); + painter->setFont(f); + DrawNodeTitle(painter, node_name, safe_label_bounds, Qt::AlignBottom, arrow_size); + } + + // Draw final border (output only) + if (IsOutputItem()) { QPen border_pen; border_pen.setWidth(node_border_width_); @@ -379,28 +410,17 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti painter->setBrush(Qt::NoBrush); painter->drawRect(rect()); - } else { - // Input item drawing code - painter->setPen(Qt::NoPen); - painter->setBrush(app_pal.color(QPalette::Window)); - - painter->drawRect(rect()); - - if (highlighted_) { - QColor highlight_col = app_pal.color(QPalette::Text); - highlight_col.setAlpha(64); - painter->setBrush(highlight_col); - painter->drawRect(rect()); - } - - painter->setPen(app_pal.color(QPalette::Text)); - - painter->drawText(rect(), Qt::AlignCenter, node_->GetInputName(input_)); } } void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { + if (last_arrow_rect_.contains(event->pos().toPoint())) { + arrow_click_ = true; + ToggleExpanded(); + return; + } + event->setModifiers(FlipControlAndShiftModifiers(event->modifiers())); QGraphicsRectItem::mousePressEvent(event); @@ -408,6 +428,10 @@ void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event) void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + if (arrow_click_) { + return; + } + event->setModifiers(FlipControlAndShiftModifiers(event->modifiers())); QGraphicsRectItem::mouseMoveEvent(event); @@ -415,20 +439,16 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { + if (arrow_click_) { + arrow_click_ = false; + return; + } + event->setModifiers(FlipControlAndShiftModifiers(event->modifiers())); QGraphicsRectItem::mouseReleaseEvent(event); } -void NodeViewItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) -{ - QGraphicsRectItem::mouseDoubleClickEvent(event); - - if (!(event->modifiers() & Qt::ControlModifier)) { - SetExpanded(!IsExpanded()); - } -} - QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) { if (change == ItemPositionHasChanged && node_) { @@ -456,29 +476,22 @@ void NodeViewItem::ReadjustAllEdges() void NodeViewItem::UpdateContextRect() { - if (NodeViewContext *ctx = dynamic_cast(parentItem())) { - ctx->UpdateRect(); + QGraphicsItem *item = parentItem(); + + while (item) { + if (NodeViewContext *ctx = dynamic_cast(item)) { + ctx->UpdateRect(); + break; + } + + item = item->parentItem(); } } -void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& rect, Qt::Alignment vertical_align, int icon_size, bool draw_arrow) +void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& rect, Qt::Alignment vertical_align, int icon_full_size) { QFontMetrics fm = painter->fontMetrics(); - painter->setRenderHint(QPainter::SmoothPixmapTransform); - - // Draw right or down arrow based on expanded state - int icon_padding = DefaultItemHeight() / 2 - icon_size / 2; - int icon_full_size = icon_size + icon_padding * 2; - if (draw_arrow) { - const QIcon& expand_icon = IsExpanded() ? icon::TriDown : icon::TriRight; - int icon_size_scaled = icon_size * painter->transform().m11(); - painter->drawPixmap(QRect(this->rect().x() + icon_padding, - this->rect().y() + icon_padding, - icon_size, - icon_size), expand_icon.pixmap(QSize(icon_size_scaled, icon_size_scaled))); - } - // Calculate how much space we have for text int item_width = this->rect().width(); int max_text_width = item_width - DefaultTextPadding() * 2 - icon_full_size; @@ -497,9 +510,6 @@ void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& text = concatenated; } - // Determine the text color (automatically calculate from node background color) - painter->setPen(ColorCoding::GetUISelectorColor(node_->color())); - // Determine X position (favors horizontal centering unless it'll overrun the arrow) QRectF text_rect = rect; Qt::Alignment text_align = Qt::AlignHCenter | vertical_align; @@ -515,6 +525,28 @@ void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& text); } +int NodeViewItem::DrawExpandArrow(QPainter *painter) +{ + // Draw right or down arrow based on expanded state + int icon_size = painter->fontMetrics().height()/2; + int icon_padding = DefaultItemHeight() / 2 - icon_size / 2; + int icon_full_size = icon_size + icon_padding * 2; + + painter->setRenderHint(QPainter::SmoothPixmapTransform); + + const QIcon& expand_icon = IsExpanded() ? icon::TriDown : icon::TriRight; + int icon_size_scaled = icon_size * painter->transform().m11(); + + last_arrow_rect_ = QRect(this->rect().x() + icon_padding, + this->rect().y() + icon_padding, + icon_size, + icon_size); + + painter->drawPixmap(last_arrow_rect_, expand_icon.pixmap(QSize(icon_size_scaled, icon_size_scaled))); + + return icon_full_size; +} + void NodeViewItem::SetLabelAsOutput(bool e) { label_as_output_ = e; @@ -642,6 +674,44 @@ void NodeViewItem::SetRectSize(int height_units) setRect(QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height * height_units)); } +bool NodeViewItem::CanBeExpanded() const +{ + if (IsOutputItem()) { + return has_connectable_inputs_; + } else { + return node_->GetInputFlags(input_) & kInputFlagArray && element_ == -1 && !node_->IsInputConnected(input_); + } +} + +void NodeViewItem::UpdateChildrenPositions() +{ + int y = 1; + int h = DefaultItemHeight(); + + foreach (NodeViewItem *c, children_) { + c->setPos(QPointF(0, y * h)); + + y += c->GetLogicalHeightWithChildren(); + } + + SetRectSize(y); + + if (NodeViewItem *p = dynamic_cast(parentItem())) { + p->UpdateChildrenPositions(); + } +} + +int NodeViewItem::GetLogicalHeightWithChildren() const +{ + int h = 1; + + foreach (NodeViewItem *c, children_) { + h += c->GetLogicalHeightWithChildren(); + } + + return h; +} + void NodeViewItem::UpdateFlowDirectionOfInputItem(NodeViewItem *child) { if (!child->IsOutputItem()) { @@ -659,16 +729,26 @@ void NodeViewItem::UpdateFlowDirectionOfInputItem(NodeViewItem *child) void NodeViewItem::RepopulateInputs() { - has_connectable_inputs_ = false; + if (IsOutputItem()) { + has_connectable_inputs_ = false; - foreach (const QString& input, node_->inputs()) { - if (IsInputValid(input)) { - has_connectable_inputs_ = true; - break; + foreach (const QString& input, node_->inputs()) { + if (IsInputValid(input)) { + has_connectable_inputs_ = true; + break; + } + } + + input_connector_->setVisible(has_connectable_inputs_); + + if (IsExpanded()) { + // Create or remove inputs when necessary + } + } else { + if (IsExpanded() && element_ == -1) { + // Create or remove array elements when necessary } } - - input_connector_->setVisible(has_connectable_inputs_); } void NodeViewItem::NodeAppearanceChanged() @@ -698,13 +778,13 @@ NodeViewItem *NodeViewItem::GetItemForInput(NodeInput input) // Look for the input in our children foreach (NodeViewItem *i, children_) { if (i->input_ == input.input()) { - return i; + return i->GetItemForInput(input); } } } else { // Look for element in our children if (input.element() >= 0 && input.element() < children_.size()) { - return children_.at(input.element()); + return children_.at(input.element())->GetItemForInput(input); } } } diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 4b2c7236f..bbd6ec395 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -143,20 +143,23 @@ public: void UpdateFlowDirectionOfInputItem(NodeViewItem *child); + bool CanBeExpanded() const; + protected: virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; - virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; private: void UpdateContextRect(); - void DrawNodeTitle(QPainter *painter, QString text, const QRectF &rect, Qt::Alignment vertical_align, int icon_size, bool draw_arrow); + void DrawNodeTitle(QPainter *painter, QString text, const QRectF &rect, Qt::Alignment vertical_align, int icon_full_size); + + int DrawExpandArrow(QPainter *painter); /** * @brief Internal update function when logical position changes @@ -170,6 +173,10 @@ private: void SetRectSize(int height_units = 1); + void UpdateChildrenPositions(); + + int GetLogicalHeightWithChildren() const; + /** * @brief Reference to attached Node */ @@ -200,6 +207,9 @@ private: QPointF cached_node_pos_; + QRect last_arrow_rect_; + bool arrow_click_; + NodeViewItemConnector *input_connector_; NodeViewItemConnector *output_connector_; From d79457678512aee0a64b888b84a43c1ed94f3466 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 1 Dec 2021 12:18:59 -0800 Subject: [PATCH 33/94] ci: revert macos to custom deps --- .github/workflows/ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be1aa890a..88b058112 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -283,6 +283,8 @@ jobs: os-arch: x86_64 os: macos-10.15 cmake-gen: Ninja + env: + DEP_LOCATION: /opt/olive-editor name: | ${{ matrix.os-name }} <${{ matrix.compiler-name }}, @@ -310,10 +312,8 @@ jobs: shell: bash working-directory: ${{ runner.workspace }} run: | - $DOWNLOAD_TOOL https://github.com/olive-editor/vcpkg-registry/releases/download/continuous/olive-dep-x64-osx.zip - unzip olive-dep-x64-osx.zip - VCPKG_DIR=(vcpkg-export-*) - echo "VCPKG_ROOT=$(pwd)/$VCPKG_DIR" >> $GITHUB_ENV + $DOWNLOAD_TOOL https://github.com/olive-editor/dependencies/releases/download/continuous/olive-dep-mac-x86_64.tar.gz + sudo tar xzf olive-dep-mac-x86_64.tar.gz -C / - name: Acquire Google Crashpad shell: bash @@ -336,7 +336,7 @@ jobs: working-directory: ${{ runner.workspace }}/build run: | brew install ninja - PATH=/opt/olive-editor/bin:/opt/olive-editor/crashpad:$PATH \ + PATH=$DEP_LOCATION:$DEP_LOCATION/crashpad:$PATH \ cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 -G "${{ matrix.cmake-gen }}" \ -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" @@ -361,7 +361,7 @@ jobs: run: | # Use macdeployqt and macdeployqt to bundle dependencies mv app/$BUNDLE_NAME . - $VCPKG_ROOT/installed/x64-osx/tools/qt5-tools/bin/macdeployqt $BUNDLE_NAME -executable=$BUNDLE_NAME/Contents/MacOS/olive-crashhandler + $DEP_LOCATION/bin/macdeployqt $BUNDLE_NAME -executable=$BUNDLE_NAME/Contents/MacOS/olive-crashhandler $DOWNLOAD_TOOL https://github.com/arl/macdeployqtfix/raw/master/macdeployqtfix.py python2 macdeployqtfix.py $BUNDLE_NAME/Contents/MacOS/Olive $VCPKG_ROOT/installed/x64-osx @@ -375,7 +375,7 @@ jobs: done # Crashpad symbols - /opt/olive-editor/bin/dump_syms $BUNDLE_NAME/Contents/MacOS/Olive > Olive.sym + $DEP_LOCATION/bin/dump_syms $BUNDLE_NAME/Contents/MacOS/Olive > Olive.sym SYM_HEADER=($(head -n 1 Olive.sym)) # Read first line of symbol file SYM_DIR=$BUNDLE_NAME/Contents/Resources/symbols/Olive/${SYM_HEADER[3]} mkdir -p "$SYM_DIR" From 288dc6a47f597f90eae29197cd704063fc15164c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 1 Dec 2021 12:21:33 -0800 Subject: [PATCH 34/94] ci: missed the toolchain line --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88b058112..c4440a531 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -338,8 +338,7 @@ jobs: brew install ninja PATH=$DEP_LOCATION:$DEP_LOCATION/crashpad:$PATH \ cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 -G "${{ matrix.cmake-gen }}" \ - -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 -G "${{ matrix.cmake-gen }}" - name: Build working-directory: ${{ runner.workspace }}/build From 7be7392a07bdb4f2b15c51cef67f3e57c1e80abd Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 1 Dec 2021 12:34:14 -0800 Subject: [PATCH 35/94] ci: explicitly declare paths --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c4440a531..4c3e87262 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -336,7 +336,7 @@ jobs: working-directory: ${{ runner.workspace }}/build run: | brew install ninja - PATH=$DEP_LOCATION:$DEP_LOCATION/crashpad:$PATH \ + PATH=$DEP_LOCATION:$DEP_LOCATION/bin:$DEP_LOCATION/include:$DEP_LOCATION/lib:$DEP_LOCATION/crashpad:$PATH \ cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 -G "${{ matrix.cmake-gen }}" From 1828c2fd73137505b34d16db7e7d64f754825b30 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 1 Dec 2021 15:41:05 -0800 Subject: [PATCH 36/94] otio: update to be v0.14 compliant --- app/task/project/loadotio/loadotio.cpp | 4 ++-- app/task/project/saveotio/saveotio.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/task/project/loadotio/loadotio.cpp b/app/task/project/loadotio/loadotio.cpp index bc1983c41..96302d0ce 100644 --- a/app/task/project/loadotio/loadotio.cpp +++ b/app/task/project/loadotio/loadotio.cpp @@ -59,7 +59,7 @@ bool LoadOTIOTask::Run() auto root = OTIO::SerializableObjectWithMetadata::from_json_file(GetFilename().toStdString(), &es); - if (es != OTIO::ErrorStatus::OK) { + if (es.outcome != OTIO::ErrorStatus::Outcome::OK) { SetError(tr("Failed to load OpenTimelineIO from file \"%1\"").arg(GetFilename())); return false; } @@ -133,7 +133,7 @@ bool LoadOTIOTask::Run() // Get clips from track auto clip_map = otio_track->children(); - if (es != OTIO::ErrorStatus::OK) { + if (es.outcome != OTIO::ErrorStatus::Outcome::OK) { SetError(tr("Failed to load clip")); return false; } diff --git a/app/task/project/saveotio/saveotio.cpp b/app/task/project/saveotio/saveotio.cpp index 92af6753b..edee89ce5 100644 --- a/app/task/project/saveotio/saveotio.cpp +++ b/app/task/project/saveotio/saveotio.cpp @@ -91,7 +91,7 @@ bool SaveOTIOTask::Run() } } - return (es == OTIO::ErrorStatus::OK); + return (es.outcome == OTIO::ErrorStatus::Outcome::OK); } OTIO::Timeline *SaveOTIOTask::SerializeTimeline(Sequence *sequence) @@ -168,7 +168,7 @@ OTIO::Track *SaveOTIOTask::SerializeTrack(Track *track) otio_track->append_child(otio_block, &es); - if (es != OTIO::ErrorStatus::OK) { + if (es.outcome != OTIO::ErrorStatus::Outcome::OK) { goto fail; } } @@ -194,7 +194,7 @@ bool SaveOTIOTask::SerializeTrackList(TrackList *list, OTIO::Timeline* otio_time otio_timeline->tracks()->append_child(otio_track, &es); - if (es != OTIO::ErrorStatus::OK) { + if (es.outcome != OTIO::ErrorStatus::Outcome::OK) { otio_track->possibly_delete(); return false; } From c04b689215f8c6d9afa78b306d9c78f968f8cacf Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 2 Dec 2021 08:56:36 -0800 Subject: [PATCH 37/94] paramview: preliminary group support --- .../nodeparamview/nodeparamviewitem.cpp | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 3347551ef..e8deef896 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -240,12 +240,21 @@ NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBe int insert_row = 0; // Create widgets all root level components - foreach (const QString& input, node->inputs()) { - CreateWidgets(root_layout, node, input, -1, insert_row); + foreach (QString input, node->inputs()) { + Node *n; + if (NodeGroup *g = dynamic_cast(node)) { + const NodeInput &ni = g->GetInputPassthroughs().value(input); + n = ni.node(); + input = ni.input(); + } else { + n = node; + } + + CreateWidgets(root_layout, n, input, -1, insert_row); insert_row++; - if (node->InputIsArray(input)) { + if (n->InputIsArray(input)) { // Insert here QWidget* array_widget = new QWidget(); @@ -265,7 +274,7 @@ NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBe array_widget->setVisible(false); - array_ui_.insert({node, input}, {array_widget, arr_sz, append_btn}); + array_ui_.insert({n, input}, {array_widget, arr_sz, append_btn}); insert_row++; } @@ -432,6 +441,12 @@ int NodeParamViewItemBody::GetElementY(NodeInput c) const c.set_element(-1); } + if (NodeGroup *g = dynamic_cast(c.node())) { + const NodeInput &passthrough = g->GetInputPassthroughs().value(c.input()); + c.set_node(passthrough.node()); + c.set_input(passthrough.input()); + } + // Find its row in the parameters QLabel* lbl = input_ui_map_.value(c).main_label; From f4230a18054e499a86a2b0d645507a24732c9a80 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 2 Dec 2021 08:56:48 -0800 Subject: [PATCH 38/94] nodeview: accept ctrl dragging inputs too --- app/widget/nodeview/nodeview.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 5f87c40b4..da10d899b 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -316,13 +316,20 @@ void NodeView::mousePressEvent(QMouseEvent *event) create_edge_from_output_ = true; if (event->modifiers() & Qt::ControlModifier) { - create_edge_output_item_ = dynamic_cast(item); - if (create_edge_output_item_ && !create_edge_output_item_->IsOutputItem()) { - create_edge_output_item_ = nullptr; + NodeViewItem *mouse_item = dynamic_cast(item); + + if (mouse_item) { + if (mouse_item->IsOutputItem()) { + create_edge_output_item_ = mouse_item; + } else { + create_edge_input_item_ = mouse_item; + create_edge_input_ = mouse_item->GetInput(); + create_edge_from_output_ = false; + } } } - if (!create_edge_output_item_) { + if (!create_edge_output_item_ && !create_edge_input_item_) { // Determine if user clicked on a connector if (NodeViewItemConnector *connector = dynamic_cast(item)) { NodeViewItem *attached = static_cast(connector->parentItem()); From cfab883374cfab09682e5a34f977bdd4adf40b69 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 2 Dec 2021 13:15:09 -0800 Subject: [PATCH 39/94] use config for viewer reassoc Fixes #1661 --- app/config/config.cpp | 1 + app/render/renderprocessor.cpp | 4 +++- app/widget/viewer/viewerdisplay.cpp | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/config/config.cpp b/app/config/config.cpp index 797cb6d9f..2f88ea1b9 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -100,6 +100,7 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("ShowClipWhileDragging"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("StopPlaybackOnLastFrame"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("UseLegacyColorInInputTab"), NodeValue::kBoolean, false); + SetEntryInternal(QStringLiteral("ReassocLinToNonLin"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("AutoCacheDelay"), NodeValue::kInt, 1000); diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 267ee723b..01bd95518 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -96,7 +96,9 @@ FramePtr RenderProcessor::GenerateFrame(TexturePtr texture, const rational& time if (output_color_transform) { // Yes color transform, blit color managed - render_ctx_->BlitColorManaged(output_color_transform, texture, Renderer::kAlphaNone, blit_tex.get(), true, matrix); + render_ctx_->BlitColorManaged(output_color_transform, texture, + Config::Current()[QStringLiteral("ReassocLinToNonLin")].toBool() ? Renderer::kAlphaAssociated : Renderer::kAlphaNone, + blit_tex.get(), true, matrix); } else { // No color transform, just blit ShaderJob job; diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 3e21bf120..4ab607154 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -391,7 +391,9 @@ void ViewerDisplayWidget::OnPaint() texture_to_draw = deinterlace_texture_; } - renderer()->BlitColorManaged(color_service(), texture_to_draw, Renderer::kAlphaNone, device_params, false, + renderer()->BlitColorManaged(color_service(), texture_to_draw, + Config::Current()[QStringLiteral("ReassocLinToNonLin")].toBool() ? Renderer::kAlphaAssociated : Renderer::kAlphaNone, + device_params, false, combined_matrix_flipped_, crop_matrix_); } } From f5f4acc451588948b53ad6001c3aa4b80ad98e7c Mon Sep 17 00:00:00 2001 From: SiderealArt Date: Fri, 3 Dec 2021 22:03:41 +0800 Subject: [PATCH 40/94] Update zh_TW.ts --- app/ts/zh_TW.ts | 5789 ++++++++++++++++++++++++----------------------- 1 file changed, 2901 insertions(+), 2888 deletions(-) diff --git a/app/ts/zh_TW.ts b/app/ts/zh_TW.ts index bf22b0f39..82a89bb7d 100755 --- a/app/ts/zh_TW.ts +++ b/app/ts/zh_TW.ts @@ -1,4767 +1,4780 @@ - - + + AudioParams - - %1 Hz - + + %1 Hz + %1 赫茲 - - Mono - 單聲道 + + Mono + 單聲道 - - Stereo - 立體聲 + + Stereo + 立體聲 - - 2.1 - 144p {2.1?} + + 2.1 + 2.1 - - 5.1 - 144p {5.1?} + + 5.1 + 5.1 - - 7.1 - 144p {7.1?} + + 7.1 + 7.1 - - Unknown (0x%1) - + + Unknown (0x%1) + 未知 (0x%1) - - + + Config - - Error loading settings - + + Error loading settings + 載入設定時發生錯誤 - - Failed to load application settings. This session will use defaults. + + Failed to load application settings. This session will use defaults. %1 - + 載入應用程式設定失敗。此工作階段將使用預設設定。 + +%1 - - Error saving settings - + + Error saving settings + 儲存設定時發生錯誤 - - Failed to save application settings. The application may lack write permissions to this location. - + + Failed to save application settings. The application may lack write permissions to this location. + 儲存應用程式設定失敗。應用程式可能沒有寫入至此位置的權限。 - - + + Footage - - %1 FPS - + + %1 FPS + %1 FPS - - %1 Hz - + + %1 Hz + %1 赫茲 - - Filename: %1 - + + Filename: %1 + 檔名:%1 - - This footage is not valid for use - + + This footage is not valid for use + 此素材無效 - - + + ImportTool - - Don't ask me again - + + Don't ask me again + 不要再詢問我 - - No Active Sequence - + + No Active Sequence + No Active Sequence - - No sequence is currently open. Would you like to create one? - + + No sequence is currently open. Would you like to create one? + No sequence is currently open. Would you like to create one? - - Automatically Detect Parameters From Footage - + + Automatically Detect Parameters From Footage + 自動從素材中偵測參數 - - Set Parameters Manually - + + Set Parameters Manually + 手動設置參數 - - + + MoveItemCommand - - Move Item - + + Move Item + 移動項目 - - + + NodeCopyPasteWidget - - Error pasting nodes - + + Error pasting nodes + 貼上節點時發生錯誤 - - Failed to paste nodes: %1 - + + Failed to paste nodes: %1 + 無法貼上節點:%1 - - + + NodeFactory - - None - + + None + - - + + NodeViewItem - - %1... - + + %1... + %1... - - + + PresetManager - - Save Preset - + + Save Preset + 儲存預設檔 - - Set preset name: - + + Set preset name: + 設定預設檔名稱: - - Invalid preset name - + + Invalid preset name + 預設檔名稱無效 - - You must enter a preset name - + + You must enter a preset name + 您必須輸入預設檔名稱 - - Preset exists - + + Preset exists + 預設檔已存在 - - A preset with this name already exists. Would you like to replace it? - + + A preset with this name already exists. Would you like to replace it? + 已有相同名稱的預設檔存在!您是否要取代它? - - + + RatioDialog - - Enter custom ratio (e.g. "4:3", "16/9", etc.): - + + Enter custom ratio (e.g. "4:3", "16/9", etc.): + 輸入自訂長寬比 (例如 "4:3"、"16/9"...) - - Invalid custom ratio - + + Invalid custom ratio + 自訂長寬比無效 - - Failed to parse "%1" into an aspect ratio. Please format a rational fraction with a ':' or a '/' separator. - + + Failed to parse "%1" into an aspect ratio. Please format a rational fraction with a ':' or a '/' separator. + 無法將 "%1" 剖析成長寬比。請用 ':' 或 '/' 來分隔有理數。 - - + + RenameItemCommand - - Rename Item - + + Rename Item + 重新命名項目 - - + + Sequence - - %1 FPS - + + %1 FPS + %1 FPS - - + + Stream - - %1: Audio - %2 Channels, %3Hz - + + %1: Audio - %2 Channels, %3Hz + %1:音訊 - %2 通道,%3 Hz - - %1: Unknown - + + %1: Unknown + %1:未知 - - %1: Image - %2x%3 - + + %1: Image - %2x%3 + %1:圖片 - %2x%3 - - %1: Video - %2x%3 - + + %1: Video - %2x%3 + %1:影片 - %2x%3 - - + + TimelineViewBlockItem - - %1 + + %1 In: %2 Out: %3 Length: %4 - + %1 + +輸入:%2 +輸出:%3 +長度:%4 - - + + Tool - - Empty - + + Empty + - - Bars - + + Bars + Bars - - Solid - + + Solid + Solid - - Title - 字幕 + + Title + 標題 - - Tone - + + Tone + 色調 - - Unknown - + + Unknown + 未知 - - + + VideoParams - - 8-bit - + + 8-bit + 8 位元 - - 16-bit Integer - + + 16-bit Integer + 16 位元整數 - - Half-Float (16-bit) - + + Half-Float (16-bit) + 半浮點數(16 位元) - - Full-Float (32-bit) - + + Full-Float (32-bit) + 全浮點數(32 位元) - - Unknown (0x%1) - + + Unknown (0x%1) + 未知 (0x%1) - - %1 FPS - + + %1 FPS + %1 FPS - - Square Pixels (%1) - + + Square Pixels (%1) + Square Pixels (%1) - - NTSC Standard (%1) - + + NTSC Standard (%1) + NTSC 標準 (%1) - - NTSC Widescreen (%1) - + + NTSC Widescreen (%1) + NTSC 寬螢幕 (%1) - - PAL Standard (%1) - + + PAL Standard (%1) + PAL 標準 (%1) - - PAL Widescreen (%1) - + + PAL Widescreen (%1) + PAL 寬螢幕 (%1) - - HD Anamorphic 1080 (%1) - + + HD Anamorphic 1080 (%1) + HD Anamorphic 1080 (%1) - - + + main - - Show this help text - + + Show this help text + 顯示此說明文字 - - Show application version - + + Show application version + 顯示應用程式版本 - - Start in full-screen mode - + + Start in full-screen mode + 以全螢幕模式啟動 - - Export only (No GUI) - + + Export only (No GUI) + 僅匯出 (無介面) - - Override language with file - + + Override language with file + Override language with file - - qm-file - + + qm-file + qm-file - - Project to open on startup - + + Project to open on startup + 啟動時要開啟的專案 - - + + olive::AboutDialog - - About %1 - + + About %1 + 關於 %1 - - Olive is a non-linear video editor. This software is free and protected by the GNU GPL. - Olive是免費的非線性視頻編輯器.基于GNU通用公共許可證(GNU GPL)條款發佈. + + Olive is a non-linear video editor. This software is free and protected by the GNU GPL. + Olive 是非線性的影片剪輯軟體。此軟體為免費且受 GNU 通用公眾授權條款保護。 - - Olive Team is obliged to inform users that Olive source code is available for download from its website. - Olive團隊有義務告知用戶可以從官網下載olive的源碼.翻譯者已嘗試用通俗易明的方式進行翻譯,希望大家使用愉快.請支持自由開源軟件謝謝. + + Olive Team is obliged to inform users that Olive source code is available for download from its website. + Olive 團隊有義務通知使用者 Olive 原始碼可從其網站下載。 - - + + olive::ActionSearch - - Search for action... - 功能搜索... + + Search for action... + Search for action... - - + + olive::AudioInput - - Audio Input - + + Audio Input + 音訊輸入 - - Audio - 音頻 + + Audio + 音訊 - - Import an audio footage stream. - + + Import an audio footage stream. + Import an audio footage stream. - - + + olive::AudioMonitorPanel - - Audio Monitor - + + Audio Monitor + Audio Monitor - - + + olive::Block - - Length - 長度 + + Length + 長度 - - Media In - + + Media In + 媒體輸入 - - Enabled - + + Enabled + 已啟用 - - Speed - + + Speed + 速度 - - + + olive::BlurFilterNode - - Blur - + + Blur + 模糊 - - Blurs an image. - + + Blurs an image. + 模糊圖片。 - - Input - + + Input + 輸入 - - Method - + + Method + 方式 - - Box - + + Box + Box - - Gaussian - + + Gaussian + 高斯 - - Radius - + + Radius + 半徑 - - Horizontal - + + Horizontal + 水平 - - Vertical - + + Vertical + 垂直 - - Repeat Edge Pixels - + + Repeat Edge Pixels + Repeat Edge Pixels - - + + olive::ClipBlock - - Clip - + + Clip + 片段 - - A time-based node that represents a media source. - + + A time-based node that represents a media source. + A time-based node that represents a media source. - - Buffer - + + Buffer + 緩衝 - - + + olive::ColorDialog - - Select Color - + + Select Color + 選取顏色 - - + + olive::ColorSpaceChooser - - Color Management - + + Color Management + 色彩管理 - - Input: - + + Input: + 輸入: - - Color Space: - + + Color Space: + 色彩空間: - - Display: - + + Display: + 顯示: - - View: - + + View: + 檢視: - - Look: - + + Look: + Look: - - (None) - + + (None) + (無) - - + + olive::ColorValuesTab - - Red - + + Red + - - Green - + + Green + - - Blue - + + Blue + - - + + olive::ColorValuesWidget - - Preview - + + Preview + 預覽 - - Input - + + Input + 輸入 - - Reference - + + Reference + 參照 - - Display - + + Display + 顯示 - - + + olive::ConformTask - - Conforming Audio %1:%2 - + + Conforming Audio %1:%2 + Conforming Audio %1:%2 - - + + olive::Core - - Import error - + + Import error + 匯入錯誤 - - Nothing to import - + + Nothing to import + 沒有要匯入的內容 - - Importing... - + + Importing... + 正在匯入... - - Import footage... - + + Import footage... + 匯入素材... - - Failed to import footage - + + Failed to import footage + 匯入素材失敗 - - Failed to find active Project panel - + + Failed to find active Project panel + Failed to find active Project panel - - No Active Project - + + No Active Project + 沒有正在使用的專案 - - No project is currently open to set the properties for - + + No project is currently open to set the properties for + No project is currently open to set the properties for - - Failed to create new folder - + + Failed to create new folder + 無法建立新資料夾 - - - Failed to find active project - + + + Failed to find active project + Failed to find active project - - New Folder - 新建檔案夾 + + New Folder + 新資料夾 - - Failed to create new sequence - + + Failed to create new sequence + 無法建立新序列 - - Possible image sequence detected - + + Possible image sequence detected + 偵測到可能的圖片序列 - - The file '%1' looks like it might be part of an image sequence. Would you like to import it as such? - + + The file '%1' looks like it might be part of an image sequence. Would you like to import it as such? + 檔案 '%1' 看起來像是圖片序列的一部分。是否要按此方式匯入? - - You must specify a project file to export - + + You must specify a project file to export + 您必須指定要匯出的專案檔 - - Specified project does not exist - + + Specified project does not exist + 指定的專案不存在 - - Project contains no sequences, nothing to export - + + Project contains no sequences, nothing to export + 專案不包含序列,沒有東西可以匯出 - - This project has multiple sequences. Which do you wish to export? - + + This project has multiple sequences. Which do you wish to export? + 此專案有多個序列。您要匯出哪一個? - - Enter number (or %1 to cancel): - + + Enter number (or %1 to cancel): + 輸入數字 (或 %1 以取消): - - Invalid sequence number - + + Invalid sequence number + 序列號碼無效 - - Export succeeded - + + Export succeeded + 已成功匯出 - - Export failed: %1 - + + Export failed: %1 + 匯出失敗:%1 - - Project failed to load: %1 - + + Project failed to load: %1 + 專案載入失敗:%1 - - Failed to open startup file - + + Failed to open startup file + 無法開啟啟動檔 - - The project "%1" doesn't exist. A new project will be started instead. - + + The project "%1" doesn't exist. A new project will be started instead. + 專案 "%1" 不存在。將啟動新專案。 - - - Missing OpenTimelineIO Libraries - + + + Missing OpenTimelineIO Libraries + OpenTimelineIO 庫缺失 - - - This build was compiled without OpenTimelineIO and therefore cannot open OpenTimelineIO files. - + + + This build was compiled without OpenTimelineIO and therefore cannot open OpenTimelineIO files. + 此組建是在沒有 OpenTimelineIO 的情況下編譯的,因此無法開啟 OpenTimelineIO 檔案。 - - Save Project - 保存項目 + + Save Project + 儲存專案 - - - Error - 錯誤 + + + Error + 錯誤 - - This Sequence is empty. There is nothing to export. - + + This Sequence is empty. There is nothing to export. + 此序列為空。沒有東西可以匯出。 - - No valid sequence detected. + + No valid sequence detected. Make sure a sequence is loaded and it has a connected Viewer node. - + No valid sequence detected. + +Make sure a sequence is loaded and it has a connected Viewer node. - - Olive Project - + + Olive Project + Olive 專案 - - OpenTimelineIO - + + OpenTimelineIO + OpenTimelineIO - - Save Project As - + + Save Project As + 另存專案為 - - Load Project - + + Load Project + 載入專案 - - Label Node - + + Label Node + 標記節點 - - Set node label - + + Set node label + 設置節點標籤 - - Sequence %1 - + + Sequence %1 + 序列 %1 - - Cannot open recent project - + + Cannot open recent project + 無法開啟最近專案 - - The project "%1" doesn't exist. Would you like to remove this file from the recent list? - + + The project "%1" doesn't exist. Would you like to remove this file from the recent list? + 專案 "%1" 不存在。是否要從最近清單中刪除此檔案? - - Unsaved Changes - + + Unsaved Changes + 未儲存的變更 - - The project '%1' has unsaved changes. Would you like to save them? - + + The project '%1' has unsaved changes. Would you like to save them? + 專案 '%1' 含有尚未儲存的變更。您要儲存嗎? - - Save - + + Save + 儲存 - - Save All - + + Save All + 儲存全部 - - Don't Save - + + Don't Save + 不要儲存 - - Don't Save All - + + Don't Save All + 不要全部儲存 - - Failed to cache sequence - + + Failed to cache sequence + 序列快取失敗 - - No active viewer found with this sequence. - + + No active viewer found with this sequence. + No active viewer found with this sequence. - - Open Project - 打開項目 + + Open Project + 開啟專案 - - + + olive::CrashHandlerDialog - - Olive - + + Olive + Olive - - We're sorry, Olive has crashed. Please help us fix it by sending an error report. - + + We're sorry, Olive has crashed. Please help us fix it by sending an error report. + 很抱歉,Olive 當機了。傳送錯誤報告能幫助我們修復問題。 - - Describe what you were doing in as much detail as possible. If you can, provide steps to reproduce this crash. - + + Describe what you were doing in as much detail as possible. If you can, provide steps to reproduce this crash. + 請盡可能詳細地描述您在做什麼。如果可以的話,請提供重現此崩潰的步驟。 - - Crash Report: - + + Crash Report: + 當機報告: - - Send Error Report - + + Send Error Report + 傳送錯誤報告 - - Don't Send - + + Don't Send + 不要傳送 - - Waiting for crash report to be generated... - + + Waiting for crash report to be generated... + 正在等候當機報告產生... - - Upload Failed - + + Upload Failed + 上傳失敗 - - Failed to send error report. Please try again later. - + + Failed to send error report. Please try again later. + 傳送錯誤報告失敗,請稍後再試。 - - No Crash Summary - + + No Crash Summary + 無當機摘要 - - Are you sure you want to send an error report with no crash summary? - + + Are you sure you want to send an error report with no crash summary? + 您確定要傳送無當機摘要的錯誤報告嗎? - - + + olive::CrossDissolveTransition - - Cross Dissolve - + + Cross Dissolve + 交叉溶解 - - Smoothly transition between two clips. - + + Smoothly transition between two clips. + Smoothly transition between two clips. - - + + olive::CurvePanel - - Curve Editor - + + Curve Editor + 曲線編輯器 - - + + olive::CurveView - - Zoom to Fit - + + Zoom to Fit + 縮放至適當比例 - - + + olive::CurveWidget - - Linear - 線性 + + Linear + 線性 - - Bezier - 貝塞爾曲綫 + + Bezier + 貝茲曲線 - - Hold - 保留 + + Hold + 凍結 - - + + olive::DipToColorTransition - - Dip To Color - + + Dip To Color + Dip To Color - - Transition between clips by dipping to a color. - + + Transition between clips by dipping to a color. + Transition between clips by dipping to a color. - - + + olive::DiskCacheDialog - - Disk Cache: %1 - + + Disk Cache: %1 + 磁碟快取:%1 - - Disk Cache Settings - + + Disk Cache Settings + 磁碟快取設定 - - Maximum Disk Cache: - + + Maximum Disk Cache: + 最大磁碟快取: - - %1 GB - + + %1 GB + %1 GB - - - - Clear Disk Cache - + + + + Clear Disk Cache + 清除磁碟快取 - - Automatically clear disk cache on close - + + Automatically clear disk cache on close + 關閉時自動清除磁碟快取 - - Are you sure you want to clear the disk cache in '%1'? - + + Are you sure you want to clear the disk cache in '%1'? + 您確定要清除 '%1' 中的磁碟快取嗎? - - Disk Cache Cleared - + + Disk Cache Cleared + 已清除磁碟快取 - - Disk cache failed to fully clear. You may have to delete the cache files manually. - + + Disk cache failed to fully clear. You may have to delete the cache files manually. + 磁碟快取無法完全清除。您可能必須手動刪除快取檔。 - - Disk Cache Partially Cleared - + + Disk Cache Partially Cleared + 已清除部分磁碟快取 - - + + olive::DiskManager - - - Disk Cache Error - + + + Disk Cache Error + 磁碟快取錯誤 - - Unable to set custom application disk cache. Using default instead. - + + Unable to set custom application disk cache. Using default instead. + 無法設定自訂應用程式磁碟快取。將改為使用預設值。 - - Disk Cache - + + Disk Cache + 磁碟快取 - - You've chosen to change the default disk cache location. This will invalidate your current cache. Would you like to continue? - + + You've chosen to change the default disk cache location. This will invalidate your current cache. Would you like to continue? + 您選擇了變更預設磁碟快取位置。這會使您目前的快取失效。要繼續嗎? - - Failed to open disk cache at "%1". Try a different folder. - + + Failed to open disk cache at "%1". Try a different folder. + 無法開啟位於 "%1" 的磁碟快取。請嘗試不同的資料夾。 - - + + olive::ElapsedCounterWidget - - Elapsed: %1 - + + Elapsed: %1 + 經過時間:%1 - - Remaining: %1 - + + Remaining: %1 + 剩餘時間:%1 - - + + olive::ExportAdvancedVideoDialog - - Advanced - 高級 + + Advanced + 進階 - - Pixel - + + Pixel + 像素 - - Pixel Format: - 視頻格式: + + Pixel Format: + 像素格式: - - Performance - + + Performance + 效能 - - Threads: - 綫程數量: + + Threads: + 執行緒: - - + + olive::ExportAudioTab - - Codec: - 編解碼器: + + Codec: + 轉碼器: - - Sample Rate: - 採樣率: + + Sample Rate: + 取樣頻率: - - Channel Layout: - + + Channel Layout: + Channel Layout: - - Format: - + + Format: + 格式: - - + + olive::ExportCodec - - DNxHD - + + DNxHD + DNxHD - - H.264 - + + H.264 + H.264 - - H.265 - + + H.265 + H.265 - - OpenEXR - + + OpenEXR + OpenEXR - - PNG - + + PNG + PNG - - ProRes - + + ProRes + ProRes - - TIFF - + + TIFF + TIFF - - MP2 - + + MP2 + MP2 - - MP3 - + + MP3 + MP3 - - AAC - + + AAC + AAC - - PCM (Uncompressed) - + + PCM (Uncompressed) + PCM (未壓縮) - - Unknown - + + Unknown + 未知 - - + + olive::ExportDialog - - Filename: - 檔案名: + + Filename: + 檔名: - - Browse for exported file filename - + + Browse for exported file filename + Browse for exported file filename - - Preset: - 預置: + + Preset: + 預設: - - Same As Source - High Quality - + + Same As Source - High Quality + 與來源相同 - 高品質 - - Same As Source - Medium Quality - + + Same As Source - Medium Quality + 與來源相同 - 中等品質 - - Same As Source - Low Quality - + + Same As Source - Low Quality + 與來源相同 - 低品質 - - Range: - 範圍: + + Range: + 範圍: - - Entire Sequence - 整個片段 + + Entire Sequence + 整個序列 - - In to Out - 已選擇的時間段 + + In to Out + 進入點至退出點 - - Format: - + + Format: + 格式: - - Export Video - + + Export Video + 匯出視訊 - - Export Audio - + + Export Audio + 匯出音訊 - - Video - 視頻 + + Video + 視訊 - - Audio - 音頻 + + Audio + 音訊 - - - Export - 匯出 + + + Export + 匯出 - - Preview - + + Preview + 預覽 - - Invalid parameters - + + Invalid parameters + 參數無效 - - Both video and audio are disabled. There's nothing to export. - + + Both video and audio are disabled. There's nothing to export. + 視訊和音訊均被停用。沒有任何東西可以匯出。 - - Invalid filename - + + Invalid filename + 檔名無效 - - The filename must contain the extension "%1". Would you like to append it automatically? - + + The filename must contain the extension "%1". Would you like to append it automatically? + 檔案名稱必須包含副檔名 "%1"。是否要自動附加? - - Failed to create output directory - + + Failed to create output directory + Failed to create output directory - - The intended output directory doesn't exist and Olive couldn't create it. Please choose a different filename. - + + The intended output directory doesn't exist and Olive couldn't create it. Please choose a different filename. + The intended output directory doesn't exist and Olive couldn't create it. Please choose a different filename. - - Confirm Overwrite - + + Confirm Overwrite + 確認覆寫 - - The file "%1" already exists. Do you want to overwrite it? - + + The file "%1" already exists. Do you want to overwrite it? + 檔案 "%1" 已經存在,您要覆寫它嗎? - - Invalid Parameters - + + Invalid Parameters + 參數無效 - - Width and height must be multiples of 2. - + + Width and height must be multiples of 2. + 寬度和高度必須是 2 的倍數。 - - + + olive::ExportFormat - - DNxHD - + + DNxHD + DNxHD - - Matroska Video - + + Matroska Video + Matroska 視訊 - - MPEG-4 Video - + + MPEG-4 Video + MPEG-4 視訊 - - OpenEXR - + + OpenEXR + OpenEXR - - PNG - + + PNG + PNG - - TIFF - + + TIFF + TIFF - - QuickTime - + + QuickTime + QuickTime - - Unknown - + + Unknown + 未知 - - + + olive::ExportTask - - Exporting "%1" - + + Exporting "%1" + 正在匯出 "%1" - - Failed to create encoder - + + Failed to create encoder + 無法建立編碼器 - - Failed to open file - + + Failed to open file + 開啟檔案失敗 - - Failed to overwrite "%1". Export has been saved as "%2" instead. - + + Failed to overwrite "%1". Export has been saved as "%2" instead. + 無法覆寫 "%1",已將匯出結果儲存為 "%2"。 - - + + olive::ExportVideoTab - - Basic - + + Basic + 基本 - - Width: - 寬度: + + Width: + 寬度: - - Height: - 高度: + + Height: + 高度: - - Maintain Aspect Ratio: - + + Maintain Aspect Ratio: + 保持長寬比: - - Scaling Method: - + + Scaling Method: + 縮放方式: - - Fit - 適合 + + Fit + Fit - - Stretch - + + Stretch + 拉伸 - - Crop - + + Crop + 裁切 - - Frame Rate: - + + Frame Rate: + 影格率: - - Pixel Aspect Ratio: - 像素長寬比 + + Pixel Aspect Ratio: + 像素長寬比: - - Interlacing: - + + Interlacing: + Interlacing: - - Quality: - + + Quality: + 品質: - - Codec - + + Codec + 轉碼器 - - Codec: - 編解碼器: + + Codec: + 轉碼器: - - Advanced - 高級 + + Advanced + 進階 - - + + olive::FloatSlider - - %1 dB - + + %1 dB + %1 dB - - %1% - + + %1% + %1% - - + + olive::FootagePropertiesDialog - - "%1" Properties - + + "%1" Properties + "%1" 屬性 - - Name: - 名稱: + + Name: + 名稱: - - Tracks: - 軌道: + + Tracks: + 軌道: - - + + olive::FootageRelinkDialog - - Footage - + + Footage + 素材 - - Filename - + + Filename + 檔名 - - Actions - + + Actions + 動作 - - Browse - 瀏覽 + + Browse + 瀏覽 - - Relink Footage - + + Relink Footage + 重新連結素材 - - Relink "%1" - + + Relink "%1" + 重新連結 "%1" - - All Files - 全部檔案 + + All Files + 所有檔案 - - + + olive::FootageViewerPanel - - Footage Viewer - + + Footage Viewer + 素材檢視器 - - + + olive::GapBlock - - Gap - + + Gap + 間距 - - A time-based node that represents an empty space. - + + A time-based node that represents an empty space. + A time-based node that represents an empty space. - - + + olive::H264BitRateSection - - Target Bit Rate (Mbps): - + + Target Bit Rate (Mbps): + 目標位元速率 (Mbps): - - Maximum Bit Rate (Mbps): - + + Maximum Bit Rate (Mbps): + 最大位元速率 (Mbps): - - Two-Pass - + + Two-Pass + 雙通 - - + + olive::H264FileSizeSection - - Target File Size (MB): - 輸出檔案大小 (MB): + + Target File Size (MB): + 目標檔案大小 (MB): - - Two-Pass - + + Two-Pass + 雙通 - - + + olive::H264Section - - Compression Method: - + + Compression Method: + 壓縮方式: - - Constant Rate Factor - + + Constant Rate Factor + Constant Rate Factor - - Target Bit Rate - + + Target Bit Rate + 目標位元速率 - - Target File Size - + + Target File Size + 目標檔案大小 - - + + olive::ImageSection - - Image Sequence: - + + Image Sequence: + 圖片序列: - - + + olive::InterlacedComboBox - - None (Progressive) - 無 (進度) + + None (Progressive) + 無(漸進) - - Top-Field First - + + Top-Field First + Top-Field First - - Bottom-Field First - + + Bottom-Field First + Bottom-Field First - - + + olive::KeyframePropertiesDialog - - Keyframe Properties - + + Keyframe Properties + 關鍵影格屬性 - - In: - + + In: + 進入點: - - Out: - + + Out: + 退出點: - - Linear - 線性 + + Linear + 線性 - - Hold - 保留 + + Hold + 凍結 - - Bezier - 貝塞爾曲綫 + + Bezier + 貝茲曲線 - - + + olive::KeyframeViewBase - - Linear - 線性 + + Linear + 線性 - - Bezier - 貝塞爾曲綫 + + Bezier + 貝茲曲線 - - Hold - 保留 + + Hold + 凍結 - - P&roperties - + + P&roperties + 屬性 (&R) - - + + olive::LoadOTIOTask - - Failed to load OpenTimelineIO from file "%1" - + + Failed to load OpenTimelineIO from file "%1" + 無法從 "%1" 檔案載入 OpenTimelineIO - - Unknown OpenTimelineIO root element - + + Unknown OpenTimelineIO root element + Unknown OpenTimelineIO root element - - Failed to load clip - + + Failed to load clip + 無法載入片段 - - + + olive::MainMenu - - &Save '%1' - + + &Save '%1' + 儲存 '%1' (&S) - - Save '%1' &As - + + Save '%1' &As + Save '%1' &As - - Close '%1' - + + Close '%1' + 關閉 '%1' - - Close All Except '%1' - + + Close All Except '%1' + Close All Except '%1' - - &Save Project - 保存項目(&S) + + &Save Project + 儲存專案 (&S) - - Save Project &As - 保存項目為(&A) + + Save Project &As + 另存專案為 (&A) - - Close Project - + + Close Project + 關閉專案 - - Close All Except Current Project - + + Close All Except Current Project + Close All Except Current Project - - (None) - + + (None) + (無) - - &File - 檔案(&F) + + &File + 檔案 (&F) - - &New - 新建(&N) + + &New + 新增 (&N) - - &Open Project - 打開項目(&O) + + &Open Project + 開啟專案 (&O) - - Open &Recent - + + Open &Recent + 開啟最近使用 (&R) - - &Clear Recent List - + + &Clear Recent List + 清除最近列表 (&C) - - Sa&ve All Projects - + + Sa&ve All Projects + 儲存所有專案 (&V) - - &Import... - 匯入(&I) + + &Import... + 匯入... (&I) - - &Export - + + &Export + 匯出 (&E) - - &Media... - + + &Media... + 媒體... (&M) - - &Project Properties... - + + &Project Properties... + 專案屬性... (&P) - - Close All Projects - + + Close All Projects + 關閉所有專案 - - E&xit - 退出(&I) + + E&xit + 離開 (&X) - - &Edit - + + &Edit + 編輯 (&E) - - Insert - + + Insert + 插入 - - Overwrite - + + Overwrite + 覆寫 - - Select &All - 選擇全部(&A) + + Select &All + 全選 (&A) - - Deselect All - 取消選擇所有 + + Deselect All + 取消全選 - - Ripple to In Point - + + Ripple to In Point + Ripple to In Point - - Ripple to Out Point - + + Ripple to Out Point + Ripple to Out Point - - Edit to In Point - + + Edit to In Point + 編輯到進入點 - - Edit to Out Point - + + Edit to Out Point + 編輯到退出點 - - Delete In/Out Point - 刪除標記的區域 + + Delete In/Out Point + 刪除進入/退出點 - - Ripple Delete In/Out Point - + + Ripple Delete In/Out Point + Ripple Delete In/Out Point - - Set/Edit Marker - 設置/編輯標記 + + Set/Edit Marker + 設置/編輯標記 - - &View - 視圖(&V) + + &View + 檢視 (&V) - - Zoom In - 放大 + + Zoom In + 放大 - - Zoom Out - 縮小 + + Zoom Out + 縮小 - - Increase Track Height - 增加軌道高度 + + Increase Track Height + 增加軌道高度 - - Decrease Track Height - 降低軌道高度 + + Decrease Track Height + 降低軌道高度 - - Toggle Show All - 軌道全部顯示 + + Toggle Show All + 切換全部顯示 - - Full Screen - 全屏 + + Full Screen + 全螢幕 - - Full Screen Viewer - 全屏預覽 + + Full Screen Viewer + 全螢幕檢視器 - - &Playback - 回放(&P) + + &Playback + &Playback - - Go to Start - 回到起始幀 + + Go to Start + 跳到開頭 - - Previous Frame - 前一幀 + + Previous Frame + 上一影格 - - Play/Pause - 播放/暫停 + + Play/Pause + 播放/暫停 - - Play In to Out - 播放已標記的區域 + + Play In to Out + Play In to Out - - Next Frame - 下一幀 + + Next Frame + 下一影格 - - Go to End - 轉到結束幀 + + Go to End + 跳至結尾 - - Go to Previous Cut - 切換到之前的位置 + + Go to Previous Cut + 跳到上一個剪輯 - - Go to Next Cut - 轉到下一個位置 + + Go to Next Cut + 跳到下一個剪輯 - - Go to In Point - 轉到時間的起始標記處 + + Go to In Point + 跳至進入點 - - Go to Out Point - 轉到時間的結束標記處 + + Go to Out Point + 跳至退出點 - - Shuttle Left - 向左播放 + + Shuttle Left + 向左往復 - - Shuttle Stop - 停止播放 + + Shuttle Stop + 停止往復 - - Shuttle Right - 向右播放 + + Shuttle Right + 向右往復 - - Loop - 循環播放 + + Loop + 循環 - - &Sequence - 片段(&S) + + &Sequence + 序列 (&S) - - Cache Entire Sequence - + + Cache Entire Sequence + 快取整個序列 - - Cache Sequence In/Out - + + Cache Sequence In/Out + 快取序列 進入點/退出點 - - Maximize Panel - 最大化面板 + + Maximize Panel + 面板最大化 - - Lock Panels - 鎖定面板 + + Lock Panels + 鎖定面板 - - Reset to Default Layout - 重置為預設佈局 + + Reset to Default Layout + 重設為預設版面 - - &Tools - 工具(&T) + + &Tools + 工具 (&T) - - Pointer Tool - 選擇/移動/預設 + + Pointer Tool + 選擇工具 - - Edit Tool - 選擇部分 + + Edit Tool + 編輯工具 - - Ripple Tool - 漣漪的工具 + + Ripple Tool + 波紋工具 - - Rolling Tool - + + Rolling Tool + 滾動工具 - - Razor Tool - 剪刀 + + Razor Tool + 分割工具 - - Slip Tool - 滑動工具 + + Slip Tool + 外滑工具 - - Slide Tool - 幻燈片工具 + + Slide Tool + 內滑工具 - - Hand Tool - + + Hand Tool + 手型工具 - - Zoom Tool - + + Zoom Tool + 縮放工具 - - Transition Tool - + + Transition Tool + 轉場工具 - - Enable Snapping - 開啟邊緣吸合/自動對齊 + + Enable Snapping + Enable Snapping - - Preferences - 首選項 + + Preferences + 設定 - - &Help - 幫助(&H) + + &Help + 說明 (&H) - - A&ction Search - 功能查找(&C) + + A&ction Search + A&ction Search - - Send &Feedback... - + + Send &Feedback... + 傳送回饋... (&F) - - &About... - 關於(&A) + + &About... + 關於... (&A) - - + + olive::MainStatusBar - - Welcome to %1 %2 - 歡迎來到 %1 %2 + + Welcome to %1 %2 + 歡迎使用 %1 %2 - - Running %1 background tasks - + + Running %1 background tasks + 正在執行 %1 個背景作業 - - + + olive::MainWindow - - Driver Warning - + + Driver Warning + 驅動程式警告 - - Olive has detected your system is using the Nouveau graphics driver. + + Olive has detected your system is using the Nouveau graphics driver. This driver is known to have stability and performance issues with Olive. It is highly recommended you install the proprietary NVIDIA driver before continuing to use Olive. - + Olive 偵測到您的系統正在使用 Nouveau 圖形驅動程式。 + +已知此驅動程式與 Olive 具有穩定性及效能問題。 強烈建議您先安裝專屬的 NVIDIA 驅動程式,然後再繼續使用 Olive 。 - - + + olive::ManagedDisplayWidget - - Color Space - + + Color Space + 色彩空間 - - No color manager connected - + + No color manager connected + No color manager connected - - Display - + + Display + 顯示 - - View - 視圖 + + View + 檢視 - - Look - + + Look + Look - - (None) - + + (None) + (無) - - OpenColorIO Error - + + OpenColorIO Error + OpenColorIO 錯誤 - - Failed to set color configuration: %1 - + + Failed to set color configuration: %1 + Failed to set color configuration: %1 - - + + olive::ManagedPixelSamplerWidget - - Display - + + Display + 顯示 - - Reference - + + Reference + 參照 - - + + olive::MathNode - - Math - + + Math + 數學運算 - - Perform a mathematical operation between two values. - + + Perform a mathematical operation between two values. + 對兩個值進行數學運算。 - - Method - + + Method + 方式 - - - Value - + + + Value + 數值 - - Add - 添加 + + Add + 相加 - - Subtract - + + Subtract + 相減 - - Multiply - + + Multiply + 相乘 - - Divide - + + Divide + 相除 - - Power - + + Power + 次方 - - + + olive::MatrixGenerator - - Orthographic Matrix - + + Orthographic Matrix + 正交矩陣 - - Ortho - + + Ortho + 正交 - - Generate an orthographic matrix using position, rotation, and scale. - + + Generate an orthographic matrix using position, rotation, and scale. + 使用位置、旋轉和縮放產生正交矩陣。 - - Position - 位置 + + Position + 位置 - - Rotation - 旋轉 + + Rotation + 旋轉 - - Scale - 縮放 + + Scale + 縮放 - - Uniform Scale - 統一縮放的大小 + + Uniform Scale + 統一縮放 - - Anchor Point - 錨點 + + Anchor Point + 錨點 - - + + olive::MediaInput - - Footage - + + Footage + 素材 - - + + olive::MenuShared - - &Project - 項目(&P) + + &Project + 專案 (&P) - - &Sequence - 片段(&S) + + &Sequence + 序列 (&S) - - &Folder - 目錄(&F) + + &Folder + 資料夾 (&F) - - Cu&t - 剪切(&T) + + Cu&t + 剪下 (&T) - - Cop&y - 複製(&Y) + + Cop&y + 複製 (&Y) - - &Paste - 粘帖(&P) + + &Paste + 貼上 (&P) - - Paste Insert - 插入式粘貼 + + Paste Insert + 貼上插入 - - Duplicate - 複製 + + Duplicate + 複製 - - Delete - 刪除 + + Delete + 刪除 - - Ripple Delete - 抽出片段並刪除 + + Ripple Delete + 波紋刪除 - - Split - 切斷 + + Split + Split - - Set In Point - 設置時間的起始標記 + + Set In Point + 設置進入點 - - Set Out Point - 設置時間的結束標記 + + Set Out Point + 設置退出點 - - Reset In Point - 重置時間的起始標記 + + Reset In Point + 重設進入點 - - Reset Out Point - 重置時間的結束標記 + + Reset Out Point + 重設退出點 - - Clear In/Out Point - 清除時間標記 + + Clear In/Out Point + 清除進入/退出點 - - Add Default Transition - 添加預設的轉場效果 + + Add Default Transition + Add Default Transition - - Link/Unlink - 連結/取消連結音頻和視頻 + + Link/Unlink + 連結/取消連結 - - Enable/Disable - 啟用/禁用 + + Enable/Disable + 啟用/停用 - - Nest - 嵌套 + + Nest + Nest - - Frames - + + Frames + 影格 - - Drop Frame - + + Drop Frame + Drop Frame - - Non-Drop Frame - + + Non-Drop Frame + Non-Drop Frame - - Milliseconds - 毫秒 + + Milliseconds + 亳秒 - - Seconds - + + Seconds + - - + + olive::MergeNode - - Merge - + + Merge + 合併 - - Merge two textures together. - + + Merge two textures together. + 將兩個材質合併在一起。 - - Base - + + Base + 基底 - - Blend - + + Blend + 混合 - - + + olive::Node - - Input - + + Input + 輸入 - - Output - + + Output + 輸出 - - General - 一般 + + General + 一般 - - Math - + + Math + 數學運算 - - Color - 顏色 + + Color + 顏色 - - Filter - + + Filter + Filter - - Timeline - 時間軸 + + Timeline + 時間軸 - - Generator - + + Generator + 產生器 - - Channel - + + Channel + 通道 - - Transition - + + Transition + 轉場 - - Uncategorized - + + Uncategorized + 未分類 - - + + olive::NodeInput - - Input - + + Input + 輸入 - - + + olive::NodeOutput - - Output - + + Output + 輸出 - - + + olive::NodePanel - - Node Editor - + + Node Editor + 節點編輯器 - - + + olive::NodeParam - - Value - + + Value + 數值 - - None - + + None + - - Integer - + + Integer + 整數 - - Float - + + Float + 浮點數 - - Rational - + + Rational + 有理數 - - Boolean - + + Boolean + 布林值 - - Color - 顏色 + + Color + 顏色 - - Matrix - + + Matrix + 矩陣 - - Text - + + Text + 文字 - - Font - 字型 + + Font + 字體 - - File - + + File + 檔案 - - Texture - + + Texture + 材質 - - Samples - + + Samples + 取樣 - - Footage - + + Footage + 素材 - - Vector 2D - + + Vector 2D + 向量 2D - - Vector 3D - + + Vector 3D + 向量 3D - - Vector 4D - + + Vector 4D + 向量 4D - - Unknown - + + Unknown + 未知 - - + + olive::NodeParamViewArrayWidget - - + - + + + + + - - %1 elements - + + %1 elements + %1 elements - - + + olive::NodeParamViewConnectedLabel - - Connected to - + + Connected to + 連接至 - - Nothing - + + Nothing + - - Disconnect - + + Disconnect + 斷開連接 - - + + olive::NodeParamViewItem - - %1 (%2) - + + %1 (%2) + %1 (%2) - - + + olive::NodeParamViewItemBody - - %1: - + + %1: + %1: - - + + olive::NodeParamViewKeyframeControl - - Warning - + + Warning + 警告 - - Are you sure you want to disable keyframing on this value? This will clear all existing keyframes. - + + Are you sure you want to disable keyframing on this value? This will clear all existing keyframes. + Are you sure you want to disable keyframing on this value? This will clear all existing keyframes. - - + + olive::NodeTablePanel - - Table View - + + Table View + 表格顯示 - - + + olive::NodeTableView - - Type - 類型 + + Type + 類型 - - Source - + + Source + 來源 - - R/X - + + R/X + R/X - - G/Y - + + G/Y + G/Y - - B/Z - + + B/Z + B/Z - - A/W - + + A/W + A/W - - (unknown) - (未知) + + (unknown) + (未知) - - + + olive::NodeTreeView - - Nodes - + + Nodes + 節點 - - + + olive::NodeView - - Label - + + Label + 標籤 - - Auto-Position - + + Auto-Position + Auto-Position - - Smooth Edges - + + Smooth Edges + 平滑邊緣 - - Filter - + + Filter + Filter - - Show All - + + Show All + 顯示全部 - - Show Selected Blocks Only - + + Show Selected Blocks Only + Show Selected Blocks Only - - Direction - + + Direction + 方向 - - Top to Bottom - + + Top to Bottom + 從上到下 - - Bottom to Top - + + Bottom to Top + 從下到上 - - Left to Right - + + Left to Right + 由左至右 - - Right to Left - + + Right to Left + 由右至左 - - Add - 添加 + + Add + 新增 - - + + olive::PanNode - - - Pan - 左右平衡/平移 + + + Pan + 平移 - - Adjust the stereo panning of an audio source. - + + Adjust the stereo panning of an audio source. + Adjust the stereo panning of an audio source. - - Samples - + + Samples + 取樣 - - + + olive::PanelWidget - - %1: %2 - + + %1: %2 + %1:%2 - - + + olive::ParamPanel - - Parameter Editor - + + Parameter Editor + 參數編輯器 - - (none) - (無) + + (none) + (無) - - (multiple) - (多個) + + (multiple) + (多個) - - + + olive::PathWidget - - Browse - 瀏覽 + + Browse + 瀏覽 - - Browse for path - + + Browse for path + 瀏覽路徑 - - + + olive::PixelAspectRatioComboBox - - Set Custom Pixel Aspect Ratio - + + Set Custom Pixel Aspect Ratio + Set Custom Pixel Aspect Ratio - - Custom... - + + Custom... + 自訂... - - Custom (%1) - + + Custom (%1) + 自訂 (%1) - - + + olive::PixelSamplerPanel - - Pixel Sampler - + + Pixel Sampler + Pixel Sampler - - + + olive::PixelSamplerWidget - - Color - 顏色 + + Color + 顏色 - - <html><font color='#FF8080'>R: %1</font><br><font color='#80FF80'>G: %2</font><br><font color='#8080FF'>B: %3</font><br>A: %4</html> - + + <html><font color='#FF8080'>R: %1</font><br><font color='#80FF80'>G: %2</font><br><font color='#8080FF'>B: %3</font><br>A: %4</html> + <html><font color='#FF8080'>R: %1</font><br><font color='#80FF80'>G: %2</font><br><font color='#8080FF'>B: %3</font><br>A: %4</html> - - + + olive::PolygonGenerator - - Polygon - + + Polygon + 多邊形 - - Generate a 2D polygon of any amount of points. - + + Generate a 2D polygon of any amount of points. + Generate a 2D polygon of any amount of points. - - Points - + + Points + - - Color - 顏色 + + Color + 顏色 - - + + olive::PreCacheTask - - Pre-caching %1:%2 - + + Pre-caching %1:%2 + 預先快取 %1:%2 - - + + olive::PreferencesAppearanceTab - - Theme - 主題 + + Theme + 主題 - - Node Color Scheme - + + Node Color Scheme + 節點配色方案 - - + + olive::PreferencesAudioTab - - Output Device: - 輸出設備: + + Output Device: + 輸出裝置: - - Input Device: - 輸入設備: + + Input Device: + 輸入裝置: - - Sample Rate: - 採樣率: + + Sample Rate: + 取樣頻率: - - Audio Recording: - 音頻錄製: + + Audio Recording: + 音訊錄製: - - Mono - 單聲道 + + Mono + 單聲道 - - Stereo - 立體聲 + + Stereo + 立體聲 - - Refresh Devices - + + Refresh Devices + 重新整理裝置 - - Please wait... - + + Please wait... + 請稍候... - - Default - 預設 + + Default + 預設 - - + + olive::PreferencesBehaviorTab - - Behavior - 行為 + + Behavior + 行為 - - General - 一般 + + General + 一般 - - Enable hover focus - + + Enable hover focus + 啟用懸停聚焦 - - Panels will be considered focused when the mouse cursor is over them without having to click them. - + + Panels will be considered focused when the mouse cursor is over them without having to click them. + Panels will be considered focused when the mouse cursor is over them without having to click them. - - Scroll wheel zooms by default instead of scrolling - + + Scroll wheel zooms by default instead of scrolling + Scroll wheel zooms by default instead of scrolling - - Holding CTRL while using Olive toggles this setting - + + Holding CTRL while using Olive toggles this setting + Holding CTRL while using Olive toggles this setting - - Audio - 音頻 + + Audio + 音訊 - - Enable audio scrubbing - + + Enable audio scrubbing + Enable audio scrubbing - - Timeline - 時間軸 + + Timeline + 時間軸 - - Auto-Seek to Imported Clips - 自動尋找並導入剪輯 + + Auto-Seek to Imported Clips + Auto-Seek to Imported Clips - - Edit Tool Also Seeks - + + Edit Tool Also Seeks + Edit Tool Also Seeks - - Edit Tool Selects Links - + + Edit Tool Selects Links + Edit Tool Selects Links - - Enable Drag Files to Timeline - + + Enable Drag Files to Timeline + Enable Drag Files to Timeline - - Invert Timeline Scroll Axes - 反轉時間軸滾動軸 + + Invert Timeline Scroll Axes + Invert Timeline Scroll Axes - - Hold ALT on any UI element to switch scrolling axes - + + Hold ALT on any UI element to switch scrolling axes + Hold ALT on any UI element to switch scrolling axes - - Seek Also Selects - + + Seek Also Selects + Seek Also Selects - - Seek to the End of Pastes - + + Seek to the End of Pastes + Seek to the End of Pastes - - Selecting Also Seeks - 選擇並查找 + + Selecting Also Seeks + Selecting Also Seeks - - Playback - 回放 + + Playback + 回放 - - Ask For Name When Setting Marker - 設置標記時詢問名稱 + + Ask For Name When Setting Marker + 設定標記時詢問名稱 - - Automatically rewind at the end of a sequence - + + Automatically rewind at the end of a sequence + Automatically rewind at the end of a sequence - - Project - 項目 + + Project + 專案 - - Drop Files on Media to Replace - 拖放檔案以代替媒體 + + Drop Files on Media to Replace + Drop Files on Media to Replace - - Nodes - + + Nodes + 節點 - - Add Default Effects to New Clips - 添加預設效果到新的剪輯 + + Add Default Effects to New Clips + Add Default Effects to New Clips - - Auto-Scale By Default - 預設情況下自動縮放 + + Auto-Scale By Default + Auto-Scale By Default - - Splitting Clips Copies Dependencies - + + Splitting Clips Copies Dependencies + Splitting Clips Copies Dependencies - - Multiple clips can share the same nodes. Disable this to automatically share node dependencies among clips when copying or splitting them. - + + Multiple clips can share the same nodes. Disable this to automatically share node dependencies among clips when copying or splitting them. + Multiple clips can share the same nodes. Disable this to automatically share node dependencies among clips when copying or splitting them. - - + + olive::PreferencesDialog - - Preferences - 首選項 + + Preferences + 設定 - - General - 一般 + + General + 一般 - - Appearance - 外觀 + + Appearance + 外觀 - - Behavior - 行為 + + Behavior + 行為 - - Disk - + + Disk + 硬碟 - - Audio - 音頻 + + Audio + 音訊 - - Keyboard - 鍵盤 + + Keyboard + 鍵盤 - - + + olive::PreferencesDiskTab - - Disk Management - + + Disk Management + 硬碟管理 - - Disk Cache Location: - + + Disk Cache Location: + 磁碟快取位置: - - Disk Cache Settings - + + Disk Cache Settings + 磁碟快取設定 - - Cache Behavior - + + Cache Behavior + 快取行為 - - Cache Ahead: - + + Cache Ahead: + Cache Ahead: - - - %1 seconds - + + + %1 seconds + %1 秒 - - Cache Behind: - + + Cache Behind: + Cache Behind: - - Disk Cache - + + Disk Cache + 磁碟快取 - - Failed to set disk cache location. Access was denied. - + + Failed to set disk cache location. Access was denied. + 無法設定磁碟快取位置。 存取遭拒。 - - + + olive::PreferencesGeneralTab - - Language: - 語言: + + Language: + 語言: - - Auto-Scroll Method: - + + Auto-Scroll Method: + Auto-Scroll Method: - - None - + + None + - - Page Scrolling - + + Page Scrolling + 頁面捲動 - - Smooth Scrolling - + + Smooth Scrolling + 平滑捲動 - - Rectified Waveforms: - + + Rectified Waveforms: + Rectified Waveforms: - - Default Still Image Length: - + + Default Still Image Length: + Default Still Image Length: - - %1 seconds - + + %1 seconds + %1 秒 - - %1 (%2) - + + %1 (%2) + %1(%2) - - + + olive::PreferencesKeyboardTab - - Search for action or shortcut - 搜索功能或者快捷鍵 + + Search for action or shortcut + 搜尋動作或快捷鍵 - - Action - 功能 + + Action + 動作 - - Shortcut - 快捷鍵 + + Shortcut + 快捷鍵 - - Import - 匯入 + + Import + 匯入 - - Export - 匯出 + + Export + 匯出 - - Reset Selected - 重新選擇 + + Reset Selected + 重設選取項目 - - Reset All - 全部重設 + + Reset All + 重設全部 - - Confirm Reset All Shortcuts - 確認重置所有快捷鍵 + + Confirm Reset All Shortcuts + Confirm Reset All Shortcuts - - Are you sure you wish to reset all keyboard shortcuts to their defaults? - 您確定要將所有鍵盤快捷鍵重置為預設值嗎? + + Are you sure you wish to reset all keyboard shortcuts to their defaults? + Are you sure you wish to reset all keyboard shortcuts to their defaults? - - Import Keyboard Shortcuts - 導入鍵盤快捷鍵配置 + + Import Keyboard Shortcuts + 匯入鍵盤快捷鍵 - - - Error saving shortcuts - 保存鍵盤快捷鍵是發生錯誤 + + + Error saving shortcuts + 儲存快捷鍵時發生錯誤 - - Failed to open file for reading - 無法讀取檔案 + + Failed to open file for reading + 開啟檔案以讀取時失敗 - - Export Keyboard Shortcuts - 匯出鍵盤快捷鍵配置 + + Export Keyboard Shortcuts + 匯出鍵盤快捷鍵 - - Export Shortcuts - 匯出快捷鍵 + + Export Shortcuts + 匯出快捷鍵 - - Shortcuts exported successfully - 快捷鍵成功匯出 + + Shortcuts exported successfully + 成功匯出快捷鍵 - - Failed to open file for writing - 無法寫入檔案 + + Failed to open file for writing + 開啟檔案以寫入時失敗 - - + + olive::ProgressDialog - - Cancel - 取消 + + Cancel + 取消 - - + + olive::Project - - - (untitled) - + + + (untitled) + (未命名) - - + + olive::ProjectExplorer - - &New - 新建(&N) + + &New + 新增 (&N) - - &Import... - 匯入(&I) + + &Import... + 匯入... (&I) - - &Project Properties... - + + &Project Properties... + 專案屬性... (&P) - - Open in New Tab - + + Open in New Tab + 在新分頁開啟 - - Open in New Window - + + Open in New Window + 在新視窗開啟 - - Reveal in Explorer - 在瀏覽器中預覽 + + Reveal in Explorer + 在檔案總管中顯示 - - Reveal in Finder - 在查找當中預覽 + + Reveal in Finder + 在 Finder 中顯示 - - Reveal in File Manager - 在檔案管理器中預覽 + + Reveal in File Manager + 在檔案總管中顯示 - - Pre-Cache - + + Pre-Cache + 預先快取 - - No sequences exist in project - + + No sequences exist in project + 專案中沒有序列 - - For "%1" - + + For "%1" + For "%1" - - P&roperties - + + P&roperties + 屬性 (&R) - - Confirm Footage Deletion - + + Confirm Footage Deletion + 確認刪除素材 - - The footage "%1" is currently used in the following sequence(s): + + The footage "%1" is currently used in the following sequence(s): %2 What would you like to do with these clips? - + The footage "%1" is currently used in the following sequence(s): + +%2 +What would you like to do with these clips? - - Offline Footage - + + Offline Footage + 離線素材 - - Delete Clips - + + Delete Clips + 刪除片段 - - + + olive::ProjectExplorerNavigation - - Go to parent folder - + + Go to parent folder + 前往父資料夾 - - + + olive::ProjectImportErrorDialog - - Import Error - + + Import Error + 匯入錯誤 - - The following files failed to import. Olive likely does not support their formats. - + + The following files failed to import. Olive likely does not support their formats. + 下列檔案無法匯入。 Olive 可能不支援其格式。 - - + + olive::ProjectImportTask - - Importing %1 files - + + Importing %1 files + 正在匯入 %1 個檔案 - - + + olive::ProjectLoadBaseTask - - Loading '%1' - + + Loading '%1' + 正在載入 '%1' - - + + olive::ProjectLoadTask - - This project is newer than this version of Olive and cannot be opened. - + + This project is newer than this version of Olive and cannot be opened. + This project is newer than this version of Olive and cannot be opened. - - - This project is from a version of Olive that is no longer supported in this version. - + + + This project is from a version of Olive that is no longer supported in this version. + This project is from a version of Olive that is no longer supported in this version. - - Failed to read file "%1" for reading. - + + Failed to read file "%1" for reading. + Failed to read file "%1" for reading. - - + + olive::ProjectPanel - - Folder - + + Folder + 資料夾 - - Project - 項目 + + Project + 專案 - - (none) - (無) + + (none) + (無) - - + + olive::ProjectPropertiesDialog - - Project Properties for '%1' - + + Project Properties for '%1' + Project Properties for '%1' - - OpenColorIO Configuration: - + + OpenColorIO Configuration: + OpenColorIO 配置: - - (default) - + + (default) + (預設) - - Default Input Color Space: - + + Default Input Color Space: + Default Input Color Space: - - Browse - 瀏覽 + + Browse + 瀏覽 - - Color Management - + + Color Management + 色彩管理 - - Use Default Location - + + Use Default Location + 使用預設位置 - - Store Alongside Project - + + Store Alongside Project + Store Alongside Project - - Use Custom Location: - + + Use Custom Location: + Use Custom Location: - - Disk Cache Settings - + + Disk Cache Settings + 磁碟快取設定 - - - "Store alignside project" functionality not implemented yet - + + + "Store alignside project" functionality not implemented yet + "Store alignside project" functionality not implemented yet - - Disk Cache - + + Disk Cache + 磁碟快取 - - OpenColorIO Config Error - + + OpenColorIO Config Error + OpenColorIO 配置錯誤 - - Failed to set OpenColorIO configuration: %1 - + + Failed to set OpenColorIO configuration: %1 + Failed to set OpenColorIO configuration: %1 - - Invalid path - + + Invalid path + 路徑無效 - - The cache path is invalid. Please check it and try again. - + + The cache path is invalid. Please check it and try again. + The cache path is invalid. Please check it and try again. - - Browse for OpenColorIO configuration - + + Browse for OpenColorIO configuration + Browse for OpenColorIO configuration - - + + olive::ProjectSaveTask - - Saving '%1' - + + Saving '%1' + 正在儲存 '%1' - - Failed to write XML data - + + Failed to write XML data + 無法寫入 XML 資料 - - Failed to overwrite "%1". Project has been saved as "%2" instead. - + + Failed to overwrite "%1". Project has been saved as "%2" instead. + 無法覆寫 "%1"。已將專案儲存成 "%2"。 - - Failed to open temporary file "%1" for writing. - + + Failed to open temporary file "%1" for writing. + 無法開啟暫存檔 "%1" 以進行寫入。 - - + + olive::ProjectToolbar - - New... - + + New... + 新... - - Open Project - 打開項目 + + Open Project + 開啟專案 - - Save Project - 保存項目 + + Save Project + 儲存專案 - - Undo - 撤銷 + + Undo + 復原 - - Redo - 重做 + + Redo + 取消復原 - - Search media, markers, etc. - 搜索媒體,標記等. + + Search media, markers, etc. + 搜尋媒體、標記... - - Switch to Tree View - + + Switch to Tree View + 切換至樹狀檢視 - - Switch to List View - + + Switch to List View + 切換至清單檢視 - - Switch to Icon View - + + Switch to Icon View + 切換至圖示檢視 - - + + olive::ProjectViewModel - - Name - 名稱 + + Name + 名稱 - - Duration - 持續時間 + + Duration + 持續時間 - - Rate - 速率 + + Rate + 評分 - - Move Items - + + Move Items + 移動項目 - - + + olive::RenderCancelDialog - - Waiting for workers to finish... - + + Waiting for workers to finish... + Waiting for workers to finish... - - Renderer - + + Renderer + 渲染器 - - + + olive::RichTextDialog - - B - + + B + B - - Bold - 粗體 + + Bold + 粗體 - - I - + + I + I - - Italic - + + Italic + 斜體 - - U - + + U + U - - Underline - + + Underline + 底線 - - S - + + S + S - - Strikethrough - + + Strikethrough + 刪除線 - - Font Family - + + Font Family + 字體 - - Font Size - + + Font Size + 字體大小 - - L - + + L + L - - Left Align - + + Left Align + 置左對齊 - - C - + + C + C - - Center Align - + + Center Align + 置中對齊 - - R - + + R + R - - Right Align - + + Right Align + 置右對齊 - - J - + + J + J - - Justify Align - + + Justify Align + 左右對齊 - - + + olive::SaveOTIOTask - - Exporting project to OpenTimelineIO - + + Exporting project to OpenTimelineIO + 正在匯出專案至 OpenTimelineIO - - Project contains no sequences to export. - + + Project contains no sequences to export. + Project contains no sequences to export. - - Failed to serialize sequence "%1" - + + Failed to serialize sequence "%1" + Failed to serialize sequence "%1" - - + + olive::ScopePanel - - Waveform - + + Waveform + 波形 - - Histogram - + + Histogram + Histogram - - Scope - + + Scope + Scope - - + + olive::SequenceDialog - - Name: - 名稱: + + Name: + 名稱: - - New Sequence - 新片段 + + New Sequence + 新序列 - - Editing "%1" - 編輯中 "%1" + + Editing "%1" + 正在編輯 "%1" - - Error editing Sequence - + + Error editing Sequence + 編輯序列時發生錯誤 - - Please enter a name for this Sequence. - + + Please enter a name for this Sequence. + 請輸入此序列的名稱。 - - + + olive::SequenceDialogParameterTab - - Video - 視頻 + + Video + 視訊 - - Width: - 寬度: + + Width: + 寬度: - - Height: - 高度: + + Height: + 高度: - - Frame Rate: - + + Frame Rate: + 影格率: - - Pixel Aspect Ratio: - 像素長寬比 + + Pixel Aspect Ratio: + 像素長寬比: - - Interlacing: - + + Interlacing: + Interlacing: - - Audio - 音頻 + + Audio + 音訊 - - Sample Rate: - 採樣率: + + Sample Rate: + 取樣頻率: - - Channels: - + + Channels: + 通道: - - Preview - + + Preview + 預覽 - - Resolution: - + + Resolution: + 解析度: - - Quality: - + + Quality: + 品質: - - Save Preset - + + Save Preset + 儲存預設檔 - - (%1x%2) - + + (%1x%2) + (%1x%2) - - + + olive::SequenceDialogPresetTab - - Preset - + + Preset + 預設 - - My Presets - + + My Presets + 我的預設檔 - - 4K UHD - + + 4K UHD + 4K UHD - - 1080p - 1080p + + 1080p + 1080p - - 720p - 720p + + 720p + 720p - - NTSC - + + NTSC + NTSC - - PAL - + + PAL + PAL - - %1 23.976 FPS - + + %1 23.976 FPS + %1 23.976 FPS - - %1 25 FPS - + + %1 25 FPS + %1 25 FPS - - %1 29.97 FPS - + + %1 29.97 FPS + %1 29.97 FPS - - %1 50 FPS - + + %1 50 FPS + %1 50 FPS - - %1 59.94 FPS - + + %1 59.94 FPS + %1 59.94 FPS - - %1 Standard - + + %1 Standard + %1 標準 - - %1 Widescreen - + + %1 Widescreen + %1 寬螢幕 - - Delete Preset - + + Delete Preset + 刪除預設檔 - - + + olive::SequenceViewerPanel - - Sequence Viewer - + + Sequence Viewer + 序列檢視器 - - + + olive::SliderBase - - Invalid Value - + + Invalid Value + 數值無效 - - The entered value is not valid for this field. - + + The entered value is not valid for this field. + 輸入的值對這個欄位無效。 - - + + olive::SolidGenerator - - Solid - + + Solid + 純色 - - Generate a solid color. - + + Generate a solid color. + 產生實心顏色。 - - Color - 顏色 + + Color + 顏色 - - + + olive::StringSlider - - (none) - (無) + + (none) + (無) - - + + olive::StrokeFilterNode - - Stroke - + + Stroke + 外框 - - Creates a stroke outline around an image. - + + Creates a stroke outline around an image. + 在圖片周圍產生邊框。 - - Input - + + Input + 輸入 - - Color - 顏色 + + Color + 顏色 - - Radius - + + Radius + 半徑 - - Opacity - 透明度 + + Opacity + 透明度 - - Inner - + + Inner + - - + + olive::Task - - Task - + + Task + 工作 - - Unknown error - + + Unknown error + 未知錯誤 - - + + olive::TaskDialog - - Task Failed - + + Task Failed + 工作失敗 - - + + olive::TaskManagerPanel - - Task Manager - + + Task Manager + 工作管理員 - - + + olive::TaskViewItem - - Error: %1 - + + Error: %1 + 錯誤:%1 - - + + olive::TextGenerator - - Sample Text - 文字樣本 + + Sample Text + 範例文字 - - - Text - + + + Text + 文字 - - Generate rich text. - + + Generate rich text. + 產生 RTF 文字。 - - Font - 字型 + + Font + 字體 - - Font Size - + + Font Size + 字體大小 - - Color - 顏色 + + Color + 顏色 - - Vertical Align - + + Vertical Align + 垂直對齊 - - Top - 頂部 + + Top + 上方 - - Center - + + Center + 中間 - - Bottom - 底下 + + Bottom + 下方 - - + + olive::TimeBasedPanel - - (none) - (無) + + (none) + (無) - - + + olive::TimeBasedWidget - - Set Marker - 設置標記 + + Set Marker + 設置標記 - - Marker name: - + + Marker name: + 標記名稱: - - + + olive::TimeInput - - Time - + + Time + 時間 - - Generates the time (in seconds) at this frame - + + Generates the time (in seconds) at this frame + Generates the time (in seconds) at this frame - - + + olive::TimelinePanel - - Timeline - 時間軸 + + Timeline + 時間軸 - - + + olive::TimelineWidget - - - Properties - 屬性 + + + Properties + 屬性 - - Use Audio Time Units - + + Use Audio Time Units + Use Audio Time Units - - + + olive::ToolPanel - - Tools - + + Tools + 工具 - - + + olive::Toolbar - - Pointer Tool - 選擇/移動/預設 + + Pointer Tool + 選擇工具 - - Edit Tool - 選擇部分 + + Edit Tool + 編輯工具 - - Ripple Tool - 漣漪的工具 + + Ripple Tool + 波紋工具 - - Rolling Tool - + + Rolling Tool + 滾動工具 - - Razor Tool - 剪刀 + + Razor Tool + 分割工具 - - Slip Tool - 滑動工具 + + Slip Tool + 外滑工具 - - Slide Tool - 幻燈片工具 + + Slide Tool + 內滑工具 - - Hand Tool - + + Hand Tool + 手型工具 - - Zoom Tool - + + Zoom Tool + 縮放工具 - - Transition Tool - + + Transition Tool + 轉場工具 - - Record Tool - + + Record Tool + 錄製工具 - - Add Tool - + + Add Tool + 新增工具 - - Toggle Snapping - + + Toggle Snapping + Toggle Snapping - - + + olive::TrackOutput - - Track - + + Track + 軌道 - - Node for representing and processing a single array of Blocks sorted by time. Also represents the end of a Sequence. - + + Node for representing and processing a single array of Blocks sorted by time. Also represents the end of a Sequence. + Node for representing and processing a single array of Blocks sorted by time. Also represents the end of a Sequence. - - Blocks - + + Blocks + Blocks - - Muted - + + Muted + 已靜音 - - Video %1 - + + Video %1 + 視訊 %1 - - Audio %1 - + + Audio %1 + 音訊 %1 - - Subtitle %1 - + + Subtitle %1 + 字幕 %1 - - Track %1 - + + Track %1 + 軌道 %1 - - + + olive::TrackViewItem - - M - + + M + M - - L - + + L + L - - + + olive::TransitionBlock - - From - + + From + - - To - + + To + - - Curve - + + Curve + 曲線 - - Linear - 線性 + + Linear + 線性 - - Exponential - + + Exponential + 指數 - - Logarithmic - + + Logarithmic + 對數 - - + + olive::TrigonometryNode - - Trigonometry - + + Trigonometry + 三角函數 - - Perform a trigonometry operation on a value. - + + Perform a trigonometry operation on a value. + 對數值進行三角函數運算。 - - Sine - 正弦 + + Sine + 正弦函數 - - Cosine - + + Cosine + 餘弦函數 - - Tangent - + + Tangent + 正切函數 - - Inverse Sine - + + Inverse Sine + 反正弦函數 - - Inverse Cosine - + + Inverse Cosine + 反餘弦函數 - - Inverse Tangent - + + Inverse Tangent + 反正切函數 - - Hyperbolic Sine - + + Hyperbolic Sine + 雙曲正弦函數 - - Hyperbolic Cosine - + + Hyperbolic Cosine + 雙曲餘弦函數 - - Hyperbolic Tangent - + + Hyperbolic Tangent + 雙曲正切函數 - - Method - + + Method + 方式 - - + + olive::VideoDividerComboBox - - Full - + + Full + 完整 - - 1/%1 - 144p {1/%1?} + + 1/%1 + 1/%1 - - + + olive::VideoInput - - Video Input - + + Video Input + 視訊輸入 - - Video - 視頻 + + Video + 視訊 - - Import a video footage stream. - + + Import a video footage stream. + Import a video footage stream. - - + + olive::VideoStreamProperties - - Pixel Aspect: - + + Pixel Aspect: + 像素長寬比: - - Interlacing: - + + Interlacing: + 隔行: - - Color Space: - + + Color Space: + 色彩空間: - - Default (%1) - + + Default (%1) + 預設 (%1) - - Premultiplied Alpha - + + Premultiplied Alpha + Premultiplied Alpha - - Image Sequence - + + Image Sequence + 圖片序列 - - Start Index: - + + Start Index: + Start Index: - - End Index: - + + End Index: + End Index: - - Frame Rate: - + + Frame Rate: + 影格率: - - Invalid Configuration - + + Invalid Configuration + 配置無效 - - Image sequence end index must be a value higher than the start index. - + + Image sequence end index must be a value higher than the start index. + Image sequence end index must be a value higher than the start index. - - + + olive::ViewerOutput - - Viewer - + + Viewer + 檢視器 - - Interface between a Viewer panel and the node system. - + + Interface between a Viewer panel and the node system. + Interface between a Viewer panel and the node system. - - Texture - + + Texture + 材質 - - Samples - + + Samples + 取樣 - - Video Tracks - + + Video Tracks + 視訊軌 - - Audio Tracks - + + Audio Tracks + 音訊軌 - - Subtitle Tracks - + + Subtitle Tracks + 字幕軌 - - + + olive::ViewerPanel - - Viewer - + + Viewer + 檢視器 - - + + olive::ViewerWidget - - Error - 錯誤 + + Error + 錯誤 - - No in or out points are set to cache. - + + No in or out points are set to cache. + No in or out points are set to cache. - - - Safe Margins - + + + Safe Margins + Safe Margins - - Zoom - 縮放 + + Zoom + 縮放 - - Fit - 適合 + + Fit + Fit - - %1% - + + %1% + %1% - - Full Screen - 全屏 + + Full Screen + 全螢幕 - - Screen %1: %2x%3 - 放映 %1: %2x%3 + + Screen %1: %2x%3 + 畫面 %1:%2x%3 - - Deinterlace - + + Deinterlace + Deinterlace - - Scopes - + + Scopes + Scopes - - Cache - + + Cache + 快取 - - Auto-Cache - + + Auto-Cache + 自動快取 - - Pause Auto-Cache During Playback - + + Pause Auto-Cache During Playback + 播放時暫停自動快取 - - Cache Entire Sequence - + + Cache Entire Sequence + 快取整個序列 - - Cache Sequence In/Out - + + Cache Sequence In/Out + 快取序列 進入點/退出點 - - Off - 關閉 + + Off + - - On - + + On + - - Custom Aspect - + + Custom Aspect + Custom Aspect - - Show Audio Waveform - + + Show Audio Waveform + 顯示音訊波形 - - + + olive::VolumeNode - - - Volume - 音量 + + + Volume + 音量 - - Adjusts the volume of an audio source. - + + Adjusts the volume of an audio source. + 調整音訊來源的音量。 - - Samples - + + Samples + 取樣 - + From e98ac256c66676091afe98fc5ce2dc61bc116b49 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 3 Dec 2021 12:03:33 -0800 Subject: [PATCH 41/94] reorganized some lines to prevent a crash --- app/widget/nodeview/nodeview.cpp | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index da10d899b..955228cbe 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -310,8 +310,6 @@ void NodeView::mousePressEvent(QMouseEvent *event) if (event->button() == Qt::LeftButton) { // Sane defaults - create_edge_output_item_ = nullptr; - create_edge_input_item_ = nullptr; create_edge_already_exists_ = false; create_edge_from_output_ = true; @@ -502,16 +500,15 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) create_edge_ = nullptr; - if (create_edge_output_item_ && create_edge_input_item_) { - // Clear highlight if we set one + // Clear highlight if we set one + if (create_edge_output_item_) { + create_edge_output_item_->SetHighlighted(false); + } + if (create_edge_input_item_) { create_edge_input_item_->SetHighlighted(false); + } - // Collapse any items we expanded - for (auto it=create_edge_expanded_items_.crbegin(); it!=create_edge_expanded_items_.crend(); it++) { - CollapseItem(*it); - } - create_edge_expanded_items_.clear(); - + if (create_edge_output_item_ && create_edge_input_item_) { NodeInput &creating_input = create_edge_input_; if (creating_input.IsValid()) { // Make connection @@ -543,11 +540,17 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) creating_input.Reset(); } - - create_edge_output_item_ = nullptr; - create_edge_input_item_ = nullptr; } + create_edge_output_item_ = nullptr; + create_edge_input_item_ = nullptr; + + // Collapse any items we expanded + for (auto it=create_edge_expanded_items_.crbegin(); it!=create_edge_expanded_items_.crend(); it++) { + CollapseItem(*it); + } + create_edge_expanded_items_.clear(); + Core::instance()->undo_stack()->pushIfHasChildren(command); } @@ -1109,7 +1112,8 @@ void NodeView::PositionNewEdge(const QPoint &pos) create_edge_expanded_items_.resize(i + 1); // Expand item if possible - if (item_at_cursor && item_at_cursor->CanBeExpanded() && !item_at_cursor->IsExpanded()) { + if (item_at_cursor && item_at_cursor->CanBeExpanded() && !item_at_cursor->IsExpanded() + && (create_edge_from_output_ || !item_at_cursor->IsOutputItem())) { ExpandItem(item_at_cursor); create_edge_expanded_items_.append(item_at_cursor); } From 090fbb09d06a6252b8b4f7c35290cc5d186c2004 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 8 Dec 2021 14:08:12 -0800 Subject: [PATCH 42/94] contexts in param view --- app/dialog/nodegroup/nodegroupdialog.cpp | 4 +- app/node/output/viewer/viewer.cpp | 4 +- app/panel/param/param.cpp | 23 +- app/panel/param/param.h | 2 + app/ui/icons/icons.cpp | 2 + app/ui/icons/icons.h | 1 + app/widget/nodeparamview/CMakeLists.txt | 24 +- app/widget/nodeparamview/nodeparamview.cpp | 227 ++++++++++++++---- app/widget/nodeparamview/nodeparamview.h | 23 +- .../nodeparamview/nodeparamviewcontext.cpp | 92 +++++++ .../nodeparamview/nodeparamviewcontext.h | 90 +++++++ .../nodeparamview/nodeparamviewdockarea.cpp | 14 ++ .../nodeparamview/nodeparamviewdockarea.h | 2 + .../nodeparamview/nodeparamviewitem.cpp | 224 ++++------------- app/widget/nodeparamview/nodeparamviewitem.h | 98 ++------ .../nodeparamview/nodeparamviewitembase.cpp | 114 +++++++++ .../nodeparamview/nodeparamviewitembase.h | 93 +++++++ .../nodeparamviewitemtitlebar.cpp | 90 +++++++ .../nodeparamview/nodeparamviewitemtitlebar.h | 89 +++++++ app/widget/nodeview/nodeview.cpp | 4 +- app/widget/nodeview/nodeviewitem.cpp | 23 +- app/widget/nodeview/nodeviewitem.h | 2 + app/window/mainwindow/mainwindow.cpp | 1 + 23 files changed, 887 insertions(+), 359 deletions(-) create mode 100644 app/widget/nodeparamview/nodeparamviewcontext.cpp create mode 100644 app/widget/nodeparamview/nodeparamviewcontext.h create mode 100644 app/widget/nodeparamview/nodeparamviewitembase.cpp create mode 100644 app/widget/nodeparamview/nodeparamviewitembase.h create mode 100644 app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp create mode 100644 app/widget/nodeparamview/nodeparamviewitemtitlebar.h diff --git a/app/dialog/nodegroup/nodegroupdialog.cpp b/app/dialog/nodegroup/nodegroupdialog.cpp index 9fd1fa4af..951c45339 100644 --- a/app/dialog/nodegroup/nodegroupdialog.cpp +++ b/app/dialog/nodegroup/nodegroupdialog.cpp @@ -66,9 +66,7 @@ NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : param_view->SetInputChecked(it.value(), true); } - connect(node_view, &NodeView::NodesSelected, param_view, &NodeParamView::SelectNodes); - connect(node_view, &NodeView::NodesDeselected, param_view, &NodeParamView::DeselectNodes); - node_view->SelectAll(); + param_view->SetContexts({group}); row++; diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 751cdbb62..87707e2c1 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -47,10 +47,10 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream video_cache_enabled_(true), audio_cache_enabled_(true) { - AddInput(kVideoParamsInput, NodeValue::kVideoParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray)); + AddInput(kVideoParamsInput, NodeValue::kVideoParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); SetInputProperty(kVideoParamsInput, QStringLiteral("mask"), QVariant::fromValue(kVideoParamEditMask)); - AddInput(kAudioParamsInput, NodeValue::kAudioParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray)); + AddInput(kAudioParamsInput, NodeValue::kAudioParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); if (create_buffer_inputs) { AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index c90875d00..dc8f5ea45 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -38,16 +38,12 @@ ParamPanel::ParamPanel(QWidget* parent) : void ParamPanel::SelectNodes(const QVector &nodes) { - static_cast(GetTimeBasedWidget())->SelectNodes(nodes); - - Retranslate(); + //static_cast(GetTimeBasedWidget())->SelectNodes(nodes); } void ParamPanel::DeselectNodes(const QVector &nodes) { - static_cast(GetTimeBasedWidget())->DeselectNodes(nodes); - - Retranslate(); + //static_cast(GetTimeBasedWidget())->DeselectNodes(nodes); } void ParamPanel::DeleteSelected() @@ -65,19 +61,14 @@ void ParamPanel::DeselectAll() static_cast(GetTimeBasedWidget())->DeselectAll(); } +void ParamPanel::SetContexts(const QVector &contexts) +{ + static_cast(GetTimeBasedWidget())->SetContexts(contexts); +} + void ParamPanel::Retranslate() { SetTitle(tr("Parameter Editor")); - - NodeParamView* view = static_cast(GetTimeBasedWidget()); - - if (view->GetItemMap().isEmpty()) { - SetSubtitle(tr("(none)")); - } else if (view->GetItemMap().size() == 1) { - SetSubtitle(view->GetItemMap().firstKey()->Name()); - } else { - SetSubtitle(tr("(multiple)")); - } } } diff --git a/app/panel/param/param.h b/app/panel/param/param.h index a20c3310a..c6af767f9 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -43,6 +43,8 @@ public slots: virtual void DeselectAll() override; + void SetContexts(const QVector &contexts); + signals: void RequestSelectNode(const QVector& target); diff --git a/app/ui/icons/icons.cpp b/app/ui/icons/icons.cpp index 322237b65..20ecdaeb2 100644 --- a/app/ui/icons/icons.cpp +++ b/app/ui/icons/icons.cpp @@ -76,6 +76,7 @@ QIcon icon::Clock; QIcon icon::Diamond; QIcon icon::Plus; QIcon icon::Minus; +QIcon icon::AddEffect; void icon::LoadAll(const QString& theme) { @@ -129,6 +130,7 @@ void icon::LoadAll(const QString& theme) Diamond = Create(theme, "diamond"); Plus = Create(theme, "plus"); Minus = Create(theme, "minus"); + AddEffect = Create(theme, "add-effect"); } QIcon icon::Create(const QString& theme, const QString &name) diff --git a/app/ui/icons/icons.h b/app/ui/icons/icons.h index c8c1788a0..2bca32bb5 100644 --- a/app/ui/icons/icons.h +++ b/app/ui/icons/icons.h @@ -86,6 +86,7 @@ extern QIcon Clock; extern QIcon Diamond; extern QIcon Plus; extern QIcon Minus; +extern QIcon AddEffect; /** * @brief Create an icon object loaded from file diff --git a/app/widget/nodeparamview/CMakeLists.txt b/app/widget/nodeparamview/CMakeLists.txt index 5d0f37f40..067ffad1f 100644 --- a/app/widget/nodeparamview/CMakeLists.txt +++ b/app/widget/nodeparamview/CMakeLists.txt @@ -16,23 +16,29 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - widget/nodeparamview/nodeparamview.h widget/nodeparamview/nodeparamview.cpp - widget/nodeparamview/nodeparamviewarraywidget.h + widget/nodeparamview/nodeparamview.h widget/nodeparamview/nodeparamviewarraywidget.cpp - widget/nodeparamview/nodeparamviewconnectedlabel.h + widget/nodeparamview/nodeparamviewarraywidget.h widget/nodeparamview/nodeparamviewconnectedlabel.cpp - widget/nodeparamview/nodeparamviewdockarea.h + widget/nodeparamview/nodeparamviewconnectedlabel.h + widget/nodeparamview/nodeparamviewcontext.cpp + widget/nodeparamview/nodeparamviewcontext.h widget/nodeparamview/nodeparamviewdockarea.cpp - widget/nodeparamview/nodeparamviewitem.h + widget/nodeparamview/nodeparamviewdockarea.h widget/nodeparamview/nodeparamviewitem.cpp - widget/nodeparamview/nodeparamviewkeyframecontrol.h + widget/nodeparamview/nodeparamviewitem.h + widget/nodeparamview/nodeparamviewitembase.cpp + widget/nodeparamview/nodeparamviewitembase.h + widget/nodeparamview/nodeparamviewitemtitlebar.cpp + widget/nodeparamview/nodeparamviewitemtitlebar.h widget/nodeparamview/nodeparamviewkeyframecontrol.cpp - widget/nodeparamview/nodeparamviewtextedit.h + widget/nodeparamview/nodeparamviewkeyframecontrol.h widget/nodeparamview/nodeparamviewtextedit.cpp - widget/nodeparamview/nodeparamviewundo.h + widget/nodeparamview/nodeparamviewtextedit.h widget/nodeparamview/nodeparamviewundo.cpp - widget/nodeparamview/nodeparamviewwidgetbridge.h + widget/nodeparamview/nodeparamviewundo.h widget/nodeparamview/nodeparamviewwidgetbridge.cpp + widget/nodeparamview/nodeparamviewwidgetbridge.h PARENT_SCOPE ) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 52b4407fc..0a6f743e5 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -25,6 +25,7 @@ #include #include +#include "common/functiontimer.h" #include "common/timecodefunctions.h" #include "node/output/viewer/viewer.h" @@ -61,12 +62,6 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : param_widget_area_ = new NodeParamViewDockArea(); - // Disable dock widgets from tabbing and disable glitchy animations - param_widget_area_->setDockOptions(static_cast(0)); - - // HACK: Hide the main window separators (unfortunately the cursors still appear) - param_widget_area_->setStyleSheet(QStringLiteral("QMainWindow::separator {background: rgba(0, 0, 0, 0)}")); - QVBoxLayout* param_widget_container_layout = new QVBoxLayout(param_widget_container_); QMargins param_widget_margin = param_widget_container_layout->contentsMargins(); param_widget_margin.setTop(ruler()->height()); @@ -76,9 +71,29 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : param_widget_container_layout->addStretch(INT_MAX); + // Create contexts for three different types + context_items_.resize(Track::kCount + 1); + for (int i=0; isetVisible(false); + static_cast(c->titleBarWidget())->SetAddEffectButtonVisible(i == Track::kVideo || i == Track::kAudio); + static_cast(c->titleBarWidget())->SetText(Footage::GetStreamTypeName(static_cast(i))); + context_items_[i] = c; + param_widget_area_->AddItem(c); + } + // Disable collapsing param view (but collapsing keyframe view is permitted) splitter->setCollapsible(0, false); + // Create global vertical scrollbar on the right + vertical_scrollbar_ = new QScrollBar(); + vertical_scrollbar_->setMaximum(0); + layout->addWidget(vertical_scrollbar_); + + // Connect scrollbars together + connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); + connect(vertical_scrollbar_, &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue); + if (create_keyframe_view) { // Set up keyframe view QWidget* keyframe_area = new QWidget(); @@ -108,20 +123,7 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : // Set both widgets to 50/50 splitter->setSizes({INT_MAX, INT_MAX}); - } else { - keyframe_view_ = nullptr; - } - // Create global vertical scrollbar on the right - vertical_scrollbar_ = new QScrollBar(); - vertical_scrollbar_->setMaximum(0); - layout->addWidget(vertical_scrollbar_); - - // Connect scrollbars together - connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); - connect(vertical_scrollbar_, &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue); - - if (keyframe_view_) { connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue); connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue); @@ -132,6 +134,8 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : keyframe_view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); connect(keyframe_view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); + } else { + keyframe_view_ = nullptr; } // Set a default scale - FIXME: Hardcoded @@ -144,8 +148,14 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : &NodeParamView::FocusChanged); } -void NodeParamView::SelectNodes(const QVector &nodes) +NodeParamView::~NodeParamView() { + qDeleteAll(context_items_); +} + +/*void NodeParamView::SelectNodes(const QVector &nodes) +{ + return; int original_node_count = items_.size(); foreach (Node* n, nodes) { @@ -158,7 +168,7 @@ void NodeParamView::SelectNodes(const QVector &nodes) active_nodes_.append(n); // Create node UI - AddNode(n); + AddNode(n, param_widget_area_); } if (items_.size() > original_node_count ) { @@ -173,6 +183,7 @@ void NodeParamView::SelectNodes(const QVector &nodes) void NodeParamView::DeselectNodes(const QVector &nodes) { + return; // Remove item from map and delete the widget int original_node_count = items_.size(); @@ -199,13 +210,59 @@ void NodeParamView::DeselectNodes(const QVector &nodes) SignalNodeOrder(); } -} +}*/ void NodeParamView::SetInputChecked(const NodeInput &input, bool e) { input_checked_.insert(input, e); - if (NodeParamViewItem *item = items_.value(input.node())) { - item->SetInputChecked(input, e); + foreach (NodeParamViewContext *ctx, context_items_) { + ctx->SetInputChecked(input, e); + } +} + +void NodeParamView::SetContexts(const QVector &contexts) +{ + TIME_THIS_FUNCTION; + + foreach (NodeParamViewContext *ctx, context_items_) { + ctx->Clear(); + ctx->setVisible(false); + } + + if (focused_node_) { + focused_node_ = nullptr; + emit FocusedNodeChanged(nullptr); + } + + foreach (Node *ctx, contexts) { + Track::Type ctx_type = Track::kCount; + + if (ClipBlock *clip = dynamic_cast(ctx)) { + if (clip->track()) { + if (clip->track()->type() != Track::kNone) { + ctx_type = clip->track()->type(); + } + } + } else if (Track *track = dynamic_cast(ctx)) { + if (track->type() != Track::kNone) { + ctx_type = track->type(); + } + } + + NodeParamViewContext *item = context_items_.at(ctx_type); + + item->AddContext(ctx); + item->setVisible(true); + + for (auto it=ctx->GetContextPositions().cbegin(); it!=ctx->GetContextPositions().cend(); it++) { + if (!dynamic_cast(it.key()) && !dynamic_cast(it.key())) { + AddNode(it.key(), item); + } + } + } + + foreach (NodeParamViewContext *ctx, context_items_) { + SortItemsInContext(ctx); } } @@ -235,8 +292,8 @@ void NodeParamView::TimebaseChangedEvent(const rational &timebase) keyframe_view_->SetTimebase(timebase); } - foreach (NodeParamViewItem* item, items_) { - item->SetTimebase(timebase); + foreach (NodeParamViewContext* ctx, context_items_) { + ctx->SetTimebase(timebase); } UpdateItemTime(GetTime()); @@ -260,7 +317,7 @@ void NodeParamView::ConnectedNodeChangeEvent(ViewerOutput *n) keyframe_view_->SetTimeTarget(n); } - foreach (NodeParamViewItem* item, items_) { + foreach (NodeParamViewContext* item, context_items_) { item->SetTimeTarget(n); } @@ -281,7 +338,7 @@ void NodeParamView::DeleteSelected() void NodeParamView::UpdateItemTime(const rational &time) { - foreach (NodeParamViewItem* item, items_) { + foreach (NodeParamViewContext* item, context_items_) { item->SetTime(time); } } @@ -293,6 +350,7 @@ void NodeParamView::QueueKeyframePositionUpdate() void NodeParamView::SignalNodeOrder() { + /* // Sort by item Y (apparently there's no way in Qt to get the order of dock widgets) QVector nodes; QVector item_ys; @@ -318,15 +376,12 @@ void NodeParamView::SignalNodeOrder() } emit NodeOrderChanged(nodes); + */ } -void NodeParamView::AddNode(Node *n) +void NodeParamView::AddNode(Node *n, NodeParamViewContext *context) { - NodeParamViewItem* item = new NodeParamViewItem(n, create_checkboxes_, param_widget_area_); - - item->setAllowedAreas(Qt::LeftDockWidgetArea); - item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable); - item->SetExpanded(node_expanded_state_.value(n, true)); + NodeParamViewItem* item = new NodeParamViewItem(n, create_checkboxes_, context); if (keyframe_view_) { connect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); @@ -357,14 +412,13 @@ void NodeParamView::AddNode(Node *n) // Set the timebase item->SetTimebase(timebase()); - items_.insert(n, item); - param_widget_area_->addDockWidget(Qt::LeftDockWidgetArea, item); + context->AddNode(item); if (!focused_node_ && n->HasGizmos()) { // We'll focus this node now item->SetHighlighted(true); - focused_node_ = n; - emit FocusedNodeChanged(focused_node_); + focused_node_ = item; + emit FocusedNodeChanged(n); } if (keyframe_view_) { @@ -372,9 +426,21 @@ void NodeParamView::AddNode(Node *n) } } +/*void NodeParamView::AddNode(Node *node, Node *context, NodeParamViewContext *ctx_item) +{ + int dist = GetDistanceBetweenNodes(context, node); + + if (dist == -1) { + dist = 0; + } + + ctx_item->GetDockArea()->insert +}*/ + void NodeParamView::RemoveNode(Node *n) { - if (keyframe_view_) { + qDebug() << "STUB!"; + /*if (keyframe_view_) { keyframe_view_->RemoveKeyframesOfNode(n); disconnect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); @@ -395,16 +461,64 @@ void NodeParamView::RemoveNode(Node *n) } emit FocusedNodeChanged(focused_node_); + }*/ +} + +int GetDistanceBetweenNodes(Node *start, Node *end) +{ + if (start == end) { + return 0; + } + + for (auto it=start->input_connections().cbegin(); it!=start->input_connections().cend(); it++) { + int this_node_dist = GetDistanceBetweenNodes(it->second, end); + if (this_node_dist != -1) { + return 1 + this_node_dist; + } + } + + return -1; +} + +void NodeParamView::SortItemsInContext(NodeParamViewContext *context_item) +{ + QVector > distances; + + for (auto it=context_item->GetItems().cbegin(); it!=context_item->GetItems().cend(); it++) { + int distance = 0; + foreach (Node *ctx, context_item->GetContexts()) { + distance = qMax(distance, GetDistanceBetweenNodes(ctx, it.key())); + } + + bool inserted = false; + QPair dist(it.value(), distance); + + for (int i=0; iGetDockArea()->AddItem(info.first); } } void NodeParamView::UpdateGlobalScrollBar() { - int height_offscreen = param_widget_container_->height() - ruler()->height() + scrollbar()->height(); + int height_offscreen = param_widget_container_->height() + scrollbar()->height(); if (keyframe_view_) { keyframe_view_->SetMaxScroll(height_offscreen); } + vertical_scrollbar_->setRange(0, height_offscreen - param_scroll_area_->height()); } @@ -430,28 +544,36 @@ void NodeParamView::FocusChanged(QWidget* old, QWidget* now) Q_UNUSED(old) QObject* parent = now; - NodeParamViewItem* item; while (parent) { - item = dynamic_cast(parent); + if (NodeParamViewItem* item = dynamic_cast(parent)) { + if (item != focused_node_) { + // Found a NodeParamViewItem that isn't already focused, see if it belongs to us + bool ours = false; - if (item) { - if (item->parent() == param_widget_area_) { - // Found it! - if (item->GetNode() != focused_node_) { + do { + parent = parent->parent(); + + if (parent == this) { + ours = true; + break; + } + } while (parent); + + if (ours) { + // This item is ours, if (focused_node_) { // De-focus current node - items_.value(focused_node_)->SetHighlighted(false); + focused_node_->SetHighlighted(false); } - focused_node_ = item->GetNode(); + focused_node_ = item; item->SetHighlighted(true); - emit FocusedNodeChanged(focused_node_); + emit FocusedNodeChanged(item->GetNode()); } } - break; } @@ -469,7 +591,8 @@ void NodeParamView::KeyframeViewDragged(int x, int y) void NodeParamView::UpdateElementY() { - if (keyframe_view_) { + qDebug() << "STUB"; + /*if (keyframe_view_) { for (auto it=items_.cbegin(); it!=items_.cend(); it++) { foreach (const QString& input, it.key()->inputs()) { int arr_sz = it.key()->InputArraySize(input); @@ -482,7 +605,7 @@ void NodeParamView::UpdateElementY() } } } - } + }*/ } } diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 677c4dce5..c1c8fb3b5 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -25,6 +25,7 @@ #include #include "node/node.h" +#include "nodeparamviewcontext.h" #include "nodeparamviewdockarea.h" #include "nodeparamviewitem.h" #include "widget/keyframeview/keyframeview.h" @@ -64,8 +65,7 @@ public: { } - void SelectNodes(const QVector &nodes); - void DeselectNodes(const QVector& nodes); + virtual ~NodeParamView() override; void SetCreateCheckBoxes(NodeParamViewCheckBoxBehavior e) { @@ -77,11 +77,6 @@ public: return input_checked_.value(input); } - const QMap& GetItemMap() const - { - return items_; - } - Node* GetTimeTarget() const; void DeleteSelected(); @@ -99,6 +94,8 @@ public: public slots: void SetInputChecked(const NodeInput &input, bool e); + void SetContexts(const QVector &contexts); + signals: void RequestSelectNode(const QVector& target); @@ -122,13 +119,17 @@ private: void SignalNodeOrder(); - void AddNode(Node* n); + void AddNode(Node* n, NodeParamViewContext *context); + + //void AddNode(Node *node, Node *context, NodeParamViewContext *ctx_item); void RemoveNode(Node* n); + void SortItemsInContext(NodeParamViewContext *context); + KeyframeView* keyframe_view_; - QMap items_; + QVector context_items_; QScrollBar* vertical_scrollbar_; @@ -144,9 +145,7 @@ private: QVector active_nodes_; - QMap node_expanded_state_; - - Node* focused_node_; + NodeParamViewItem* focused_node_; NodeParamViewCheckBoxBehavior create_checkboxes_; diff --git a/app/widget/nodeparamview/nodeparamviewcontext.cpp b/app/widget/nodeparamview/nodeparamviewcontext.cpp new file mode 100644 index 000000000..c8b409f0b --- /dev/null +++ b/app/widget/nodeparamview/nodeparamviewcontext.cpp @@ -0,0 +1,92 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "nodeparamviewcontext.h" + +#include "node/block/clip/clip.h" + +namespace olive { + +#define super NodeParamViewItemBase + +NodeParamViewContext::NodeParamViewContext(QWidget *parent) : + super(parent) +{ + QWidget *body = new QWidget(); + QHBoxLayout *body_layout = new QHBoxLayout(body); + SetBody(body); + + dock_area_ = new NodeParamViewDockArea(); + body_layout->addWidget(dock_area_); + + setBackgroundRole(QPalette::Base); + + Retranslate(); +} + +void NodeParamViewContext::AddNode(NodeParamViewItem *item) +{ + items_.insert(item->GetNode(), item); + dock_area_->AddItem(item); +} + +void NodeParamViewContext::RemoveNode(Node *node) +{ +} + +void NodeParamViewContext::Clear() +{ + qDeleteAll(items_); + items_.clear(); +} + +void NodeParamViewContext::SetInputChecked(const NodeInput &input, bool e) +{ + if (NodeParamViewItem *item = items_.value(input.node())) { + item->SetInputChecked(input, e); + } +} + +void NodeParamViewContext::SetTimebase(const rational &timebase) +{ + foreach (NodeParamViewItem* item, items_) { + item->SetTimebase(timebase); + } +} + +void NodeParamViewContext::SetTimeTarget(Node *n) +{ + foreach (NodeParamViewItem* item, items_) { + item->SetTimeTarget(n); + } +} + +void NodeParamViewContext::SetTime(const rational &time) +{ + foreach (NodeParamViewItem* item, items_) { + item->SetTime(time); + } +} + +void NodeParamViewContext::Retranslate() +{ +} + +} diff --git a/app/widget/nodeparamview/nodeparamviewcontext.h b/app/widget/nodeparamview/nodeparamviewcontext.h new file mode 100644 index 000000000..2b59920c6 --- /dev/null +++ b/app/widget/nodeparamview/nodeparamviewcontext.h @@ -0,0 +1,90 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef NODEPARAMVIEWCONTEXT_H +#define NODEPARAMVIEWCONTEXT_H + +#include "nodeparamviewdockarea.h" +#include "nodeparamviewitembase.h" +#include "nodeparamviewitem.h" + +namespace olive { + +class NodeParamViewContext : public NodeParamViewItemBase +{ + Q_OBJECT +public: + NodeParamViewContext(QWidget *parent = nullptr); + + NodeParamViewDockArea *GetDockArea() const + { + return dock_area_; + } + + const QVector &GetContexts() const + { + return contexts_; + } + + const QMap &GetItems() const + { + return items_; + } + + void AddNode(NodeParamViewItem *item); + + void RemoveNode(Node *node); + + void Clear(); + + void SetInputChecked(const NodeInput &input, bool e); + + void SetTimebase(const rational &timebase); + + void SetTimeTarget(Node *n); + + void SetTime(const rational &time); + +public slots: + void AddContext(Node *node) + { + contexts_.append(node); + } + + void RemoveContext(Node *node) + { + contexts_.removeOne(node); + } + +protected slots: + virtual void Retranslate() override; + +private: + NodeParamViewDockArea *dock_area_; + + QVector contexts_; + + QMap items_; + +}; + +} + +#endif // NODEPARAMVIEWCONTEXT_H diff --git a/app/widget/nodeparamview/nodeparamviewdockarea.cpp b/app/widget/nodeparamview/nodeparamviewdockarea.cpp index a875bbf24..7f6c32e0d 100644 --- a/app/widget/nodeparamview/nodeparamviewdockarea.cpp +++ b/app/widget/nodeparamview/nodeparamviewdockarea.cpp @@ -20,11 +20,18 @@ #include "nodeparamviewdockarea.h" +#include + namespace olive { NodeParamViewDockArea::NodeParamViewDockArea(QWidget *parent) : QMainWindow(parent) { + // Disable dock widgets from tabbing and disable glitchy animations + setDockOptions(static_cast(0)); + + // HACK: Hide the main window separators (unfortunately the cursors still appear) + setStyleSheet(QStringLiteral("QMainWindow::separator {background: rgba(0, 0, 0, 0)}")); } QMenu *NodeParamViewDockArea::createPopupMenu() @@ -32,4 +39,11 @@ QMenu *NodeParamViewDockArea::createPopupMenu() return nullptr; } +void NodeParamViewDockArea::AddItem(QDockWidget *item) +{ + item->setAllowedAreas(Qt::LeftDockWidgetArea); + item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable); + addDockWidget(Qt::LeftDockWidgetArea, item); +} + } diff --git a/app/widget/nodeparamview/nodeparamviewdockarea.h b/app/widget/nodeparamview/nodeparamviewdockarea.h index c313236cd..099eb087c 100644 --- a/app/widget/nodeparamview/nodeparamviewdockarea.h +++ b/app/widget/nodeparamview/nodeparamviewdockarea.h @@ -35,6 +35,8 @@ public: virtual QMenu *createPopupMenu() override; + void AddItem(QDockWidget *item); + }; } diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index e8deef896..50e275a0c 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -22,8 +22,6 @@ #include #include -#include -#include #include "common/qtutils.h" #include "core.h" @@ -43,18 +41,13 @@ const int NodeParamViewItemBody::kArrayCollapseBtnColumn = 1; const int NodeParamViewItemBody::kLabelColumn = 2; const int NodeParamViewItemBody::kWidgetStartColumn = 3; -#define super QDockWidget +#define super NodeParamViewItemBase NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) : super(parent), - node_(node), - highlighted_(false) + node_(node) { - // Create title bar widget - title_bar_ = new NodeParamViewItemTitleBar(this); - - // Add title bar to widget - this->setTitleBarWidget(title_bar_); + node_->Retranslate(); // Create and add contents widget body_ = new NodeParamViewItemBody(node_, create_checkboxes); @@ -62,109 +55,31 @@ NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior c connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged); connect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged); - connect(title_bar_, &NodeParamViewItemTitleBar::ExpandedStateChanged, this, &NodeParamViewItem::SetExpanded); - connect(title_bar_, &NodeParamViewItemTitleBar::PinToggled, this, &NodeParamViewItem::PinToggled); - - this->setWidget(body_); - - // Use dummy QWidget to retain width when not expanded (QDockWidget seems to ignore the titlebar - // size hints and will shrink as small as possible if the body is hidden) - hidden_body_ = new QWidget(this); + SetBody(body_); connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate); - setBackgroundRole(QPalette::Base); - setAutoFillBackground(true); - - setFocusPolicy(Qt::ClickFocus); + setBackgroundRole(QPalette::Window); Retranslate(); } -void NodeParamViewItem::SetTimeTarget(Node *target) -{ - body_->SetTimeTarget(target); -} - -void NodeParamViewItem::SetTime(const rational &time) -{ - time_ = time; - - body_->SetTime(time_); -} - -void NodeParamViewItem::SetTimebase(const rational& timebase) -{ - body_->SetTimebase(timebase); -} - -Node *NodeParamViewItem::GetNode() const -{ - return node_; -} - -void NodeParamViewItem::changeEvent(QEvent *e) -{ - if (e->type() == QEvent::LanguageChange) { - Retranslate(); - } - - super::changeEvent(e); -} - -void NodeParamViewItem::paintEvent(QPaintEvent *event) -{ - super::paintEvent(event); - - // Draw border if focused - if (highlighted_) { - QPainter p(this); - p.setBrush(Qt::NoBrush); - p.setPen(palette().highlight().color()); - p.drawRect(rect().adjusted(0, 0, -1, -1)); - } -} - -void NodeParamViewItem::moveEvent(QMoveEvent *event) -{ - super::moveEvent(event); - - emit Moved(); -} - void NodeParamViewItem::Retranslate() { node_->Retranslate(); - if (node_->GetLabel().isEmpty()) { - title_bar_->SetText(node_->Name()); - } else { - title_bar_->SetText(tr("%1 (%2)").arg(node_->GetLabel(), node_->Name())); - } + title_bar()->SetText(GetTitleBarTextFromNode(node_)); body_->Retranslate(); } -void NodeParamViewItem::SetExpanded(bool e) -{ - setWidget(e ? body_ : hidden_body_); - title_bar_->SetExpanded(e); - - emit ExpandedChanged(e); -} - -bool NodeParamViewItem::IsExpanded() const -{ - return body_->isVisible(); -} - int NodeParamViewItem::GetElementY(const NodeInput &c) const { if (IsExpanded()) { return body_->GetElementY(c); } else { // Not expanded, put keyframes at the titlebar Y - return mapToGlobal(title_bar_->rect().center()).y(); + return mapToGlobal(title_bar()->rect().center()).y(); } } @@ -173,63 +88,6 @@ void NodeParamViewItem::SetInputChecked(const NodeInput &input, bool e) body_->SetInputChecked(input, e); } -void NodeParamViewItem::ToggleExpanded() -{ - SetExpanded(!IsExpanded()); -} - -NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) : - QWidget(parent), - draw_border_(true) -{ - QHBoxLayout* layout = new QHBoxLayout(this); - - collapse_btn_ = new CollapseButton(); - connect(collapse_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::ExpandedStateChanged); - layout->addWidget(collapse_btn_); - - lbl_ = new QLabel(); - layout->addWidget(lbl_); - - // Place next buttons on the far side - layout->addStretch(); - - QPushButton* pin_btn = new QPushButton(QStringLiteral("P")); - pin_btn->setCheckable(true); - pin_btn->setFixedSize(pin_btn->sizeHint().height(), pin_btn->sizeHint().height()); - layout->addWidget(pin_btn); - connect(pin_btn, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::PinToggled); -} - -void NodeParamViewItemTitleBar::SetExpanded(bool e) -{ - draw_border_ = e; - collapse_btn_->setChecked(e); - - update(); -} - -void NodeParamViewItemTitleBar::paintEvent(QPaintEvent *event) -{ - QWidget::paintEvent(event); - - if (draw_border_) { - QPainter p(this); - - // Draw bottom border using text color - int bottom = height() - 1; - p.setPen(palette().text().color()); - p.drawLine(0, bottom, width(), bottom); - } -} - -void NodeParamViewItemTitleBar::mouseDoubleClickEvent(QMouseEvent *event) -{ - QWidget::mouseDoubleClickEvent(event); - - collapse_btn_->click(); -} - NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) : QWidget(parent), node_(node), @@ -241,42 +99,42 @@ NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBe // Create widgets all root level components foreach (QString input, node->inputs()) { - Node *n; - if (NodeGroup *g = dynamic_cast(node)) { + Node *n = node; + while (NodeGroup *g = dynamic_cast(n)) { const NodeInput &ni = g->GetInputPassthroughs().value(input); n = ni.node(); input = ni.input(); - } else { - n = node; } - CreateWidgets(root_layout, n, input, -1, insert_row); - - insert_row++; - - if (n->InputIsArray(input)) { - // Insert here - QWidget* array_widget = new QWidget(); - - QGridLayout* array_layout = new QGridLayout(array_widget); - array_layout->setContentsMargins(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral(" ")), 0, 0, 0); - - root_layout->addWidget(array_widget, insert_row, 1, 1, 10); - - // Start with zero elements for efficiency. We will make the widgets for them if the user - // requests the array UI to be expanded - int arr_sz = 0; - - // Add one last add button for appending to the array - NodeParamViewArrayButton* append_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd); - connect(append_btn, &NodeParamViewArrayButton::clicked, this, &NodeParamViewItemBody::ArrayAppendClicked); - array_layout->addWidget(append_btn, arr_sz, kArrayInsertColumn); - - array_widget->setVisible(false); - - array_ui_.insert({n, input}, {array_widget, arr_sz, append_btn}); + if (!(n->GetInputFlags(input) & kInputFlagHidden)) { + CreateWidgets(root_layout, n, input, -1, insert_row); insert_row++; + + if (n->InputIsArray(input)) { + // Insert here + QWidget* array_widget = new QWidget(); + + QGridLayout* array_layout = new QGridLayout(array_widget); + array_layout->setContentsMargins(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral(" ")), 0, 0, 0); + + root_layout->addWidget(array_widget, insert_row, 1, 1, 10); + + // Start with zero elements for efficiency. We will make the widgets for them if the user + // requests the array UI to be expanded + int arr_sz = 0; + + // Add one last add button for appending to the array + NodeParamViewArrayButton* append_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd); + connect(append_btn, &NodeParamViewArrayButton::clicked, this, &NodeParamViewItemBody::ArrayAppendClicked); + array_layout->addWidget(append_btn, arr_sz, kArrayInsertColumn); + + array_widget->setVisible(false); + + array_ui_.insert({n, input}, {array_widget, arr_sz, append_btn}); + + insert_row++; + } } } @@ -441,7 +299,7 @@ int NodeParamViewItemBody::GetElementY(NodeInput c) const c.set_element(-1); } - if (NodeGroup *g = dynamic_cast(c.node())) { + while (NodeGroup *g = dynamic_cast(c.node())) { const NodeInput &passthrough = g->GetInputPassthroughs().value(c.input()); c.set_node(passthrough.node()); c.set_input(passthrough.input()); @@ -503,7 +361,13 @@ void NodeParamViewItemBody::PlaceWidgetsFromBridge(QGridLayout* layout, NodePara void NodeParamViewItemBody::InputArraySizeChangedInternal(Node *node, const QString &input, int size) { - ArrayUI& array_ui = array_ui_[{node, input}]; + NodeInputPair nip = {node, input}; + + if (!array_ui_.contains(nip)) { + return; + } + + ArrayUI& array_ui = array_ui_[nip]; if (size != array_ui.count) { QGridLayout* grid = static_cast(array_ui.widget->layout()); diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 55a0a3174..445bcace2 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -22,7 +22,6 @@ #define NODEPARAMVIEWITEM_H #include -#include #include #include #include @@ -33,6 +32,7 @@ #include "nodeparamviewarraywidget.h" #include "nodeparamviewconnectedlabel.h" #include "nodeparamviewkeyframecontrol.h" +#include "nodeparamviewitembase.h" #include "nodeparamviewwidgetbridge.h" #include "widget/clickablelabel/clickablelabel.h" #include "widget/collapsebutton/collapsebutton.h" @@ -45,40 +45,6 @@ enum NodeParamViewCheckBoxBehavior { kCheckBoxesOnNonConnected }; -class NodeParamViewItemTitleBar : public QWidget -{ - Q_OBJECT -public: - NodeParamViewItemTitleBar(QWidget* parent = nullptr); - - void SetExpanded(bool e); - - void SetText(const QString& s) - { - lbl_->setText(s); - lbl_->setToolTip(s); - lbl_->setMinimumWidth(1); - } - -signals: - void ExpandedStateChanged(bool e); - - void PinToggled(bool e); - -protected: - virtual void paintEvent(QPaintEvent *event) override; - - virtual void mouseDoubleClickEvent(QMouseEvent *event) override; - -private: - bool draw_border_; - - QLabel* lbl_; - - CollapseButton* collapse_btn_; - -}; - class NodeParamViewItemBody : public QWidget { Q_OBJECT public: @@ -189,77 +155,57 @@ private slots: }; -class NodeParamViewItem : public QDockWidget +class NodeParamViewItem : public NodeParamViewItemBase { Q_OBJECT public: NodeParamViewItem(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget* parent = nullptr); - void SetTimeTarget(Node* target); - - void SetTime(const rational& time); - - // Set the timebase of the NodeParamViewItemBody - void SetTimebase(const rational& timebase); - - Node* GetNode() const; - - bool IsExpanded() const; - - void SetHighlighted(bool e) + void SetTimeTarget(Node* target) { - highlighted_ = e; + body_->SetTimeTarget(target); + } - update(); + void SetTime(const rational& time) + { + time_ = time; + + body_->SetTime(time_); + } + + void SetTimebase(const rational& timebase) + { + body_->SetTimebase(timebase); + } + + Node* GetNode() const + { + return node_; } int GetElementY(const NodeInput& c) const; void SetInputChecked(const NodeInput &input, bool e); -public slots: - void SetExpanded(bool e); - - void ToggleExpanded(); - signals: void RequestSetTime(const rational& time); void RequestSelectNode(const QVector& node); - void PinToggled(bool e); - - void ExpandedChanged(bool e); - void ArrayExpandedChanged(bool e); - void Moved(); - void InputCheckedChanged(const NodeInput &input, bool e); -protected: - virtual void changeEvent(QEvent *e) override; - - virtual void paintEvent(QPaintEvent *event) override; - - virtual void moveEvent(QMoveEvent *event) override; +protected slots: + virtual void Retranslate() override; private: - NodeParamViewItemTitleBar* title_bar_; - NodeParamViewItemBody* body_; - QWidget *hidden_body_; - Node* node_; rational time_; - bool highlighted_; - -private slots: - void Retranslate(); - }; } diff --git a/app/widget/nodeparamview/nodeparamviewitembase.cpp b/app/widget/nodeparamview/nodeparamviewitembase.cpp new file mode 100644 index 000000000..709514326 --- /dev/null +++ b/app/widget/nodeparamview/nodeparamviewitembase.cpp @@ -0,0 +1,114 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "nodeparamviewitembase.h" + +#include +#include + +namespace olive { + +#define super QDockWidget + +NodeParamViewItemBase::NodeParamViewItemBase(QWidget *parent) : + super(parent), + highlighted_(false) +{ + // Create title bar widget + title_bar_ = new NodeParamViewItemTitleBar(this); + + // Add title bar to widget + this->setTitleBarWidget(title_bar_); + + // Connect title bar to this + connect(title_bar_, &NodeParamViewItemTitleBar::ExpandedStateChanged, this, &NodeParamViewItemBase::SetExpanded); + connect(title_bar_, &NodeParamViewItemTitleBar::PinToggled, this, &NodeParamViewItemBase::PinToggled); + + // Use dummy QWidget to retain width when not expanded (QDockWidget seems to ignore the titlebar + // size hints and will shrink as small as possible if the body is hidden) + hidden_body_ = new QWidget(this); + + setAutoFillBackground(true); + + setFocusPolicy(Qt::ClickFocus); +} + +bool NodeParamViewItemBase::IsExpanded() const +{ + return body_->isVisible(); +} + +QString NodeParamViewItemBase::GetTitleBarTextFromNode(Node *n) +{ + if (n->GetLabel().isEmpty()) { + return n->Name(); + } else { + return tr("%1 (%2)").arg(n->GetLabel(), n->Name()); + } +} + +void NodeParamViewItemBase::SetBody(QWidget *body) +{ + body_ = body; + body_->setParent(this); + + if (title_bar_->IsExpanded()) { + setWidget(body_); + } +} + +void NodeParamViewItemBase::paintEvent(QPaintEvent *event) +{ + super::paintEvent(event); + + // Draw border if focused + if (highlighted_) { + QPainter p(this); + p.setBrush(Qt::NoBrush); + p.setPen(palette().highlight().color()); + p.drawRect(rect().adjusted(0, 0, -1, -1)); + } +} + +void NodeParamViewItemBase::SetExpanded(bool e) +{ + setWidget(e ? body_ : hidden_body_); + title_bar_->SetExpanded(e); + + emit ExpandedChanged(e); +} + +void NodeParamViewItemBase::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::LanguageChange) { + Retranslate(); + } + + super::changeEvent(e); +} + +void NodeParamViewItemBase::moveEvent(QMoveEvent *event) +{ + super::moveEvent(event); + + emit Moved(); +} + +} diff --git a/app/widget/nodeparamview/nodeparamviewitembase.h b/app/widget/nodeparamview/nodeparamviewitembase.h new file mode 100644 index 000000000..d5f570656 --- /dev/null +++ b/app/widget/nodeparamview/nodeparamviewitembase.h @@ -0,0 +1,93 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef NODEPARAMVIEWITEMBASE_H +#define NODEPARAMVIEWITEMBASE_H + +#include + +#include "nodeparamviewitemtitlebar.h" +#include "node/node.h" + +namespace olive { + +class NodeParamViewItemBase : public QDockWidget +{ + Q_OBJECT +public: + NodeParamViewItemBase(QWidget* parent = nullptr); + + void SetHighlighted(bool e) + { + highlighted_ = e; + + update(); + } + + bool IsExpanded() const; + + static QString GetTitleBarTextFromNode(Node *n); + +public slots: + void SetExpanded(bool e); + + void ToggleExpanded() + { + SetExpanded(!IsExpanded()); + } + +signals: + void PinToggled(bool e); + + void ExpandedChanged(bool e); + + void Moved(); + +protected: + void SetBody(QWidget *body); + + virtual void paintEvent(QPaintEvent *event) override; + + NodeParamViewItemTitleBar* title_bar() const + { + return title_bar_; + } + + virtual void changeEvent(QEvent *e) override; + + virtual void moveEvent(QMoveEvent *event) override; + +protected slots: + virtual void Retranslate(){} + +private: + NodeParamViewItemTitleBar* title_bar_; + + QWidget *body_; + + QWidget *hidden_body_; + + bool highlighted_; + +}; + +} + +#endif // NODEPARAMVIEWITEMBASE_H diff --git a/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp b/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp new file mode 100644 index 000000000..6d61bd1ba --- /dev/null +++ b/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp @@ -0,0 +1,90 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "nodeparamviewitemtitlebar.h" + +#include +#include + +#include "ui/icons/icons.h" + +namespace olive { + +NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) : + QWidget(parent), + draw_border_(true) +{ + QHBoxLayout* layout = new QHBoxLayout(this); + + collapse_btn_ = new CollapseButton(); + connect(collapse_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::ExpandedStateChanged); + layout->addWidget(collapse_btn_); + + lbl_ = new QLabel(); + layout->addWidget(lbl_); + + // Place next buttons on the far side + layout->addStretch(); + + add_fx_btn_ = new QPushButton(); + add_fx_btn_->setIcon(icon::AddEffect); + add_fx_btn_->setFixedSize(add_fx_btn_->sizeHint().height(), add_fx_btn_->sizeHint().height()); + add_fx_btn_->setVisible(false); + layout->addWidget(add_fx_btn_); + connect(add_fx_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::AddEffectButtonClicked); + + pin_btn_ = new QPushButton(QStringLiteral("P")); + pin_btn_->setCheckable(true); + pin_btn_->setFixedSize(pin_btn_->sizeHint().height(), pin_btn_->sizeHint().height()); + pin_btn_->setVisible(false); + layout->addWidget(pin_btn_); + connect(pin_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::PinToggled); +} + +void NodeParamViewItemTitleBar::SetExpanded(bool e) +{ + draw_border_ = e; + collapse_btn_->setChecked(e); + + update(); +} + +void NodeParamViewItemTitleBar::paintEvent(QPaintEvent *event) +{ + QWidget::paintEvent(event); + + if (draw_border_) { + QPainter p(this); + + // Draw bottom border using text color + int bottom = height() - 1; + p.setPen(palette().text().color()); + p.drawLine(0, bottom, width(), bottom); + } +} + +void NodeParamViewItemTitleBar::mouseDoubleClickEvent(QMouseEvent *event) +{ + QWidget::mouseDoubleClickEvent(event); + + collapse_btn_->click(); +} + +} diff --git a/app/widget/nodeparamview/nodeparamviewitemtitlebar.h b/app/widget/nodeparamview/nodeparamviewitemtitlebar.h new file mode 100644 index 000000000..7ddaac1aa --- /dev/null +++ b/app/widget/nodeparamview/nodeparamviewitemtitlebar.h @@ -0,0 +1,89 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef NODEPARAMVIEWITEMTITLEBAR_H +#define NODEPARAMVIEWITEMTITLEBAR_H + +#include +#include + +#include "widget/collapsebutton/collapsebutton.h" + +namespace olive { + +class NodeParamViewItemTitleBar : public QWidget +{ + Q_OBJECT +public: + NodeParamViewItemTitleBar(QWidget* parent = nullptr); + + bool IsExpanded() const + { + return collapse_btn_->isChecked(); + } + +public slots: + void SetExpanded(bool e); + + void SetText(const QString& s) + { + lbl_->setText(s); + lbl_->setToolTip(s); + lbl_->setMinimumWidth(1); + } + + void SetPinButtonVisible(bool e) + { + pin_btn_->setVisible(e); + } + + void SetAddEffectButtonVisible(bool e) + { + add_fx_btn_->setVisible(e); + } + +signals: + void ExpandedStateChanged(bool e); + + void PinToggled(bool e); + + void AddEffectButtonClicked(); + +protected: + virtual void paintEvent(QPaintEvent *event) override; + + virtual void mouseDoubleClickEvent(QMouseEvent *event) override; + +private: + bool draw_border_; + + QLabel* lbl_; + + CollapseButton* collapse_btn_; + + QPushButton *pin_btn_; + + QPushButton *add_fx_btn_; + +}; + +} + +#endif // NODEPARAMVIEWITEMTITLEBAR_H diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 955228cbe..3c9a855ff 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -515,11 +515,11 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) if (!reconnected_to_itself) { Node *creating_output = create_edge_output_item_->GetNode(); - if (NodeGroup *output_group = dynamic_cast(creating_output)) { + while (NodeGroup *output_group = dynamic_cast(creating_output)) { creating_output = output_group->GetOutputPassthrough(); } - if (NodeGroup *input_group = dynamic_cast(creating_input.node())) { + while (NodeGroup *input_group = dynamic_cast(creating_input.node())) { creating_input = input_group->GetInputPassthroughs().value(creating_input.input()); } diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index d6bb565e9..04849fbb2 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -84,6 +84,9 @@ NodeViewItem::NodeViewItem(Node *node, const QString &input, int element, Node * } } else { output_connector_->setVisible(false); + + connect(node_, &Node::InputArraySizeChanged, this, &NodeViewItem::InputArraySizeChanged); + connect(node_, &Node::InputArraySizeChanged, this, &NodeViewItem::InputArraySizeChanged); } // This should be set during runtime, but just in case here's a default fallback @@ -740,14 +743,20 @@ void NodeViewItem::RepopulateInputs() } input_connector_->setVisible(has_connectable_inputs_); + } - if (IsExpanded()) { - // Create or remove inputs when necessary - } - } else { - if (IsExpanded() && element_ == -1) { - // Create or remove array elements when necessary - } + if (IsExpanded() && (IsOutputItem() || element_ == -1)) { + // Create or remove inputs when necessary + // NOTE: This is not the most efficient thing in the world, but it does work + SetExpanded(false); + SetExpanded(true); + } +} + +void NodeViewItem::InputArraySizeChanged(const QString &input) +{ + if (input == input_) { + RepopulateInputs(); } } diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index bbd6ec395..898c49a41 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -222,6 +222,8 @@ private slots: void RepopulateInputs(); + void InputArraySizeChanged(const QString &input); + }; } diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index ddc0e6c0c..c26b9bdf9 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -720,6 +720,7 @@ void MainWindow::UpdateNodePanelContextFromTimelinePanel(TimelinePanel *panel) } node_panel_->SetContexts(context); + param_panel_->SetContexts(context); } void MainWindow::FocusedPanelChanged(PanelWidget *panel) From 516c5cb3b1b3852eb99b31409f88e8b7ff7fd1d4 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Fri, 10 Dec 2021 16:46:21 +0000 Subject: [PATCH 43/94] saveotio: Use built in function and check for NaN --- app/task/project/saveotio/saveotio.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/app/task/project/saveotio/saveotio.cpp b/app/task/project/saveotio/saveotio.cpp index 107e36ec0..ecc1497b0 100644 --- a/app/task/project/saveotio/saveotio.cpp +++ b/app/task/project/saveotio/saveotio.cpp @@ -135,14 +135,10 @@ OTIO::Track *SaveOTIOTask::SerializeTrack(Track *track) if (dynamic_cast(block)) { auto otio_clip = new OTIO::Clip(block->GetLabel().toStdString()); - auto rate = 0.0; - rational frame_rate = static_cast(block)->connected_viewer()->GetVideoParams().frame_rate(); - if (frame_rate.denominator() == 1) { - rate = frame_rate.numerator(); - } else { - // Handle strange rates - rate = static_cast(frame_rate.numerator()) / - static_cast(frame_rate.denominator()); + double rate = static_cast(block)->connected_viewer()->GetVideoParams().frame_rate().toDouble(); + if (rate == qSNaN()) { + // We shouldn't ever get here, but catch without crashing if we ever do + goto fail; } otio_clip->set_source_range(OTIO::TimeRange(block->in().toRationalTime(), From dcaf34c69d657c1d8476006b2db8c10864ccd011 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Fri, 10 Dec 2021 17:34:51 +0000 Subject: [PATCH 44/94] saveotio: Use qIsNan rather than comparison --- app/task/project/saveotio/saveotio.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/task/project/saveotio/saveotio.cpp b/app/task/project/saveotio/saveotio.cpp index ecc1497b0..3764fc932 100644 --- a/app/task/project/saveotio/saveotio.cpp +++ b/app/task/project/saveotio/saveotio.cpp @@ -136,7 +136,7 @@ OTIO::Track *SaveOTIOTask::SerializeTrack(Track *track) auto otio_clip = new OTIO::Clip(block->GetLabel().toStdString()); double rate = static_cast(block)->connected_viewer()->GetVideoParams().frame_rate().toDouble(); - if (rate == qSNaN()) { + if (qIsNaN(rate)) { // We shouldn't ever get here, but catch without crashing if we ever do goto fail; } From b2b28344f43ac2a47e5cb0b157cd53d1a47f70e3 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Fri, 10 Dec 2021 22:25:42 +0000 Subject: [PATCH 45/94] saveotio: Use correct frame rates available_range is the total length of the source clip and is stored using the same frame rate as the source clip. source_range is the length of the clip in the timeline and so is stored using the sequence frame rate. --- app/task/project/saveotio/saveotio.cpp | 38 ++++++++++++++------------ app/task/project/saveotio/saveotio.h | 4 +-- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/app/task/project/saveotio/saveotio.cpp b/app/task/project/saveotio/saveotio.cpp index 3764fc932..76a6d01ed 100644 --- a/app/task/project/saveotio/saveotio.cpp +++ b/app/task/project/saveotio/saveotio.cpp @@ -102,8 +102,13 @@ OTIO::Timeline *SaveOTIOTask::SerializeTimeline(Sequence *sequence) // Suppress unused variable warning Q_UNUSED(timeline_retainer); - if (!SerializeTrackList(sequence->track_list(Track::kVideo), otio_timeline) - || !SerializeTrackList(sequence->track_list(Track::kAudio), otio_timeline)) { + double rate = sequence->GetVideoParams().frame_rate().toDouble(); + if (qIsNaN(rate)) { + return nullptr; + } + + if (!SerializeTrackList(sequence->track_list(Track::kVideo), otio_timeline, rate) + || !SerializeTrackList(sequence->track_list(Track::kAudio), otio_timeline, rate)) { otio_timeline->possibly_delete(); return nullptr; } @@ -111,7 +116,7 @@ OTIO::Timeline *SaveOTIOTask::SerializeTimeline(Sequence *sequence) return otio_timeline; } -OTIO::Track *SaveOTIOTask::SerializeTrack(Track *track) +OTIO::Track *SaveOTIOTask::SerializeTrack(Track *track, double sequence_rate) { auto otio_track = new OTIO::Track(); @@ -135,29 +140,26 @@ OTIO::Track *SaveOTIOTask::SerializeTrack(Track *track) if (dynamic_cast(block)) { auto otio_clip = new OTIO::Clip(block->GetLabel().toStdString()); - double rate = static_cast(block)->connected_viewer()->GetVideoParams().frame_rate().toDouble(); - if (qIsNaN(rate)) { - // We shouldn't ever get here, but catch without crashing if we ever do - goto fail; - } - - otio_clip->set_source_range(OTIO::TimeRange(block->in().toRationalTime(), - block->length().toRationalTime(rate))); + otio_clip->set_source_range(OTIO::TimeRange(block->in().toRationalTime(sequence_rate), + block->length().toRationalTime(sequence_rate))); QVector media_nodes = block->FindInputNodes(); if (!media_nodes.isEmpty()) { - OTIO::TimeRange range; + OTIO::TimeRange available_range; if (otio_track->kind().compare("Video") == 0) { - range = OTIO::TimeRange(OTIO::RationalTime(0, rate), + // OTIO ExternalReference uses the source clips frame rate (or sample rate) as opposed to + // the sequences rate + double source_frame_rate = static_cast(block)->connected_viewer()->GetVideoParams().frame_rate().toDouble(); + available_range = OTIO::TimeRange(OTIO::RationalTime(0, source_frame_rate), OTIO::RationalTime(media_nodes.first()->GetVideoParams().duration(), - rate)); + source_frame_rate)); } else if (otio_track->kind().compare("Audio") == 0) { - range = OTIO::TimeRange(OTIO::RationalTime(0, media_nodes.first()->GetAudioParams().sample_rate()), + available_range = OTIO::TimeRange(OTIO::RationalTime(0, media_nodes.first()->GetAudioParams().sample_rate()), OTIO::RationalTime(media_nodes.first()->GetAudioParams().duration(), media_nodes.first()->GetAudioParams().sample_rate())); } - auto media_ref = new OTIO::ExternalReference(media_nodes.first()->filename().toStdString(), range); + auto media_ref = new OTIO::ExternalReference(media_nodes.first()->filename().toStdString(), available_range); otio_clip->set_media_reference(media_ref); } @@ -198,12 +200,12 @@ fail: return nullptr; } -bool SaveOTIOTask::SerializeTrackList(TrackList *list, OTIO::Timeline* otio_timeline) +bool SaveOTIOTask::SerializeTrackList(TrackList *list, OTIO::Timeline* otio_timeline, double sequence_rate) { OTIO::ErrorStatus es; foreach (Track* track, list->GetTracks()) { - auto otio_track = SerializeTrack(track); + auto otio_track = SerializeTrack(track, sequence_rate); if (!otio_track) { return false; diff --git a/app/task/project/saveotio/saveotio.h b/app/task/project/saveotio/saveotio.h index f71df8151..0239beb4b 100644 --- a/app/task/project/saveotio/saveotio.h +++ b/app/task/project/saveotio/saveotio.h @@ -44,9 +44,9 @@ protected: private: OTIO::Timeline* SerializeTimeline(Sequence* sequence); - OTIO::Track* SerializeTrack(Track* track); + OTIO::Track* SerializeTrack(Track* track, double sequence_rate); - bool SerializeTrackList(TrackList* list, OTIO::Timeline *otio_timeline); + bool SerializeTrackList(TrackList* list, OTIO::Timeline *otio_timeline, double sequence_rate); Project* project_; From 7d82d56f31371ea096a898f73d2b9e43a4d833cc Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 13 Dec 2021 14:12:30 -0800 Subject: [PATCH 46/94] reworked much of keyframeview for new architecture --- CMakeLists.txt | 3 + app/panel/param/param.cpp | 1 - app/panel/param/param.h | 2 - app/widget/curvewidget/curveview.cpp | 154 +++--- app/widget/curvewidget/curveview.h | 18 +- app/widget/curvewidget/curvewidget.cpp | 43 +- app/widget/curvewidget/curvewidget.h | 6 - app/widget/keyframeview/CMakeLists.txt | 10 +- app/widget/keyframeview/keyframeview.cpp | 19 - app/widget/keyframeview/keyframeview.h | 8 +- app/widget/keyframeview/keyframeviewbase.cpp | 441 +++++++++--------- app/widget/keyframeview/keyframeviewbase.h | 75 +-- .../keyframeviewinputconnection.cpp | 83 ++++ .../keyframeviewinputconnection.h | 88 ++++ app/widget/keyframeview/keyframeviewitem.cpp | 129 ----- app/widget/keyframeview/keyframeviewitem.h | 68 --- app/widget/nodeparamview/nodeparamview.cpp | 132 ++---- app/widget/nodeparamview/nodeparamview.h | 8 - app/widget/nodeparamview/nodeparamviewitem.h | 13 + app/widget/timebased/CMakeLists.txt | 2 + .../timebasedviewselectionmanager.cpp | 26 ++ .../timebased/timebasedviewselectionmanager.h | 256 ++++++++++ app/widget/timebased/timescaledobject.cpp | 12 + app/widget/timebased/timescaledobject.h | 2 + app/window/mainwindow/mainwindow.cpp | 7 +- 25 files changed, 852 insertions(+), 754 deletions(-) create mode 100644 app/widget/keyframeview/keyframeviewinputconnection.cpp create mode 100644 app/widget/keyframeview/keyframeviewinputconnection.h delete mode 100644 app/widget/keyframeview/keyframeviewitem.cpp delete mode 100644 app/widget/keyframeview/keyframeviewitem.h create mode 100644 app/widget/timebased/timebasedviewselectionmanager.cpp create mode 100644 app/widget/timebased/timebasedviewselectionmanager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e8c28db2a..2b3ad5a73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,9 @@ else() endif() set(OLIVE_DEFINITIONS -DQT_DEPRECATED_WARNINGS) +if (WIN32) + list(APPEND OLIVE_DEFINITIONS -DUNICODE -D_UNICODE) +endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index dc8f5ea45..bf44d5db3 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -29,7 +29,6 @@ ParamPanel::ParamPanel(QWidget* parent) : { NodeParamView* view = new NodeParamView(); connect(view, &NodeParamView::RequestSelectNode, this, &ParamPanel::RequestSelectNode); - connect(view, &NodeParamView::NodeOrderChanged, this, &ParamPanel::NodeOrderChanged); connect(view, &NodeParamView::FocusedNodeChanged, this, &ParamPanel::FocusedNodeChanged); SetTimeBasedWidget(view); diff --git a/app/panel/param/param.h b/app/panel/param/param.h index c6af767f9..e298ccf3e 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -48,8 +48,6 @@ public slots: signals: void RequestSelectNode(const QVector& target); - void NodeOrderChanged(const QVector& nodes); - void FocusedNodeChanged(Node* n); protected: diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index 80108a0d5..e83909702 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -47,22 +47,6 @@ CurveView::CurveView(QWidget *parent) : connect(scene(), &QGraphicsScene::selectionChanged, this, &CurveView::SelectionChanged); } -CurveView::~CurveView() -{ - // Quick way to avoid segfault when QGraphicsScene::selectionChanged is emitted after other members have been destroyed - Clear(); -} - -void CurveView::Clear() -{ - KeyframeViewBase::Clear(); - - foreach (QGraphicsLineItem* line, lines_) { - delete line; - } - lines_.clear(); -} - void CurveView::ConnectInput(const NodeKeyframeTrackReference& ref) { if (connected_inputs_.contains(ref)) { @@ -71,7 +55,9 @@ void CurveView::ConnectInput(const NodeKeyframeTrackReference& ref) } // Add keyframes from track - AddKeyframesOfTrack(ref); + KeyframeViewInputConnection *track_con = AddKeyframesOfTrack(ref); + track_con->SetBrush(keyframe_colors_.value(ref)); + track_connections_.insert(ref, track_con); // Append to the list connected_inputs_.append(ref); @@ -85,7 +71,7 @@ void CurveView::DisconnectInput(const NodeKeyframeTrackReference& ref) } // Remove keyframes belonging to this element and track - RemoveKeyframesOfTrack(ref); + RemoveKeyframesOfTrack(track_connections_.take(ref)); // Remove from the list connected_inputs_.removeOne(ref); @@ -95,24 +81,16 @@ void CurveView::SelectKeyframesOfInput(const NodeKeyframeTrackReference& ref) { DeselectAll(); - for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) { - if (it.key()->key_track_ref() == ref) { - it.value()->setSelected(true); + foreach (KeyframeViewInputConnection *con, track_connections_) { + foreach (NodeKeyframe *key, con->GetKeyframes()) { + SelectKeyframe(key); } } } void CurveView::ZoomToFitInput(const NodeKeyframeTrackReference& ref) { - QList keys; - - for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) { - if (it.key()->key_track_ref() == ref) { - keys.append(it.key()); - } - } - - ZoomToFitInternal(keys); + ZoomToFitInternal(track_connections_.value(ref)->GetKeyframes()); } void CurveView::SetKeyframeTrackColor(const NodeKeyframeTrackReference &ref, const QColor &color) @@ -121,11 +99,7 @@ void CurveView::SetKeyframeTrackColor(const NodeKeyframeTrackReference &ref, con keyframe_colors_.insert(ref, color); // Update all keyframes - for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) { - if (it.key()->key_track_ref() == ref) { - it.value()->SetOverrideBrush(color); - } - } + track_connections_.value(ref)->SetBrush(color); } void CurveView::drawBackground(QPainter *painter, const QRectF &rect) @@ -196,7 +170,7 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) QPainterPath path; // Draw straight line leading to first keyframe - QPointF first_key_pos = item_map().value(track.first())->pos(); + QPointF first_key_pos = GetKeyframePosition(track.first()); path.moveTo(QPointF(scene_bottom_left.x(), first_key_pos.y())); path.lineTo(first_key_pos); @@ -205,24 +179,24 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) NodeKeyframe* before = track.at(i-1); NodeKeyframe* after = track.at(i); - KeyframeViewItem* before_item = item_map().value(before); - KeyframeViewItem* after_item = item_map().value(after); + QPointF before_pos = GetKeyframePosition(before); + QPointF after_pos = GetKeyframePosition(after); if (before->type() == NodeKeyframe::kHold) { // Draw a hold keyframe (basically a right angle) - path.lineTo(after_item->pos().x(), before_item->pos().y()); - path.lineTo(after_item->pos().x(), after_item->pos().y()); + path.lineTo(after_pos.x(), before_pos.y()); + path.lineTo(after_pos.x(), after_pos.y()); } else if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) { // Draw a cubic bezier // Cubic beziers have two control points, so we can just use both - QPointF before_control_point = before_item->pos() + ScalePoint(before->valid_bezier_control_out()); - QPointF after_control_point = after_item->pos() + ScalePoint(after->valid_bezier_control_in()); + QPointF before_control_point = before_pos + ScalePoint(before->valid_bezier_control_out()); + QPointF after_control_point = after_pos + ScalePoint(after->valid_bezier_control_in()); - path.cubicTo(before_control_point, after_control_point, after_item->pos()); + path.cubicTo(before_control_point, after_control_point, after_pos); } else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) { // Draw a quadratic bezier @@ -232,10 +206,10 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) QPointF control_point; if (before->type() == NodeKeyframe::kBezier) { - key_anchor = before_item->pos(); + key_anchor = before_pos; control_point = before->valid_bezier_control_out(); } else { - key_anchor = after_item->pos(); + key_anchor = after_pos; control_point = after->valid_bezier_control_in(); } @@ -243,18 +217,18 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) control_point = key_anchor + ScalePoint(control_point); // Create the path from both keyframes - path.quadTo(control_point, after_item->pos()); + path.quadTo(control_point, after_pos); } else { // Linear to linear - path.lineTo(after_item->pos()); + path.lineTo(after_pos); } } // Draw straight line leading from end keyframe - QPointF last_key_pos = item_map().value(track.last())->pos(); + QPointF last_key_pos = GetKeyframePosition(track.last()); path.lineTo(QPointF(scene_top_right.x(), last_key_pos.y())); painter->drawPath(path); @@ -263,7 +237,7 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) } // Draw bezier control point lines - if (!bezier_control_points_.isEmpty()) { + /*if (!bezier_control_points_.isEmpty()) { painter->setPen(QPen(palette().text().color(), 1)); QVector bezier_lines; @@ -274,13 +248,7 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) bezier_lines.append(QLineF(par->pos(), par->pos() + item->pos())); } painter->drawLines(bezier_lines); - } -} - -void CurveView::KeyframeAboutToBeRemoved(NodeKeyframe *key) -{ - disconnect(key, &NodeKeyframe::ValueChanged, this, &CurveView::KeyframeValueChanged); - disconnect(key, &NodeKeyframe::TypeChanged, this, &CurveView::KeyframeTypeChanged); + }*/ } void CurveView::ScaleChangedEvent(const double& scale) @@ -296,13 +264,11 @@ void CurveView::VerticalScaleChangedEvent(double scale) { Q_UNUSED(scale) - for (auto iterator=item_map().begin();iterator!=item_map().end();iterator++) { - SetItemYFromKeyframeValue(iterator.value()->key(), iterator.value()); - } - foreach (BezierControlPointItem* item, bezier_control_points_) { item->SetYScale(scale); } + + viewport()->update(); } void CurveView::ContextMenuEvent(Menu &m) @@ -324,7 +290,7 @@ void CurveView::SceneRectUpdateEvent(QRectF &r) r.setBottom(r.bottom() + this->height()); } -void CurveView::ZoomToFitInternal(const QList &keys) +void CurveView::ZoomToFitInternal(const QVector &keys) { if (keys.isEmpty()) { // Prevent scaling to DBL_MIN/DBL_MAX @@ -373,53 +339,58 @@ qreal CurveView::GetItemYFromKeyframeValue(double value) return -value * GetYScale(); } -void CurveView::SetItemYFromKeyframeValue(NodeKeyframe *key, KeyframeViewItem *item) -{ - item->SetOverrideY(GetItemYFromKeyframeValue(key)); -} - QPointF CurveView::ScalePoint(const QPointF &point) { // Flips Y coordinate because curves are drawn bottom to top return QPointF(point.x() * GetScale(), - point.y() * GetYScale()); } -void CurveView::CreateBezierControlPoints(KeyframeViewItem* item) +void CurveView::CreateBezierControlPoints(NodeKeyframe* item) { - BezierControlPointItem* bezier_in_pt = new BezierControlPointItem(item->key(), NodeKeyframe::kInHandle, item); + qDebug() << "STUB!"; + /*BezierControlPointItem* bezier_in_pt = new BezierControlPointItem(item, NodeKeyframe::kInHandle, item); bezier_in_pt->SetXScale(GetScale()); bezier_in_pt->SetYScale(GetYScale()); bezier_control_points_.append(bezier_in_pt); connect(bezier_in_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection); - BezierControlPointItem* bezier_out_pt = new BezierControlPointItem(item->key(), NodeKeyframe::kOutHandle, item); + BezierControlPointItem* bezier_out_pt = new BezierControlPointItem(item, NodeKeyframe::kOutHandle, item); bezier_out_pt->SetXScale(GetScale()); bezier_out_pt->SetYScale(GetYScale()); bezier_control_points_.append(bezier_out_pt); - connect(bezier_out_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection); + connect(bezier_out_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection);*/ +} + +QPointF CurveView::GetKeyframePosition(NodeKeyframe *key) +{ + return QPointF(GetKeyframeSceneX(key), GetItemYFromKeyframeValue(key)); } void CurveView::KeyframeValueChanged() { - NodeKeyframe* key = static_cast(sender()); + qDebug() << "STUB!"; + /*NodeKeyframe* key = static_cast(sender()); KeyframeViewItem* item = item_map().value(key); - SetItemYFromKeyframeValue(key, item); + SetItemYFromKeyframeValue(key, item);*/ } void CurveView::KeyframeTypeChanged() { - NodeKeyframe* key = static_cast(sender()); + qDebug() << "STUB!"; + /*NodeKeyframe* key = static_cast(sender()); KeyframeViewItem* item = item_map().value(key); if (item->isSelected()) { item->setSelected(false); item->setSelected(true); - } + }*/ } void CurveView::SelectionChanged() { + qDebug() << "STUB!"; + /* // Clear current bezier handles while (!bezier_control_points_.isEmpty()) { delete bezier_control_points_.first(); @@ -434,6 +405,7 @@ void CurveView::SelectionChanged() CreateBezierControlPoints(this_item); } } + */ } void CurveView::BezierControlPointDestroyed() @@ -444,20 +416,22 @@ void CurveView::BezierControlPointDestroyed() void CurveView::ZoomToFit() { - ZoomToFitInternal(item_map().keys()); + QVector keys; + + foreach (KeyframeViewInputConnection *con, track_connections_) { + foreach (NodeKeyframe *k, con->GetKeyframes()) { + if (!keys.contains(k)) { + keys.append(k); + } + } + } + + ZoomToFitInternal(keys); } void CurveView::ZoomToFitSelected() { - QList selected_keys; - - for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) { - if (it.value()->isSelected()) { - selected_keys.append(it.key()); - } - } - - ZoomToFitInternal(selected_keys); + ZoomToFitInternal(GetSelectedKeyframes()); } void CurveView::ResetZoom() @@ -466,16 +440,4 @@ void CurveView::ResetZoom() SetYScale(1.0); } -KeyframeViewItem* CurveView::AddKeyframe(NodeKeyframe* key) -{ - KeyframeViewItem* item = super::AddKeyframe(key); - SetItemYFromKeyframeValue(key, item); - item->SetOverrideBrush(keyframe_colors_.value(key->key_track_ref())); - - connect(key, &NodeKeyframe::ValueChanged, this, &CurveView::KeyframeValueChanged); - connect(key, &NodeKeyframe::TypeChanged, this, &CurveView::KeyframeTypeChanged); - - return item; -} - } diff --git a/app/widget/curvewidget/curveview.h b/app/widget/curvewidget/curveview.h index 6a4e155c7..e5b056830 100644 --- a/app/widget/curvewidget/curveview.h +++ b/app/widget/curvewidget/curveview.h @@ -24,7 +24,6 @@ #include "beziercontrolpointitem.h" #include "node/keyframe.h" #include "widget/keyframeview/keyframeview.h" -#include "widget/keyframeview/keyframeviewitem.h" namespace olive { @@ -34,10 +33,6 @@ class CurveView : public KeyframeViewBase public: CurveView(QWidget* parent = nullptr); - virtual ~CurveView() override; - - virtual void Clear() override; - void ConnectInput(const NodeKeyframeTrackReference &ref); void DisconnectInput(const NodeKeyframeTrackReference &ref); @@ -49,8 +44,6 @@ public: void SetKeyframeTrackColor(const NodeKeyframeTrackReference& ref, const QColor& color); public slots: - virtual KeyframeViewItem* AddKeyframe(NodeKeyframe* key) override; - void ZoomToFit(); void ZoomToFitSelected(); @@ -60,8 +53,6 @@ public slots: protected: virtual void drawBackground(QPainter* painter, const QRectF& rect) override; - virtual void KeyframeAboutToBeRemoved(NodeKeyframe *key) override; - virtual void ScaleChangedEvent(const double &scale) override; virtual void VerticalScaleChangedEvent(double scale) override; @@ -71,20 +62,21 @@ protected: virtual void SceneRectUpdateEvent(QRectF &r) override; private: - void ZoomToFitInternal(const QList &keys); + void ZoomToFitInternal(const QVector &keys); qreal GetItemYFromKeyframeValue(NodeKeyframe* key); qreal GetItemYFromKeyframeValue(double value); - void SetItemYFromKeyframeValue(NodeKeyframe* key, KeyframeViewItem* item); - QPointF ScalePoint(const QPointF& point); void AdjustLines(); - void CreateBezierControlPoints(KeyframeViewItem *item); + void CreateBezierControlPoints(NodeKeyframe *item); + + QPointF GetKeyframePosition(NodeKeyframe *key); QHash keyframe_colors_; + QHash track_connections_; int text_padding_; diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index 4de5787ef..c77a553aa 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -113,12 +113,6 @@ CurveWidget::CurveWidget(QWidget *parent) : SetScale(120.0); } -CurveWidget::~CurveWidget() -{ - // Quick way to avoid segfault when QGraphicsScene::selectionChanged is emitted after other members have been destroyed - view_->Clear(); -} - const double &CurveWidget::GetVerticalScale() { return view_->GetYScale(); @@ -223,15 +217,6 @@ void CurveWidget::ConnectNode(Node *node, bool connect) ConnectInput(node, input, connect); } } - - // Connect add/remove signals - if (connect) { - QObject::connect(node, &Node::KeyframeAdded, this, &CurveWidget::AddKeyframe); - QObject::connect(node, &Node::KeyframeRemoved, this, &CurveWidget::RemoveKeyframe); - } else { - QObject::disconnect(node, &Node::KeyframeAdded, this, &CurveWidget::AddKeyframe); - QObject::disconnect(node, &Node::KeyframeRemoved, this, &CurveWidget::RemoveKeyframe); - } } void CurveWidget::ConnectInput(Node *node, const QString &input, bool connect) @@ -287,20 +272,20 @@ void CurveWidget::ConnectInput(Node *node, const QString &input, bool connect) void CurveWidget::SelectionChanged() { - QList selected = view_->scene()->selectedItems(); + const QVector &selected = view_->GetSelectedKeyframes(); SetKeyframeButtonChecked(false); SetKeyframeButtonEnabled(!selected.isEmpty()); if (!selected.isEmpty()) { bool all_same_type = true; - NodeKeyframe::Type type = static_cast(selected.first())->key()->type(); + NodeKeyframe::Type type = selected.first()->type(); for (int i=1;i(selected.at(i-1)); - KeyframeViewItem* this_item = static_cast(selected.at(i)); + NodeKeyframe* prev_item = selected.at(i-1); + NodeKeyframe* this_item = selected.at(i); - if (prev_item->key()->type() != this_item->key()->type()) { + if (prev_item->type() != this_item->type()) { all_same_type = false; break; } @@ -323,7 +308,7 @@ void CurveWidget::KeyframeTypeButtonTriggered(bool checked) } // Get selected items and do nothing if there are none - QList selected = view_->scene()->selectedItems(); + const QVector &selected = view_->GetSelectedKeyframes(); if (selected.isEmpty()) { return; } @@ -345,10 +330,8 @@ void CurveWidget::KeyframeTypeButtonTriggered(bool checked) MultiUndoCommand* command = new MultiUndoCommand(); - foreach (QGraphicsItem* item, selected) { - KeyframeViewItem* key_item = static_cast(item); - - command->add_child(new KeyframeSetTypeCommand(key_item->key(), new_type)); + foreach (NodeKeyframe* item, selected) { + command->add_child(new KeyframeSetTypeCommand(item, new_type)); } Core::instance()->undo_stack()->push(command); @@ -368,16 +351,6 @@ void CurveWidget::InputEnabledChanged(const NodeKeyframeTrackReference& ref, boo } } -void CurveWidget::AddKeyframe(NodeKeyframe *key) -{ - view_->AddKeyframe(key); -} - -void CurveWidget::RemoveKeyframe(NodeKeyframe *key) -{ - view_->RemoveKeyframe(key); -} - void CurveWidget::InputSelectionChanged(const NodeKeyframeTrackReference& ref) { key_control_->SetInput(ref.input()); diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index 7b9e01021..db14f2955 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -40,8 +40,6 @@ class CurveWidget : public TimeBasedWidget, public TimeTargetObject public: CurveWidget(QWidget* parent = nullptr); - virtual ~CurveWidget() override; - const double& GetVerticalScale(); void SetVerticalScale(const double& vscale); @@ -107,10 +105,6 @@ private slots: void InputEnabledChanged(const NodeKeyframeTrackReference &ref, bool e); - void AddKeyframe(NodeKeyframe* key); - - void RemoveKeyframe(NodeKeyframe* key); - void InputSelectionChanged(const NodeKeyframeTrackReference& ref); void InputDoubleClicked(const NodeKeyframeTrackReference& ref); diff --git a/app/widget/keyframeview/CMakeLists.txt b/app/widget/keyframeview/CMakeLists.txt index c4d5ef43e..1ab460182 100644 --- a/app/widget/keyframeview/CMakeLists.txt +++ b/app/widget/keyframeview/CMakeLists.txt @@ -16,13 +16,13 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - widget/keyframeview/keyframeview.h widget/keyframeview/keyframeview.cpp - widget/keyframeview/keyframeviewbase.h + widget/keyframeview/keyframeview.h widget/keyframeview/keyframeviewbase.cpp - widget/keyframeview/keyframeviewitem.h - widget/keyframeview/keyframeviewitem.cpp - widget/keyframeview/keyframeviewundo.h + widget/keyframeview/keyframeviewbase.h + widget/keyframeview/keyframeviewinputconnection.cpp + widget/keyframeview/keyframeviewinputconnection.h widget/keyframeview/keyframeviewundo.cpp + widget/keyframeview/keyframeviewundo.h PARENT_SCOPE ) diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index bba5f29f9..f8cf5413f 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -31,29 +31,10 @@ KeyframeView::KeyframeView(QWidget *parent) : setAlignment(Qt::AlignLeft | Qt::AlignTop); } -void KeyframeView::SetElementY(const NodeInput &c, int y) -{ - qreal scene_y = mapToScene(mapFromGlobal(QPoint(0, y))).y(); - - element_y_.insert(c, scene_y); - - for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) { - if (it.key()->key_track_ref().input() == c) { - it.value()->SetOverrideY(scene_y); - } - } -} void KeyframeView::SceneRectUpdateEvent(QRectF &rect) { rect.setY(0); rect.setHeight(max_scroll_); } -KeyframeViewItem* KeyframeView::AddKeyframe(NodeKeyframe* key) -{ - KeyframeViewItem* item = super::AddKeyframe(key); - item->SetOverrideY(element_y_.value(key->key_track_ref().input())); - return item; -} - } diff --git a/app/widget/keyframeview/keyframeview.h b/app/widget/keyframeview/keyframeview.h index 9b7f76d89..56874232a 100644 --- a/app/widget/keyframeview/keyframeview.h +++ b/app/widget/keyframeview/keyframeview.h @@ -34,19 +34,13 @@ public: void SetMaxScroll(int i) { max_scroll_ = i; + UpdateSceneRect(); } - void SetElementY(const NodeInput& c, int y); - protected: virtual void SceneRectUpdateEvent(QRectF& rect) override; -public slots: - virtual KeyframeViewItem* AddKeyframe(NodeKeyframe* key) override; - private: - QHash element_y_; - int max_scroll_; }; diff --git a/app/widget/keyframeview/keyframeviewbase.cpp b/app/widget/keyframeview/keyframeviewbase.cpp index 5d0384708..c3be1e273 100644 --- a/app/widget/keyframeview/keyframeviewbase.cpp +++ b/app/widget/keyframeview/keyframeviewbase.cpp @@ -24,6 +24,7 @@ #include #include +#include "common/qtutils.h" #include "dialog/keyframeproperties/keyframeproperties.h" #include "keyframeviewundo.h" #include "node/node.h" @@ -33,201 +34,144 @@ namespace olive { +#define super TimeBasedView + KeyframeViewBase::KeyframeViewBase(QWidget *parent) : - TimeBasedView(parent), + super(parent), dragging_bezier_point_(nullptr), currently_autoselecting_(false), - dragging_(false) + dragging_(false), + selection_manager_(this) { SetDefaultDragMode(RubberBandDrag); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &KeyframeViewBase::customContextMenuRequested, this, &KeyframeViewBase::ShowContextMenu); - connect(scene(), &QGraphicsScene::selectionChanged, this, &KeyframeViewBase::AutoSelectKeyTimeNeighbors); -} - -void KeyframeViewBase::Clear() -{ - QMap::iterator iterator; - - for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) { - delete iterator.value(); - } - - item_map_.clear(); } void KeyframeViewBase::DeleteSelected() { MultiUndoCommand* command = new MultiUndoCommand(); - QMap::const_iterator i; - - for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) { - if (i.value()->isSelected()) { - command->add_child(new NodeParamRemoveKeyframeCommand(i.key())); - } + foreach (NodeKeyframe *key, GetSelectedKeyframes()) { + command->add_child(new NodeParamRemoveKeyframeCommand(key)); } Core::instance()->undo_stack()->pushIfHasChildren(command); } -void KeyframeViewBase::AddKeyframesOfNode(Node *n) +KeyframeViewBase::NodeConnections KeyframeViewBase::AddKeyframesOfNode(Node *n) { + NodeConnections map; + foreach (const QString& i, n->inputs()) { - AddKeyframesOfInput(n, i); + map.insert(i, AddKeyframesOfInput(n, i)); } + + return map; } -void KeyframeViewBase::AddKeyframesOfInput(Node* n, const QString& input) +KeyframeViewBase::InputConnections KeyframeViewBase::AddKeyframesOfInput(Node* n, const QString& input) { - if (!n->IsInputKeyframable(input)) { - return; + InputConnections vec; + + if (n->IsInputKeyframable(input)) { + int arr_sz = n->InputArraySize(input); + vec.resize(arr_sz + 1); + for (int i=-1; iInputArraySize(input); - for (int i=-1; i& tracks = input.node()->GetKeyframeTracks(input); + ElementConnections vec(tracks.size()); for (int i=0; i& tracks = ref.input().node()->GetKeyframeTracks(ref.input()); - const NodeKeyframeTrack& t = tracks.at(ref.track()); - - foreach (NodeKeyframe* key, t) { - AddKeyframe(key); - } + KeyframeViewInputConnection *track = new KeyframeViewInputConnection(ref, this); + connect(track, &KeyframeViewInputConnection::RequireUpdate, this, &KeyframeViewBase::Redraw); + tracks_.append(track); + Redraw(); + return track; } -void KeyframeViewBase::RemoveKeyframesOfNode(Node *n) +void KeyframeViewBase::RemoveKeyframesOfTrack(KeyframeViewInputConnection *connection) { - foreach (const QString& i, n->inputs()) { - RemoveKeyframesOfInput(n, i); - } -} - -void KeyframeViewBase::RemoveKeyframesOfInput(Node* n, const QString& input) -{ - if (!n->IsInputKeyframable(input)) { - return; - } - - int arr_sz = n->InputArraySize(input); - for (int i=-1; i& tracks = input.node()->GetKeyframeTracks(input); - - for (int i=0; i& tracks = ref.input().node()->GetKeyframeTracks(ref.input()); - const NodeKeyframeTrack& t = tracks.at(ref.track()); - - foreach (NodeKeyframe* key, t) { - RemoveKeyframe(key); + if (tracks_.removeOne(connection)) { + delete connection; + Redraw(); } } void KeyframeViewBase::SelectAll() { - for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) { - it.value()->setSelected(true); + foreach (KeyframeViewInputConnection *track, tracks_) { + foreach (NodeKeyframe *key, track->GetKeyframes()) { + SelectKeyframe(key); + } } } void KeyframeViewBase::DeselectAll() { - for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) { - it.value()->setSelected(false); - } + selection_manager_.ClearSelection(); + + Redraw(); } -void KeyframeViewBase::RemoveKeyframe(NodeKeyframe* key) +void KeyframeViewBase::Clear() { - KeyframeAboutToBeRemoved(key); - - delete item_map_.take(key); -} - -KeyframeViewItem *KeyframeViewBase::AddKeyframe(NodeKeyframe* key) -{ - KeyframeViewItem* item = item_map_.value(key); - - if (!item) { - item = new KeyframeViewItem(key); - item->SetTimeTarget(GetTimeTarget()); - item->SetScale(GetScale()); - item_map_.insert(key, item); - scene()->addItem(item); + if (!tracks_.isEmpty()) { + qDeleteAll(tracks_); + tracks_.clear(); + Redraw(); } - - return item; } void KeyframeViewBase::mousePressEvent(QMouseEvent *event) { - QGraphicsItem* item_under_cursor = itemAt(event->pos()); + NodeKeyframe *key_under_cursor = selection_manager_.MousePress(event); + if (key_under_cursor) { + AutoSelectKeyTimeNeighbors(); + } - if (HandPress(event) || (!item_under_cursor && PlayheadPress(event))) { + BezierControlPointItem *bezier_under_cursor = dynamic_cast(itemAt(event->pos())); + + Redraw(); + + if (HandPress(event) || (!bezier_under_cursor && !key_under_cursor && PlayheadPress(event))) { return; } - active_tool_ = Core::instance()->tool(); - if (event->button() == Qt::LeftButton) { - QGraphicsView::mousePressEvent(event); + if (key_under_cursor || bezier_under_cursor) { + dragging_ = true; + drag_start_ = mapToScene(event->pos()); - if (active_tool_ == Tool::kPointer) { - if (item_under_cursor) { + // Determine what type of item is under the cursor + dragging_bezier_point_ = bezier_under_cursor; - dragging_ = true; - drag_start_ = mapToScene(event->pos()); + if (dragging_bezier_point_) { - // Determine what type of item is under the cursor - dragging_bezier_point_ = dynamic_cast(item_under_cursor); + dragging_bezier_point_start_ = dragging_bezier_point_->GetCorrespondingKeyframeHandle(); + dragging_bezier_point_opposing_start_ = dragging_bezier_point_->key()->bezier_control(NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode())); - if (dragging_bezier_point_) { + } else { - dragging_bezier_point_start_ = dragging_bezier_point_->GetCorrespondingKeyframeHandle(); - dragging_bezier_point_opposing_start_ = dragging_bezier_point_->key()->bezier_control(NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode())); + selection_manager_.DragStart(key_under_cursor, event); - } else { - - QList selected_items = scene()->selectedItems(); - - selected_keys_.resize(selected_items.size()); - - initial_drag_item_ = static_cast(item_under_cursor); - - for (int i=0;i(selected_items.at(i)); - - selected_keys_.replace(i, {key, - key->x(), - GetAdjustedTime(key->key()->parent(), GetTimeTarget(), key->key()->time(), false), - key->key()->value().toDouble()}); - } - } } } } @@ -240,11 +184,10 @@ void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event) } if (event->buttons() & Qt::LeftButton) { - QGraphicsView::mouseMoveEvent(event); - if (dragging_) { // Calculate cursor difference and scale it - QPointF mouse_diff_scaled = GetScaledCursorPos(mapToScene(event->pos()) - drag_start_); + QPointF scene_pos = mapToScene(event->pos()); + QPointF mouse_diff_scaled = GetScaledCursorPos(scene_pos - drag_start_); if (event->modifiers() & Qt::ShiftModifier) { // If holding shift, only move one axis @@ -284,15 +227,19 @@ void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event) QPointF bezier_pos = dragging_bezier_point_->pos() + dragging_bezier_point_->parentItem()->pos(); emit Dragged(qRound(bezier_pos.x()), qRound(bezier_pos.y())); - } else if (!selected_keys_.isEmpty()) { + } else if (selection_manager_.IsDragging()) { + QString tip; + + /* // Validate movement - ensure no keyframe goes above its max point or below its min point FloatSlider::DisplayType display_type = FloatSlider::kNormal; if (IsYAxisEnabled()) { - foreach (const KeyframeItemAndTime& keypair, selected_keys_) { - Node* node = keypair.key->key()->parent(); - const QString& input = keypair.key->key()->input(); + foreach (const KeyframeItemAndTime& keypair, dragging_keyframes_) { + NodeKeyframe *key = keypair.key; + Node* node = key->parent(); + const QString& input = key->input(); double new_val = keypair.value - mouse_diff_scaled.y(); double limited = new_val; @@ -309,48 +256,37 @@ void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event) } } - Node* initial_drag_input = initial_drag_item_->key()->parent(); - const QString& initial_drag_input_id = initial_drag_item_->key()->input(); + Node* initial_drag_input = initial_drag_item_->parent(); + const QString& initial_drag_input_id = initial_drag_item_->input(); if (initial_drag_input->HasInputProperty(initial_drag_input_id, QStringLiteral("view"))) { display_type = static_cast(initial_drag_input->GetInputProperty(initial_drag_input_id, QStringLiteral("view")).toInt()); } } - foreach (const KeyframeItemAndTime& keypair, selected_keys_) { - rational node_time = GetAdjustedTime(GetTimeTarget(), - keypair.key->key()->parent(), - CalculateNewTimeFromScreen(keypair.time, mouse_diff_scaled.x()), - true); - - keypair.key->key()->set_time(node_time); - + foreach (const KeyframeItemAndTime& keypair, dragging_keyframes_) { if (IsYAxisEnabled()) { - keypair.key->key()->set_value(keypair.value - mouse_diff_scaled.y()); + key->set_value(keypair.value - mouse_diff_scaled.y()); } } - // Show information about this keyframe - QString tip = Timecode::time_to_timecode(initial_drag_item_->key()->time(), timebase(), - Core::instance()->GetTimecodeDisplay(), false); + if (IsYAxisEnabled()) { bool ok; - double num_value = initial_drag_item_->key()->value().toDouble(&ok); + double num_value = initial_drag_item_->value().toDouble(&ok); if (ok) { - tip.append('\n'); + tip = QStringLiteral("%1\n"); tip.append(FloatSlider::ValueToString(num_value, display_type, 2, true)); } - - // Force viewport to update since Qt might try to optimize it out if the keyframe is - // offscreen - viewport()->update(); } + */ - QToolTip::hideText(); - QToolTip::showText(QCursor::pos(), tip); + selection_manager_.DragMove(event, tip); - emit Dragged(qRound(initial_drag_item_->x()), qRound(initial_drag_item_->y())); + Redraw(); + + emit Dragged(scene_pos.x(), scene_pos.y()); } } @@ -364,8 +300,6 @@ void KeyframeViewBase::mouseReleaseEvent(QMouseEvent *event) } if (event->button() == Qt::LeftButton) { - QGraphicsView::mouseReleaseEvent(event); - if (dragging_) { if (dragging_bezier_point_) { MultiUndoCommand* command = new MultiUndoCommand(); @@ -388,30 +322,19 @@ void KeyframeViewBase::mouseReleaseEvent(QMouseEvent *event) dragging_bezier_point_ = nullptr; Core::instance()->undo_stack()->push(command); - } else if (!selected_keys_.isEmpty()) { + } else if (selection_manager_.IsDragging()) { MultiUndoCommand* command = new MultiUndoCommand(); - foreach (const KeyframeItemAndTime& keypair, selected_keys_) { - NodeKeyframe* item = keypair.key->key(); - - // Commit movement - command->add_child(new NodeParamSetKeyframeTimeCommand(item, - item->time(), - keypair.time)); - - // Commit value if we're setting a value - if (IsYAxisEnabled()) { - command->add_child(new NodeParamSetKeyframeValueCommand(item, - item->value(), - keypair.value)); - } - } + selection_manager_.DragStop(command); + /*if (IsYAxisEnabled()) { + command->add_child(new NodeParamSetKeyframeValueCommand(item, + item->value(), + keypair.value)); + }*/ Core::instance()->undo_stack()->push(command); } - selected_keys_.clear(); - dragging_ = false; QToolTip::hideText(); @@ -419,31 +342,77 @@ void KeyframeViewBase::mouseReleaseEvent(QMouseEvent *event) } } +void KeyframeViewBase::drawForeground(QPainter *painter, const QRectF &rect) +{ + int key_sz = QtUtils::QFontMetricsWidth(fontMetrics(), "Oi"); + int key_rad = key_sz/2; + + selection_manager_.ClearDrawnObjects(); + + painter->setRenderHint(QPainter::Antialiasing); + + painter->setPen(Qt::black); + + foreach (KeyframeViewInputConnection *track, tracks_) { + foreach (NodeKeyframe *key, track->GetKeyframes()) { + QRectF key_rect(-key_rad, -key_rad, key_sz, key_sz); + key_rect.translate(GetKeyframeSceneX(key), mapFromGlobal(QPoint(0, track->GetKeyframeY())).y()); + + if (!rect.intersects(key_rect)) { + continue; + } + + if (IsKeyframeSelected(key)) { + painter->setBrush(palette().highlight()); + } else { + painter->setBrush(track->GetBrush()); + } + + selection_manager_.DeclareDrawnObject(key, key_rect); + + switch (key->type()) { + case NodeKeyframe::kLinear: + { + QPointF points[] = { + QPointF(key_rect.center().x(), key_rect.top()), + QPointF(key_rect.right(), key_rect.center().y()), + QPointF(key_rect.center().x(), key_rect.bottom()), + QPointF(key_rect.left(), key_rect.center().y()) + }; + + painter->drawPolygon(points, 4); + break; + } + case NodeKeyframe::kBezier: + painter->drawEllipse(key_rect); + break; + case NodeKeyframe::kHold: + painter->drawRect(key_rect); + break; + } + } + } + + super::drawForeground(painter, rect); +} + void KeyframeViewBase::ScaleChangedEvent(const double &scale) { - TimeBasedView::ScaleChangedEvent(scale); + super::ScaleChangedEvent(scale); - for (auto iterator=item_map_.begin();iterator!=item_map_.end();iterator++) { - iterator.value()->SetScale(scale); - } -} - -const QMap &KeyframeViewBase::item_map() const -{ - return item_map_; -} - -void KeyframeViewBase::KeyframeAboutToBeRemoved(NodeKeyframe *) -{ + Redraw(); } void KeyframeViewBase::TimeTargetChangedEvent(Node *target) { - QMap::const_iterator i; + Redraw(); +} - for (i=item_map_.begin();i!=item_map_.end();i++) { - i.value()->SetTimeTarget(target); - } +void KeyframeViewBase::TimebaseChangedEvent(const rational &timebase) +{ + super::TimebaseChangedEvent(timebase); + + selection_manager_.SetTimebase(timebase); } void KeyframeViewBase::ContextMenuEvent(Menu& m) @@ -451,6 +420,30 @@ void KeyframeViewBase::ContextMenuEvent(Menu& m) Q_UNUSED(m) } +void KeyframeViewBase::SelectKeyframe(NodeKeyframe *key) +{ + if (selection_manager_.Select(key)) { + Redraw(); + } +} + +void KeyframeViewBase::DeselectKeyframe(NodeKeyframe *key) +{ + if (selection_manager_.Deselect(key)) { + Redraw(); + } +} + +rational KeyframeViewBase::GetAdjustedKeyframeTime(NodeKeyframe *key) +{ + return GetAdjustedTime(key->parent(), GetTimeTarget(), key->time(), false); +} + +double KeyframeViewBase::GetKeyframeSceneX(NodeKeyframe *key) +{ + return TimeToScene(GetAdjustedKeyframeTime(key)); +} + rational KeyframeViewBase::CalculateNewTimeFromScreen(const rational &old_time, double cursor_diff) { return rational::fromDouble(old_time.toDouble() + cursor_diff); @@ -492,16 +485,15 @@ void KeyframeViewBase::ShowContextMenu() QAction* bezier_key_action = nullptr; QAction* hold_key_action = nullptr; - QList items = scene()->selectedItems(); - if (!items.isEmpty()) { + if (!GetSelectedKeyframes().isEmpty()) { bool all_keys_are_same_type = true; - NodeKeyframe::Type type = static_cast(items.first())->key()->type(); + NodeKeyframe::Type type = GetSelectedKeyframes().first()->type(); - for (int i=1;i(items.at(i)); - KeyframeViewItem* prev_item = static_cast(items.at(i-1)); + for (int i=1;ikey()->type() != prev_item->key()->type()) { + if (key_item->type() != prev_item->type()) { all_keys_are_same_type = false; break; } @@ -536,7 +528,7 @@ void KeyframeViewBase::ShowContextMenu() ContextMenuEvent(m); - if (!items.isEmpty()) { + if (!GetSelectedKeyframes().isEmpty()) { m.addSeparator(); QAction* properties_action = m.addAction(tr("P&roperties")); @@ -546,7 +538,7 @@ void KeyframeViewBase::ShowContextMenu() QAction* selected = m.exec(QCursor::pos()); // Process keyframe type changes - if (!items.isEmpty()) { + if (selected) { if (selected == linear_key_action || selected == bezier_key_action || selected == hold_key_action) { @@ -561,26 +553,18 @@ void KeyframeViewBase::ShowContextMenu() } MultiUndoCommand* command = new MultiUndoCommand(); - foreach (QGraphicsItem* item, items) { - command->add_child(new KeyframeSetTypeCommand(static_cast(item)->key(), - new_type)); + foreach (NodeKeyframe* item, GetSelectedKeyframes()) { + command->add_child(new KeyframeSetTypeCommand(item, new_type)); } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } } } void KeyframeViewBase::ShowKeyframePropertiesDialog() { - QList items = scene()->selectedItems(); - QVector keys; - - foreach (QGraphicsItem* item, items) { - keys.append(static_cast(item)->key()); - } - - if (!keys.isEmpty()) { - KeyframePropertiesDialog kd(keys, timebase(), this); + if (!GetSelectedKeyframes().isEmpty()) { + KeyframePropertiesDialog kd(GetSelectedKeyframes(), timebase(), this); kd.exec(); } } @@ -594,25 +578,15 @@ void KeyframeViewBase::AutoSelectKeyTimeNeighbors() // Prevents infinite loop currently_autoselecting_ = true; - QList selected_items = scene()->selectedItems(); + QVector copy = GetSelectedKeyframes(); + foreach (NodeKeyframe *key, copy) { + rational key_time = key->time(); - foreach (QGraphicsItem* g, selected_items) { - KeyframeViewItem* key_item = static_cast(g); - - rational key_time = key_item->key()->time(); - - QVector keys = key_item->key()->parent()->GetKeyframesAtTime(key_item->key()->input(), key_time, key_item->key()->element()); + QVector keys = key->parent()->GetKeyframesAtTime(key->input(), key_time, key->element()); foreach (NodeKeyframe* k, keys) { - if (k == key_item->key()) { - continue; - } - - // Ensure this key is not already selected - KeyframeViewItem* item = item_map_.value(k); - - if (item) { - item->setSelected(true); + if (k != key) { + SelectKeyframe(k); } } } @@ -620,4 +594,9 @@ void KeyframeViewBase::AutoSelectKeyTimeNeighbors() currently_autoselecting_ = false; } +void KeyframeViewBase::Redraw() +{ + viewport()->update(); +} + } diff --git a/app/widget/keyframeview/keyframeviewbase.h b/app/widget/keyframeview/keyframeviewbase.h index 55b6ecf17..2ce0673f7 100644 --- a/app/widget/keyframeview/keyframeviewbase.h +++ b/app/widget/keyframeview/keyframeviewbase.h @@ -21,11 +21,12 @@ #ifndef KEYFRAMEVIEWBASE_H #define KEYFRAMEVIEWBASE_H -#include "keyframeviewitem.h" +#include "keyframeviewinputconnection.h" #include "node/keyframe.h" #include "widget/curvewidget/beziercontrolpointitem.h" #include "widget/menu/menu.h" #include "widget/timebased/timebasedview.h" +#include "widget/timebased/timebasedviewselectionmanager.h" #include "widget/timetarget/timetarget.h" namespace olive { @@ -36,51 +37,49 @@ class KeyframeViewBase : public TimeBasedView, public TimeTargetObject public: KeyframeViewBase(QWidget* parent = nullptr); - virtual void Clear(); - void DeleteSelected(); - void AddKeyframesOfNode(Node* n); + using ElementConnections = QVector; + using InputConnections = QVector; + using NodeConnections = QMap; - void AddKeyframesOfInput(Node *n, const QString &input); + NodeConnections AddKeyframesOfNode(Node* n); - void AddKeyframesOfElement(const NodeInput &input); + InputConnections AddKeyframesOfInput(Node *n, const QString &input); - void AddKeyframesOfTrack(const NodeKeyframeTrackReference &ref); + ElementConnections AddKeyframesOfElement(const NodeInput &input); - void RemoveKeyframesOfNode(Node* n); + KeyframeViewInputConnection *AddKeyframesOfTrack(const NodeKeyframeTrackReference &ref); - void RemoveKeyframesOfInput(Node *n, const QString &input); - - void RemoveKeyframesOfElement(const NodeInput &input); - - void RemoveKeyframesOfTrack(const NodeKeyframeTrackReference &ref); + void RemoveKeyframesOfTrack(KeyframeViewInputConnection *connection); void SelectAll(); void DeselectAll(); + void Clear(); + + const QVector &GetSelectedKeyframes() const + { + return selection_manager_.GetSelectedObjects(); + } + signals: void Dragged(int current_x, int current_y); -public slots: - virtual KeyframeViewItem* AddKeyframe(NodeKeyframe* key); - - void RemoveKeyframe(NodeKeyframe* key); - protected: virtual void mousePressEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override; virtual void mouseReleaseEvent(QMouseEvent *event) override; + virtual void drawForeground(QPainter *painter, const QRectF &rect) override; + virtual void ScaleChangedEvent(const double& scale) override; - const QMap& item_map() const; - - virtual void KeyframeAboutToBeRemoved(NodeKeyframe* key); - virtual void TimeTargetChangedEvent(Node*) override; + virtual void TimebaseChangedEvent(const rational &timebase) override; + virtual void ContextMenuEvent(Menu &m); bool IsDragging() const @@ -88,6 +87,19 @@ protected: return dragging_; } + void SelectKeyframe(NodeKeyframe *key); + + void DeselectKeyframe(NodeKeyframe *key); + + bool IsKeyframeSelected(NodeKeyframe *key) const + { + return selection_manager_.IsSelected(key); + } + + rational GetAdjustedKeyframeTime(NodeKeyframe *key); + + double GetKeyframeSceneX(NodeKeyframe *key); + private: rational CalculateNewTimeFromScreen(const rational& old_time, double cursor_diff); @@ -97,31 +109,20 @@ private: QPointF GetScaledCursorPos(const QPointF &cursor_pos); - struct KeyframeItemAndTime { - KeyframeViewItem* key; - qreal item_x; - rational time; - double value; - }; - - QMap item_map_; - - Tool::Item active_tool_; - QPointF drag_start_; BezierControlPointItem* dragging_bezier_point_; QPointF dragging_bezier_point_start_; QPointF dragging_bezier_point_opposing_start_; - KeyframeViewItem* initial_drag_item_; - - QVector selected_keys_; + QVector tracks_; bool currently_autoselecting_; bool dragging_; + TimeBasedViewSelectionManager selection_manager_; + private slots: void ShowContextMenu(); @@ -129,6 +130,8 @@ private slots: void AutoSelectKeyTimeNeighbors(); + void Redraw(); + }; } diff --git a/app/widget/keyframeview/keyframeviewinputconnection.cpp b/app/widget/keyframeview/keyframeviewinputconnection.cpp new file mode 100644 index 000000000..a8e9a35e5 --- /dev/null +++ b/app/widget/keyframeview/keyframeviewinputconnection.cpp @@ -0,0 +1,83 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "keyframeviewinputconnection.h" + +#include "keyframeview.h" + +namespace olive { + +KeyframeViewInputConnection::KeyframeViewInputConnection(const NodeKeyframeTrackReference &input, KeyframeViewBase *parent) : + QObject(parent), + keyframe_view_(parent), + input_(input), + y_(0), + y_behavior_(kSingleRow), + brush_(Qt::white) +{ + Node *n = input.input().node(); + + connect(n, &Node::KeyframeAdded, this, &KeyframeViewInputConnection::AddKeyframe); + connect(n, &Node::KeyframeRemoved, this, &KeyframeViewInputConnection::RemoveKeyframe); + connect(n, &Node::KeyframeTimeChanged, this, &KeyframeViewInputConnection::RequireUpdate); +} + +void KeyframeViewInputConnection::SetKeyframeY(int y) +{ + if (y_ != y) { + y_ = y; + + emit RequireUpdate(); + } +} + +void KeyframeViewInputConnection::SetYBehavior(YBehavior e) +{ + if (y_behavior_ != e) { + y_behavior_ = e; + + emit RequireUpdate(); + } +} + +void KeyframeViewInputConnection::SetBrush(const QBrush &brush) +{ + if (brush_ != brush) { + brush_ = brush; + + emit RequireUpdate(); + } +} + +void KeyframeViewInputConnection::AddKeyframe(NodeKeyframe *key) +{ + if (key->key_track_ref() == input_) { + emit RequireUpdate(); + } +} + +void KeyframeViewInputConnection::RemoveKeyframe(NodeKeyframe *key) +{ + if (key->key_track_ref() == input_) { + emit RequireUpdate(); + } +} + +} diff --git a/app/widget/keyframeview/keyframeviewinputconnection.h b/app/widget/keyframeview/keyframeviewinputconnection.h new file mode 100644 index 000000000..b8f951d22 --- /dev/null +++ b/app/widget/keyframeview/keyframeviewinputconnection.h @@ -0,0 +1,88 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef KEYFRAMEVIEWINPUTCONNECTION_H +#define KEYFRAMEVIEWINPUTCONNECTION_H + +#include + +#include "node/node.h" +#include "node/param.h" + +namespace olive { + +class KeyframeViewBase; + +class KeyframeViewInputConnection : public QObject +{ + Q_OBJECT +public: + KeyframeViewInputConnection(const NodeKeyframeTrackReference &input, KeyframeViewBase *parent); + + const int &GetKeyframeY() const + { + return y_; + } + + void SetKeyframeY(int y); + + enum YBehavior { + kSingleRow, + kValueIsHeight + }; + + void SetYBehavior(YBehavior e); + + const QVector GetKeyframes() const + { + return input_.input().node()->GetKeyframeTracks(input_.input()).at(input_.track()); + } + + const QBrush &GetBrush() const + { + return brush_; + } + + void SetBrush(const QBrush &brush); + +signals: + void RequireUpdate(); + +private: + KeyframeViewBase *keyframe_view_; + + NodeKeyframeTrackReference input_; + + int y_; + + YBehavior y_behavior_; + + QBrush brush_; + +private slots: + void AddKeyframe(NodeKeyframe *key); + + void RemoveKeyframe(NodeKeyframe *key); + +}; + +} + +#endif // KEYFRAMEVIEWINPUTCONNECTION_H diff --git a/app/widget/keyframeview/keyframeviewitem.cpp b/app/widget/keyframeview/keyframeviewitem.cpp deleted file mode 100644 index 45becc01b..000000000 --- a/app/widget/keyframeview/keyframeviewitem.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "keyframeviewitem.h" - -#include -#include -#include -#include - -#include "common/qtutils.h" - -namespace olive { - -KeyframeViewItem::KeyframeViewItem(NodeKeyframe* key, QGraphicsItem *parent) : - QGraphicsRectItem(parent), - key_(key), - scale_(1.0), - vert_center_(0), - use_custom_brush_(false) -{ - setFlag(QGraphicsItem::ItemIsSelectable); - - connect(key, &NodeKeyframe::TimeChanged, this, &KeyframeViewItem::UpdatePos); - connect(key, &NodeKeyframe::TypeChanged, this, &KeyframeViewItem::Redraw); - - int keyframe_size = QtUtils::QFontMetricsWidth(qApp->fontMetrics(), "Oi"); - int half_sz = keyframe_size/2; - setRect(-half_sz, -half_sz, keyframe_size, keyframe_size); - - UpdatePos(); - - // Set default brush -} - -void KeyframeViewItem::SetOverrideY(qreal vertical_center) -{ - vert_center_ = vertical_center; - UpdatePos(); -} - -void KeyframeViewItem::SetScale(double scale) -{ - scale_ = scale; - UpdatePos(); -} - -void KeyframeViewItem::SetOverrideBrush(const QBrush &b) -{ - use_custom_brush_ = true; - setBrush(b); -} - -NodeKeyframe* KeyframeViewItem::key() const -{ - return key_; -} - -void KeyframeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) -{ - painter->setRenderHint(QPainter::Antialiasing); - - painter->setPen(Qt::black); - - if (option->state & QStyle::State_Selected) { - painter->setBrush(widget->palette().highlight()); - } else if (use_custom_brush_) { - painter->setBrush(brush()); - } else { - painter->setBrush(widget->palette().text()); - } - - switch (key_->type()) { - case NodeKeyframe::kLinear: - { - QPointF points[] = { - QPointF(rect().center().x(), rect().top()), - QPointF(rect().right(), rect().center().y()), - QPointF(rect().center().x(), rect().bottom()), - QPointF(rect().left(), rect().center().y()) - }; - - painter->drawPolygon(points, 4); - break; - } - case NodeKeyframe::kBezier: - painter->drawEllipse(rect()); - break; - case NodeKeyframe::kHold: - painter->drawRect(rect()); - break; - } -} - -void KeyframeViewItem::TimeTargetChangedEvent(Node *) -{ - UpdatePos(); -} - -void KeyframeViewItem::UpdatePos() -{ - rational adjusted = GetAdjustedTime(key_->parent(), GetTimeTarget(), key_->time(), false); - - setPos(adjusted.toDouble() * scale_, vert_center_); -} - -void KeyframeViewItem::Redraw() -{ - QGraphicsItem::update(); -} - -} diff --git a/app/widget/keyframeview/keyframeviewitem.h b/app/widget/keyframeview/keyframeviewitem.h deleted file mode 100644 index 3f3725dc6..000000000 --- a/app/widget/keyframeview/keyframeviewitem.h +++ /dev/null @@ -1,68 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef KEYFRAMEVIEWITEM_H -#define KEYFRAMEVIEWITEM_H - -#include - -#include "node/keyframe.h" -#include "widget/timetarget/timetarget.h" - -namespace olive { - -class KeyframeViewItem : public QObject, public QGraphicsRectItem, public TimeTargetObject -{ - Q_OBJECT -public: - KeyframeViewItem(NodeKeyframe* key, QGraphicsItem *parent = nullptr); - - void SetOverrideY(qreal vertical_center); - - void SetScale(double scale); - - void SetOverrideBrush(const QBrush& b); - - NodeKeyframe* key() const; - -protected: - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; - - virtual void TimeTargetChangedEvent(Node* ) override; - -private: - NodeKeyframe* key_; - - double scale_; - - qreal vert_center_; - - bool use_custom_brush_; - -private slots: - void UpdatePos(); - - void Redraw(); - -}; - -} - -#endif // KEYFRAMEVIEWITEM_H diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 0a6f743e5..b2e219478 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -229,6 +229,10 @@ void NodeParamView::SetContexts(const QVector &contexts) ctx->setVisible(false); } + if (keyframe_view_) { + keyframe_view_->Clear(); + } + if (focused_node_) { focused_node_ = nullptr; emit FocusedNodeChanged(nullptr); @@ -264,6 +268,10 @@ void NodeParamView::SetContexts(const QVector &contexts) foreach (NodeParamViewContext *ctx, context_items_) { SortItemsInContext(ctx); } + + if (keyframe_view_) { + QueueKeyframePositionUpdate(); + } } void NodeParamView::resizeEvent(QResizeEvent *event) @@ -348,54 +356,13 @@ void NodeParamView::QueueKeyframePositionUpdate() QMetaObject::invokeMethod(this, &NodeParamView::UpdateElementY, Qt::QueuedConnection); } -void NodeParamView::SignalNodeOrder() -{ - /* - // Sort by item Y (apparently there's no way in Qt to get the order of dock widgets) - QVector nodes; - QVector item_ys; - - for (auto it=items_.cbegin(); it!=items_.cend(); it++) { - int item_y = it.value()->pos().y(); - - bool inserted = false; - - for (int i=0; i item_y) { - item_ys.insert(i, item_y); - nodes.insert(i, it.key()); - inserted = true; - break; - } - } - - if (!inserted) { - item_ys.append(item_y); - nodes.append(it.key()); - } - } - - emit NodeOrderChanged(nodes); - */ -} - void NodeParamView::AddNode(Node *n, NodeParamViewContext *context) { NodeParamViewItem* item = new NodeParamViewItem(n, create_checkboxes_, context); - if (keyframe_view_) { - connect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); - connect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); - } - connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::SetTimeAndSignal); connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode); - connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::QueueKeyframePositionUpdate); - connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::SignalNodeOrder); connect(item, &NodeParamViewItem::PinToggled, this, &NodeParamView::PinNode); - connect(item, &NodeParamViewItem::ArrayExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate); - connect(item, &NodeParamViewItem::ExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate); - connect(item, &NodeParamViewItem::Moved, this, &NodeParamView::QueueKeyframePositionUpdate); connect(item, &NodeParamViewItem::InputCheckedChanged, this, &NodeParamView::SetInputChecked); if (create_checkboxes_) { @@ -422,48 +389,15 @@ void NodeParamView::AddNode(Node *n, NodeParamViewContext *context) } if (keyframe_view_) { - keyframe_view_->AddKeyframesOfNode(n); + connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::QueueKeyframePositionUpdate); + connect(item, &NodeParamViewItem::ArrayExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate); + connect(item, &NodeParamViewItem::ExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate); + connect(item, &NodeParamViewItem::Moved, this, &NodeParamView::QueueKeyframePositionUpdate); + + item->SetKeyframeConnections(keyframe_view_->AddKeyframesOfNode(n)); } } -/*void NodeParamView::AddNode(Node *node, Node *context, NodeParamViewContext *ctx_item) -{ - int dist = GetDistanceBetweenNodes(context, node); - - if (dist == -1) { - dist = 0; - } - - ctx_item->GetDockArea()->insert -}*/ - -void NodeParamView::RemoveNode(Node *n) -{ - qDebug() << "STUB!"; - /*if (keyframe_view_) { - keyframe_view_->RemoveKeyframesOfNode(n); - - disconnect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); - disconnect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); - } - - delete items_.take(n); - - if (focused_node_ == n) { - // Try to find new node with gizmos to focus - focused_node_ = nullptr; - for (auto it=items_.cbegin(); it!=items_.cend(); it++) { - if (it.key()->HasGizmos()) { - focused_node_ = it.key(); - it.value()->SetHighlighted(true); - break; - } - } - - emit FocusedNodeChanged(focused_node_); - }*/ -} - int GetDistanceBetweenNodes(Node *start, Node *end) { if (start == end) { @@ -516,7 +450,7 @@ void NodeParamView::UpdateGlobalScrollBar() int height_offscreen = param_widget_container_->height() + scrollbar()->height(); if (keyframe_view_) { - keyframe_view_->SetMaxScroll(height_offscreen); + keyframe_view_->SetMaxScroll(height_offscreen + 2000); } vertical_scrollbar_->setRange(0, height_offscreen - param_scroll_area_->height()); @@ -533,8 +467,7 @@ void NodeParamView::PinNode(bool pin) pinned_nodes_.removeOne(node); if (!active_nodes_.contains(node)) { - RemoveNode(node); - SignalNodeOrder(); + //RemoveNode(node); } } } @@ -591,21 +524,34 @@ void NodeParamView::KeyframeViewDragged(int x, int y) void NodeParamView::UpdateElementY() { - qDebug() << "STUB"; - /*if (keyframe_view_) { - for (auto it=items_.cbegin(); it!=items_.cend(); it++) { - foreach (const QString& input, it.key()->inputs()) { - int arr_sz = it.key()->InputArraySize(input); + foreach (NodeParamViewContext *ctx, context_items_) { + for (auto it=ctx->GetItems().cbegin(); it!=ctx->GetItems().cend(); it++) { + const KeyframeViewBase::NodeConnections &connections = it.value()->GetKeyframeConnections(); - for (int i=-1; iinputs()) { + if (!(it.key()->GetInputFlags(input) & kInputFlagHidden)) { + int arr_sz = it.key()->InputArraySize(input); - int y = it.value()->GetElementY(ic); - keyframe_view_->SetElementY(ic, y); + for (int i=-1; iGetElementY(ic); + + const KeyframeViewBase::InputConnections &input_con = connections.value(input); + int use_index = i + 1; + if (use_index < input_con.size()) { + const KeyframeViewBase::ElementConnections &ele_con = input_con.at(ic.element()+1); + foreach (KeyframeViewInputConnection *track, ele_con) { + track->SetKeyframeY(y); + } + } + } + } } } } - }*/ + } } } diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index c1c8fb3b5..b7eb50edd 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -99,8 +99,6 @@ public slots: signals: void RequestSelectNode(const QVector& target); - void NodeOrderChanged(const QVector& nodes); - void FocusedNodeChanged(Node* n); protected: @@ -117,14 +115,8 @@ private: void QueueKeyframePositionUpdate(); - void SignalNodeOrder(); - void AddNode(Node* n, NodeParamViewContext *context); - //void AddNode(Node *node, Node *context, NodeParamViewContext *ctx_item); - - void RemoveNode(Node* n); - void SortItemsInContext(NodeParamViewContext *context); KeyframeView* keyframe_view_; diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 445bcace2..1e0aae242 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -36,6 +36,7 @@ #include "nodeparamviewwidgetbridge.h" #include "widget/clickablelabel/clickablelabel.h" #include "widget/collapsebutton/collapsebutton.h" +#include "widget/keyframeview/keyframeviewbase.h" namespace olive { @@ -187,6 +188,16 @@ public: void SetInputChecked(const NodeInput &input, bool e); + const KeyframeViewBase::NodeConnections &GetKeyframeConnections() const + { + return keyframe_connections_; + } + + void SetKeyframeConnections(const KeyframeViewBase::NodeConnections &c) + { + keyframe_connections_ = c; + } + signals: void RequestSetTime(const rational& time); @@ -206,6 +217,8 @@ private: rational time_; + KeyframeViewBase::NodeConnections keyframe_connections_; + }; } diff --git a/app/widget/timebased/CMakeLists.txt b/app/widget/timebased/CMakeLists.txt index e2a35686f..ed200fc08 100644 --- a/app/widget/timebased/CMakeLists.txt +++ b/app/widget/timebased/CMakeLists.txt @@ -18,6 +18,8 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} widget/timebased/timebasedview.cpp widget/timebased/timebasedview.h + widget/timebased/timebasedviewselectionmanager.cpp + widget/timebased/timebasedviewselectionmanager.h widget/timebased/timebasedwidget.cpp widget/timebased/timebasedwidget.h widget/timebased/timescaledobject.cpp diff --git a/app/widget/timebased/timebasedviewselectionmanager.cpp b/app/widget/timebased/timebasedviewselectionmanager.cpp new file mode 100644 index 000000000..dd44a172e --- /dev/null +++ b/app/widget/timebased/timebasedviewselectionmanager.cpp @@ -0,0 +1,26 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "timebasedviewselectionmanager.h" + +namespace olive { + + +} diff --git a/app/widget/timebased/timebasedviewselectionmanager.h b/app/widget/timebased/timebasedviewselectionmanager.h new file mode 100644 index 000000000..d1f53d7d6 --- /dev/null +++ b/app/widget/timebased/timebasedviewselectionmanager.h @@ -0,0 +1,256 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef TIMEBASEDVIEWSELECTIONMANAGER_H +#define TIMEBASEDVIEWSELECTIONMANAGER_H + +#include +#include +#include + +#include "common/rational.h" +#include "common/timecodefunctions.h" +#include "timebasedview.h" + +namespace olive { + +template +class TimeBasedViewSelectionManager +{ +public: + TimeBasedViewSelectionManager(TimeBasedView *view) : + view_(view) + {} + + void ClearDrawnObjects() + { + drawn_objects_.clear(); + } + + void DeclareDrawnObject(T *object, const QRectF &pos) + { + drawn_objects_.append({object, pos}); + } + + bool Select(T *key) + { + if (!IsSelected(key)) { + selected_.append(key); + return true; + } + + return false; + } + + bool Deselect(T *key) + { + return selected_.removeOne(key); + } + + void ClearSelection() + { + selected_.clear(); + } + + bool IsSelected(T *key) const + { + return selected_.contains(key); + } + + const QVector &GetSelectedObjects() const + { + return selected_; + } + + void SetTimebase(const rational &tb) + { + timebase_ = tb; + } + + T *MousePress(QMouseEvent *event) + { + T *key_under_cursor = nullptr; + + if (event->button() == Qt::LeftButton) { + // See if there's a keyframe in this position + QPointF scene_pos = view_->mapToScene(event->pos()); + foreach (const DrawnObject &kp, drawn_objects_) { + if (kp.second.contains(scene_pos)) { + key_under_cursor = kp.first; + break; + } + } + + bool holding_shift = event->modifiers() & Qt::ShiftModifier; + + if (IsSelected(key_under_cursor)) { + if (holding_shift) { + // If selected and holding shift, de-select this item but do nothing else + Deselect(key_under_cursor); + } + } else { + if (!holding_shift) { + // If not already selecting and not holding shift, clear the current selection + ClearSelection(); + } + + // Add item to selection, either nothing if shift wasn't held, or the existing selection + Select(key_under_cursor); + } + } + + return key_under_cursor; + } + + bool IsDragging() const + { + return !dragging_.isEmpty(); + } + + void DragStart(T *initial_item, QMouseEvent *event) + { + initial_drag_item_ = initial_item; + + dragging_.clear(); + + dragging_.resize(selected_.size()); + for (int i=0; itime()}; + } + + drag_mouse_start_ = view_->mapToScene(event->pos()); + } + + void DragMove(QMouseEvent *event, const QString &tip_format) + { + QPointF diff = view_->mapToScene(event->pos()) - drag_mouse_start_; + + for (int i=0; iSceneToTimeNoGrid(diff.x()); + T *sel = selected_.at(i); + + // Magic number: use interval of 1ms to avoid collisions + rational adj(1, 1000); + if (old_time < proposed_time) { + adj = -adj; + } + while (true) { + NodeKeyframe *key_at_time = sel->parent()->GetKeyframeAtTimeOnTrack(sel->input(), proposed_time, sel->track(), sel->element()); + if (!key_at_time || key_at_time == sel) { + break; + } + + proposed_time += adj; + } + + sel->set_time(proposed_time); + } + + // Show information about this keyframe + QString tip = Timecode::time_to_timecode(initial_drag_item_->time(), timebase_, + Core::instance()->GetTimecodeDisplay(), false); + + if (!tip_format.isEmpty()) { + tip = tip_format.arg(tip); + } + + QToolTip::hideText(); + QToolTip::showText(QCursor::pos(), tip); + } + + void DragStop(MultiUndoCommand *command) + { + QToolTip::hideText(); + + for (int i=0; iadd_child(new SetTimeCommand(selected_.at(i), selected_.at(i)->time(), dragging_.at(i).time)); + } + } + +private: + class SetTimeCommand : public UndoCommand + { + public: + SetTimeCommand(T* key, const rational& time) + { + key_ = key; + new_time_ = time; + old_time_ = key_->time(); + } + + SetTimeCommand(T* key, const rational& new_time, const rational& old_time) + { + key_ = key; + new_time_ = new_time; + old_time_ = old_time; + } + + virtual Project* GetRelevantProject() const override + { + return key_->parent()->project(); + } + + protected: + virtual void redo() override + { + key_->set_time(new_time_); + } + + virtual void undo() override + { + key_->set_time(old_time_); + } + + private: + T* key_; + + rational old_time_; + rational new_time_; + + }; + + TimeBasedView *view_; + + using DrawnObject = QPair; + QVector drawn_objects_; + + QVector selected_; + + struct DragObject + { + rational time; + }; + + QVector dragging_; + + T *initial_drag_item_; + + QPointF drag_mouse_start_; + + rational timebase_; + +}; + +} + +#endif // TIMEBASEDVIEWSELECTIONMANAGER_H diff --git a/app/widget/timebased/timescaledobject.cpp b/app/widget/timebased/timescaledobject.cpp index 85b154a48..5ea4176aa 100644 --- a/app/widget/timebased/timescaledobject.cpp +++ b/app/widget/timebased/timescaledobject.cpp @@ -72,6 +72,13 @@ rational TimeScaledObject::SceneToTime(const double &x, const double &x_scale, c return rational(rounded_x_mvmt * timebase.numerator(), timebase.denominator()); } +rational TimeScaledObject::SceneToTimeNoGrid(const double &x, const double &x_scale) +{ + double unscaled_time = x / x_scale; + + return rational::fromDouble(unscaled_time); +} + double TimeScaledObject::TimeToScene(const rational &time) const { return time.toDouble() * scale_; @@ -82,6 +89,11 @@ rational TimeScaledObject::SceneToTime(const double &x, bool round) const return SceneToTime(x, scale_, timebase_, round); } +rational TimeScaledObject::SceneToTimeNoGrid(const double &x) const +{ + return SceneToTimeNoGrid(x, scale_); +} + void TimeScaledObject::SetMaximumScale(const double &max) { max_scale_ = max; diff --git a/app/widget/timebased/timescaledobject.h b/app/widget/timebased/timescaledobject.h index 8c858c8fc..acdc44acb 100644 --- a/app/widget/timebased/timescaledobject.h +++ b/app/widget/timebased/timescaledobject.h @@ -42,6 +42,7 @@ public: const double& timebase_dbl() const; static rational SceneToTime(const double &x, const double& x_scale, const rational& timebase, bool round = false); + static rational SceneToTimeNoGrid(const double &x, const double& x_scale); const double& GetScale() const; const double &GetMaximumScale() const { return max_scale_; } @@ -54,6 +55,7 @@ public: double TimeToScene(const rational& time) const; rational SceneToTime(const double &x, bool round = false) const; + rational SceneToTimeNoGrid(const double &x) const; protected: virtual void TimebaseChangedEvent(const rational&){} diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index c26b9bdf9..2cbf094d0 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -47,7 +47,7 @@ MainWindow::MainWindow(QWidget *parent) : #ifdef Q_OS_WINDOWS // Set up taskbar button progress bar (used for some modal tasks like exporting) - taskbar_btn_id_ = RegisterWindowMessage("TaskbarButtonCreated"); + taskbar_btn_id_ = RegisterWindowMessage(TEXT("TaskbarButtonCreated")); taskbar_interface_ = nullptr; #endif @@ -106,9 +106,6 @@ MainWindow::MainWindow(QWidget *parent) : connect(curve_panel_, &ParamPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime); connect(curve_panel_, &ParamPanel::TimeChanged, param_panel_, &NodeTablePanel::SetTime); - // Connect node order signals - connect(param_panel_, &ParamPanel::NodeOrderChanged, curve_panel_, &CurvePanel::SetNodes); - connect(PanelManager::instance(), &PanelManager::FocusedPanelChanged, this, &MainWindow::FocusedPanelChanged); sequence_viewer_panel_->ConnectTimeBasedPanel(param_panel_); @@ -116,7 +113,7 @@ MainWindow::MainWindow(QWidget *parent) : UpdateTitle(); - QMetaObject::invokeMethod(this, "SetDefaultLayout", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &MainWindow::SetDefaultLayout, Qt::QueuedConnection); } MainWindow::~MainWindow() From 1644b9e4c15465f42fb37ddd3c453f5d56be3ac0 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 15 Dec 2021 13:45:51 -0800 Subject: [PATCH 47/94] remove dodgy node properties dialog --- app/dialog/CMakeLists.txt | 2 +- .../CMakeLists.txt | 8 +- .../footageproperties/footageproperties.cpp | 251 ++++++++++++++++ .../footageproperties/footageproperties.h | 120 ++++++++ .../streamproperties/CMakeLists.txt | 26 ++ .../audiostreamproperties.cpp | 37 +++ .../streamproperties/audiostreamproperties.h} | 29 +- .../streamproperties/streamproperties.cpp | 30 ++ .../streamproperties/streamproperties.h | 44 +++ .../videostreamproperties.cpp | 271 ++++++++++++++++++ .../streamproperties/videostreamproperties.h | 146 ++++++++++ .../nodeproperties/nodepropertiesdialog.cpp | 74 ----- .../projectexplorer/projectexplorer.cpp | 6 +- app/widget/timelinewidget/timelinewidget.cpp | 13 +- 14 files changed, 946 insertions(+), 111 deletions(-) rename app/dialog/{nodeproperties => footageproperties}/CMakeLists.txt (81%) create mode 100644 app/dialog/footageproperties/footageproperties.cpp create mode 100644 app/dialog/footageproperties/footageproperties.h create mode 100644 app/dialog/footageproperties/streamproperties/CMakeLists.txt create mode 100644 app/dialog/footageproperties/streamproperties/audiostreamproperties.cpp rename app/dialog/{nodeproperties/nodepropertiesdialog.h => footageproperties/streamproperties/audiostreamproperties.h} (54%) create mode 100644 app/dialog/footageproperties/streamproperties/streamproperties.cpp create mode 100644 app/dialog/footageproperties/streamproperties/streamproperties.h create mode 100644 app/dialog/footageproperties/streamproperties/videostreamproperties.cpp create mode 100644 app/dialog/footageproperties/streamproperties/videostreamproperties.h delete mode 100644 app/dialog/nodeproperties/nodepropertiesdialog.cpp diff --git a/app/dialog/CMakeLists.txt b/app/dialog/CMakeLists.txt index 01bbfd5ec..afe445704 100644 --- a/app/dialog/CMakeLists.txt +++ b/app/dialog/CMakeLists.txt @@ -21,10 +21,10 @@ add_subdirectory(color) add_subdirectory(configbase) add_subdirectory(diskcache) add_subdirectory(export) +add_subdirectory(footageproperties) add_subdirectory(footagerelink) add_subdirectory(keyframeproperties) add_subdirectory(nodegroup) -add_subdirectory(nodeproperties) add_subdirectory(preferences) add_subdirectory(progress) add_subdirectory(rendercancel) diff --git a/app/dialog/nodeproperties/CMakeLists.txt b/app/dialog/footageproperties/CMakeLists.txt similarity index 81% rename from app/dialog/nodeproperties/CMakeLists.txt rename to app/dialog/footageproperties/CMakeLists.txt index f1c44b5af..25287c233 100644 --- a/app/dialog/nodeproperties/CMakeLists.txt +++ b/app/dialog/footageproperties/CMakeLists.txt @@ -1,5 +1,5 @@ # Olive - Non-Linear Video Editor -# Copyright (C) 2021 Olive Team +# Copyright (C) 2020 Olive Team # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -14,9 +14,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +add_subdirectory(streamproperties) + set(OLIVE_SOURCES ${OLIVE_SOURCES} - dialog/nodeproperties/nodepropertiesdialog.cpp - dialog/nodeproperties/nodepropertiesdialog.h + dialog/footageproperties/footageproperties.cpp + dialog/footageproperties/footageproperties.h PARENT_SCOPE ) diff --git a/app/dialog/footageproperties/footageproperties.cpp b/app/dialog/footageproperties/footageproperties.cpp new file mode 100644 index 000000000..7cb56c63a --- /dev/null +++ b/app/dialog/footageproperties/footageproperties.cpp @@ -0,0 +1,251 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "footageproperties.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "streamproperties/audiostreamproperties.h" +#include "streamproperties/videostreamproperties.h" +#include "widget/nodeview/nodeviewundo.h" + +namespace olive { + +FootagePropertiesDialog::FootagePropertiesDialog(QWidget *parent, Footage *footage) : + QDialog(parent), + footage_(footage) +{ + QGridLayout* layout = new QGridLayout(this); + + setWindowTitle(tr("\"%1\" Properties").arg(footage_->GetLabelOrName())); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + int row = 0; + + layout->addWidget(new QLabel(tr("Name:")), row, 0); + + footage_name_field_ = new QLineEdit(footage_->GetLabel()); + layout->addWidget(footage_name_field_, row, 1); + row++; + + layout->addWidget(new QLabel(tr("Tracks:")), row, 0, 1, 2); + row++; + + track_list = new QListWidget(); + layout->addWidget(track_list, row, 0, 1, 2); + + row++; + + stacked_widget_ = new QStackedWidget(); + layout->addWidget(stacked_widget_, row, 0, 1, 2); + + int first_usable_stream = -1; + + for (int i=0; iGetTotalStreamCount(); i++) { + Track::Reference reference = footage_->GetReferenceFromRealIndex(i); + + QString description; + bool is_enabled = false; + + switch (reference.type()) { + case Track::kVideo: + { + stacked_widget_->addWidget(new VideoStreamProperties(footage_, reference.index())); + + VideoParams vp = footage_->GetVideoParams(reference.index()); + is_enabled = vp.enabled(); + description = tr("%1x%2 %3 FPS").arg(QString::number(vp.width()), QString::number(vp.height()), QString::number(vp.frame_rate().toDouble())); + break; + } + case Track::kAudio: + { + stacked_widget_->addWidget(new AudioStreamProperties(footage_, reference.index())); + + AudioParams ap = footage_->GetAudioParams(reference.index()); + is_enabled = ap.enabled(); + description = tr("%1 Hz %2 channels").arg(QString::number(ap.sample_rate()), QString::number(ap.channel_count())); + break; + } + default: + stacked_widget_->addWidget(new StreamProperties()); + description = tr("Unknown"); + break; + } + + QListWidgetItem* item = new QListWidgetItem(description, track_list); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(is_enabled ? Qt::Checked : Qt::Unchecked); + track_list->addItem(item); + + if (first_usable_stream == -1 + && (reference.type() == Track::kVideo + || reference.type() == Track::kAudio)) { + first_usable_stream = i; + } + } + + row++; + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + buttons->setCenterButtons(true); + layout->addWidget(buttons, row, 0, 1, 2); + + connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + + connect(track_list, &QListWidget::currentRowChanged, stacked_widget_, &QStackedWidget::setCurrentIndex); + + // Auto-select first item that actually has properties + if (first_usable_stream >= 0) { + track_list->setCurrentRow(first_usable_stream); + } + track_list->setFocus(); +} + +void FootagePropertiesDialog::accept() +{ + // Perform sanity check on all pages + for (int i=0;icount();i++) { + if (!static_cast(stacked_widget_->widget(i))->SanityCheck()) { + // Switch to the failed panel in question + stacked_widget_->setCurrentIndex(i); + + // Do nothing (it's up to the property panel itself to throw the error message) + return; + } + } + + MultiUndoCommand* command = new MultiUndoCommand(); + + if (footage_->GetLabel() != footage_name_field_->text()) { + NodeRenameCommand *nrc = new NodeRenameCommand(); + nrc->AddNode(footage_, footage_name_field_->text()); + command->add_child(nrc); + } + + for (int i=0; iGetTotalStreamCount(); i++) { + Track::Reference reference = footage_->GetReferenceFromRealIndex(i); + bool new_stream_enabled = (track_list->item(i)->checkState() == Qt::Checked); + bool old_stream_enabled = new_stream_enabled; + + switch (reference.type()) { + case Track::kVideo: + old_stream_enabled = footage_->GetVideoParams(reference.index()).enabled(); + break; + case Track::kAudio: + old_stream_enabled = footage_->GetAudioParams(reference.index()).enabled(); + break; + case Track::kSubtitle: + case Track::kNone: + case Track::kCount: + break; + } + + if (old_stream_enabled != new_stream_enabled) { + command->add_child(new StreamEnableChangeCommand(footage_, + reference.type(), + reference.index(), + new_stream_enabled)); + } + } + + for (int i=0;icount();i++) { + static_cast(stacked_widget_->widget(i))->Accept(command); + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); + + QDialog::accept(); +} + +FootagePropertiesDialog::StreamEnableChangeCommand::StreamEnableChangeCommand(Footage *footage, Track::Type type, int index_in_type, bool enabled) : + footage_(footage), + type_(type), + index_(index_in_type), + new_enabled_(enabled) +{ +} + +Project *FootagePropertiesDialog::StreamEnableChangeCommand::GetRelevantProject() const +{ + return footage_->project(); +} + +void FootagePropertiesDialog::StreamEnableChangeCommand::redo() +{ + switch (type_) { + case Track::kVideo: + { + VideoParams vp = footage_->GetVideoParams(index_); + old_enabled_ = vp.enabled(); + vp.set_enabled(new_enabled_); + footage_->SetVideoParams(vp, index_); + break; + } + case Track::kAudio: + { + AudioParams ap = footage_->GetAudioParams(index_); + old_enabled_ = ap.enabled(); + ap.set_enabled(new_enabled_); + footage_->SetAudioParams(ap, index_); + break; + } + case Track::kSubtitle: + case Track::kNone: + case Track::kCount: + break; + } +} + +void FootagePropertiesDialog::StreamEnableChangeCommand::undo() +{ + switch (type_) { + case Track::kVideo: + { + VideoParams vp = footage_->GetVideoParams(index_); + vp.set_enabled(old_enabled_); + footage_->SetVideoParams(vp, index_); + break; + } + case Track::kAudio: + { + AudioParams ap = footage_->GetAudioParams(index_); + ap.set_enabled(old_enabled_); + footage_->SetAudioParams(ap, index_); + break; + } + case Track::kSubtitle: + case Track::kNone: + case Track::kCount: + break; + } +} + +} diff --git a/app/dialog/footageproperties/footageproperties.h b/app/dialog/footageproperties/footageproperties.h new file mode 100644 index 000000000..7fd58a14e --- /dev/null +++ b/app/dialog/footageproperties/footageproperties.h @@ -0,0 +1,120 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef MEDIAPROPERTIESDIALOG_H +#define MEDIAPROPERTIESDIALOG_H + +#include +#include +#include +#include +#include +#include +#include + +#include "node/project/footage/footage.h" +#include "undo/undocommand.h" + +namespace olive { + +/** + * @brief The MediaPropertiesDialog class + * + * A dialog for setting properties on Media. This can be loaded from any part of the application provided it's given + * a valid Media object. + */ +class FootagePropertiesDialog : public QDialog { + Q_OBJECT +public: + /** + * @brief MediaPropertiesDialog Constructor + * + * @param parent + * + * QWidget parent. Usually MainWindow or Project panel. + * + * @param i + * + * Media object to set properties for. + */ + FootagePropertiesDialog(QWidget *parent, Footage* footage); +private: + class StreamEnableChangeCommand : public UndoCommand { + public: + StreamEnableChangeCommand(Footage *footage, + Track::Type type, + int index_in_type, + bool enabled); + + virtual Project* GetRelevantProject() const override; + + virtual void redo() override; + virtual void undo() override; + + private: + Footage *footage_; + Track::Type type_; + int index_; + + bool old_enabled_; + bool new_enabled_; + }; + + /** + * @brief Stack of widgets that changes based on whether the stream is a video or audio stream + */ + QStackedWidget* stacked_widget_; + + /** + * @brief ComboBox for interlacing setting + */ + QComboBox* interlacing_box; + + /** + * @brief Media name text field + */ + QLineEdit* footage_name_field_; + + /** + * @brief Internal pointer to Media object (set in constructor) + */ + Footage* footage_; + + /** + * @brief A list widget for listing the tracks in Media + */ + QListWidget* track_list; + + /** + * @brief Frame rate to conform to + */ + QDoubleSpinBox* conform_fr; + +private slots: + /** + * @brief Overridden accept function for saving the properties back to the Media class + */ + void accept(); + +}; + +} + +#endif // MEDIAPROPERTIESDIALOG_H diff --git a/app/dialog/footageproperties/streamproperties/CMakeLists.txt b/app/dialog/footageproperties/streamproperties/CMakeLists.txt new file mode 100644 index 000000000..3228e9520 --- /dev/null +++ b/app/dialog/footageproperties/streamproperties/CMakeLists.txt @@ -0,0 +1,26 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2020 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + dialog/footageproperties/streamproperties/streamproperties.h + dialog/footageproperties/streamproperties/streamproperties.cpp + dialog/footageproperties/streamproperties/audiostreamproperties.h + dialog/footageproperties/streamproperties/audiostreamproperties.cpp + dialog/footageproperties/streamproperties/videostreamproperties.h + dialog/footageproperties/streamproperties/videostreamproperties.cpp + PARENT_SCOPE +) diff --git a/app/dialog/footageproperties/streamproperties/audiostreamproperties.cpp b/app/dialog/footageproperties/streamproperties/audiostreamproperties.cpp new file mode 100644 index 000000000..0359e7577 --- /dev/null +++ b/app/dialog/footageproperties/streamproperties/audiostreamproperties.cpp @@ -0,0 +1,37 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "audiostreamproperties.h" + +namespace olive { + +AudioStreamProperties::AudioStreamProperties(Footage *footage, int audio_index) : + footage_(footage), + audio_index_(audio_index) +{ +} + +void AudioStreamProperties::Accept(MultiUndoCommand*) +{ + Q_UNUSED(footage_) + Q_UNUSED(audio_index_) +} + +} diff --git a/app/dialog/nodeproperties/nodepropertiesdialog.h b/app/dialog/footageproperties/streamproperties/audiostreamproperties.h similarity index 54% rename from app/dialog/nodeproperties/nodepropertiesdialog.h rename to app/dialog/footageproperties/streamproperties/audiostreamproperties.h index 5375a1313..058ff2bfc 100644 --- a/app/dialog/nodeproperties/nodepropertiesdialog.h +++ b/app/dialog/footageproperties/streamproperties/audiostreamproperties.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2020 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,35 +18,28 @@ ***/ -#ifndef NODEPROPERTIESDIALOG_H -#define NODEPROPERTIESDIALOG_H +#ifndef AUDIOSTREAMPROPERTIES_H +#define AUDIOSTREAMPROPERTIES_H -#include - -#include "widget/nodeparamview/nodeparamviewitem.h" +#include "node/project/footage/footage.h" +#include "streamproperties.h" namespace olive { -class NodePropertiesDialog : public QDialog +class AudioStreamProperties : public StreamProperties { - Q_OBJECT public: - NodePropertiesDialog(Node *node, const rational &timebase, QWidget *parent = nullptr); - NodePropertiesDialog(const QVector &node, const rational &timebase, QWidget *parent = nullptr) : - NodePropertiesDialog(node.first(), timebase, parent) - { - } + AudioStreamProperties(Footage *footage, int audio_index); -public slots: - virtual void accept() override; + virtual void Accept(MultiUndoCommand* parent) override; private: - Node *node_; + Footage *footage_; - QLineEdit *label_edit_; + int audio_index_; }; } -#endif // NODEPROPERTIESDIALOG_H +#endif // AUDIOSTREAMPROPERTIES_H diff --git a/app/dialog/footageproperties/streamproperties/streamproperties.cpp b/app/dialog/footageproperties/streamproperties/streamproperties.cpp new file mode 100644 index 000000000..96f3bbd5a --- /dev/null +++ b/app/dialog/footageproperties/streamproperties/streamproperties.cpp @@ -0,0 +1,30 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "streamproperties.h" + +namespace olive { + +StreamProperties::StreamProperties(QWidget *parent) : + QWidget(parent) +{ +} + +} diff --git a/app/dialog/footageproperties/streamproperties/streamproperties.h b/app/dialog/footageproperties/streamproperties/streamproperties.h new file mode 100644 index 000000000..c8457216b --- /dev/null +++ b/app/dialog/footageproperties/streamproperties/streamproperties.h @@ -0,0 +1,44 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef STREAMPROPERTIES_H +#define STREAMPROPERTIES_H + +#include + +#include "common/define.h" +#include "undo/undocommand.h" + +namespace olive { + +class StreamProperties : public QWidget +{ +public: + StreamProperties(QWidget* parent = nullptr); + + virtual void Accept(MultiUndoCommand*){} + + virtual bool SanityCheck(){return true;} + +}; + +} + +#endif // STREAMPROPERTIES_H diff --git a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp new file mode 100644 index 000000000..6fcbdd914 --- /dev/null +++ b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp @@ -0,0 +1,271 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "videostreamproperties.h" + +#include +#include +#include +#include +#include + +#include "common/ocioutils.h" +#include "core.h" +#include "undo/undostack.h" + +namespace olive { + +VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index) : + footage_(footage), + video_index_(video_index), + video_premultiply_alpha_(nullptr) +{ + QGridLayout* video_layout = new QGridLayout(this); + video_layout->setMargin(0); + + int row = 0; + + video_layout->addWidget(new QLabel(tr("Pixel Aspect:")), row, 0); + + VideoParams vp = footage_->GetVideoParams(video_index_); + + pixel_aspect_combo_ = new PixelAspectRatioComboBox(); + pixel_aspect_combo_->SetPixelAspectRatio(vp.pixel_aspect_ratio()); + video_layout->addWidget(pixel_aspect_combo_, row, 1); + + row++; + + video_layout->addWidget(new QLabel(tr("Interlacing:")), row, 0); + + video_interlace_combo_ = new InterlacedComboBox(); + video_interlace_combo_->SetInterlaceMode(vp.interlacing()); + + video_layout->addWidget(video_interlace_combo_, row, 1); + + row++; + + video_layout->addWidget(new QLabel(tr("Color Space:")), row, 0); + + video_color_space_ = new QComboBox(); + OCIO::ConstConfigRcPtr config = footage_->project()->color_manager()->GetConfig(); + int number_of_colorspaces = config->getNumColorSpaces(); + + video_color_space_->addItem(tr("Default (%1)").arg(footage_->project()->color_manager()->GetDefaultInputColorSpace())); + + for (int i=0;igetColorSpaceNameByIndex(i); + + video_color_space_->addItem(colorspace); + } + + video_color_space_->setCurrentText(vp.colorspace()); + + video_layout->addWidget(video_color_space_, row, 1); + + if (vp.channel_count() == VideoParams::kRGBAChannelCount) { + row++; + + video_premultiply_alpha_ = new QCheckBox(tr("Premultiplied Alpha")); + video_premultiply_alpha_->setChecked(vp.premultiplied_alpha()); + video_layout->addWidget(video_premultiply_alpha_, row, 0, 1, 2); + } + + row++; + + if (vp.video_type() == VideoParams::kVideoTypeImageSequence) { + QGroupBox* imgseq_group = new QGroupBox(tr("Image Sequence")); + QGridLayout* imgseq_layout = new QGridLayout(imgseq_group); + + int imgseq_row = 0; + + imgseq_layout->addWidget(new QLabel(tr("Start Index:")), imgseq_row, 0); + + imgseq_start_time_ = new IntegerSlider(); + imgseq_start_time_->SetMinimum(0); + imgseq_start_time_->SetValue(vp.start_time()); + imgseq_layout->addWidget(imgseq_start_time_, imgseq_row, 1); + + imgseq_row++; + + imgseq_layout->addWidget(new QLabel(tr("End Index:")), imgseq_row, 0); + + imgseq_end_time_ = new IntegerSlider(); + imgseq_end_time_->SetMinimum(0); + imgseq_end_time_->SetValue(vp.start_time() + vp.duration() - 1); + imgseq_layout->addWidget(imgseq_end_time_, imgseq_row, 1); + + imgseq_row++; + + imgseq_layout->addWidget(new QLabel(tr("Frame Rate:")), imgseq_row, 0); + + imgseq_frame_rate_ = new FrameRateComboBox(); + imgseq_frame_rate_->SetFrameRate(vp.frame_rate()); + imgseq_layout->addWidget(imgseq_frame_rate_, imgseq_row, 1); + + video_layout->addWidget(imgseq_group, row, 0, 1, 2); + } +} + +void VideoStreamProperties::Accept(MultiUndoCommand *parent) +{ + QString set_colorspace; + + if (video_color_space_->currentIndex() > 0) { + set_colorspace = video_color_space_->currentText(); + } + + VideoParams vp = footage_->GetVideoParams(video_index_); + + if ((video_premultiply_alpha_ && video_premultiply_alpha_->isChecked() != vp.premultiplied_alpha()) + || set_colorspace != vp.colorspace() + || static_cast(video_interlace_combo_->currentIndex()) != vp.interlacing() + || pixel_aspect_combo_->GetPixelAspectRatio() != vp.pixel_aspect_ratio()) { + + parent->add_child(new VideoStreamChangeCommand(footage_, + video_index_, + video_premultiply_alpha_ ? video_premultiply_alpha_->isChecked() : vp.premultiplied_alpha(), + set_colorspace, + static_cast(video_interlace_combo_->currentIndex()), + pixel_aspect_combo_->GetPixelAspectRatio())); + } + + if (vp.video_type() == VideoParams::kVideoTypeImageSequence) { + int64_t new_dur = imgseq_end_time_->GetValue() - imgseq_start_time_->GetValue() + 1; + + if (vp.start_time() != imgseq_start_time_->GetValue() + || vp.duration() != new_dur + || vp.frame_rate() != imgseq_frame_rate_->GetFrameRate()) { + parent->add_child(new ImageSequenceChangeCommand(footage_, + video_index_, + imgseq_start_time_->GetValue(), + new_dur, + imgseq_frame_rate_->GetFrameRate())); + } + } +} + +bool VideoStreamProperties::SanityCheck() +{ + if (footage_->GetVideoParams(video_index_).video_type() == VideoParams::kVideoTypeImageSequence) { + if (imgseq_start_time_->GetValue() >= imgseq_end_time_->GetValue()) { + QMessageBox::critical(this, + tr("Invalid Configuration"), + tr("Image sequence end index must be a value higher than the start index."), + QMessageBox::Ok); + return false; + } + } + + return true; +} + +VideoStreamProperties::VideoStreamChangeCommand::VideoStreamChangeCommand(Footage *footage, + int video_index, + bool premultiplied, + QString colorspace, + VideoParams::Interlacing interlacing, + const rational &pixel_ar) : + footage_(footage), + video_index_(video_index), + new_premultiplied_(premultiplied), + new_colorspace_(colorspace), + new_interlacing_(interlacing), + new_pixel_ar_(pixel_ar) +{ +} + +Project *VideoStreamProperties::VideoStreamChangeCommand::GetRelevantProject() const +{ + return footage_->project(); +} + +void VideoStreamProperties::VideoStreamChangeCommand::redo() +{ + VideoParams vp = footage_->GetVideoParams(video_index_); + + old_premultiplied_ = vp.premultiplied_alpha(); + old_colorspace_ = vp.colorspace(); + old_interlacing_ = vp.interlacing(); + old_pixel_ar_ = vp.pixel_aspect_ratio(); + + vp.set_premultiplied_alpha(new_premultiplied_); + vp.set_colorspace(new_colorspace_); + vp.set_interlacing(new_interlacing_); + vp.set_pixel_aspect_ratio(new_pixel_ar_); + + footage_->SetVideoParams(vp, video_index_); +} + +void VideoStreamProperties::VideoStreamChangeCommand::undo() +{ + VideoParams vp = footage_->GetVideoParams(video_index_); + + vp.set_premultiplied_alpha(old_premultiplied_); + vp.set_colorspace(old_colorspace_); + vp.set_interlacing(old_interlacing_); + vp.set_pixel_aspect_ratio(old_pixel_ar_); + + footage_->SetVideoParams(vp, video_index_); +} + +VideoStreamProperties::ImageSequenceChangeCommand::ImageSequenceChangeCommand(Footage *footage, int video_index, int64_t start_index, int64_t duration, const rational &frame_rate) : + footage_(footage), + video_index_(video_index), + new_start_index_(start_index), + new_duration_(duration), + new_frame_rate_(frame_rate) +{ +} + +Project *VideoStreamProperties::ImageSequenceChangeCommand::GetRelevantProject() const +{ + return footage_->project(); +} + +void VideoStreamProperties::ImageSequenceChangeCommand::redo() +{ + VideoParams vp = footage_->GetVideoParams(video_index_); + + old_start_index_ = vp.start_time(); + vp.set_start_time(new_start_index_); + + old_duration_ = vp.duration(); + vp.set_duration(new_duration_); + + old_frame_rate_ = vp.frame_rate(); + vp.set_frame_rate(new_frame_rate_); + vp.set_time_base(new_frame_rate_.flipped()); + + footage_->SetVideoParams(vp, video_index_); +} + +void VideoStreamProperties::ImageSequenceChangeCommand::undo() +{ + VideoParams vp = footage_->GetVideoParams(video_index_); + + vp.set_start_time(old_start_index_); + vp.set_duration(old_duration_); + vp.set_frame_rate(old_frame_rate_); + vp.set_time_base(old_frame_rate_.flipped()); + + footage_->SetVideoParams(vp, video_index_); +} + +} diff --git a/app/dialog/footageproperties/streamproperties/videostreamproperties.h b/app/dialog/footageproperties/streamproperties/videostreamproperties.h new file mode 100644 index 000000000..3d858b2c1 --- /dev/null +++ b/app/dialog/footageproperties/streamproperties/videostreamproperties.h @@ -0,0 +1,146 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef VIDEOSTREAMPROPERTIES_H +#define VIDEOSTREAMPROPERTIES_H + +#include +#include + +#include "node/project/footage/footage.h" +#include "streamproperties.h" +#include "widget/slider/integerslider.h" +#include "widget/standardcombos/standardcombos.h" + +namespace olive { + +class VideoStreamProperties : public StreamProperties +{ + Q_OBJECT +public: + VideoStreamProperties(Footage *footage, int video_index); + + virtual void Accept(MultiUndoCommand *parent) override; + + virtual bool SanityCheck() override; + +private: + Footage *footage_; + + int video_index_; + + /** + * @brief Setting for associated/premultiplied alpha + */ + QCheckBox* video_premultiply_alpha_; + + /** + * @brief Setting for this media's color space + */ + QComboBox* video_color_space_; + + /** + * @brief Setting for video interlacing + */ + InterlacedComboBox* video_interlace_combo_; + + /** + * @brief Sets the start index for image sequences + */ + IntegerSlider* imgseq_start_time_; + + /** + * @brief Sets the end index for image sequences + */ + IntegerSlider* imgseq_end_time_; + + /** + * @brief Sets the frame rate for image sequences + */ + FrameRateComboBox* imgseq_frame_rate_; + + /** + * @brief Sets the pixel aspect ratio of the stream + */ + PixelAspectRatioComboBox* pixel_aspect_combo_; + + class VideoStreamChangeCommand : public UndoCommand { + public: + VideoStreamChangeCommand(Footage *footage, + int video_index, + bool premultiplied, + QString colorspace, + VideoParams::Interlacing interlacing, + const rational& pixel_ar); + + virtual Project* GetRelevantProject() const override; + + virtual void redo() override; + virtual void undo() override; + + private: + Footage *footage_; + int video_index_; + + bool new_premultiplied_; + QString new_colorspace_; + VideoParams::Interlacing new_interlacing_; + rational new_pixel_ar_; + + bool old_premultiplied_; + QString old_colorspace_; + VideoParams::Interlacing old_interlacing_; + rational old_pixel_ar_; + + }; + + class ImageSequenceChangeCommand : public UndoCommand { + public: + ImageSequenceChangeCommand(Footage *footage, + int video_index, + int64_t start_index, + int64_t duration, + const rational& frame_rate); + + virtual Project* GetRelevantProject() const override; + + virtual void redo() override; + virtual void undo() override; + + private: + Footage *footage_; + int video_index_; + + int64_t new_start_index_; + int64_t old_start_index_; + + int64_t new_duration_; + int64_t old_duration_; + + rational new_frame_rate_; + rational old_frame_rate_; + + }; + +}; + +} + +#endif // VIDEOSTREAMPROPERTIES_H diff --git a/app/dialog/nodeproperties/nodepropertiesdialog.cpp b/app/dialog/nodeproperties/nodepropertiesdialog.cpp deleted file mode 100644 index fca1935ba..000000000 --- a/app/dialog/nodeproperties/nodepropertiesdialog.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "nodepropertiesdialog.h" - -#include -#include - -#include "core.h" -#include "widget/nodeview/nodeviewundo.h" - -namespace olive { - -NodePropertiesDialog::NodePropertiesDialog(Node *node, const rational &timebase, QWidget *parent) : - QDialog(parent), - node_(node) -{ - setWindowTitle(tr("Node Properties")); - - QVBoxLayout *layout = new QVBoxLayout(this); - - QHBoxLayout *label_layout = new QHBoxLayout(); - label_layout->setMargin(0); - layout->addLayout(label_layout); - - label_layout->addWidget(new QLabel(tr("Name:"))); - - label_edit_ = new QLineEdit(); - label_edit_->setText(node->GetLabel()); - label_layout->addWidget(label_edit_); - - NodeParamViewItem *item = new NodeParamViewItem(node, kNoCheckBoxes); - item->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - item->SetTimebase(timebase); - item->setTitleBarWidget(new QWidget()); - layout->addWidget(item); - - layout->addStretch(); - - QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(btns, &QDialogButtonBox::accepted, this, &NodePropertiesDialog::accept); - connect(btns, &QDialogButtonBox::rejected, this, &NodePropertiesDialog::reject); - layout->addWidget(btns); -} - -void NodePropertiesDialog::accept() -{ - if (label_edit_->text() != node_->GetLabel()) { - NodeRenameCommand* rename_command = new NodeRenameCommand(); - rename_command->AddNode(node_, label_edit_->text()); - Core::instance()->undo_stack()->push(rename_command); - } - - QDialog::accept(); -} - -} diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 9326cd357..4482ac8d7 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -30,7 +30,7 @@ #include "common/define.h" #include "core.h" -#include "dialog/nodeproperties/nodepropertiesdialog.h" +#include "dialog/footageproperties/footageproperties.h" #include "dialog/sequence/sequence.h" #include "projectexplorerundo.h" #include "task/precache/precachetask.h" @@ -446,8 +446,8 @@ void ProjectExplorer::ShowItemPropertiesDialog() // FIXME: Support for multiple items if (dynamic_cast(sel)) { - NodePropertiesDialog npd(sel, static_cast(sel)->GetVideoParams().time_base(), this); - npd.exec(); + FootagePropertiesDialog fpd(this, static_cast(sel)); + fpd.exec(); } else if (dynamic_cast(sel)) { diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 3416f6e05..a16bdbc31 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -28,7 +28,6 @@ #include "core.h" #include "common/range.h" #include "common/timecodefunctions.h" -#include "dialog/nodeproperties/nodepropertiesdialog.h" #include "dialog/sequence/sequence.h" #include "dialog/speedduration/speeddurationdialog.h" #include "node/block/transition/transition.h" @@ -1023,17 +1022,7 @@ void TimelineWidget::ShowContextMenu() menu.addSeparator(); QAction* properties_action = menu.addAction(tr("Properties")); - connect(properties_action, &QAction::triggered, this, [this](){ - QVector block_items = GetSelectedBlocks(); - QVector nodes; - - foreach (Block* i, block_items) { - nodes.append(i); - } - - NodePropertiesDialog npd(nodes, timebase(), this); - npd.exec(); - }); + connect(properties_action, &QAction::triggered, this, &TimelineWidget::ShowSpeedDurationDialogForSelectedClips); } if (selected.isEmpty()) { From bf196e790b5a28d50ad37b6317da45b34ad7bbf9 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 15 Dec 2021 19:45:04 -0800 Subject: [PATCH 48/94] refined keyframeview and curveview --- app/common/bezier.cpp | 4 + .../sequence/sequencedialogparametertab.cpp | 47 +- .../sequence/sequencedialogparametertab.h | 22 +- app/node/node.cpp | 12 +- app/node/node.h | 6 +- app/node/output/viewer/viewer.cpp | 4 - app/node/output/viewer/viewer.h | 2 - app/node/project/footage/footage.cpp | 34 - app/panel/curve/curve.h | 12 + app/widget/CMakeLists.txt | 1 - app/widget/curvewidget/CMakeLists.txt | 6 +- .../curvewidget/beziercontrolpointitem.cpp | 106 --- .../curvewidget/beziercontrolpointitem.h | 68 -- app/widget/curvewidget/curveview.cpp | 330 +++++++--- app/widget/curvewidget/curveview.h | 54 +- app/widget/curvewidget/curvewidget.cpp | 12 +- app/widget/keyframeview/CMakeLists.txt | 2 - app/widget/keyframeview/keyframeview.cpp | 429 ++++++++++++- app/widget/keyframeview/keyframeview.h | 110 +++- app/widget/keyframeview/keyframeviewbase.cpp | 602 ------------------ app/widget/keyframeview/keyframeviewbase.h | 139 ---- .../keyframeviewinputconnection.cpp | 13 +- .../keyframeviewinputconnection.h | 8 +- app/widget/nodeparamview/nodeparamview.cpp | 10 +- app/widget/nodeparamview/nodeparamviewitem.h | 8 +- .../nodeparamviewwidgetbridge.cpp | 47 +- app/widget/nodetreeview/nodetreeview.cpp | 2 + app/widget/timebased/timebasedview.h | 4 + .../timebased/timebasedviewselectionmanager.h | 100 ++- app/widget/videoparamedit/CMakeLists.txt | 22 - app/widget/videoparamedit/videoparamedit.cpp | 391 ------------ app/widget/videoparamedit/videoparamedit.h | 182 ------ app/window/mainwindow/mainwindow.cpp | 1 + 33 files changed, 1013 insertions(+), 1777 deletions(-) delete mode 100644 app/widget/curvewidget/beziercontrolpointitem.cpp delete mode 100644 app/widget/curvewidget/beziercontrolpointitem.h delete mode 100644 app/widget/keyframeview/keyframeviewbase.cpp delete mode 100644 app/widget/keyframeview/keyframeviewbase.h delete mode 100644 app/widget/videoparamedit/CMakeLists.txt delete mode 100644 app/widget/videoparamedit/videoparamedit.cpp delete mode 100644 app/widget/videoparamedit/videoparamedit.h diff --git a/app/common/bezier.cpp b/app/common/bezier.cpp index 10fd88a2a..a1d900d57 100644 --- a/app/common/bezier.cpp +++ b/app/common/bezier.cpp @@ -58,6 +58,10 @@ double Bezier::CalculateTFromX(bool cubic, double x, double a, double b, double double top = 1.0; while (true) { + if (bottom == top) { + return bottom; + } + double mid = (bottom + top) * 0.5; double test = cubic ? CubicTtoY(a, b, c, d, mid) : QuadraticTtoY(a, b, c, mid); diff --git a/app/dialog/sequence/sequencedialogparametertab.cpp b/app/dialog/sequence/sequencedialogparametertab.cpp index 9d77d8bc7..00f89e002 100644 --- a/app/dialog/sequence/sequencedialogparametertab.cpp +++ b/app/dialog/sequence/sequencedialogparametertab.cpp @@ -19,11 +19,28 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg // Set up video section QGroupBox* video_group = new QGroupBox(); video_group->setTitle(tr("Video")); - QHBoxLayout* video_layout = new QHBoxLayout(video_group); - video_section_ = new VideoParamEdit(); - video_section_->SetParameterMask(Sequence::kVideoParamEditMask); - connect(video_section_, &VideoParamEdit::Changed, this, &SequenceDialogParameterTab::UpdatePreviewResolutionLabel); - video_layout->addWidget(video_section_); + QGridLayout *video_layout = new QGridLayout(video_group); + video_layout->addWidget(new QLabel(tr("Width:")), row, 0); + width_slider_ = new IntegerSlider(); + width_slider_->SetMinimum(0); + video_layout->addWidget(width_slider_, row, 1); + row++; + video_layout->addWidget(new QLabel(tr("Height:")), row, 0); + height_slider_ = new IntegerSlider(); + height_slider_->SetMinimum(0); + video_layout->addWidget(height_slider_, row, 1); + row++; + video_layout->addWidget(new QLabel(tr("Frame Rate:")), row, 0); + framerate_combo_ = new FrameRateComboBox(); + video_layout->addWidget(framerate_combo_, row, 1); + row++; + video_layout->addWidget(new QLabel(tr("Pixel Aspect Ratio:")), row, 0); + pixelaspect_combo_ = new PixelAspectRatioComboBox(); + video_layout->addWidget(pixelaspect_combo_, row, 1); + row++; + video_layout->addWidget(new QLabel(tr("Interlacing:")), row, 0); + interlacing_combo_ = new InterlacedComboBox(); + video_layout->addWidget(interlacing_combo_, row, 1); layout->addWidget(video_group); row = 0; @@ -65,7 +82,11 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg // Set values based on input sequence VideoParams vp = sequence->GetVideoParams(); AudioParams ap = sequence->GetAudioParams(); - video_section_->SetVideoParams(vp); + width_slider_->SetValue(vp.width()); + height_slider_->SetValue(vp.height()); + framerate_combo_->SetFrameRate(vp.time_base().flipped()); + pixelaspect_combo_->SetPixelAspectRatio(vp.pixel_aspect_ratio()); + interlacing_combo_->SetInterlaceMode(vp.interlacing()); preview_resolution_field_->SetDivider(vp.divider()); preview_format_field_->SetPixelFormat(vp.format()); preview_autocache_field_->setChecked(sequence->GetVideoAutoCacheEnabled()); @@ -86,11 +107,11 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg void SequenceDialogParameterTab::PresetChanged(const SequencePreset &preset) { - video_section_->SetWidth(preset.width()); - video_section_->SetHeight(preset.height()); - video_section_->SetFrameRate(preset.frame_rate()); - video_section_->SetPixelAspectRatio(preset.pixel_aspect()); - video_section_->SetInterlaceMode(preset.interlacing()); + width_slider_->SetValue(preset.width()); + height_slider_->SetValue(preset.height()); + framerate_combo_->SetFrameRate(preset.frame_rate()); + pixelaspect_combo_->SetPixelAspectRatio(preset.pixel_aspect()); + interlacing_combo_->SetInterlaceMode(preset.interlacing()); audio_sample_rate_field_->SetSampleRate(preset.sample_rate()); audio_channels_field_->SetChannelLayout(preset.channel_layout()); preview_resolution_field_->SetDivider(preset.preview_divider()); @@ -115,8 +136,8 @@ void SequenceDialogParameterTab::SavePresetClicked() void SequenceDialogParameterTab::UpdatePreviewResolutionLabel() { - VideoParams test_param(video_section_->GetWidth(), - video_section_->GetHeight(), + VideoParams test_param(GetSelectedVideoWidth(), + GetSelectedVideoHeight(), VideoParams::kFormatInvalid, VideoParams::kInternalChannelCount, rational(1), diff --git a/app/dialog/sequence/sequencedialogparametertab.h b/app/dialog/sequence/sequencedialogparametertab.h index 6d4d3bf08..e11561b2f 100644 --- a/app/dialog/sequence/sequencedialogparametertab.h +++ b/app/dialog/sequence/sequencedialogparametertab.h @@ -1,6 +1,7 @@ #ifndef SEQUENCEDIALOGPARAMETERTAB_H #define SEQUENCEDIALOGPARAMETERTAB_H +#include #include #include #include @@ -9,7 +10,6 @@ #include "sequencepreset.h" #include "widget/slider/integerslider.h" #include "widget/standardcombos/standardcombos.h" -#include "widget/videoparamedit/videoparamedit.h" namespace olive { @@ -21,27 +21,27 @@ public: int GetSelectedVideoWidth() const { - return video_section_->GetWidth(); + return width_slider_->GetValue(); } int GetSelectedVideoHeight() const { - return video_section_->GetHeight(); + return height_slider_->GetValue(); } rational GetSelectedVideoFrameRate() const { - return video_section_->GetFrameRate(); + return framerate_combo_->GetFrameRate(); } rational GetSelectedVideoPixelAspect() const { - return video_section_->GetPixelAspectRatio(); + return pixelaspect_combo_->GetPixelAspectRatio(); } VideoParams::Interlacing GetSelectedVideoInterlacingMode() const { - return video_section_->GetInterlaceMode(); + return interlacing_combo_->GetInterlaceMode(); } int GetSelectedAudioSampleRate() const @@ -76,7 +76,15 @@ signals: void SaveParametersAsPreset(const SequencePreset& preset); private: - VideoParamEdit* video_section_; + IntegerSlider *width_slider_; + + IntegerSlider *height_slider_; + + FrameRateComboBox *framerate_combo_; + + PixelAspectRatioComboBox *pixelaspect_combo_; + + InterlacedComboBox *interlacing_combo_; SampleRateComboBox* audio_sample_rate_field_; diff --git a/app/node/node.cpp b/app/node/node.cpp index 4e850e821..ad406a8bb 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -2246,7 +2246,6 @@ void Node::childEvent(QChildEvent *event) GetImmediate(key->input(), key->element())->insert_keyframe(key); connect(key, &NodeKeyframe::TimeChanged, this, &Node::InvalidateFromKeyframeTimeChange); - connect(key, &NodeKeyframe::TimeChanged, this, &Node::KeyframeTimeChanged); connect(key, &NodeKeyframe::ValueChanged, this, &Node::InvalidateFromKeyframeValueChange); connect(key, &NodeKeyframe::TypeChanged, this, &Node::InvalidateFromKeyframeTypeChanged); connect(key, &NodeKeyframe::BezierControlInChanged, this, &Node::InvalidateFromKeyframeBezierInChange); @@ -2258,15 +2257,14 @@ void Node::childEvent(QChildEvent *event) TimeRange time_affected = GetRangeAffectedByKeyframe(key); disconnect(key, &NodeKeyframe::TimeChanged, this, &Node::InvalidateFromKeyframeTimeChange); - disconnect(key, &NodeKeyframe::TimeChanged, this, &Node::KeyframeTimeChanged); disconnect(key, &NodeKeyframe::ValueChanged, this, &Node::InvalidateFromKeyframeValueChange); disconnect(key, &NodeKeyframe::TypeChanged, this, &Node::InvalidateFromKeyframeTypeChanged); disconnect(key, &NodeKeyframe::BezierControlInChanged, this, &Node::InvalidateFromKeyframeBezierInChange); disconnect(key, &NodeKeyframe::BezierControlOutChanged, this, &Node::InvalidateFromKeyframeBezierOutChange); - GetImmediate(key->input(), key->element())->remove_keyframe(key); - emit KeyframeRemoved(key); + + GetImmediate(key->input(), key->element())->remove_keyframe(key); ParameterValueChanged(i, time_affected); } } @@ -2329,12 +2327,16 @@ void Node::InvalidateFromKeyframeTimeChange() foreach (const TimeRange& r, invalidate_range) { ParameterValueChanged(key->key_track_ref().input(), r); } + + emit KeyframeTimeChanged(key); } void Node::InvalidateFromKeyframeValueChange() { NodeKeyframe* key = static_cast(sender()); ParameterValueChanged(key->key_track_ref().input(), GetRangeAffectedByKeyframe(key)); + + emit KeyframeValueChanged(key); } void Node::InvalidateFromKeyframeTypeChanged() @@ -2349,6 +2351,8 @@ void Node::InvalidateFromKeyframeTypeChanged() // Invalidate entire range ParameterValueChanged(key->key_track_ref().input(), GetRangeAroundIndex(key->input(), track.indexOf(key), key->track(), key->element())); + + emit KeyframeTypeChanged(key); } Project *Node::ArrayInsertCommand::GetRelevantProject() const diff --git a/app/node/node.h b/app/node/node.h index f38835ce9..92d06e63f 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -1066,7 +1066,11 @@ signals: void KeyframeRemoved(NodeKeyframe* key); - void KeyframeTimeChanged(); + void KeyframeTimeChanged(NodeKeyframe* key); + + void KeyframeTypeChanged(NodeKeyframe* key); + + void KeyframeValueChanged(NodeKeyframe* key); void KeyframeEnableChanged(const NodeInput& input, bool enabled); diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 87707e2c1..8e257f6e9 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -23,7 +23,6 @@ #include "config/config.h" #include "core.h" #include "node/traverser.h" -#include "widget/videoparamedit/videoparamedit.h" namespace olive { @@ -34,8 +33,6 @@ const QString ViewerOutput::kSamplesInput = QStringLiteral("samples_in"); const QString ViewerOutput::kVideoAutoCacheInput = QStringLiteral("video_autocache_in"); const QString ViewerOutput::kAudioAutoCacheInput = QStringLiteral("audio_autocache_in"); -const uint64_t ViewerOutput::kVideoParamEditMask = VideoParamEdit::kWidthHeight | VideoParamEdit::kInterlacing | VideoParamEdit::kFrameRate | VideoParamEdit::kPixelAspect; - #define super Node ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_streams) : @@ -48,7 +45,6 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream audio_cache_enabled_(true) { AddInput(kVideoParamsInput, NodeValue::kVideoParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); - SetInputProperty(kVideoParamsInput, QStringLiteral("mask"), QVariant::fromValue(kVideoParamEditMask)); AddInput(kAudioParamsInput, NodeValue::kAudioParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 5c516000c..2be79bf2f 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -200,8 +200,6 @@ public: static const QString kVideoAutoCacheInput; static const QString kAudioAutoCacheInput; - static const uint64_t kVideoParamEditMask; - signals: void FrameRateChanged(const rational&); diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 4ab67ebb8..a9aa54705 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -33,7 +33,6 @@ #include "core.h" #include "render/job/footagejob.h" #include "ui/icons/icons.h" -#include "widget/videoparamedit/videoparamedit.h" namespace olive { @@ -144,39 +143,6 @@ void Footage::InputValueChangedEvent(const QString &input, int element) AddStream(Track::kVideo, QVariant::fromValue(vp)); } - if (!footage_info.GetVideoStreams().isEmpty()) { - // FIXME: This will break on multiple video streams. Currently we don't have - // infrastructure for different properties per element. We'll see if this becomes - // a problem. - VideoParams vp = footage_info.GetVideoStreams().first(); - - uint64_t video_param_mask = 0; - - video_param_mask |= VideoParamEdit::kEnabled; - video_param_mask |= VideoParamEdit::kColorspace; - video_param_mask |= VideoParamEdit::kPixelAspect; - video_param_mask |= VideoParamEdit::kInterlacing; - video_param_mask |= VideoParamEdit::kFrameRateIsArbitrary; - - if (vp.channel_count() == VideoParams::kRGBAChannelCount) { - // Add premultiplied setting if this footage has an alpha channel - video_param_mask |= VideoParamEdit::kPremultipliedAlpha; - } - - if (vp.video_type() == VideoParams::kVideoTypeVideo) { - // This is video, ensure that the frame rate does not overwrite the timebase - video_param_mask |= VideoParamEdit::kFrameRateIsNotTimebase; - } else { - // This is not a video, so it's either a still image or an image sequence - video_param_mask |= VideoParamEdit::kIsImageSequence; - video_param_mask |= VideoParamEdit::kStartTime; - video_param_mask |= VideoParamEdit::kEndTime; - video_param_mask |= VideoParamEdit::kFrameRate; - } - - SetInputProperty(kVideoParamsInput, QStringLiteral("mask"), QVariant::fromValue(video_param_mask)); - } - for (int i=0; i nodes; + + if (node) { + nodes.append(node); + } + + SetNodes(nodes); + } + void SetNodes(const QVector &nodes); virtual void IncreaseTrackHeight() override; diff --git a/app/widget/CMakeLists.txt b/app/widget/CMakeLists.txt index 38fefccbd..915214d9e 100644 --- a/app/widget/CMakeLists.txt +++ b/app/widget/CMakeLists.txt @@ -51,7 +51,6 @@ add_subdirectory(timelinewidget) add_subdirectory(timeruler) add_subdirectory(timetarget) add_subdirectory(toolbar) -add_subdirectory(videoparamedit) add_subdirectory(viewer) set(OLIVE_SOURCES diff --git a/app/widget/curvewidget/CMakeLists.txt b/app/widget/curvewidget/CMakeLists.txt index 1ceeadf85..baafedb04 100644 --- a/app/widget/curvewidget/CMakeLists.txt +++ b/app/widget/curvewidget/CMakeLists.txt @@ -16,11 +16,9 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - widget/curvewidget/beziercontrolpointitem.h - widget/curvewidget/beziercontrolpointitem.cpp - widget/curvewidget/curveview.h widget/curvewidget/curveview.cpp - widget/curvewidget/curvewidget.h + widget/curvewidget/curveview.h widget/curvewidget/curvewidget.cpp + widget/curvewidget/curvewidget.h PARENT_SCOPE ) diff --git a/app/widget/curvewidget/beziercontrolpointitem.cpp b/app/widget/curvewidget/beziercontrolpointitem.cpp deleted file mode 100644 index 26f062803..000000000 --- a/app/widget/curvewidget/beziercontrolpointitem.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "beziercontrolpointitem.h" - -#include -#include -#include -#include - -#include "common/qtutils.h" - -namespace olive { - -BezierControlPointItem::BezierControlPointItem(NodeKeyframe* key, NodeKeyframe::BezierType mode, QGraphicsItem *parent) : - QGraphicsRectItem(parent), - key_(key), - mode_(mode), - x_scale_(1.0), - y_scale_(1.0) -{ - setFlag(QGraphicsItem::ItemIsMovable); - - connect(key, &NodeKeyframe::TimeChanged, this, &BezierControlPointItem::UpdatePos); - - if (mode_ == NodeKeyframe::kInHandle) { - connect(key, &NodeKeyframe::BezierControlInChanged, this, &BezierControlPointItem::UpdatePos); - } else { - connect(key, &NodeKeyframe::BezierControlOutChanged, this, &BezierControlPointItem::UpdatePos); - } - - - int control_point_size = QtUtils::QFontMetricsWidth(qApp->fontMetrics(), "o"); - int half_sz = control_point_size / 2; - setRect(-half_sz, -half_sz, control_point_size, control_point_size); -} - -void BezierControlPointItem::SetXScale(double scale) -{ - x_scale_ = scale; - UpdatePos(); -} - -void BezierControlPointItem::SetYScale(double scale) -{ - y_scale_ = scale; - UpdatePos(); -} - -NodeKeyframe* BezierControlPointItem::key() const -{ - return key_; -} - -const NodeKeyframe::BezierType &BezierControlPointItem::mode() const -{ - return mode_; -} - -QPointF BezierControlPointItem::GetCorrespondingKeyframeHandle() const -{ - return key_->bezier_control(mode_); -} - -void BezierControlPointItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) -{ - if (option->state & QStyle::State_Selected) { - painter->setPen(widget->palette().highlight().color()); - } else { - painter->setPen(widget->palette().text().color()); - } - - painter->drawEllipse(rect()); -} - -void BezierControlPointItem::UpdatePos() -{ - QPointF handle_offset = GetCorrespondingKeyframeHandle(); - - // Scale handle offset - handle_offset.setX(handle_offset.x() * x_scale_); - - // Flip the Y coordinate because bezier curves are drawn bottom to top - handle_offset.setY(-handle_offset.y() * y_scale_); - - setPos(handle_offset - rect().center()); -} - -} diff --git a/app/widget/curvewidget/beziercontrolpointitem.h b/app/widget/curvewidget/beziercontrolpointitem.h deleted file mode 100644 index 26d87f8dc..000000000 --- a/app/widget/curvewidget/beziercontrolpointitem.h +++ /dev/null @@ -1,68 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef BEZIERCONTROLPOINTITEM_H -#define BEZIERCONTROLPOINTITEM_H - -#include - -#include "node/keyframe.h" - -namespace olive { - -class BezierControlPointItem : public QObject, public QGraphicsRectItem -{ -public: - BezierControlPointItem(NodeKeyframe* key, NodeKeyframe::BezierType mode, QGraphicsItem* parent = nullptr); - - void SetXScale(double scale); - - void SetYScale(double scale); - - NodeKeyframe* key() const; - - const NodeKeyframe::BezierType& mode() const; - - QPointF GetCorrespondingKeyframeHandle() const; - - void SetCorrespondingKeyframeHandle(const QPointF& handle); - - void SetOpposingKeyframeHandle(const QPointF& handle); - -protected: - virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; - -private: - NodeKeyframe* key_; - - NodeKeyframe::BezierType mode_; - - double x_scale_; - - double y_scale_; - -private slots: - void UpdatePos(); - -}; - -} - -#endif // BEZIERCONTROLPOINTITEM_H diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index e83909702..8fee9d725 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -20,31 +20,33 @@ #include "curveview.h" +#include #include #include +#include #include #include -#include #include "common/qtutils.h" +#include "widget/keyframeview/keyframeviewundo.h" +#include "widget/nodeparamview/nodeparamviewundo.h" +#include "widget/slider/floatslider.h" namespace olive { -#define super KeyframeViewBase +#define super KeyframeView CurveView::CurveView(QWidget *parent) : - KeyframeViewBase(parent) + KeyframeView(parent), + dragging_bezier_pt_(nullptr) { setAlignment(Qt::AlignLeft | Qt::AlignVCenter); - setDragMode(RubberBandDrag); - setViewportUpdateMode(FullViewportUpdate); SetYAxisEnabled(true); + SetAutoSelectSiblings(false); text_padding_ = QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral("i")); minimum_grid_space_ = QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral("00000")); - - connect(scene(), &QGraphicsScene::selectionChanged, this, &CurveView::SelectionChanged); } void CurveView::ConnectInput(const NodeKeyframeTrackReference& ref) @@ -90,7 +92,9 @@ void CurveView::SelectKeyframesOfInput(const NodeKeyframeTrackReference& ref) void CurveView::ZoomToFitInput(const NodeKeyframeTrackReference& ref) { - ZoomToFitInternal(track_connections_.value(ref)->GetKeyframes()); + if (KeyframeViewInputConnection *con = track_connections_.value(ref)) { + ZoomToFitInternal(con->GetKeyframes()); + } } void CurveView::SetKeyframeTrackColor(const NodeKeyframeTrackReference &ref, const QColor &color) @@ -98,8 +102,10 @@ void CurveView::SetKeyframeTrackColor(const NodeKeyframeTrackReference &ref, con // Insert color into hashmap keyframe_colors_.insert(ref, color); - // Update all keyframes - track_connections_.value(ref)->SetBrush(color); + if (KeyframeViewInputConnection *con = track_connections_.value(ref)) { + // Update all keyframes + con->SetBrush(color); + } } void CurveView::drawBackground(QPainter *painter, const QRectF &rect) @@ -235,40 +241,13 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect) } } } - - // Draw bezier control point lines - /*if (!bezier_control_points_.isEmpty()) { - painter->setPen(QPen(palette().text().color(), 1)); - - QVector bezier_lines; - foreach (BezierControlPointItem* item, bezier_control_points_) { - // All BezierControlPointItems should be children of a KeyframeViewItem - KeyframeViewItem* par = static_cast(item->parentItem()); - - bezier_lines.append(QLineF(par->pos(), par->pos() + item->pos())); - } - painter->drawLines(bezier_lines); - }*/ } -void CurveView::ScaleChangedEvent(const double& scale) +void CurveView::drawForeground(QPainter *painter, const QRectF &rect) { - KeyframeViewBase::ScaleChangedEvent(scale); + bezier_pts_.clear(); - foreach (BezierControlPointItem* item, bezier_control_points_) { - item->SetXScale(scale); - } -} - -void CurveView::VerticalScaleChangedEvent(double scale) -{ - Q_UNUSED(scale) - - foreach (BezierControlPointItem* item, bezier_control_points_) { - item->SetYScale(scale); - } - - viewport()->update(); + super::drawForeground(painter, rect); } void CurveView::ContextMenuEvent(Menu &m) @@ -290,6 +269,225 @@ void CurveView::SceneRectUpdateEvent(QRectF &r) r.setBottom(r.bottom() + this->height()); } +qreal CurveView::GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key) +{ + return GetItemYFromKeyframeValue(key); +} + +void CurveView::DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeViewInputConnection *track, const QRectF &key_rect) +{ + if (IsKeyframeSelected(key) && key->type() == NodeKeyframe::kBezier) { + // Draw bezier control points if keyframe is selected + int control_point_size = QtUtils::QFontMetricsWidth(fontMetrics(), "o"); + int half_sz = control_point_size / 2; + QRectF control_point_rect(-half_sz, -half_sz, control_point_size, control_point_size); + + painter->setPen(palette().text().color()); + painter->setBrush(Qt::NoBrush); + + QRectF cp_in = control_point_rect.translated(key_rect.center() + ScalePoint(key->bezier_control_in())); + QRectF cp_out = control_point_rect.translated(key_rect.center() + ScalePoint(key->bezier_control_out())); + + painter->drawLine(key_rect.center(), cp_in.center()); + painter->drawLine(key_rect.center(), cp_out.center()); + + painter->drawEllipse(cp_in); + painter->drawEllipse(cp_out); + + bezier_pts_.append({cp_in, key, NodeKeyframe::kInHandle}); + bezier_pts_.append({cp_out, key, NodeKeyframe::kOutHandle}); + } + + super::DrawKeyframe(painter, key, track, key_rect); +} + +bool CurveView::FirstChanceMousePress(QMouseEvent *event) +{ + dragging_bezier_pt_ = nullptr; + QPointF scene_pt = mapToScene(event->pos()); + foreach (const BezierPoint &b, bezier_pts_) { + if (b.rect.contains(scene_pt)) { + dragging_bezier_pt_ = &b; + break; + } + } + + if (dragging_bezier_pt_) { + NodeKeyframe *key = dragging_bezier_pt_->keyframe; + dragging_bezier_point_start_ = (dragging_bezier_pt_->type == NodeKeyframe::kInHandle) ? key->bezier_control_in() : key->bezier_control_out(); + dragging_bezier_point_opposing_start_ = (dragging_bezier_pt_->type == NodeKeyframe::kInHandle) ? key->bezier_control_out() : key->bezier_control_in(); + + drag_start_ = mapToScene(event->pos()); + return true; + } else { + return false; + } +} + +void CurveView::FirstChanceMouseMove(QMouseEvent *event) +{ + // Calculate cursor difference and scale it + QPointF scene_pos = mapToScene(event->pos()); + QPointF mouse_diff_scaled = GetScaledCursorPos(scene_pos - drag_start_); + + if (event->modifiers() & Qt::ShiftModifier) { + // If holding shift, only move one axis + mouse_diff_scaled.setY(0); + } + + // Flip the mouse Y because bezier control points are drawn bottom to top, not top to bottom + mouse_diff_scaled.setY(-mouse_diff_scaled.y()); + + QPointF new_bezier_pos = GenerateBezierControlPosition(dragging_bezier_pt_->type, + dragging_bezier_point_start_, + mouse_diff_scaled); + + // If the user is NOT holding control, we set the other handle to the exact negative of this handle + QPointF new_opposing_pos; + NodeKeyframe::BezierType opposing_type = NodeKeyframe::get_opposing_bezier_type(dragging_bezier_pt_->type); + + + if (!(event->modifiers() & Qt::ControlModifier)) { + new_opposing_pos = GenerateBezierControlPosition(opposing_type, + dragging_bezier_point_opposing_start_, + -mouse_diff_scaled); + } else { + new_opposing_pos = dragging_bezier_point_opposing_start_; + } + + dragging_bezier_pt_->keyframe->set_bezier_control(dragging_bezier_pt_->type, + new_bezier_pos); + + dragging_bezier_pt_->keyframe->set_bezier_control(opposing_type, + new_opposing_pos); + + Redraw(); +} + +void CurveView::FirstChanceMouseRelease(QMouseEvent *event) +{ + MultiUndoCommand* command = new MultiUndoCommand(); + + // Create undo command with the current bezier point and the old one + command->add_child(new KeyframeSetBezierControlPoint(dragging_bezier_pt_->keyframe, + dragging_bezier_pt_->type, + dragging_bezier_pt_->keyframe->bezier_control(dragging_bezier_pt_->type), + dragging_bezier_point_start_)); + + if (!(event->modifiers() & Qt::ControlModifier)) { + auto opposing_type = NodeKeyframe::get_opposing_bezier_type(dragging_bezier_pt_->type); + + command->add_child(new KeyframeSetBezierControlPoint(dragging_bezier_pt_->keyframe, + opposing_type, + dragging_bezier_pt_->keyframe->bezier_control(opposing_type), + dragging_bezier_point_opposing_start_)); + } + + dragging_bezier_pt_ = nullptr; + + Core::instance()->undo_stack()->push(command); +} + +void CurveView::KeyframeDragStart(QMouseEvent *event) +{ + drag_keyframe_values_.resize(GetSelectedKeyframes().size()); + for (int i=0; ivalue(); + } + + drag_start_ = mapToScene(event->pos()); +} + +void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip) +{ + if (event->modifiers() & Qt::ShiftModifier) { + // Lock to X axis only + return; + } + + // Calculate cursor difference + double scaled_diff = (mapToScene(event->pos()).y() - drag_start_.y()) / GetYScale(); + + // Validate movement - ensure no keyframe goes above its max point or below its min point + for (int i=0; iparent(); + double original_val = drag_keyframe_values_.at(i).toDouble(); + const QString& input = key->input(); + double new_val = original_val - scaled_diff; + double limited = new_val; + + if (node->HasInputProperty(input, QStringLiteral("min"))) { + limited = qMax(limited, node->GetInputProperty(input, QStringLiteral("min")).toDouble()); + } + + if (node->HasInputProperty(input, QStringLiteral("max"))) { + limited = qMin(limited, node->GetInputProperty(input, QStringLiteral("max")).toDouble()); + } + + if (limited != new_val) { + scaled_diff = original_val - limited; + } + } + + // Set values + for (int i=0; iset_value(drag_keyframe_values_.at(i).toDouble() - scaled_diff); + } + + NodeKeyframe *tip_item = GetSelectedKeyframes().first(); + FloatSlider::DisplayType display_type = FloatSlider::kNormal; + Node* initial_drag_input = tip_item->parent(); + const QString& initial_drag_input_id = tip_item->input(); + if (initial_drag_input->HasInputProperty(initial_drag_input_id, QStringLiteral("view"))) { + display_type = static_cast(initial_drag_input->GetInputProperty(initial_drag_input_id, QStringLiteral("view")).toInt()); + } + + bool ok; + double num_value = tip_item->value().toDouble(&ok); + + if (ok) { + tip = QStringLiteral("%1\n"); + tip.append(FloatSlider::ValueToString(num_value, display_type, 2, true)); + } +} + +void CurveView::KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *command) +{ + for (int i=0; iadd_child(new NodeParamSetKeyframeValueCommand(k, k->value(), drag_keyframe_values_.at(i))); + } +} + +QPointF CurveView::GenerateBezierControlPosition(const NodeKeyframe::BezierType mode, const QPointF &start_point, const QPointF &scaled_cursor_diff) +{ + QPointF new_bezier_pos = start_point; + + new_bezier_pos += scaled_cursor_diff; + + // LIMIT bezier handles from overlapping each other + if (mode == NodeKeyframe::kInHandle) { + if (new_bezier_pos.x() > 0) { + new_bezier_pos.setX(0); + } + } else { + if (new_bezier_pos.x() < 0) { + new_bezier_pos.setX(0); + } + } + + return new_bezier_pos; +} + +QPointF CurveView::GetScaledCursorPos(const QPointF &cursor_pos) +{ + return QPointF(cursor_pos.x() / GetScale(), + cursor_pos.y() / GetYScale()); +} + void CurveView::ZoomToFitInternal(const QVector &keys) { if (keys.isEmpty()) { @@ -345,36 +543,11 @@ QPointF CurveView::ScalePoint(const QPointF &point) return QPointF(point.x() * GetScale(), - point.y() * GetYScale()); } -void CurveView::CreateBezierControlPoints(NodeKeyframe* item) -{ - qDebug() << "STUB!"; - /*BezierControlPointItem* bezier_in_pt = new BezierControlPointItem(item, NodeKeyframe::kInHandle, item); - bezier_in_pt->SetXScale(GetScale()); - bezier_in_pt->SetYScale(GetYScale()); - bezier_control_points_.append(bezier_in_pt); - connect(bezier_in_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection); - - BezierControlPointItem* bezier_out_pt = new BezierControlPointItem(item, NodeKeyframe::kOutHandle, item); - bezier_out_pt->SetXScale(GetScale()); - bezier_out_pt->SetYScale(GetYScale()); - bezier_control_points_.append(bezier_out_pt); - connect(bezier_out_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection);*/ -} - QPointF CurveView::GetKeyframePosition(NodeKeyframe *key) { return QPointF(GetKeyframeSceneX(key), GetItemYFromKeyframeValue(key)); } -void CurveView::KeyframeValueChanged() -{ - qDebug() << "STUB!"; - /*NodeKeyframe* key = static_cast(sender()); - KeyframeViewItem* item = item_map().value(key); - - SetItemYFromKeyframeValue(key, item);*/ -} - void CurveView::KeyframeTypeChanged() { qDebug() << "STUB!"; @@ -387,33 +560,6 @@ void CurveView::KeyframeTypeChanged() }*/ } -void CurveView::SelectionChanged() -{ - qDebug() << "STUB!"; - /* - // Clear current bezier handles - while (!bezier_control_points_.isEmpty()) { - delete bezier_control_points_.first(); - } - - QList selected = scene()->selectedItems(); - - foreach (QGraphicsItem* item, selected) { - KeyframeViewItem* this_item = static_cast(item); - - if (this_item->key()->type() == NodeKeyframe::kBezier) { - CreateBezierControlPoints(this_item); - } - } - */ -} - -void CurveView::BezierControlPointDestroyed() -{ - BezierControlPointItem* item = static_cast(sender()); - bezier_control_points_.removeOne(item); -} - void CurveView::ZoomToFit() { QVector keys; diff --git a/app/widget/curvewidget/curveview.h b/app/widget/curvewidget/curveview.h index e5b056830..3858368e8 100644 --- a/app/widget/curvewidget/curveview.h +++ b/app/widget/curvewidget/curveview.h @@ -21,13 +21,12 @@ #ifndef CURVEVIEW_H #define CURVEVIEW_H -#include "beziercontrolpointitem.h" #include "node/keyframe.h" #include "widget/keyframeview/keyframeview.h" namespace olive { -class CurveView : public KeyframeViewBase +class CurveView : public KeyframeView { Q_OBJECT public: @@ -52,15 +51,24 @@ public slots: protected: virtual void drawBackground(QPainter* painter, const QRectF& rect) override; - - virtual void ScaleChangedEvent(const double &scale) override; - - virtual void VerticalScaleChangedEvent(double scale) override; + virtual void drawForeground(QPainter *painter, const QRectF &rect) override; virtual void ContextMenuEvent(Menu &m) override; virtual void SceneRectUpdateEvent(QRectF &r) override; + virtual qreal GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key) override; + + virtual void DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeViewInputConnection *track, const QRectF &key_rect) override; + + virtual bool FirstChanceMousePress(QMouseEvent *event) override; + virtual void FirstChanceMouseMove(QMouseEvent *event) override; + virtual void FirstChanceMouseRelease(QMouseEvent *event) override; + + virtual void KeyframeDragStart(QMouseEvent *event) override; + virtual void KeyframeDragMove(QMouseEvent *event, QString &tip) override; + virtual void KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *command) override; + private: void ZoomToFitInternal(const QVector &keys); @@ -71,10 +79,14 @@ private: void AdjustLines(); - void CreateBezierControlPoints(NodeKeyframe *item); - QPointF GetKeyframePosition(NodeKeyframe *key); + static QPointF GenerateBezierControlPosition(const NodeKeyframe::BezierType mode, + const QPointF& start_point, + const QPointF& scaled_cursor_diff); + + QPointF GetScaledCursorPos(const QPointF &cursor_pos); + QHash keyframe_colors_; QHash track_connections_; @@ -82,21 +94,27 @@ private: int minimum_grid_space_; - QVector lines_; - - QVector bezier_control_points_; - QVector connected_inputs_; + struct BezierPoint + { + QRectF rect; + NodeKeyframe *keyframe; + NodeKeyframe::BezierType type; + }; + + QVector bezier_pts_; + const BezierPoint *dragging_bezier_pt_; + + QPointF dragging_bezier_point_start_; + QPointF dragging_bezier_point_opposing_start_; + QPointF drag_start_; + + QVector drag_keyframe_values_; + private slots: - void KeyframeValueChanged(); - void KeyframeTypeChanged(); - void SelectionChanged(); - - void BezierControlPointDestroyed(); - }; } diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index c77a553aa..6dac944dd 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -34,8 +34,10 @@ namespace olive { +#define super TimeBasedWidget + CurveWidget::CurveWidget(QWidget *parent) : - TimeBasedWidget(parent) + super(parent) { QHBoxLayout* outer_layout = new QHBoxLayout(this); @@ -152,7 +154,7 @@ void CurveWidget::SetNodes(const QVector &nodes) void CurveWidget::TimeChangedEvent(const rational &time) { - TimeBasedWidget::TimeChangedEvent(time); + super::TimeChangedEvent(time); view_->SetTime(time); UpdateBridgeTime(time); @@ -160,14 +162,14 @@ void CurveWidget::TimeChangedEvent(const rational &time) void CurveWidget::TimebaseChangedEvent(const rational &timebase) { - TimeBasedWidget::TimebaseChangedEvent(timebase); + super::TimebaseChangedEvent(timebase); view_->SetTimebase(timebase); } void CurveWidget::ScaleChangedEvent(const double &scale) { - TimeBasedWidget::ScaleChangedEvent(scale); + super::ScaleChangedEvent(scale); view_->SetScale(scale); } @@ -238,7 +240,7 @@ void CurveWidget::ConnectInput(Node *node, const QString &input, bool connect) NodeKeyframeTrackReference ref(NodeInput(node, input, i), j); if (!keyframe_colors_.contains(ref)) { - QColor c = QColor::fromHsv(std::rand()%360, std::rand()%255, 255); + QColor c = QColor::fromHsl(std::rand()%360, 255, 160); keyframe_colors_.insert(ref, c); tree_view_->SetKeyframeTrackColor(ref, c); diff --git a/app/widget/keyframeview/CMakeLists.txt b/app/widget/keyframeview/CMakeLists.txt index 1ab460182..d61022fbe 100644 --- a/app/widget/keyframeview/CMakeLists.txt +++ b/app/widget/keyframeview/CMakeLists.txt @@ -18,8 +18,6 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} widget/keyframeview/keyframeview.cpp widget/keyframeview/keyframeview.h - widget/keyframeview/keyframeviewbase.cpp - widget/keyframeview/keyframeviewbase.h widget/keyframeview/keyframeviewinputconnection.cpp widget/keyframeview/keyframeviewinputconnection.h widget/keyframeview/keyframeviewundo.cpp diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index f8cf5413f..cec7aca99 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -20,15 +20,334 @@ #include "keyframeview.h" +#include +#include +#include + +#include "common/qtutils.h" +#include "dialog/keyframeproperties/keyframeproperties.h" +#include "keyframeviewundo.h" +#include "node/node.h" +#include "widget/menu/menu.h" +#include "widget/menu/menushared.h" +#include "widget/nodeparamview/nodeparamviewundo.h" + namespace olive { -#define super KeyframeViewBase +#define super TimeBasedView KeyframeView::KeyframeView(QWidget *parent) : - KeyframeViewBase(parent), - max_scroll_(0) + super(parent), + selection_manager_(this), + autoselect_siblings_(true), + max_scroll_(0), + first_chance_mouse_event_(false) { setAlignment(Qt::AlignLeft | Qt::AlignTop); + SetDefaultDragMode(RubberBandDrag); + setContextMenuPolicy(Qt::CustomContextMenu); + + connect(this, &KeyframeView::customContextMenuRequested, this, &KeyframeView::ShowContextMenu); +} + +void KeyframeView::DeleteSelected() +{ + MultiUndoCommand* command = new MultiUndoCommand(); + + foreach (NodeKeyframe *key, GetSelectedKeyframes()) { + command->add_child(new NodeParamRemoveKeyframeCommand(key)); + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); +} + +KeyframeView::NodeConnections KeyframeView::AddKeyframesOfNode(Node *n) +{ + NodeConnections map; + + foreach (const QString& i, n->inputs()) { + map.insert(i, AddKeyframesOfInput(n, i)); + } + + return map; +} + +KeyframeView::InputConnections KeyframeView::AddKeyframesOfInput(Node* n, const QString& input) +{ + InputConnections vec; + + if (n->IsInputKeyframable(input)) { + int arr_sz = n->InputArraySize(input); + vec.resize(arr_sz + 1); + for (int i=-1; i& tracks = input.node()->GetKeyframeTracks(input); + ElementConnections vec(tracks.size()); + + for (int i=0; iGetKeyframes()) { + selection_manager_.Deselect(key); + } + delete connection; + Redraw(); + } +} + +void KeyframeView::SelectAll() +{ + foreach (KeyframeViewInputConnection *track, tracks_) { + foreach (NodeKeyframe *key, track->GetKeyframes()) { + SelectKeyframe(key); + } + } +} + +void KeyframeView::DeselectAll() +{ + selection_manager_.ClearSelection(); + + Redraw(); +} + +void KeyframeView::Clear() +{ + if (!tracks_.isEmpty()) { + qDeleteAll(tracks_); + tracks_.clear(); + Redraw(); + } + + selection_manager_.ClearSelection(); +} + +void KeyframeView::SelectionManagerSelectEvent(void *obj) +{ + if (autoselect_siblings_) { + NodeKeyframe *key = static_cast(obj); + QVector keys = key->parent()->GetKeyframesAtTime(key->input(), key->time(), key->element()); + foreach (NodeKeyframe* k, keys) { + if (k != key) { + SelectKeyframe(k); + } + } + } +} + +void KeyframeView::SelectionManagerDeselectEvent(void *obj) +{ + if (autoselect_siblings_) { + NodeKeyframe *key = static_cast(obj); + QVector keys = key->parent()->GetKeyframesAtTime(key->input(), key->time(), key->element()); + foreach (NodeKeyframe* k, keys) { + if (k != key) { + DeselectKeyframe(k); + } + } + } +} + +void KeyframeView::mousePressEvent(QMouseEvent *event) +{ + NodeKeyframe *key_under_cursor = selection_manager_.GetObjectAtPoint(event->pos()); + + if (HandPress(event) || (!key_under_cursor && PlayheadPress(event))) { + return; + } + + // Do mouse press things + if (FirstChanceMousePress(event)) { + first_chance_mouse_event_ = true; + } else if (NodeKeyframe *initial_key = selection_manager_.MousePress(event)) { + selection_manager_.DragStart(initial_key, event); + KeyframeDragStart(event); + } else { + selection_manager_.RubberBandStart(event); + } + + // Update view + Redraw(); +} + +void KeyframeView::mouseMoveEvent(QMouseEvent *event) +{ + if (HandMove(event) || PlayheadMove(event)) { + return; + } + + if (first_chance_mouse_event_) { + FirstChanceMouseMove(event); + } else if (selection_manager_.IsDragging()) { + QString tip; + KeyframeDragMove(event, tip); + selection_manager_.DragMove(event, tip); + } else if (selection_manager_.IsRubberBanding()) { + selection_manager_.RubberBandMove(event); + Redraw(); + } + + if (event->buttons()) { + // Signal cursor pos in case we should scroll to catch up to it + QPointF scene_pos = mapToScene(event->pos()); + emit Dragged(scene_pos.x(), scene_pos.y()); + } +} + +void KeyframeView::mouseReleaseEvent(QMouseEvent *event) +{ + if (HandRelease(event) || PlayheadRelease(event)) { + return; + } + + if (first_chance_mouse_event_) { + FirstChanceMouseRelease(event); + first_chance_mouse_event_ = false; + } else if (selection_manager_.IsDragging()) { + MultiUndoCommand* command = new MultiUndoCommand(); + selection_manager_.DragStop(command); + KeyframeDragRelease(event, command); + Core::instance()->undo_stack()->push(command); + } else if (selection_manager_.IsRubberBanding()) { + selection_manager_.RubberBandStop(); + Redraw(); + } +} + +void KeyframeView::drawForeground(QPainter *painter, const QRectF &rect) +{ + int key_sz = QtUtils::QFontMetricsWidth(fontMetrics(), "Oi"); + int key_rad = key_sz/2; + + selection_manager_.ClearDrawnObjects(); + + painter->setRenderHint(QPainter::Antialiasing); + + foreach (KeyframeViewInputConnection *track, tracks_) { + foreach (NodeKeyframe *key, track->GetKeyframes()) { + QRectF key_rect(-key_rad, -key_rad, key_sz, key_sz); + key_rect.translate(GetKeyframeSceneX(key), GetKeyframeSceneY(track, key)); + + if (!rect.intersects(key_rect)) { + continue; + } + + DrawKeyframe(painter, key, track, key_rect); + } + } + + super::drawForeground(painter, rect); +} + +void KeyframeView::DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeViewInputConnection *track, const QRectF &key_rect) +{ + painter->setPen(Qt::black); + + if (IsKeyframeSelected(key)) { + painter->setBrush(palette().highlight()); + } else { + painter->setBrush(track->GetBrush()); + } + + selection_manager_.DeclareDrawnObject(key, key_rect); + + switch (key->type()) { + case NodeKeyframe::kLinear: + { + QPointF points[] = { + QPointF(key_rect.center().x(), key_rect.top()), + QPointF(key_rect.right(), key_rect.center().y()), + QPointF(key_rect.center().x(), key_rect.bottom()), + QPointF(key_rect.left(), key_rect.center().y()) + }; + + painter->drawPolygon(points, 4); + break; + } + case NodeKeyframe::kBezier: + painter->drawEllipse(key_rect); + break; + case NodeKeyframe::kHold: + painter->drawRect(key_rect); + break; + } +} + +void KeyframeView::ScaleChangedEvent(const double &scale) +{ + super::ScaleChangedEvent(scale); + + Redraw(); +} + +void KeyframeView::TimeTargetChangedEvent(Node *target) +{ + Redraw(); +} + +void KeyframeView::TimebaseChangedEvent(const rational &timebase) +{ + super::TimebaseChangedEvent(timebase); + + selection_manager_.SetTimebase(timebase); +} + +void KeyframeView::ContextMenuEvent(Menu& m) +{ + Q_UNUSED(m) +} + +void KeyframeView::SelectKeyframe(NodeKeyframe *key) +{ + if (selection_manager_.Select(key)) { + Redraw(); + } +} + +void KeyframeView::DeselectKeyframe(NodeKeyframe *key) +{ + if (selection_manager_.Deselect(key)) { + Redraw(); + } +} + +rational KeyframeView::GetAdjustedKeyframeTime(NodeKeyframe *key) +{ + return GetAdjustedTime(key->parent(), GetTimeTarget(), key->time(), false); +} + +double KeyframeView::GetKeyframeSceneX(NodeKeyframe *key) +{ + return TimeToScene(GetAdjustedKeyframeTime(key)); +} + +qreal KeyframeView::GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key) +{ + return mapFromGlobal(QPoint(0, track->GetKeyframeY())).y(); } void KeyframeView::SceneRectUpdateEvent(QRectF &rect) @@ -37,4 +356,108 @@ void KeyframeView::SceneRectUpdateEvent(QRectF &rect) rect.setHeight(max_scroll_); } +rational KeyframeView::CalculateNewTimeFromScreen(const rational &old_time, double cursor_diff) +{ + return rational::fromDouble(old_time.toDouble() + cursor_diff); +} + +void KeyframeView::ShowContextMenu() +{ + Menu m; + + MenuShared::instance()->AddItemsForEditMenu(&m, false); + + QAction* linear_key_action = nullptr; + QAction* bezier_key_action = nullptr; + QAction* hold_key_action = nullptr; + + if (!GetSelectedKeyframes().isEmpty()) { + bool all_keys_are_same_type = true; + NodeKeyframe::Type type = GetSelectedKeyframes().first()->type(); + + for (int i=1;itype() != prev_item->type()) { + all_keys_are_same_type = false; + break; + } + } + + m.addSeparator(); + + linear_key_action = m.addAction(tr("Linear")); + bezier_key_action = m.addAction(tr("Bezier")); + hold_key_action = m.addAction(tr("Hold")); + + if (all_keys_are_same_type) { + switch (type) { + case NodeKeyframe::kLinear: + linear_key_action->setChecked(true); + break; + case NodeKeyframe::kBezier: + bezier_key_action->setChecked(true); + break; + case NodeKeyframe::kHold: + hold_key_action->setChecked(true); + break; + } + } + } + + m.addSeparator(); + + AddSetScrollZoomsByDefaultActionToMenu(&m); + + m.addSeparator(); + + ContextMenuEvent(m); + + if (!GetSelectedKeyframes().isEmpty()) { + m.addSeparator(); + + QAction* properties_action = m.addAction(tr("P&roperties")); + connect(properties_action, &QAction::triggered, this, &KeyframeView::ShowKeyframePropertiesDialog); + } + + QAction* selected = m.exec(QCursor::pos()); + + // Process keyframe type changes + if (selected) { + if (selected == linear_key_action + || selected == bezier_key_action + || selected == hold_key_action) { + NodeKeyframe::Type new_type; + + if (selected == hold_key_action) { + new_type = NodeKeyframe::kHold; + } else if (selected == bezier_key_action) { + new_type = NodeKeyframe::kBezier; + } else { + new_type = NodeKeyframe::kLinear; + } + + MultiUndoCommand* command = new MultiUndoCommand(); + foreach (NodeKeyframe* item, GetSelectedKeyframes()) { + command->add_child(new KeyframeSetTypeCommand(item, new_type)); + } + Core::instance()->undo_stack()->push(command); + } + } +} + +void KeyframeView::ShowKeyframePropertiesDialog() +{ + if (!GetSelectedKeyframes().isEmpty()) { + KeyframePropertiesDialog kd(GetSelectedKeyframes(), timebase(), this); + kd.exec(); + } +} + +void KeyframeView::Redraw() +{ + viewport()->update(); +} + } diff --git a/app/widget/keyframeview/keyframeview.h b/app/widget/keyframeview/keyframeview.h index 56874232a..a0c1aa99a 100644 --- a/app/widget/keyframeview/keyframeview.h +++ b/app/widget/keyframeview/keyframeview.h @@ -18,33 +18,133 @@ ***/ -#ifndef KEYFRAMEVIEW_H -#define KEYFRAMEVIEW_H +#ifndef KEYFRAMEVIEWBASE_H +#define KEYFRAMEVIEWBASE_H -#include "keyframeviewbase.h" +#include "keyframeviewinputconnection.h" +#include "node/keyframe.h" +#include "widget/menu/menu.h" +#include "widget/timebased/timebasedview.h" +#include "widget/timebased/timebasedviewselectionmanager.h" +#include "widget/timetarget/timetarget.h" namespace olive { -class KeyframeView : public KeyframeViewBase +class KeyframeView : public TimeBasedView, public TimeTargetObject { Q_OBJECT public: KeyframeView(QWidget* parent = nullptr); + void DeleteSelected(); + + using ElementConnections = QVector; + using InputConnections = QVector; + using NodeConnections = QMap; + + NodeConnections AddKeyframesOfNode(Node* n); + + InputConnections AddKeyframesOfInput(Node *n, const QString &input); + + ElementConnections AddKeyframesOfElement(const NodeInput &input); + + KeyframeViewInputConnection *AddKeyframesOfTrack(const NodeKeyframeTrackReference &ref); + + void RemoveKeyframesOfTrack(KeyframeViewInputConnection *connection); + + void SelectAll(); + + void DeselectAll(); + + void Clear(); + + const QVector &GetSelectedKeyframes() const + { + return selection_manager_.GetSelectedObjects(); + } + + virtual void SelectionManagerSelectEvent(void *obj) override; + virtual void SelectionManagerDeselectEvent(void *obj) override; + void SetMaxScroll(int i) { max_scroll_ = i; UpdateSceneRect(); } +signals: + void Dragged(int current_x, int current_y); + protected: + virtual void mousePressEvent(QMouseEvent *event) override; + virtual void mouseMoveEvent(QMouseEvent *event) override; + virtual void mouseReleaseEvent(QMouseEvent *event) override; + + virtual void drawForeground(QPainter *painter, const QRectF &rect) override; + + virtual void DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeViewInputConnection *track, const QRectF &key_rect); + + virtual void ScaleChangedEvent(const double& scale) override; + + virtual void TimeTargetChangedEvent(Node*) override; + + virtual void TimebaseChangedEvent(const rational &timebase) override; + + virtual void ContextMenuEvent(Menu &m); + + virtual bool FirstChanceMousePress(QMouseEvent *event){return false;} + virtual void FirstChanceMouseMove(QMouseEvent *event){} + virtual void FirstChanceMouseRelease(QMouseEvent *event){} + + virtual void KeyframeDragStart(QMouseEvent *event){} + virtual void KeyframeDragMove(QMouseEvent *event, QString &tip){} + virtual void KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *command){} + + void SelectKeyframe(NodeKeyframe *key); + + void DeselectKeyframe(NodeKeyframe *key); + + bool IsKeyframeSelected(NodeKeyframe *key) const + { + return selection_manager_.IsSelected(key); + } + + rational GetAdjustedKeyframeTime(NodeKeyframe *key); + + double GetKeyframeSceneX(NodeKeyframe *key); + + virtual qreal GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key); + + void SetAutoSelectSiblings(bool e) + { + autoselect_siblings_ = e; + } + virtual void SceneRectUpdateEvent(QRectF& rect) override; +protected slots: + void Redraw(); + private: + rational CalculateNewTimeFromScreen(const rational& old_time, double cursor_diff); + + QVector tracks_; + + TimeBasedViewSelectionManager selection_manager_; + + bool autoselect_siblings_; + int max_scroll_; + bool first_chance_mouse_event_; + +private slots: + void ShowContextMenu(); + + void ShowKeyframePropertiesDialog(); + }; } -#endif // KEYFRAMEVIEW_H +#endif // KEYFRAMEVIEWBASE_H diff --git a/app/widget/keyframeview/keyframeviewbase.cpp b/app/widget/keyframeview/keyframeviewbase.cpp deleted file mode 100644 index c3be1e273..000000000 --- a/app/widget/keyframeview/keyframeviewbase.cpp +++ /dev/null @@ -1,602 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "keyframeviewbase.h" - -#include -#include -#include - -#include "common/qtutils.h" -#include "dialog/keyframeproperties/keyframeproperties.h" -#include "keyframeviewundo.h" -#include "node/node.h" -#include "widget/menu/menu.h" -#include "widget/menu/menushared.h" -#include "widget/nodeparamview/nodeparamviewundo.h" - -namespace olive { - -#define super TimeBasedView - -KeyframeViewBase::KeyframeViewBase(QWidget *parent) : - super(parent), - dragging_bezier_point_(nullptr), - currently_autoselecting_(false), - dragging_(false), - selection_manager_(this) -{ - SetDefaultDragMode(RubberBandDrag); - setContextMenuPolicy(Qt::CustomContextMenu); - - connect(this, &KeyframeViewBase::customContextMenuRequested, this, &KeyframeViewBase::ShowContextMenu); -} - -void KeyframeViewBase::DeleteSelected() -{ - MultiUndoCommand* command = new MultiUndoCommand(); - - foreach (NodeKeyframe *key, GetSelectedKeyframes()) { - command->add_child(new NodeParamRemoveKeyframeCommand(key)); - } - - Core::instance()->undo_stack()->pushIfHasChildren(command); -} - -KeyframeViewBase::NodeConnections KeyframeViewBase::AddKeyframesOfNode(Node *n) -{ - NodeConnections map; - - foreach (const QString& i, n->inputs()) { - map.insert(i, AddKeyframesOfInput(n, i)); - } - - return map; -} - -KeyframeViewBase::InputConnections KeyframeViewBase::AddKeyframesOfInput(Node* n, const QString& input) -{ - InputConnections vec; - - if (n->IsInputKeyframable(input)) { - int arr_sz = n->InputArraySize(input); - vec.resize(arr_sz + 1); - for (int i=-1; i& tracks = input.node()->GetKeyframeTracks(input); - ElementConnections vec(tracks.size()); - - for (int i=0; iGetKeyframes()) { - SelectKeyframe(key); - } - } -} - -void KeyframeViewBase::DeselectAll() -{ - selection_manager_.ClearSelection(); - - Redraw(); -} - -void KeyframeViewBase::Clear() -{ - if (!tracks_.isEmpty()) { - qDeleteAll(tracks_); - tracks_.clear(); - Redraw(); - } -} - -void KeyframeViewBase::mousePressEvent(QMouseEvent *event) -{ - NodeKeyframe *key_under_cursor = selection_manager_.MousePress(event); - if (key_under_cursor) { - AutoSelectKeyTimeNeighbors(); - } - - BezierControlPointItem *bezier_under_cursor = dynamic_cast(itemAt(event->pos())); - - Redraw(); - - if (HandPress(event) || (!bezier_under_cursor && !key_under_cursor && PlayheadPress(event))) { - return; - } - - if (event->button() == Qt::LeftButton) { - if (key_under_cursor || bezier_under_cursor) { - dragging_ = true; - drag_start_ = mapToScene(event->pos()); - - // Determine what type of item is under the cursor - dragging_bezier_point_ = bezier_under_cursor; - - if (dragging_bezier_point_) { - - dragging_bezier_point_start_ = dragging_bezier_point_->GetCorrespondingKeyframeHandle(); - dragging_bezier_point_opposing_start_ = dragging_bezier_point_->key()->bezier_control(NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode())); - - } else { - - selection_manager_.DragStart(key_under_cursor, event); - - } - } - } -} - -void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event) -{ - if (HandMove(event) || PlayheadMove(event)) { - return; - } - - if (event->buttons() & Qt::LeftButton) { - if (dragging_) { - // Calculate cursor difference and scale it - QPointF scene_pos = mapToScene(event->pos()); - QPointF mouse_diff_scaled = GetScaledCursorPos(scene_pos - drag_start_); - - if (event->modifiers() & Qt::ShiftModifier) { - // If holding shift, only move one axis - mouse_diff_scaled.setY(0); - } - - if (dragging_bezier_point_) { - - // Flip the mouse Y because bezier control points are drawn bottom to top, not top to bottom - mouse_diff_scaled.setY(-mouse_diff_scaled.y()); - - QPointF new_bezier_pos = GenerateBezierControlPosition(dragging_bezier_point_->mode(), - dragging_bezier_point_start_, - mouse_diff_scaled); - - // If the user is NOT holding control, we set the other handle to the exact negative of this handle - QPointF new_opposing_pos; - NodeKeyframe::BezierType opposing_type = NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode()); - - - if (!(event->modifiers() & Qt::ControlModifier)) { - new_opposing_pos = GenerateBezierControlPosition(opposing_type, - dragging_bezier_point_opposing_start_, - -mouse_diff_scaled); - } else { - new_opposing_pos = dragging_bezier_point_opposing_start_; - } - - dragging_bezier_point_->key()->set_bezier_control(dragging_bezier_point_->mode(), - new_bezier_pos); - - dragging_bezier_point_->key()->set_bezier_control(opposing_type, - new_opposing_pos); - - // Bezier control points are parented to keyframe items making their positions relative - // to those items. We need to map them to the scene coordinates for this to work properly. - QPointF bezier_pos = dragging_bezier_point_->pos() + dragging_bezier_point_->parentItem()->pos(); - emit Dragged(qRound(bezier_pos.x()), qRound(bezier_pos.y())); - - } else if (selection_manager_.IsDragging()) { - - QString tip; - - /* - // Validate movement - ensure no keyframe goes above its max point or below its min point - FloatSlider::DisplayType display_type = FloatSlider::kNormal; - - if (IsYAxisEnabled()) { - foreach (const KeyframeItemAndTime& keypair, dragging_keyframes_) { - NodeKeyframe *key = keypair.key; - Node* node = key->parent(); - const QString& input = key->input(); - double new_val = keypair.value - mouse_diff_scaled.y(); - double limited = new_val; - - if (node->HasInputProperty(input, QStringLiteral("min"))) { - limited = qMax(limited, node->GetInputProperty(input, QStringLiteral("min")).toDouble()); - } - - if (node->HasInputProperty(input, QStringLiteral("max"))) { - limited = qMin(limited, node->GetInputProperty(input, QStringLiteral("max")).toDouble()); - } - - if (limited != new_val) { - mouse_diff_scaled.setY(keypair.value - limited); - } - } - - Node* initial_drag_input = initial_drag_item_->parent(); - const QString& initial_drag_input_id = initial_drag_item_->input(); - if (initial_drag_input->HasInputProperty(initial_drag_input_id, QStringLiteral("view"))) { - display_type = static_cast(initial_drag_input->GetInputProperty(initial_drag_input_id, QStringLiteral("view")).toInt()); - } - } - - foreach (const KeyframeItemAndTime& keypair, dragging_keyframes_) { - if (IsYAxisEnabled()) { - key->set_value(keypair.value - mouse_diff_scaled.y()); - } - } - - - - if (IsYAxisEnabled()) { - bool ok; - double num_value = initial_drag_item_->value().toDouble(&ok); - - if (ok) { - tip = QStringLiteral("%1\n"); - tip.append(FloatSlider::ValueToString(num_value, display_type, 2, true)); - } - } - */ - - selection_manager_.DragMove(event, tip); - - Redraw(); - - emit Dragged(scene_pos.x(), scene_pos.y()); - - } - } - } -} - -void KeyframeViewBase::mouseReleaseEvent(QMouseEvent *event) -{ - if (HandRelease(event) || PlayheadRelease(event)) { - return; - } - - if (event->button() == Qt::LeftButton) { - if (dragging_) { - if (dragging_bezier_point_) { - MultiUndoCommand* command = new MultiUndoCommand(); - - // Create undo command with the current bezier point and the old one - command->add_child(new KeyframeSetBezierControlPoint(dragging_bezier_point_->key(), - dragging_bezier_point_->mode(), - dragging_bezier_point_->key()->bezier_control(dragging_bezier_point_->mode()), - dragging_bezier_point_start_)); - - if (!(event->modifiers() & Qt::ControlModifier)) { - auto opposing_type = NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode()); - - command->add_child(new KeyframeSetBezierControlPoint(dragging_bezier_point_->key(), - opposing_type, - dragging_bezier_point_->key()->bezier_control(opposing_type), - dragging_bezier_point_opposing_start_)); - } - - dragging_bezier_point_ = nullptr; - - Core::instance()->undo_stack()->push(command); - } else if (selection_manager_.IsDragging()) { - MultiUndoCommand* command = new MultiUndoCommand(); - - selection_manager_.DragStop(command); - /*if (IsYAxisEnabled()) { - command->add_child(new NodeParamSetKeyframeValueCommand(item, - item->value(), - keypair.value)); - }*/ - - Core::instance()->undo_stack()->push(command); - } - - dragging_ = false; - - QToolTip::hideText(); - } - } -} - -void KeyframeViewBase::drawForeground(QPainter *painter, const QRectF &rect) -{ - int key_sz = QtUtils::QFontMetricsWidth(fontMetrics(), "Oi"); - int key_rad = key_sz/2; - - selection_manager_.ClearDrawnObjects(); - - painter->setRenderHint(QPainter::Antialiasing); - - painter->setPen(Qt::black); - - foreach (KeyframeViewInputConnection *track, tracks_) { - foreach (NodeKeyframe *key, track->GetKeyframes()) { - QRectF key_rect(-key_rad, -key_rad, key_sz, key_sz); - key_rect.translate(GetKeyframeSceneX(key), mapFromGlobal(QPoint(0, track->GetKeyframeY())).y()); - - if (!rect.intersects(key_rect)) { - continue; - } - - if (IsKeyframeSelected(key)) { - painter->setBrush(palette().highlight()); - } else { - painter->setBrush(track->GetBrush()); - } - - selection_manager_.DeclareDrawnObject(key, key_rect); - - switch (key->type()) { - case NodeKeyframe::kLinear: - { - QPointF points[] = { - QPointF(key_rect.center().x(), key_rect.top()), - QPointF(key_rect.right(), key_rect.center().y()), - QPointF(key_rect.center().x(), key_rect.bottom()), - QPointF(key_rect.left(), key_rect.center().y()) - }; - - painter->drawPolygon(points, 4); - break; - } - case NodeKeyframe::kBezier: - painter->drawEllipse(key_rect); - break; - case NodeKeyframe::kHold: - painter->drawRect(key_rect); - break; - } - } - } - - super::drawForeground(painter, rect); -} - -void KeyframeViewBase::ScaleChangedEvent(const double &scale) -{ - super::ScaleChangedEvent(scale); - - Redraw(); -} - -void KeyframeViewBase::TimeTargetChangedEvent(Node *target) -{ - Redraw(); -} - -void KeyframeViewBase::TimebaseChangedEvent(const rational &timebase) -{ - super::TimebaseChangedEvent(timebase); - - selection_manager_.SetTimebase(timebase); -} - -void KeyframeViewBase::ContextMenuEvent(Menu& m) -{ - Q_UNUSED(m) -} - -void KeyframeViewBase::SelectKeyframe(NodeKeyframe *key) -{ - if (selection_manager_.Select(key)) { - Redraw(); - } -} - -void KeyframeViewBase::DeselectKeyframe(NodeKeyframe *key) -{ - if (selection_manager_.Deselect(key)) { - Redraw(); - } -} - -rational KeyframeViewBase::GetAdjustedKeyframeTime(NodeKeyframe *key) -{ - return GetAdjustedTime(key->parent(), GetTimeTarget(), key->time(), false); -} - -double KeyframeViewBase::GetKeyframeSceneX(NodeKeyframe *key) -{ - return TimeToScene(GetAdjustedKeyframeTime(key)); -} - -rational KeyframeViewBase::CalculateNewTimeFromScreen(const rational &old_time, double cursor_diff) -{ - return rational::fromDouble(old_time.toDouble() + cursor_diff); -} - -QPointF KeyframeViewBase::GenerateBezierControlPosition(const NodeKeyframe::BezierType mode, const QPointF &start_point, const QPointF &scaled_cursor_diff) -{ - QPointF new_bezier_pos = start_point; - - new_bezier_pos += scaled_cursor_diff; - - // LIMIT bezier handles from overlapping each other - if (mode == NodeKeyframe::kInHandle) { - if (new_bezier_pos.x() > 0) { - new_bezier_pos.setX(0); - } - } else { - if (new_bezier_pos.x() < 0) { - new_bezier_pos.setX(0); - } - } - - return new_bezier_pos; -} - -QPointF KeyframeViewBase::GetScaledCursorPos(const QPointF &cursor_pos) -{ - return QPointF(cursor_pos.x() / GetScale(), - cursor_pos.y() / GetYScale()); -} - -void KeyframeViewBase::ShowContextMenu() -{ - Menu m; - - MenuShared::instance()->AddItemsForEditMenu(&m, false); - - QAction* linear_key_action = nullptr; - QAction* bezier_key_action = nullptr; - QAction* hold_key_action = nullptr; - - if (!GetSelectedKeyframes().isEmpty()) { - bool all_keys_are_same_type = true; - NodeKeyframe::Type type = GetSelectedKeyframes().first()->type(); - - for (int i=1;itype() != prev_item->type()) { - all_keys_are_same_type = false; - break; - } - } - - m.addSeparator(); - - linear_key_action = m.addAction(tr("Linear")); - bezier_key_action = m.addAction(tr("Bezier")); - hold_key_action = m.addAction(tr("Hold")); - - if (all_keys_are_same_type) { - switch (type) { - case NodeKeyframe::kLinear: - linear_key_action->setChecked(true); - break; - case NodeKeyframe::kBezier: - bezier_key_action->setChecked(true); - break; - case NodeKeyframe::kHold: - hold_key_action->setChecked(true); - break; - } - } - } - - m.addSeparator(); - - AddSetScrollZoomsByDefaultActionToMenu(&m); - - m.addSeparator(); - - ContextMenuEvent(m); - - if (!GetSelectedKeyframes().isEmpty()) { - m.addSeparator(); - - QAction* properties_action = m.addAction(tr("P&roperties")); - connect(properties_action, &QAction::triggered, this, &KeyframeViewBase::ShowKeyframePropertiesDialog); - } - - QAction* selected = m.exec(QCursor::pos()); - - // Process keyframe type changes - if (selected) { - if (selected == linear_key_action - || selected == bezier_key_action - || selected == hold_key_action) { - NodeKeyframe::Type new_type; - - if (selected == hold_key_action) { - new_type = NodeKeyframe::kHold; - } else if (selected == bezier_key_action) { - new_type = NodeKeyframe::kBezier; - } else { - new_type = NodeKeyframe::kLinear; - } - - MultiUndoCommand* command = new MultiUndoCommand(); - foreach (NodeKeyframe* item, GetSelectedKeyframes()) { - command->add_child(new KeyframeSetTypeCommand(item, new_type)); - } - Core::instance()->undo_stack()->push(command); - } - } -} - -void KeyframeViewBase::ShowKeyframePropertiesDialog() -{ - if (!GetSelectedKeyframes().isEmpty()) { - KeyframePropertiesDialog kd(GetSelectedKeyframes(), timebase(), this); - kd.exec(); - } -} - -void KeyframeViewBase::AutoSelectKeyTimeNeighbors() -{ - if (currently_autoselecting_ || IsYAxisEnabled()) { - return; - } - - // Prevents infinite loop - currently_autoselecting_ = true; - - QVector copy = GetSelectedKeyframes(); - foreach (NodeKeyframe *key, copy) { - rational key_time = key->time(); - - QVector keys = key->parent()->GetKeyframesAtTime(key->input(), key_time, key->element()); - - foreach (NodeKeyframe* k, keys) { - if (k != key) { - SelectKeyframe(k); - } - } - } - - currently_autoselecting_ = false; -} - -void KeyframeViewBase::Redraw() -{ - viewport()->update(); -} - -} diff --git a/app/widget/keyframeview/keyframeviewbase.h b/app/widget/keyframeview/keyframeviewbase.h deleted file mode 100644 index 2ce0673f7..000000000 --- a/app/widget/keyframeview/keyframeviewbase.h +++ /dev/null @@ -1,139 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef KEYFRAMEVIEWBASE_H -#define KEYFRAMEVIEWBASE_H - -#include "keyframeviewinputconnection.h" -#include "node/keyframe.h" -#include "widget/curvewidget/beziercontrolpointitem.h" -#include "widget/menu/menu.h" -#include "widget/timebased/timebasedview.h" -#include "widget/timebased/timebasedviewselectionmanager.h" -#include "widget/timetarget/timetarget.h" - -namespace olive { - -class KeyframeViewBase : public TimeBasedView, public TimeTargetObject -{ - Q_OBJECT -public: - KeyframeViewBase(QWidget* parent = nullptr); - - void DeleteSelected(); - - using ElementConnections = QVector; - using InputConnections = QVector; - using NodeConnections = QMap; - - NodeConnections AddKeyframesOfNode(Node* n); - - InputConnections AddKeyframesOfInput(Node *n, const QString &input); - - ElementConnections AddKeyframesOfElement(const NodeInput &input); - - KeyframeViewInputConnection *AddKeyframesOfTrack(const NodeKeyframeTrackReference &ref); - - void RemoveKeyframesOfTrack(KeyframeViewInputConnection *connection); - - void SelectAll(); - - void DeselectAll(); - - void Clear(); - - const QVector &GetSelectedKeyframes() const - { - return selection_manager_.GetSelectedObjects(); - } - -signals: - void Dragged(int current_x, int current_y); - -protected: - virtual void mousePressEvent(QMouseEvent *event) override; - virtual void mouseMoveEvent(QMouseEvent *event) override; - virtual void mouseReleaseEvent(QMouseEvent *event) override; - - virtual void drawForeground(QPainter *painter, const QRectF &rect) override; - - virtual void ScaleChangedEvent(const double& scale) override; - - virtual void TimeTargetChangedEvent(Node*) override; - - virtual void TimebaseChangedEvent(const rational &timebase) override; - - virtual void ContextMenuEvent(Menu &m); - - bool IsDragging() const - { - return dragging_; - } - - void SelectKeyframe(NodeKeyframe *key); - - void DeselectKeyframe(NodeKeyframe *key); - - bool IsKeyframeSelected(NodeKeyframe *key) const - { - return selection_manager_.IsSelected(key); - } - - rational GetAdjustedKeyframeTime(NodeKeyframe *key); - - double GetKeyframeSceneX(NodeKeyframe *key); - -private: - rational CalculateNewTimeFromScreen(const rational& old_time, double cursor_diff); - - static QPointF GenerateBezierControlPosition(const NodeKeyframe::BezierType mode, - const QPointF& start_point, - const QPointF& scaled_cursor_diff); - - QPointF GetScaledCursorPos(const QPointF &cursor_pos); - - QPointF drag_start_; - - BezierControlPointItem* dragging_bezier_point_; - QPointF dragging_bezier_point_start_; - QPointF dragging_bezier_point_opposing_start_; - - QVector tracks_; - - bool currently_autoselecting_; - - bool dragging_; - - TimeBasedViewSelectionManager selection_manager_; - -private slots: - void ShowContextMenu(); - - void ShowKeyframePropertiesDialog(); - - void AutoSelectKeyTimeNeighbors(); - - void Redraw(); - -}; - -} - -#endif // KEYFRAMEVIEWBASE_H diff --git a/app/widget/keyframeview/keyframeviewinputconnection.cpp b/app/widget/keyframeview/keyframeviewinputconnection.cpp index a8e9a35e5..b7b3c2dca 100644 --- a/app/widget/keyframeview/keyframeviewinputconnection.cpp +++ b/app/widget/keyframeview/keyframeviewinputconnection.cpp @@ -24,7 +24,7 @@ namespace olive { -KeyframeViewInputConnection::KeyframeViewInputConnection(const NodeKeyframeTrackReference &input, KeyframeViewBase *parent) : +KeyframeViewInputConnection::KeyframeViewInputConnection(const NodeKeyframeTrackReference &input, KeyframeView *parent) : QObject(parent), keyframe_view_(parent), input_(input), @@ -36,7 +36,9 @@ KeyframeViewInputConnection::KeyframeViewInputConnection(const NodeKeyframeTrack connect(n, &Node::KeyframeAdded, this, &KeyframeViewInputConnection::AddKeyframe); connect(n, &Node::KeyframeRemoved, this, &KeyframeViewInputConnection::RemoveKeyframe); - connect(n, &Node::KeyframeTimeChanged, this, &KeyframeViewInputConnection::RequireUpdate); + connect(n, &Node::KeyframeTimeChanged, this, &KeyframeViewInputConnection::KeyframeChanged); + connect(n, &Node::KeyframeTypeChanged, this, &KeyframeViewInputConnection::KeyframeChanged); + connect(n, &Node::KeyframeValueChanged, this, &KeyframeViewInputConnection::KeyframeChanged); } void KeyframeViewInputConnection::SetKeyframeY(int y) @@ -80,4 +82,11 @@ void KeyframeViewInputConnection::RemoveKeyframe(NodeKeyframe *key) } } +void KeyframeViewInputConnection::KeyframeChanged(NodeKeyframe *key) +{ + if (key->key_track_ref() == input_) { + emit RequireUpdate(); + } +} + } diff --git a/app/widget/keyframeview/keyframeviewinputconnection.h b/app/widget/keyframeview/keyframeviewinputconnection.h index b8f951d22..f10f37d2b 100644 --- a/app/widget/keyframeview/keyframeviewinputconnection.h +++ b/app/widget/keyframeview/keyframeviewinputconnection.h @@ -28,13 +28,13 @@ namespace olive { -class KeyframeViewBase; +class KeyframeView; class KeyframeViewInputConnection : public QObject { Q_OBJECT public: - KeyframeViewInputConnection(const NodeKeyframeTrackReference &input, KeyframeViewBase *parent); + KeyframeViewInputConnection(const NodeKeyframeTrackReference &input, KeyframeView *parent); const int &GetKeyframeY() const { @@ -66,7 +66,7 @@ signals: void RequireUpdate(); private: - KeyframeViewBase *keyframe_view_; + KeyframeView *keyframe_view_; NodeKeyframeTrackReference input_; @@ -81,6 +81,8 @@ private slots: void RemoveKeyframe(NodeKeyframe *key); + void KeyframeChanged(NodeKeyframe *key); + }; } diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index b2e219478..e21ead255 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -373,11 +373,9 @@ void NodeParamView::AddNode(Node *n, NodeParamViewContext *context) } } - // Set time target item->SetTimeTarget(GetTimeTarget()); - - // Set the timebase item->SetTimebase(timebase()); + item->SetTime(GetTime()); context->AddNode(item); @@ -526,7 +524,7 @@ void NodeParamView::UpdateElementY() { foreach (NodeParamViewContext *ctx, context_items_) { for (auto it=ctx->GetItems().cbegin(); it!=ctx->GetItems().cend(); it++) { - const KeyframeViewBase::NodeConnections &connections = it.value()->GetKeyframeConnections(); + const KeyframeView::NodeConnections &connections = it.value()->GetKeyframeConnections(); if (!connections.isEmpty()) { foreach (const QString& input, it.key()->inputs()) { @@ -538,10 +536,10 @@ void NodeParamView::UpdateElementY() int y = it.value()->GetElementY(ic); - const KeyframeViewBase::InputConnections &input_con = connections.value(input); + const KeyframeView::InputConnections &input_con = connections.value(input); int use_index = i + 1; if (use_index < input_con.size()) { - const KeyframeViewBase::ElementConnections &ele_con = input_con.at(ic.element()+1); + const KeyframeView::ElementConnections &ele_con = input_con.at(ic.element()+1); foreach (KeyframeViewInputConnection *track, ele_con) { track->SetKeyframeY(y); } diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 1e0aae242..9e45f55f7 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -36,7 +36,7 @@ #include "nodeparamviewwidgetbridge.h" #include "widget/clickablelabel/clickablelabel.h" #include "widget/collapsebutton/collapsebutton.h" -#include "widget/keyframeview/keyframeviewbase.h" +#include "widget/keyframeview/keyframeview.h" namespace olive { @@ -188,12 +188,12 @@ public: void SetInputChecked(const NodeInput &input, bool e); - const KeyframeViewBase::NodeConnections &GetKeyframeConnections() const + const KeyframeView::NodeConnections &GetKeyframeConnections() const { return keyframe_connections_; } - void SetKeyframeConnections(const KeyframeViewBase::NodeConnections &c) + void SetKeyframeConnections(const KeyframeView::NodeConnections &c) { keyframe_connections_ = c; } @@ -217,7 +217,7 @@ private: rational time_; - KeyframeViewBase::NodeConnections keyframe_connections_; + KeyframeView::NodeConnections keyframe_connections_; }; diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index e54350ef4..dda919eee 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -38,7 +38,6 @@ #include "widget/slider/floatslider.h" #include "widget/slider/integerslider.h" #include "widget/slider/rationalslider.h" -#include "widget/videoparamedit/videoparamedit.h" namespace olive { @@ -89,6 +88,8 @@ void NodeParamViewWidgetBridge::CreateWidgets() case NodeValue::kShaderJob: case NodeValue::kSampleJob: case NodeValue::kGenerateJob: + case NodeValue::kVideoParams: + case NodeValue::kAudioParams: break; case NodeValue::kInt: { @@ -156,19 +157,6 @@ void NodeParamViewWidgetBridge::CreateWidgets() connect(font_combobox, &QFontComboBox::currentFontChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); break; } - case NodeValue::kVideoParams: - { - VideoParamEdit* edit = new VideoParamEdit(); - edit->SetColorManager(input_.node()->project()->color_manager()); - widgets_.append(edit); - connect(edit, &VideoParamEdit::Changed, this, &NodeParamViewWidgetBridge::WidgetCallback); - break; - } - case NodeValue::kAudioParams: - { - // FIXME: Create audio param widget - break; - } } // Check all properties @@ -278,6 +266,8 @@ void NodeParamViewWidgetBridge::WidgetCallback() case NodeValue::kShaderJob: case NodeValue::kSampleJob: case NodeValue::kGenerateJob: + case NodeValue::kVideoParams: + case NodeValue::kAudioParams: break; case NodeValue::kInt: { @@ -388,15 +378,6 @@ void NodeParamViewWidgetBridge::WidgetCallback() SetInputValue(index, 0); break; } - case NodeValue::kVideoParams: - { - VideoParamEdit* edit = static_cast(sender()); - SetInputValue(QVariant::fromValue(edit->GetVideoParams()), 0); - break; - } - case NodeValue::kAudioParams: - // FIXME: No audio param widget yet - break; } } @@ -434,6 +415,8 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() case NodeValue::kShaderJob: case NodeValue::kSampleJob: case NodeValue::kGenerateJob: + case NodeValue::kVideoParams: + case NodeValue::kAudioParams: break; case NodeValue::kInt: { @@ -533,15 +516,6 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() cb->blockSignals(false); break; } - case NodeValue::kVideoParams: - { - VideoParamEdit* edit = static_cast(widgets_.first()); - edit->SetVideoParams(input_.GetValueAtTime(node_time).value()); - break; - } - case NodeValue::kAudioParams: - // FIXME: No audio param widget - break; } } @@ -796,15 +770,6 @@ void NodeParamViewWidgetBridge::PropertyChanged(const QString& input, const QStr ff->SetDirectoryMode(value.toBool()); } } - - // Parameters for video param objects - if (data_type == NodeValue::kVideoParams) { - VideoParamEdit* edit = static_cast(widgets_.first()); - - if (key == QStringLiteral("mask")) { - edit->SetParameterMask(value.toULongLong()); - } - } } void NodeParamViewWidgetBridge::InputDataTypeChanged(const QString &input, NodeValue::Type type) diff --git a/app/widget/nodetreeview/nodetreeview.cpp b/app/widget/nodetreeview/nodetreeview.cpp index a1ee354a3..a2f8ec9df 100644 --- a/app/widget/nodetreeview/nodetreeview.cpp +++ b/app/widget/nodetreeview/nodetreeview.cpp @@ -114,6 +114,8 @@ void NodeTreeView::SetNodes(const QVector &nodes) } else { delete node_item; } + + node_item->setExpanded(true); } } diff --git a/app/widget/timebased/timebasedview.h b/app/widget/timebased/timebasedview.h index 247948b61..f6f46d285 100644 --- a/app/widget/timebased/timebasedview.h +++ b/app/widget/timebased/timebasedview.h @@ -53,6 +53,10 @@ public: return dragging_playhead_; } + // To be called only by selection managers + virtual void SelectionManagerSelectEvent(void *obj){} + virtual void SelectionManagerDeselectEvent(void *obj){} + public slots: void SetTime(const rational &time); diff --git a/app/widget/timebased/timebasedviewselectionmanager.h b/app/widget/timebased/timebasedviewselectionmanager.h index d1f53d7d6..19d61e203 100644 --- a/app/widget/timebased/timebasedviewselectionmanager.h +++ b/app/widget/timebased/timebasedviewselectionmanager.h @@ -23,6 +23,7 @@ #include #include +#include #include #include "common/rational.h" @@ -36,7 +37,8 @@ class TimeBasedViewSelectionManager { public: TimeBasedViewSelectionManager(TimeBasedView *view) : - view_(view) + view_(view), + rubberband_(nullptr) {} void ClearDrawnObjects() @@ -51,6 +53,8 @@ public: bool Select(T *key) { + Q_ASSERT(key); + if (!IsSelected(key)) { selected_.append(key); return true; @@ -61,6 +65,8 @@ public: bool Deselect(T *key) { + Q_ASSERT(key); + return selected_.removeOne(key); } @@ -84,35 +90,48 @@ public: timebase_ = tb; } + T *GetObjectAtPoint(const QPointF &scene_pt) + { + foreach (const DrawnObject &kp, drawn_objects_) { + if (kp.second.contains(scene_pt)) { + return kp.first; + } + } + + return nullptr; + } + + T *GetObjectAtPoint(const QPoint &pt) + { + return GetObjectAtPoint(view_->mapToScene(pt)); + } + T *MousePress(QMouseEvent *event) { T *key_under_cursor = nullptr; - if (event->button() == Qt::LeftButton) { + if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton) { // See if there's a keyframe in this position - QPointF scene_pos = view_->mapToScene(event->pos()); - foreach (const DrawnObject &kp, drawn_objects_) { - if (kp.second.contains(scene_pos)) { - key_under_cursor = kp.first; - break; - } - } + key_under_cursor = GetObjectAtPoint(event->pos()); bool holding_shift = event->modifiers() & Qt::ShiftModifier; - if (IsSelected(key_under_cursor)) { - if (holding_shift) { - // If selected and holding shift, de-select this item but do nothing else - Deselect(key_under_cursor); - } - } else { + if (!key_under_cursor || !IsSelected(key_under_cursor)) { if (!holding_shift) { // If not already selecting and not holding shift, clear the current selection ClearSelection(); } // Add item to selection, either nothing if shift wasn't held, or the existing selection - Select(key_under_cursor); + if (key_under_cursor) { + Select(key_under_cursor); + view_->SelectionManagerSelectEvent(key_under_cursor); + } + } else if (holding_shift) { + // If selected and holding shift, de-select this item but do nothing else + Deselect(key_under_cursor); + view_->SelectionManagerDeselectEvent(key_under_cursor); + key_under_cursor = nullptr; } } @@ -185,6 +204,51 @@ public: for (int i=0; iadd_child(new SetTimeCommand(selected_.at(i), selected_.at(i)->time(), dragging_.at(i).time)); } + + dragging_.clear(); + } + + void RubberBandStart(QMouseEvent *event) + { + if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton) { + rubberband_start_ = event->pos(); + + rubberband_ = new QRubberBand(QRubberBand::Rectangle, view_); + rubberband_->setGeometry(QRect(rubberband_start_.x(), rubberband_start_.y(), 0, 0)); + rubberband_->show(); + + rubberband_preselected_ = selected_; + } + } + + void RubberBandMove(QMouseEvent *event) + { + if (IsRubberBanding()) { + QRect band_rect = QRect(rubberband_start_, event->pos()).normalized(); + rubberband_->setGeometry(band_rect); + + QRectF scene_rect = view_->mapToScene(band_rect).boundingRect(); + + selected_ = rubberband_preselected_; + foreach (const DrawnObject &kp, drawn_objects_) { + if (scene_rect.intersects(kp.second)) { + Select(kp.first); + } + } + } + } + + void RubberBandStop() + { + if (IsRubberBanding()) { + delete rubberband_; + rubberband_ = nullptr; + } + } + + bool IsRubberBanding() const + { + return rubberband_; } private: @@ -249,6 +313,10 @@ private: rational timebase_; + QRubberBand *rubberband_; + QPoint rubberband_start_; + QVector rubberband_preselected_; + }; } diff --git a/app/widget/videoparamedit/CMakeLists.txt b/app/widget/videoparamedit/CMakeLists.txt deleted file mode 100644 index bd799b34d..000000000 --- a/app/widget/videoparamedit/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -# Olive - Non-Linear Video Editor -# Copyright (C) 2021 Olive Team -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -set(OLIVE_SOURCES - ${OLIVE_SOURCES} - widget/videoparamedit/videoparamedit.cpp - widget/videoparamedit/videoparamedit.h - PARENT_SCOPE -) diff --git a/app/widget/videoparamedit/videoparamedit.cpp b/app/widget/videoparamedit/videoparamedit.cpp deleted file mode 100644 index 13ddfc825..000000000 --- a/app/widget/videoparamedit/videoparamedit.cpp +++ /dev/null @@ -1,391 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "videoparamedit.h" - -#include - -namespace olive { - -VideoParamEdit::VideoParamEdit(QWidget* parent) : - QWidget(parent), - color_manager_(nullptr), - mask_(0) -{ - QGridLayout* layout = new QGridLayout(this); - - layout->setMargin(0); - - int row = 0; - - // Enabled - enabled_lbl_ = new QLabel(tr("Enabled:")); - layout->addWidget(enabled_lbl_, row, 0); - enabled_box_ = new QCheckBox(); - connect(enabled_box_, &QCheckBox::clicked, this, &VideoParamEdit::Changed); - layout->addWidget(enabled_box_, row, 1); - - row++; - - // Width - width_lbl_ = new QLabel(tr("Width:")); - layout->addWidget(width_lbl_, row, 0); - - width_slider_ = new IntegerSlider(); - width_slider_->SetMinimum(1); - width_slider_->SetMaximum(32768); - connect(width_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed); - layout->addWidget(width_slider_, row, 1); - - row++; - - // Height - height_lbl_ = new QLabel(tr("Height:")); - layout->addWidget(height_lbl_, row, 0); - - height_slider_ = new IntegerSlider(); - height_slider_->SetMinimum(1); - height_slider_->SetMaximum(32768); - connect(height_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed); - layout->addWidget(height_slider_, row, 1); - - row++; - - // Depth - depth_lbl_ = new QLabel(tr("Depth:")); - layout->addWidget(depth_lbl_, row, 0); - - depth_slider_ = new IntegerSlider(); - depth_slider_->SetMinimum(1); - depth_slider_->SetMaximum(32768); - connect(depth_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed); - layout->addWidget(depth_slider_, row, 1); - - row++; - - // Pixel Format - format_lbl_ = new QLabel(tr("Format:")); - layout->addWidget(format_lbl_, row, 0); - format_combobox_ = new PixelFormatComboBox(true); - connect(format_combobox_, static_cast(&PixelFormatComboBox::currentIndexChanged), this, &VideoParamEdit::Changed); - layout->addWidget(format_combobox_, row, 1); - - row++; - - // Frame Rate - frame_rate_lbl_ = new QLabel(tr("Frame Rate:")); - layout->addWidget(frame_rate_lbl_, row, 0); - - frame_rate_combobox_ = new FrameRateComboBox(); - connect(frame_rate_combobox_, &FrameRateComboBox::FrameRateChanged, this, &VideoParamEdit::Changed); - layout->addWidget(frame_rate_combobox_, row, 1); - - frame_rate_slider_ = new RationalSlider(); - frame_rate_slider_->SetMinimum(0); - frame_rate_slider_->SetDecimalPlaces(3); - frame_rate_slider_->SetAutoTrimDecimalPlaces(true); - frame_rate_slider_->SetTimebase(rational(1, 1000)); // Drag interval - frame_rate_slider_->DisableDisplayType(RationalSlider::kTime); - connect(frame_rate_slider_, &RationalSlider::ValueChanged, this, &VideoParamEdit::Changed); - layout->addWidget(frame_rate_slider_, row, 1); - - row++; - - // Pixel Aspect Ratio - pixel_aspect_lbl_ = new QLabel(tr("Pixel Aspect Ratio:")); - layout->addWidget(pixel_aspect_lbl_, row, 0); - - pixel_aspect_combobox_ = new PixelAspectRatioComboBox(); - connect(pixel_aspect_combobox_, static_cast(&PixelAspectRatioComboBox::currentIndexChanged), this, &VideoParamEdit::Changed); - layout->addWidget(pixel_aspect_combobox_, row, 1); - - row++; - - // Interlacing - interlaced_lbl_ = new QLabel(tr("Interlacing:")); - layout->addWidget(interlaced_lbl_, row, 0); - - interlaced_combobox_ = new InterlacedComboBox(); - connect(interlaced_combobox_, static_cast(&InterlacedComboBox::currentIndexChanged), this, &VideoParamEdit::Changed); - layout->addWidget(interlaced_combobox_, row, 1); - - row++; - - // Channel Count - channel_count_lbl_ = new QLabel(tr("Channel Count:")); - layout->addWidget(channel_count_lbl_, row, 0); - - channel_count_combobox_ = new QComboBox(); - channel_count_combobox_->addItem(tr("RGB"), VideoParams::kRGBChannelCount); - channel_count_combobox_->addItem(tr("RGBA"), VideoParams::kRGBAChannelCount); - connect(channel_count_combobox_, static_cast(&QComboBox::currentIndexChanged), this, &VideoParamEdit::Changed); - layout->addWidget(channel_count_combobox_, row, 1); - - row++; - - // Divider - divider_lbl_ = new QLabel(tr("Divider:")); - layout->addWidget(divider_lbl_, row, 0); - - divider_combobox_ = new VideoDividerComboBox(); - connect(divider_combobox_, static_cast(&VideoDividerComboBox::currentIndexChanged), this, &VideoParamEdit::Changed); - layout->addWidget(divider_combobox_, row, 1); - - row++; - - // Stream Index - stream_index_lbl_ = new QLabel(tr("Stream Index:")); - layout->addWidget(stream_index_lbl_, row, 0); - stream_index_slider_ = new IntegerSlider(); - stream_index_slider_->SetMinimum(0); - connect(stream_index_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed); - layout->addWidget(stream_index_slider_, row, 1); - - row++; - - // Video type - video_type_lbl_ = new QLabel(tr("Video Type:")); - layout->addWidget(video_type_lbl_, row, 0); - video_type_combobox_ = new QComboBox(); - video_type_combobox_->addItem(tr("Video"), VideoParams::kVideoTypeVideo); - video_type_combobox_->addItem(tr("Still"), VideoParams::kVideoTypeStill); - video_type_combobox_->addItem(tr("Image Sequence"), VideoParams::kVideoTypeImageSequence); - connect(video_type_combobox_, static_cast(&QComboBox::currentIndexChanged), this, &VideoParamEdit::Changed); - layout->addWidget(video_type_combobox_, row, 1); - - row++; - - // Start time (for image sequences) - start_time_lbl_ = new QLabel(tr("Start Time")); - layout->addWidget(start_time_lbl_, row, 0); - - start_time_slider_ = new IntegerSlider(); - start_time_slider_->SetMinimum(0); - connect(start_time_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed); - layout->addWidget(start_time_slider_, row, 1); - - row++; - - // End time (for image sequences) - end_time_lbl_ = new QLabel(tr("End Time")); - layout->addWidget(end_time_lbl_, row, 0); - - end_time_slider_ = new IntegerSlider(); - end_time_slider_->SetMinimum(0); - connect(end_time_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed); - layout->addWidget(end_time_slider_, row, 1); - - row++; - - // Premultiplied alpha - premultiplied_alpha_lbl_ = new QLabel(tr("Premultiplied Alpha")); - layout->addWidget(premultiplied_alpha_lbl_, row, 0); - - premultiplied_alpha_box_ = new QCheckBox(); - connect(premultiplied_alpha_box_, &QCheckBox::clicked, this, &VideoParamEdit::Changed); - layout->addWidget(premultiplied_alpha_box_, row, 1); - - row++; - - // Colorspace - colorspace_lbl_ = new QLabel(tr("Colorspace")); - layout->addWidget(colorspace_lbl_, row, 0); - - colorspace_combobox_ = new QComboBox(); - connect(colorspace_combobox_, static_cast(&QComboBox::currentIndexChanged), this, &VideoParamEdit::Changed); - layout->addWidget(colorspace_combobox_, row, 1); -} - -void VideoParamEdit::SetParameterMask(uint64_t mask) -{ - mask_ = mask; - - width_lbl_->setVisible(mask & kWidthHeight); - width_slider_->setVisible(mask & kWidthHeight); - height_lbl_->setVisible(mask & kWidthHeight); - height_slider_->setVisible(mask & kWidthHeight); - - depth_lbl_->setVisible(mask & kDepth); - depth_slider_->setVisible(mask & kDepth); - - frame_rate_lbl_->setVisible(mask & kFrameRate); - frame_rate_combobox_->setVisible((mask & kFrameRate) && !(mask & kFrameRateIsArbitrary)); - frame_rate_slider_->setVisible((mask & kFrameRate) && (mask & kFrameRateIsArbitrary)); - - pixel_aspect_lbl_->setVisible(mask & kPixelAspect); - pixel_aspect_combobox_->setVisible(mask & kPixelAspect); - - interlaced_lbl_->setVisible(mask & kInterlacing); - interlaced_combobox_->setVisible(mask & kInterlacing); - - enabled_lbl_->setVisible(mask & kEnabled); - enabled_box_->setVisible(mask & kEnabled); - - format_lbl_->setVisible(mask & kFormat); - format_combobox_->setVisible(mask & kFormat); - - channel_count_lbl_->setVisible(mask & kChannelCount); - channel_count_combobox_->setVisible(mask & kChannelCount); - - divider_lbl_->setVisible(mask & kDivider); - divider_combobox_->setVisible(mask & kDivider); - - stream_index_lbl_->setVisible(mask & kStreamIndex); - stream_index_slider_->setVisible(mask & kStreamIndex); - - video_type_lbl_->setVisible(mask & kIsImageSequence); - video_type_combobox_->setVisible(mask & kIsImageSequence); - - start_time_lbl_->setVisible(mask & kStartTime); - start_time_slider_->setVisible(mask & kStartTime); - - end_time_lbl_->setVisible(mask & kEndTime); - end_time_slider_->setVisible(mask & kEndTime); - - premultiplied_alpha_lbl_->setVisible(mask & kPremultipliedAlpha); - premultiplied_alpha_box_->setVisible(mask & kPremultipliedAlpha); - - colorspace_lbl_->setVisible(mask & kColorspace); - colorspace_combobox_->setVisible(mask & kColorspace); -} - -VideoParams VideoParamEdit::GetVideoParams() const -{ - VideoParams p; - - p.set_enabled(enabled_box_->isChecked()); - p.set_width(width_slider_->GetValue()); - p.set_height(height_slider_->GetValue()); - p.set_depth(depth_slider_->GetValue()); - - { - rational using_frame_rate; - - if (mask_ & kFrameRateIsArbitrary) { - using_frame_rate = frame_rate_slider_->GetValue(); - } else { - using_frame_rate = frame_rate_combobox_->GetFrameRate(); - } - - p.set_frame_rate(using_frame_rate); - if (mask_ & kFrameRateIsNotTimebase) { - // Frame rate editor will only edit the frame rate - p.set_time_base(timebase_temp_); - } else { - p.set_time_base(using_frame_rate.flipped()); - } - } - - p.set_pixel_aspect_ratio(pixel_aspect_combobox_->GetPixelAspectRatio()); - p.set_interlacing(interlaced_combobox_->GetInterlaceMode()); - p.set_format(format_combobox_->GetPixelFormat()); - p.set_channel_count(channel_count_combobox_->currentData().toInt()); - p.set_divider(divider_combobox_->GetDivider()); - p.set_stream_index(stream_index_slider_->GetValue()); - p.set_video_type(static_cast(video_type_combobox_->currentData().toInt())); - p.set_start_time(start_time_slider_->GetValue()); - p.set_duration(end_time_slider_->GetValue() - start_time_slider_->GetValue() + 1); - p.set_premultiplied_alpha(premultiplied_alpha_box_->isChecked()); - p.set_colorspace(colorspace_combobox_->currentData().toString()); - - return p; -} - -void VideoParamEdit::SetVideoParams(const VideoParams &p) -{ - blockSignals(true); - - enabled_box_->setChecked(p.enabled()); - width_slider_->SetValue(p.width()); - height_slider_->SetValue(p.height()); - depth_slider_->SetValue(p.depth()); - - frame_rate_combobox_->SetFrameRate(p.frame_rate()); - frame_rate_slider_->SetValue(p.frame_rate()); - timebase_temp_ = p.time_base(); - - pixel_aspect_combobox_->SetPixelAspectRatio(p.pixel_aspect_ratio()); - interlaced_combobox_->SetInterlaceMode(p.interlacing()); - format_combobox_->SetPixelFormat(p.format()); - SetChannelCount(p.channel_count()); - divider_combobox_->SetDivider(p.divider()); - stream_index_slider_->SetValue(p.stream_index()); - SetVideoTypeComboBox(p.video_type()); - start_time_slider_->SetValue(p.start_time()); - end_time_slider_->SetValue(p.start_time() + p.duration() - 1); - premultiplied_alpha_box_->setChecked(p.premultiplied_alpha()); - - if (color_manager_) { - // Assume colorspace box has been populated correctly - for (int i=0; icount(); i++) { - if (colorspace_combobox_->itemData(i).toString() == p.colorspace()) { - colorspace_combobox_->setCurrentIndex(i); - break; - } - } - } else { - // Box is empty, fill with single option so that it gets preserved in GetVideoParams() - colorspace_combobox_->clear(); - colorspace_combobox_->addItem(p.colorspace(), p.colorspace()); - } - - blockSignals(false); -} - -void VideoParamEdit::SetColorManager(ColorManager *cm) -{ - color_manager_ = cm; - - // Re-populate colorspace combobox - colorspace_combobox_->clear(); - - if (color_manager_) { - // Add default colorspace - colorspace_combobox_->addItem(tr("Default (%1)").arg(color_manager_->GetDefaultInputColorSpace()), QString()); - - // Add remaining - QStringList spaces = color_manager_->ListAvailableColorspaces(); - foreach (const QString& s, spaces) { - colorspace_combobox_->addItem(s, s); - } - } -} - -void VideoParamEdit::SetChannelCount(int count) -{ - for (int i=0; icount(); i++) { - if (channel_count_combobox_->itemData(i).toInt() == count) { - channel_count_combobox_->setCurrentIndex(i); - break; - } - } -} - -void VideoParamEdit::SetVideoTypeComboBox(VideoParams::Type type) -{ - for (int i=0; icount(); i++) { - if (video_type_combobox_->itemData(i).toInt() == type) { - video_type_combobox_->setCurrentIndex(i); - break; - } - } -} - -} diff --git a/app/widget/videoparamedit/videoparamedit.h b/app/widget/videoparamedit/videoparamedit.h deleted file mode 100644 index 0aa372869..000000000 --- a/app/widget/videoparamedit/videoparamedit.h +++ /dev/null @@ -1,182 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef VIDEOPARAMEDIT_H -#define VIDEOPARAMEDIT_H - -#include -#include -#include - -#include "node/color/colormanager/colormanager.h" -#include "render/videoparams.h" -#include "widget/slider/integerslider.h" -#include "widget/slider/rationalslider.h" -#include "widget/standardcombos/frameratecombobox.h" -#include "widget/standardcombos/interlacedcombobox.h" -#include "widget/standardcombos/pixelaspectratiocombobox.h" -#include "widget/standardcombos/pixelformatcombobox.h" -#include "widget/standardcombos/videodividercombobox.h" - -namespace olive { - -class VideoParamEdit : public QWidget -{ - Q_OBJECT -public: - VideoParamEdit(QWidget* parent = nullptr); - - enum ParamMask { - kNone = 0x0, - kEnabled = 0x1, - kWidthHeight = 0x2, - kDepth = 0x4, - kFrameRate = 0x8, - kFormat = 0x10, - kChannelCount = 0x20, - kPixelAspect = 0x40, - kInterlacing = 0x80, - kDivider = 0x100, - kStreamIndex = 0x200, - kIsImageSequence = 0x400, - kStartTime = 0x800, - kEndTime = 0x1000, - kPremultipliedAlpha = 0x2000, - kColorspace = 0x4000, - kFrameRateIsNotTimebase = 0x8000, - kFrameRateIsArbitrary = 0x10000 - }; - - void SetParameterMask(uint64_t mask); - - VideoParams GetVideoParams() const; - void SetVideoParams(const VideoParams& p); - - /** - * @brief Set pointer to ColorManager - * - * Call this before calling SetVideoParams because it'll populate the colorspace list so it - * can correctly be chosen from in the UI. - */ - void SetColorManager(ColorManager* cm); - - int GetWidth() const - { - return width_slider_->GetValue(); - } - - void SetWidth(int w) - { - width_slider_->SetValue(w); - } - - int GetHeight() const - { - return height_slider_->GetValue(); - } - - void SetHeight(int h) - { - height_slider_->SetValue(h); - } - - rational GetFrameRate() const - { - return frame_rate_combobox_->GetFrameRate(); - } - - void SetFrameRate(const rational& r) - { - frame_rate_combobox_->SetFrameRate(r); - } - - rational GetPixelAspectRatio() const - { - return pixel_aspect_combobox_->GetPixelAspectRatio(); - } - - void SetPixelAspectRatio(const rational& r) - { - pixel_aspect_combobox_->SetPixelAspectRatio(r); - } - - VideoParams::Interlacing GetInterlaceMode() const - { - return interlaced_combobox_->GetInterlaceMode(); - } - - void SetInterlaceMode(VideoParams::Interlacing i) - { - interlaced_combobox_->SetInterlaceMode(i); - } - -signals: - void Changed(); - -private: - void SetChannelCount(int count); - - void SetVideoTypeComboBox(VideoParams::Type type); - - QLabel* enabled_lbl_; - QCheckBox* enabled_box_; - QLabel* width_lbl_; - IntegerSlider* width_slider_; - QLabel* height_lbl_; - IntegerSlider* height_slider_; - QLabel* depth_lbl_; - IntegerSlider* depth_slider_; - QLabel* frame_rate_lbl_; - FrameRateComboBox* frame_rate_combobox_; - RationalSlider* frame_rate_slider_; - QLabel* pixel_aspect_lbl_; - PixelAspectRatioComboBox* pixel_aspect_combobox_; - QLabel* interlaced_lbl_; - InterlacedComboBox* interlaced_combobox_; - QLabel* format_lbl_; - PixelFormatComboBox* format_combobox_; - QLabel* channel_count_lbl_; - QComboBox* channel_count_combobox_; - QLabel* divider_lbl_; - VideoDividerComboBox* divider_combobox_; - QLabel* stream_index_lbl_; - IntegerSlider* stream_index_slider_; - QLabel* video_type_lbl_; - QComboBox* video_type_combobox_; - QLabel* start_time_lbl_; - IntegerSlider* start_time_slider_; - QLabel* end_time_lbl_; - IntegerSlider* end_time_slider_; - QLabel* premultiplied_alpha_lbl_; - QCheckBox* premultiplied_alpha_box_; - QLabel* colorspace_lbl_; - QComboBox* colorspace_combobox_; - - ColorManager* color_manager_; - - rational timebase_temp_; - - uint64_t mask_; - -}; - -} - -#endif // VIDEOPARAMEDIT_H diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 2cbf094d0..bceb6a935 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -97,6 +97,7 @@ MainWindow::MainWindow(QWidget *parent) : node_panel_->Select(target, true); }); connect(param_panel_, &ParamPanel::FocusedNodeChanged, sequence_viewer_panel_, &ViewerPanel::SetGizmos); + connect(param_panel_, &ParamPanel::FocusedNodeChanged, curve_panel_, &CurvePanel::SetNode); // Connect time signals together connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, param_panel_, &ParamPanel::SetTime); From e902507e47ce310745efebc37211db4674bc35fc Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 15 Dec 2021 19:49:10 -0800 Subject: [PATCH 49/94] core: don't open cancelled project load --- app/core.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index 48830651a..c79d11a0a 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -482,19 +482,21 @@ bool Core::AddOpenProjectFromTask(Task *task) { ProjectLoadBaseTask* load_task = static_cast(task); - Project* project = load_task->GetLoadedProject(); - MainWindowLayoutInfo layout = load_task->GetLoadedLayout(); + if (!load_task->IsCancelled()) { + Project* project = load_task->GetLoadedProject(); + MainWindowLayoutInfo layout = load_task->GetLoadedLayout(); - if (ValidateFootageInLoadedProject(project, load_task->GetFilenameProjectWasSavedAs())) { - AddOpenProject(project); - main_window_->LoadLayout(layout); + if (ValidateFootageInLoadedProject(project, load_task->GetFilenameProjectWasSavedAs())) { + AddOpenProject(project); + main_window_->LoadLayout(layout); - return true; - } else { - delete project; - - return false; + return true; + } else { + delete project; + } } + + return false; } void Core::ImportTaskComplete(Task* task) From 619de01db4ef62247e1c052220bb6269b23c7536 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri, 17 Dec 2021 17:07:59 -0800 Subject: [PATCH 50/94] refined curve view --- app/widget/curvewidget/curveview.cpp | 218 +++++++++++------- app/widget/curvewidget/curveview.h | 14 +- app/widget/curvewidget/curvewidget.cpp | 79 +++---- app/widget/curvewidget/curvewidget.h | 8 +- app/widget/keyframeview/keyframeview.cpp | 10 + app/widget/keyframeview/keyframeview.h | 2 + .../keyframeviewinputconnection.cpp | 8 + .../keyframeviewinputconnection.h | 4 + app/widget/nodeparamview/nodeparamview.cpp | 7 +- .../nodeparamviewwidgetbridge.cpp | 42 +--- app/widget/nodetreeview/nodetreeview.cpp | 43 ++-- app/widget/nodetreeview/nodetreeview.h | 9 + app/widget/slider/floatslider.cpp | 88 ++++--- app/widget/slider/floatslider.h | 4 + app/widget/timebased/timebasedview.h | 11 +- .../timebased/timebasedviewselectionmanager.h | 15 +- 16 files changed, 309 insertions(+), 253 deletions(-) diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index 8fee9d725..e756d7376 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -30,7 +30,6 @@ #include "common/qtutils.h" #include "widget/keyframeview/keyframeviewundo.h" #include "widget/nodeparamview/nodeparamviewundo.h" -#include "widget/slider/floatslider.h" namespace olive { @@ -61,6 +60,9 @@ void CurveView::ConnectInput(const NodeKeyframeTrackReference& ref) track_con->SetBrush(keyframe_colors_.value(ref)); track_connections_.insert(ref, track_con); + // Signal to CurveWidget to update its bezier/linear/hold buttons if a key type changes + connect(track_con, &KeyframeViewInputConnection::TypeChanged, this, &CurveView::SelectionChanged); + // Append to the list connected_inputs_.append(ref); } @@ -90,13 +92,6 @@ void CurveView::SelectKeyframesOfInput(const NodeKeyframeTrackReference& ref) } } -void CurveView::ZoomToFitInput(const NodeKeyframeTrackReference& ref) -{ - if (KeyframeViewInputConnection *con = track_connections_.value(ref)) { - ZoomToFitInternal(con->GetKeyframes()); - } -} - void CurveView::SetKeyframeTrackColor(const NodeKeyframeTrackReference &ref, const QColor &color) { // Insert color into hashmap @@ -265,8 +260,28 @@ void CurveView::ContextMenuEvent(Menu &m) void CurveView::SceneRectUpdateEvent(QRectF &r) { - r.setTop(r.top() - this->height()); - r.setBottom(r.bottom() + this->height()); + double min_val, max_val; + bool got_val = false; + + foreach (KeyframeViewInputConnection *con, track_connections_) { + foreach (NodeKeyframe *key, con->GetKeyframes()) { + qreal key_y = GetItemYFromKeyframeValue(key); + + if (got_val) { + min_val = qMin(key_y, min_val); + max_val = qMax(key_y, max_val); + } else { + min_val = key_y; + max_val = key_y; + got_val = true; + } + } + } + + if (got_val) { + r.setTop(min_val - this->height()); + r.setBottom(max_val + this->height()); + } } qreal CurveView::GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key) @@ -392,7 +407,8 @@ void CurveView::KeyframeDragStart(QMouseEvent *event) { drag_keyframe_values_.resize(GetSelectedKeyframes().size()); for (int i=0; ivalue(); + NodeKeyframe *key = GetSelectedKeyframes().at(i); + drag_keyframe_values_[i] = key->value(); } drag_start_ = mapToScene(event->pos()); @@ -401,7 +417,11 @@ void CurveView::KeyframeDragStart(QMouseEvent *event) void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip) { if (event->modifiers() & Qt::ShiftModifier) { - // Lock to X axis only + // Lock to X axis only and set original values on all keys + for (int i=0; iset_value(drag_keyframe_values_.at(i)); + } return; } @@ -412,10 +432,11 @@ void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip) for (int i=0; iparent(); - double original_val = drag_keyframe_values_.at(i).toDouble(); + double original_val = FloatSlider::TransformValueToDisplay(drag_keyframe_values_.at(i).toDouble(), display); const QString& input = key->input(); - double new_val = original_val - scaled_diff; + double new_val = FloatSlider::TransformDisplayToValue(original_val - scaled_diff, display); double limited = new_val; if (node->HasInputProperty(input, QStringLiteral("min"))) { @@ -434,23 +455,18 @@ void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip) // Set values for (int i=0; iset_value(drag_keyframe_values_.at(i).toDouble() - scaled_diff); + FloatSlider::DisplayType display = GetFloatDisplayTypeFromKeyframe(key); + key->set_value(FloatSlider::TransformDisplayToValue(FloatSlider::TransformValueToDisplay(drag_keyframe_values_.at(i).toDouble(), display) - scaled_diff, display)); } NodeKeyframe *tip_item = GetSelectedKeyframes().first(); - FloatSlider::DisplayType display_type = FloatSlider::kNormal; - Node* initial_drag_input = tip_item->parent(); - const QString& initial_drag_input_id = tip_item->input(); - if (initial_drag_input->HasInputProperty(initial_drag_input_id, QStringLiteral("view"))) { - display_type = static_cast(initial_drag_input->GetInputProperty(initial_drag_input_id, QStringLiteral("view")).toInt()); - } bool ok; double num_value = tip_item->value().toDouble(&ok); if (ok) { tip = QStringLiteral("%1\n"); - tip.append(FloatSlider::ValueToString(num_value, display_type, 2, true)); + tip.append(FloatSlider::ValueToString(num_value + GetOffsetFromKeyframe(tip_item), GetFloatDisplayTypeFromKeyframe(tip_item), 2, true)); } } @@ -458,7 +474,9 @@ void CurveView::KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *comman { for (int i=0; iadd_child(new NodeParamSetKeyframeValueCommand(k, k->value(), drag_keyframe_values_.at(i))); + if (!qFuzzyCompare(k->value().toDouble(), drag_keyframe_values_.at(i).toDouble())) { + command->add_child(new NodeParamSetKeyframeValueCommand(k, k->value(), drag_keyframe_values_.at(i))); + } } } @@ -488,53 +506,88 @@ QPointF CurveView::GetScaledCursorPos(const QPointF &cursor_pos) cursor_pos.y() / GetYScale()); } -void CurveView::ZoomToFitInternal(const QVector &keys) +void CurveView::ZoomToFitInternal(bool selected_only) { - if (keys.isEmpty()) { - // Prevent scaling to DBL_MIN/DBL_MAX - return; + bool got_val = false; + + rational min_time, max_time; + double min_val, max_val; + + foreach (KeyframeViewInputConnection *con, track_connections_) { + foreach (NodeKeyframe *key, con->GetKeyframes()) { + if (!selected_only || IsKeyframeSelected(key)) { + rational transformed_time = GetAdjustedTime(key->parent(), + GetTimeTarget(), + key->time(), + false); + + qreal key_y = GetUnscaledItemYFromKeyframeValue(key); + + if (got_val) { + min_time = qMin(transformed_time, min_time); + max_time = qMax(transformed_time, max_time); + + min_val = qMin(key_y, min_val); + max_val = qMax(key_y, max_val); + } else { + min_time = transformed_time; + max_time = transformed_time; + + min_val = key_y; + max_val = key_y; + + got_val = true; + } + } + } } - rational min_time = RATIONAL_MAX; - rational max_time = RATIONAL_MIN; + // Prevent scaling if no keyframes were found + if (got_val) { + QRectF desired(QPointF(min_time.toDouble(), min_val), QPointF(max_time.toDouble(), max_val)); - double min_val = DBL_MAX; - double max_val = DBL_MIN; + const double scale_divider = 0.5; + double scale_half_divider = scale_divider*0.5; - foreach (NodeKeyframe* key, keys) { - rational transformed_time = GetAdjustedTime(key->parent(), - GetTimeTarget(), - key->time(), - false); + double new_x_scale = viewport()->width() / desired.width() * scale_divider; + double new_y_scale; - min_time = qMin(transformed_time, min_time); - max_time = qMax(transformed_time, max_time); + if (qFuzzyIsNull(desired.height())) { + // Catch divide by zero + new_y_scale = 1.0; + scale_half_divider = 0.5; + } else { + // Use height as normal + new_y_scale = viewport()->height() / desired.height() * scale_divider; + } - min_val = qMin(key->value().toDouble(), min_val); - max_val = qMax(key->value().toDouble(), max_val); + emit ScaleChanged(new_x_scale); + SetYScale(new_y_scale); + + UpdateSceneRect(); + + int sb_x = desired.left() * new_x_scale - viewport()->width() * scale_half_divider; + QMetaObject::invokeMethod(horizontalScrollBar(), "setValue", Qt::QueuedConnection, Q_ARG(int, sb_x)); + + int sb_y = desired.top() * new_y_scale - viewport()->height() * scale_half_divider; + QMetaObject::invokeMethod(verticalScrollBar(), "setValue", Qt::QueuedConnection, Q_ARG(int, sb_y)); } - - double time_range = max_time.toDouble() - min_time.toDouble(); - double new_x_scale = CalculateScaleFromDimensions(this->width(), time_range); - double new_y_scale = CalculateScaleFromDimensions(this->height(), max_val - min_val); - - emit ScaleChanged(new_x_scale); - SetYScale(new_y_scale); - - QMetaObject::invokeMethod(horizontalScrollBar(), "setValue", Qt::QueuedConnection, - Q_ARG(int, TimeToScene(min_time) - CalculatePaddingFromDimensionScale(this->width()))); - QMetaObject::invokeMethod(verticalScrollBar(), "setValue", Qt::QueuedConnection, - Q_ARG(int, GetItemYFromKeyframeValue(max_val) - CalculatePaddingFromDimensionScale(this->height()))); } qreal CurveView::GetItemYFromKeyframeValue(NodeKeyframe *key) { - return GetItemYFromKeyframeValue(key->value().toDouble()); + return GetUnscaledItemYFromKeyframeValue(key) * GetYScale(); } -qreal CurveView::GetItemYFromKeyframeValue(double value) +qreal CurveView::GetUnscaledItemYFromKeyframeValue(NodeKeyframe *key) { - return -value * GetYScale(); + double val = key->value().toDouble(); + + val = FloatSlider::TransformValueToDisplay(val, GetFloatDisplayTypeFromKeyframe(key)); + + val += GetOffsetFromKeyframe(key); + + return -val; } QPointF CurveView::ScalePoint(const QPointF &point) @@ -543,41 +596,48 @@ QPointF CurveView::ScalePoint(const QPointF &point) return QPointF(point.x() * GetScale(), - point.y() * GetYScale()); } +FloatSlider::DisplayType CurveView::GetFloatDisplayTypeFromKeyframe(NodeKeyframe *key) +{ + Node* node = key->parent(); + const QString& input = key->input(); + if (node->HasInputProperty(input, QStringLiteral("view"))) { + // Try to get view from input (which will be normal if unset) + return static_cast(node->GetInputProperty(input, QStringLiteral("view")).toInt()); + } + + // Fallback to normal + return FloatSlider::kNormal; +} + +double CurveView::GetOffsetFromKeyframe(NodeKeyframe *key) +{ + Node *node = key->parent(); + const QString &input = key->input(); + if (node->HasInputProperty(input, QStringLiteral("offset"))) { + QVariant v = node->GetInputProperty(input, QStringLiteral("offset")); + + // NOTE: Implement getting correct offset for the track based on the data type + QVector track_vals = NodeValue::split_normal_value_into_track_values(node->GetInputDataType(input), v); + + return track_vals.at(key->track()).toDouble(); + } + + return 0; +} + QPointF CurveView::GetKeyframePosition(NodeKeyframe *key) { return QPointF(GetKeyframeSceneX(key), GetItemYFromKeyframeValue(key)); } -void CurveView::KeyframeTypeChanged() -{ - qDebug() << "STUB!"; - /*NodeKeyframe* key = static_cast(sender()); - KeyframeViewItem* item = item_map().value(key); - - if (item->isSelected()) { - item->setSelected(false); - item->setSelected(true); - }*/ -} - void CurveView::ZoomToFit() { - QVector keys; - - foreach (KeyframeViewInputConnection *con, track_connections_) { - foreach (NodeKeyframe *k, con->GetKeyframes()) { - if (!keys.contains(k)) { - keys.append(k); - } - } - } - - ZoomToFitInternal(keys); + ZoomToFitInternal(false); } void CurveView::ZoomToFitSelected() { - ZoomToFitInternal(GetSelectedKeyframes()); + ZoomToFitInternal(true); } void CurveView::ResetZoom() diff --git a/app/widget/curvewidget/curveview.h b/app/widget/curvewidget/curveview.h index 3858368e8..1eb8355f1 100644 --- a/app/widget/curvewidget/curveview.h +++ b/app/widget/curvewidget/curveview.h @@ -23,6 +23,7 @@ #include "node/keyframe.h" #include "widget/keyframeview/keyframeview.h" +#include "widget/slider/floatslider.h" namespace olive { @@ -38,8 +39,6 @@ public: void SelectKeyframesOfInput(const NodeKeyframeTrackReference &ref); - void ZoomToFitInput(const NodeKeyframeTrackReference &ref); - void SetKeyframeTrackColor(const NodeKeyframeTrackReference& ref, const QColor& color); public slots: @@ -70,13 +69,17 @@ protected: virtual void KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *command) override; private: - void ZoomToFitInternal(const QVector &keys); + void ZoomToFitInternal(bool selected_only); qreal GetItemYFromKeyframeValue(NodeKeyframe* key); - qreal GetItemYFromKeyframeValue(double value); + qreal GetUnscaledItemYFromKeyframeValue(NodeKeyframe* key); QPointF ScalePoint(const QPointF& point); + static FloatSlider::DisplayType GetFloatDisplayTypeFromKeyframe(NodeKeyframe *key); + + static double GetOffsetFromKeyframe(NodeKeyframe *key); + void AdjustLines(); QPointF GetKeyframePosition(NodeKeyframe *key); @@ -112,9 +115,6 @@ private: QVector drag_keyframe_values_; -private slots: - void KeyframeTypeChanged(); - }; } diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index 6dac944dd..e32f75d67 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -47,10 +47,7 @@ CurveWidget::CurveWidget(QWidget *parent) : tree_view_ = new NodeTreeView(); tree_view_->SetOnlyShowKeyframable(true); tree_view_->SetShowKeyframeTracksAsRows(true); - connect(tree_view_, &NodeTreeView::NodeEnableChanged, this, &CurveWidget::NodeEnabledChanged); - connect(tree_view_, &NodeTreeView::InputEnableChanged, this, &CurveWidget::InputEnabledChanged); connect(tree_view_, &NodeTreeView::InputSelectionChanged, this, &CurveWidget::InputSelectionChanged); - connect(tree_view_, &NodeTreeView::InputDoubleClicked, this, &CurveWidget::InputDoubleClicked); splitter->addWidget(tree_view_); QWidget* workarea = new QWidget(); @@ -101,7 +98,7 @@ CurveWidget::CurveWidget(QWidget *parent) : // Connect ruler and view together connect(view_, &CurveView::TimeChanged, this, &CurveWidget::SetTimeAndSignal); - connect(view_->scene(), &QGraphicsScene::selectionChanged, this, &CurveWidget::SelectionChanged); + connect(view_, &CurveView::SelectionChanged, this, &CurveWidget::SelectionChanged); connect(view_, &CurveView::ScaleChanged, this, &CurveWidget::SetScale); connect(view_, &CurveView::Dragged, this, &CurveWidget::KeyframeViewDragged); @@ -176,6 +173,8 @@ void CurveWidget::ScaleChangedEvent(const double &scale) void CurveWidget::TimeTargetChangedEvent(Node *target) { + TimeTargetObject::TimeTargetChangedEvent(target); + key_control_->SetTimeTarget(target); view_->SetTimeTarget(target); @@ -183,6 +182,8 @@ void CurveWidget::TimeTargetChangedEvent(Node *target) void CurveWidget::ConnectedNodeChangeEvent(ViewerOutput *n) { + super::ConnectedNodeChangeEvent(n); + SetTimeTarget(n); } @@ -215,7 +216,7 @@ void CurveWidget::UpdateBridgeTime(const rational &time) void CurveWidget::ConnectNode(Node *node, bool connect) { foreach (const QString& input, node->inputs()) { - if (node->IsInputKeyframable(input)) { + if (node->IsInputKeyframable(input) && !node->IsInputHidden(input)) { ConnectInput(node, input, connect); } } @@ -228,9 +229,6 @@ void CurveWidget::ConnectInput(Node *node, const QString &input, bool connect) return; } - int track_count = NodeValue::get_number_of_keyframe_tracks(node->GetInputDataType(input)); - bool multiple_tracks = track_count > 1; - int arr_sz = node->InputArraySize(input); for (int i=-1; iSetKeyframeTrackColor(ref, c); } } - - if (tree_view_->IsInputEnabled(NodeKeyframeTrackReference(NodeInput(node, input, i), multiple_tracks ? -1 : 0))) { - if (multiple_tracks) { - for (int j=0; jIsInputEnabled(ref)) { - if (connect) { - view_->ConnectInput(ref); - } else { - view_->DisconnectInput(ref); - } - } - } - } else { - NodeKeyframeTrackReference ref(NodeInput(node, input, i), 0); - if (connect) { - view_->ConnectInput(ref); - } else { - view_->DisconnectInput(ref); - } - } - } } } @@ -339,32 +315,35 @@ void CurveWidget::KeyframeTypeButtonTriggered(bool checked) Core::instance()->undo_stack()->push(command); } -void CurveWidget::NodeEnabledChanged(Node* n, bool e) -{ - ConnectNode(n, e); -} - -void CurveWidget::InputEnabledChanged(const NodeKeyframeTrackReference& ref, bool e) -{ - if (e) { - view_->ConnectInput(ref); - } else { - view_->DisconnectInput(ref); - } -} - void CurveWidget::InputSelectionChanged(const NodeKeyframeTrackReference& ref) { key_control_->SetInput(ref.input()); - if (ref.IsValid()) { - view_->SelectKeyframesOfInput(ref); + foreach (const NodeKeyframeTrackReference &c, selected_tracks_) { + view_->DisconnectInput(c); } -} -void CurveWidget::InputDoubleClicked(const NodeKeyframeTrackReference& ref) -{ - view_->ZoomToFitInput(ref); + selected_tracks_.clear(); + + if (ref.IsValid()) { + view_->ConnectInput(ref); + selected_tracks_.append(ref); + } else if (ref.input().IsValid()) { + int track_count = NodeValue::get_number_of_keyframe_tracks(ref.input().GetDataType()); + for (int i=0; iConnectInput(track_ref); + selected_tracks_.append(track_ref); + } + } else if (Node *node = ref.input().node()) { + foreach (const QString &input, node->inputs()) { + if (!node->IsInputKeyframable(input) || node->IsInputHidden(input)) { + + } + } + } + + view_->ZoomToFit(); } void CurveWidget::KeyframeViewDragged(int x, int y) diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index db14f2955..c27057fc8 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -96,19 +96,15 @@ private: QVector nodes_; + QVector selected_tracks_; + private slots: void SelectionChanged(); void KeyframeTypeButtonTriggered(bool checked); - void NodeEnabledChanged(Node* n, bool e); - - void InputEnabledChanged(const NodeKeyframeTrackReference &ref, bool e); - void InputSelectionChanged(const NodeKeyframeTrackReference& ref); - void InputDoubleClicked(const NodeKeyframeTrackReference& ref); - void KeyframeViewDragged(int x, int y); void CatchUpYScrollToPoint(int point); diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index cec7aca99..0e751265a 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -116,6 +116,7 @@ void KeyframeView::RemoveKeyframesOfTrack(KeyframeViewInputConnection *connectio } delete connection; Redraw(); + emit SelectionChanged(); } } @@ -157,6 +158,8 @@ void KeyframeView::SelectionManagerSelectEvent(void *obj) } } } + + emit SelectionChanged(); } void KeyframeView::SelectionManagerDeselectEvent(void *obj) @@ -170,6 +173,8 @@ void KeyframeView::SelectionManagerDeselectEvent(void *obj) } } } + + emit SelectionChanged(); } void KeyframeView::mousePressEvent(QMouseEvent *event) @@ -235,6 +240,7 @@ void KeyframeView::mouseReleaseEvent(QMouseEvent *event) } else if (selection_manager_.IsRubberBanding()) { selection_manager_.RubberBandStop(); Redraw(); + emit SelectionChanged(); } } @@ -325,6 +331,8 @@ void KeyframeView::SelectKeyframe(NodeKeyframe *key) { if (selection_manager_.Select(key)) { Redraw(); + + emit SelectionChanged(); } } @@ -332,6 +340,8 @@ void KeyframeView::DeselectKeyframe(NodeKeyframe *key) { if (selection_manager_.Deselect(key)) { Redraw(); + + emit SelectionChanged(); } } diff --git a/app/widget/keyframeview/keyframeview.h b/app/widget/keyframeview/keyframeview.h index a0c1aa99a..09565f1b2 100644 --- a/app/widget/keyframeview/keyframeview.h +++ b/app/widget/keyframeview/keyframeview.h @@ -75,6 +75,8 @@ public: signals: void Dragged(int current_x, int current_y); + void SelectionChanged(); + protected: virtual void mousePressEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override; diff --git a/app/widget/keyframeview/keyframeviewinputconnection.cpp b/app/widget/keyframeview/keyframeviewinputconnection.cpp index b7b3c2dca..c0d2c3f31 100644 --- a/app/widget/keyframeview/keyframeviewinputconnection.cpp +++ b/app/widget/keyframeview/keyframeviewinputconnection.cpp @@ -38,6 +38,7 @@ KeyframeViewInputConnection::KeyframeViewInputConnection(const NodeKeyframeTrack connect(n, &Node::KeyframeRemoved, this, &KeyframeViewInputConnection::RemoveKeyframe); connect(n, &Node::KeyframeTimeChanged, this, &KeyframeViewInputConnection::KeyframeChanged); connect(n, &Node::KeyframeTypeChanged, this, &KeyframeViewInputConnection::KeyframeChanged); + connect(n, &Node::KeyframeTypeChanged, this, &KeyframeViewInputConnection::KeyframeTypeChanged); connect(n, &Node::KeyframeValueChanged, this, &KeyframeViewInputConnection::KeyframeChanged); } @@ -89,4 +90,11 @@ void KeyframeViewInputConnection::KeyframeChanged(NodeKeyframe *key) } } +void KeyframeViewInputConnection::KeyframeTypeChanged(NodeKeyframe *key) +{ + if (key->key_track_ref() == input_) { + emit TypeChanged(); + } +} + } diff --git a/app/widget/keyframeview/keyframeviewinputconnection.h b/app/widget/keyframeview/keyframeviewinputconnection.h index f10f37d2b..b6b0c1da5 100644 --- a/app/widget/keyframeview/keyframeviewinputconnection.h +++ b/app/widget/keyframeview/keyframeviewinputconnection.h @@ -65,6 +65,8 @@ public: signals: void RequireUpdate(); + void TypeChanged(); + private: KeyframeView *keyframe_view_; @@ -83,6 +85,8 @@ private slots: void KeyframeChanged(NodeKeyframe *key); + void KeyframeTypeChanged(NodeKeyframe *key); + }; } diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index e21ead255..697067343 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -417,11 +417,15 @@ void NodeParamView::SortItemsInContext(NodeParamViewContext *context_item) QVector > distances; for (auto it=context_item->GetItems().cbegin(); it!=context_item->GetItems().cend(); it++) { - int distance = 0; + int distance = -1; foreach (Node *ctx, context_item->GetContexts()) { distance = qMax(distance, GetDistanceBetweenNodes(ctx, it.key())); } + if (distance == -1) { + distance = INT_MAX; + } + bool inserted = false; QPair dist(it.value(), distance); @@ -439,6 +443,7 @@ void NodeParamView::SortItemsInContext(NodeParamViewContext *context_item) } foreach (auto info, distances) { + qDebug() << "Inserting" << info.first->GetNode() << "with distance" << info.second; context_item->GetDockArea()->AddItem(info.first); } } diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index dda919eee..854d4a15f 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -648,42 +648,12 @@ void NodeParamViewWidgetBridge::PropertyChanged(const QString& input, const QStr break; } } else if (key == QStringLiteral("offset")) { - switch (data_type) { - case NodeValue::kInt: - static_cast(widgets_.first())->SetOffset(value); - break; - case NodeValue::kFloat: - static_cast(widgets_.first())->SetOffset(value); - break; - case NodeValue::kRational: - static_cast(widgets_.first())->SetOffset(value); - break; - case NodeValue::kVec2: - { - QVector2D offs = value.value(); - static_cast(widgets_.at(0))->SetOffset(offs.x()); - static_cast(widgets_.at(1))->SetOffset(offs.y()); - break; - } - case NodeValue::kVec3: - { - QVector3D offs = value.value(); - static_cast(widgets_.at(0))->SetOffset(offs.x()); - static_cast(widgets_.at(1))->SetOffset(offs.y()); - static_cast(widgets_.at(2))->SetOffset(offs.z()); - break; - } - case NodeValue::kVec4: - { - QVector4D offs = value.value(); - static_cast(widgets_.at(0))->SetOffset(offs.x()); - static_cast(widgets_.at(1))->SetOffset(offs.y()); - static_cast(widgets_.at(2))->SetOffset(offs.z()); - static_cast(widgets_.at(3))->SetOffset(offs.w()); - break; - } - default: - break; + int tracks = NodeValue::get_number_of_keyframe_tracks(data_type); + + QVector offsets = NodeValue::split_normal_value_into_track_values(data_type, value); + + for (int i=0; i(widgets_.at(i))->SetOffset(offsets.at(i)); } UpdateWidgetValues(); diff --git a/app/widget/nodetreeview/nodetreeview.cpp b/app/widget/nodetreeview/nodetreeview.cpp index a2f8ec9df..fd0afc2d9 100644 --- a/app/widget/nodetreeview/nodetreeview.cpp +++ b/app/widget/nodetreeview/nodetreeview.cpp @@ -27,7 +27,8 @@ namespace olive { NodeTreeView::NodeTreeView(QWidget *parent) : QTreeWidget(parent), only_show_keyframable_(false), - show_keyframe_tracks_as_rows_(false) + show_keyframe_tracks_as_rows_(false), + checkboxes_enabled_(false) { connect(this, &NodeTreeView::itemChanged, this, &NodeTreeView::ItemCheckStateChanged); connect(this, &NodeTreeView::itemSelectionChanged, this, &NodeTreeView::SelectionChanged); @@ -67,12 +68,14 @@ void NodeTreeView::SetNodes(const QVector &nodes) foreach (Node* n, nodes_) { QTreeWidgetItem* node_item = new QTreeWidgetItem(); node_item->setText(0, n->Name()); - node_item->setCheckState(0, disabled_nodes_.contains(n) ? Qt::Unchecked : Qt::Checked); + if (checkboxes_enabled_) { + node_item->setCheckState(0, disabled_nodes_.contains(n) ? Qt::Unchecked : Qt::Checked); + } node_item->setData(0, kItemType, kItemTypeNode); node_item->setData(0, kItemNodePointer, Node::PtrToValue(n)); foreach (const QString& input, n->inputs()) { - if (only_show_keyframable_ && !n->IsInputKeyframable(input)) { + if (n->IsInputHidden(input) || (only_show_keyframable_ && !n->IsInputKeyframable(input))) { continue; } @@ -105,7 +108,6 @@ void NodeTreeView::SetNodes(const QVector &nodes) CreateItemsForTracks(element_item, input_ref, key_tracks.size()); } } - } // Add at the end to prevent unnecessary signalling while we're setting these objects up @@ -114,9 +116,9 @@ void NodeTreeView::SetNodes(const QVector &nodes) } else { delete node_item; } - - node_item->setExpanded(true); } + + expandAll(); } void NodeTreeView::changeEvent(QEvent *e) @@ -155,6 +157,8 @@ NodeKeyframeTrackReference NodeTreeView::GetSelectedInput() if (item->data(0, kItemType).toInt() == kItemTypeInput) { selected_ref = item->data(0, kItemInputReference).value(); + } else { + selected_ref = NodeKeyframeTrackReference(NodeInput(Node::ValueToPtr(item->data(0, kItemNodePointer)), QString())); } } @@ -166,21 +170,27 @@ QTreeWidgetItem* NodeTreeView::CreateItem(QTreeWidgetItem *parent, const NodeKey QTreeWidgetItem* input_item = new QTreeWidgetItem(parent); QString item_name; - if (ref.track() == -1 || NodeValue::get_number_of_keyframe_tracks(ref.input().GetDataType()) == 1) { - item_name = ref.input().name(); + if (ref.track() == -1 + || NodeValue::get_number_of_keyframe_tracks(ref.input().GetDataType()) == 1 + || (ref.input().IsArray() && ref.input().element() == -1)) { + if (ref.input().element() == -1) { + item_name = ref.input().name(); + } else { + item_name = QString::number(ref.input().element()); + } } else { switch (ref.track()) { case 0: - item_name = tr("X"); + item_name = UseRGBAOverXYZW(ref) ? tr("R") : tr("X"); break; case 1: - item_name = tr("Y"); + item_name = UseRGBAOverXYZW(ref) ? tr("G") : tr("Y"); break; case 2: - item_name = tr("Z"); + item_name = UseRGBAOverXYZW(ref) ? tr("B") : tr("Z"); break; case 3: - item_name = tr("W"); + item_name = UseRGBAOverXYZW(ref) ? tr("A") : tr("W"); break; default: item_name = QString::number(ref.track()); @@ -188,7 +198,9 @@ QTreeWidgetItem* NodeTreeView::CreateItem(QTreeWidgetItem *parent, const NodeKey } input_item->setText(0, item_name); - input_item->setCheckState(0, disabled_inputs_.contains(ref) ? Qt::Unchecked : Qt::Checked); + if (checkboxes_enabled_) { + input_item->setCheckState(0, disabled_inputs_.contains(ref) ? Qt::Unchecked : Qt::Checked); + } input_item->setData(0, kItemType, kItemTypeInput); input_item->setData(0, kItemInputReference, QVariant::fromValue(ref)); @@ -208,6 +220,11 @@ void NodeTreeView::CreateItemsForTracks(QTreeWidgetItem *parent, const NodeInput } } +bool NodeTreeView::UseRGBAOverXYZW(const NodeKeyframeTrackReference &ref) +{ + return ref.input().GetDataType() == NodeValue::kColor; +} + void NodeTreeView::ItemCheckStateChanged(QTreeWidgetItem *item, int column) { Q_UNUSED(column) diff --git a/app/widget/nodetreeview/nodetreeview.h b/app/widget/nodetreeview/nodetreeview.h index 3528b2822..067a4c82a 100644 --- a/app/widget/nodetreeview/nodetreeview.h +++ b/app/widget/nodetreeview/nodetreeview.h @@ -37,6 +37,11 @@ public: bool IsInputEnabled(const NodeKeyframeTrackReference& ref) const; + void SetCheckBoxesEnabled(bool e) + { + checkboxes_enabled_ = e; + } + void SetKeyframeTrackColor(const NodeKeyframeTrackReference& ref, const QColor& color); void SetOnlyShowKeyframable(bool e) @@ -75,6 +80,8 @@ private: void CreateItemsForTracks(QTreeWidgetItem* parent, const NodeInput& input, int track_count); + static bool UseRGBAOverXYZW(const NodeKeyframeTrackReference &ref); + enum ItemType { kItemTypeNode, kItemTypeInput @@ -98,6 +105,8 @@ private: QHash keyframe_colors_; + bool checkboxes_enabled_; + private slots: void ItemCheckStateChanged(QTreeWidgetItem* item, int column); diff --git a/app/widget/slider/floatslider.cpp b/app/widget/slider/floatslider.cpp index 39c00f686..7510c36ef 100644 --- a/app/widget/slider/floatslider.cpp +++ b/app/widget/slider/floatslider.cpp @@ -78,29 +78,46 @@ void FloatSlider::SetDisplayType(const FloatSlider::DisplayType &type) } } -QString FloatSlider::ValueToString(double val, FloatSlider::DisplayType display, int decimal_places, bool autotrim_decimal_places) +double FloatSlider::TransformValueToDisplay(double val, DisplayType display) { switch (display) { case kNormal: - // Do nothing, skip to the return string at the end break; case kDecibel: - // Convert to decibels and return dB formatted string - - // Return negative infinity for zero volume - if (qIsNull(val)) { - return tr("\xE2\x88\x9E"); - } - val = Decibel::fromLinear(val); break; case kPercentage: - // Multiply value by 100 for user-friendly percentage val *= 100.0; break; } - return FloatToString(val, decimal_places, autotrim_decimal_places); + return val; +} + +double FloatSlider::TransformDisplayToValue(double val, DisplayType display) +{ + switch (display) { + case kNormal: + break; + case kDecibel: + val = Decibel::toLinear(val); + break; + case kPercentage: + val *= 0.01; + break; + } + + return val; +} + +QString FloatSlider::ValueToString(double val, FloatSlider::DisplayType display, int decimal_places, bool autotrim_decimal_places) +{ + // Return negative infinity for zero volume + if (display == kDecibel && qIsNull(val)) { + return tr("\xE2\x88\x9E"); + } + + return FloatToString(TransformValueToDisplay(val, display), decimal_places, autotrim_decimal_places); } QString FloatSlider::ValueToString(const QVariant &v) const @@ -110,46 +127,21 @@ QString FloatSlider::ValueToString(const QVariant &v) const QVariant FloatSlider::StringToValue(const QString &s, bool *ok) const { - switch (display_type_) { - case kNormal: - // Do nothing, skip to the return string at the end - break; - case kDecibel: - { - bool valid; + bool valid; + double val = s.toDouble(&valid); - // See if we can get a decimal number out of this - qreal decibels = s.toDouble(&valid); - - if (ok) *ok = valid; - - if (valid) { - // Convert from decibel scale to linear decimal - return Decibel::toLinear(decibels); - } - - break; - } - case kPercentage: - { - bool valid; - - // Try to get double value - double val = s.toDouble(&valid); - - if (ok) *ok = valid; - - // If we could get it, convert back to a 0.0 - 1.0 value and return - if (valid) { - return val * 0.01; - } - - break; - } + // If we were given an `ok` pointer, set it to `valid` + if (ok) { + *ok = valid; } - // Just try to convert the string to a double - return s.toDouble(ok) - GetOffset().toDouble(); + // If valid, transform it from display + if (valid) { + val = TransformDisplayToValue(val, display_type_); + } + + // Return un-offset value + return val - GetOffset().toDouble(); } QVariant FloatSlider::AdjustDragDistanceInternal(const QVariant &start, const double &drag) const diff --git a/app/widget/slider/floatslider.h b/app/widget/slider/floatslider.h index 4000e41bd..fe392a983 100644 --- a/app/widget/slider/floatslider.h +++ b/app/widget/slider/floatslider.h @@ -49,6 +49,10 @@ public: void SetDisplayType(const DisplayType& type); + static double TransformValueToDisplay(double val, DisplayType display); + + static double TransformDisplayToValue(double val, DisplayType display); + static QString ValueToString(double val, DisplayType display, int decimal_places, bool autotrim_decimal_places); protected: diff --git a/app/widget/timebased/timebasedview.h b/app/widget/timebased/timebasedview.h index f6f46d285..a51728afb 100644 --- a/app/widget/timebased/timebasedview.h +++ b/app/widget/timebased/timebasedview.h @@ -62,6 +62,11 @@ public slots: void SetEndTime(const rational& length); + /** + * @brief Slot called whenever the view resizes or the scene contents change to enforce minimum scene sizes + */ + void UpdateSceneRect(); + signals: void TimeChanged(const rational& time); @@ -101,12 +106,6 @@ protected: y_axis_enabled_ = e; } -protected slots: - /** - * @brief Slot called whenever the view resizes or the scene contents change to enforce minimum scene sizes - */ - void UpdateSceneRect(); - private: qreal GetPlayheadX(); diff --git a/app/widget/timebased/timebasedviewselectionmanager.h b/app/widget/timebased/timebasedviewselectionmanager.h index 19d61e203..ae5e32230 100644 --- a/app/widget/timebased/timebasedviewselectionmanager.h +++ b/app/widget/timebased/timebasedviewselectionmanager.h @@ -92,7 +92,9 @@ public: T *GetObjectAtPoint(const QPointF &scene_pt) { - foreach (const DrawnObject &kp, drawn_objects_) { + // Iterate in reverse order because the objects drawn later will appear on top to the user + for (auto it=drawn_objects_.crbegin(); it!=drawn_objects_.crend(); it++) { + const DrawnObject &kp = *it; if (kp.second.contains(scene_pt)) { return kp.first; } @@ -147,13 +149,11 @@ public: { initial_drag_item_ = initial_item; - dragging_.clear(); - dragging_.resize(selected_.size()); for (int i=0; itime()}; + dragging_[i] = {obj->time(), view_->TimeToScene(obj->time())}; } drag_mouse_start_ = view_->mapToScene(event->pos()); @@ -164,15 +164,15 @@ public: QPointF diff = view_->mapToScene(event->pos()) - drag_mouse_start_; for (int i=0; iSceneToTimeNoGrid(diff.x()); + rational proposed_time = view_->SceneToTimeNoGrid(dragging_.at(i).x + diff.x()); T *sel = selected_.at(i); // Magic number: use interval of 1ms to avoid collisions rational adj(1, 1000); - if (old_time < proposed_time) { + if (dragging_.at(i).time < proposed_time) { adj = -adj; } + while (true) { NodeKeyframe *key_at_time = sel->parent()->GetKeyframeAtTimeOnTrack(sel->input(), proposed_time, sel->track(), sel->element()); if (!key_at_time || key_at_time == sel) { @@ -303,6 +303,7 @@ private: struct DragObject { rational time; + double x; }; QVector dragging_; From 408b3e6da6787550f23e5bb3104e9603e7eea230 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 18 Dec 2021 11:43:08 -0800 Subject: [PATCH 51/94] remove debug line --- app/widget/nodeparamview/nodeparamview.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 697067343..f4dee8b7c 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -443,7 +443,6 @@ void NodeParamView::SortItemsInContext(NodeParamViewContext *context_item) } foreach (auto info, distances) { - qDebug() << "Inserting" << info.first->GetNode() << "with distance" << info.second; context_item->GetDockArea()->AddItem(info.first); } } From 8ba5107e73d80df014fd8ac54fd39a7f77b668fc Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 18 Dec 2021 11:43:20 -0800 Subject: [PATCH 52/94] fix curveview selections --- app/widget/curvewidget/curvewidget.cpp | 97 +++++++++++++------------- app/widget/curvewidget/curvewidget.h | 4 +- 2 files changed, 50 insertions(+), 51 deletions(-) diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index e32f75d67..4d56896ae 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -131,22 +131,33 @@ void CurveWidget::SetNodes(const QVector &nodes) { tree_view_->SetNodes(nodes); - // Detect removed nodes - foreach (Node* n, nodes_) { - if (!nodes.contains(n)) { - ConnectNode(n, false); - } - } - - // Detect added nodes - foreach (Node* n, nodes) { - if (tree_view_->IsNodeEnabled(n) && !nodes_.contains(n)) { - ConnectNode(n, true); - } - } - // Save new node list nodes_ = nodes; + + // Generate colors + foreach (Node *node, nodes_) { + foreach (const QString& input, node->inputs()) { + if (node->IsInputKeyframable(input) && !node->IsInputHidden(input)) { + int arr_sz = node->InputArraySize(input); + for (int i=-1; i& tracks = node->GetKeyframeTracks(input, i); + + for (int j=0; jSetKeyframeTrackColor(ref, c); + view_->SetKeyframeTrackColor(ref, c); + } + } + } + } + } + } } void CurveWidget::TimeChangedEvent(const rational &time) @@ -213,38 +224,28 @@ void CurveWidget::UpdateBridgeTime(const rational &time) key_control_->SetTime(time); } -void CurveWidget::ConnectNode(Node *node, bool connect) +void CurveWidget::ConnectInput(Node *node, const QString &input, int element) { - foreach (const QString& input, node->inputs()) { - if (node->IsInputKeyframable(input) && !node->IsInputHidden(input)) { - ConnectInput(node, input, connect); + if (element == -1 && node->InputIsArray(input)) { + // This is the root element, connect all elements (if applicable) + int arr_sz = node->InputArraySize(input); + for (int i=-1; iIsInputKeyframable(input)) { - qWarning() << "Tried to connect input that isn't keyframable"; - return; - } - - int arr_sz = node->InputArraySize(input); - for (int i=-1; i& tracks = node->GetKeyframeTracks(input, i); - - for (int j=0; jSetKeyframeTrackColor(ref, c); - view_->SetKeyframeTrackColor(ref, c); - } - } + NodeInput input_ref(node, input, element); + int track_count = NodeValue::get_number_of_keyframe_tracks(input_ref.GetDataType()); + for (int i=0; iConnectInput(track_ref); + selected_tracks_.append(track_ref); } } @@ -325,20 +326,18 @@ void CurveWidget::InputSelectionChanged(const NodeKeyframeTrackReference& ref) selected_tracks_.clear(); - if (ref.IsValid()) { + if (ref.IsValid() && !ref.input().IsArray()) { + // This reference is a track, connect it only view_->ConnectInput(ref); selected_tracks_.append(ref); } else if (ref.input().IsValid()) { - int track_count = NodeValue::get_number_of_keyframe_tracks(ref.input().GetDataType()); - for (int i=0; iConnectInput(track_ref); - selected_tracks_.append(track_ref); - } + // This reference is a input, connect all tracks + ConnectInput(ref.input().node(), ref.input().input(), ref.input().element()); } else if (Node *node = ref.input().node()) { + // This is a node, add all inputs foreach (const QString &input, node->inputs()) { - if (!node->IsInputKeyframable(input) || node->IsInputHidden(input)) { - + if (node->IsInputKeyframable(input) && !node->IsInputHidden(input)) { + ConnectInput(node, input, -1); } } } diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index c27057fc8..ba6d1304b 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -76,9 +76,9 @@ private: void UpdateBridgeTime(const rational &time); - void ConnectNode(Node* node, bool connect); + void ConnectInput(Node *node, const QString &input, int element); - void ConnectInput(Node* node, const QString& input, bool connect); + void ConnectInputInternal(Node *node, const QString &input, int element); QHash keyframe_colors_; From 671fafb4964bd87aea5adfc79298a0afd3e36543 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 18 Dec 2021 14:03:44 -0800 Subject: [PATCH 53/94] refined some nodeview behavior --- app/widget/nodeview/nodeview.cpp | 17 ++++++++++++++--- app/widget/nodeview/nodeviewitem.h | 5 +++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 3c9a855ff..c6a3c9746 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -312,6 +312,7 @@ void NodeView::mousePressEvent(QMouseEvent *event) // Sane defaults create_edge_already_exists_ = false; create_edge_from_output_ = true; + create_edge_input_.Reset(); if (event->modifiers() & Qt::ControlModifier) { NodeViewItem *mouse_item = dynamic_cast(item); @@ -324,6 +325,9 @@ void NodeView::mousePressEvent(QMouseEvent *event) create_edge_input_ = mouse_item->GetInput(); create_edge_from_output_ = false; } + + // Highlight start item for better user experience + mouse_item->SetHighlighted(true); } } @@ -1082,7 +1086,7 @@ void NodeView::PositionNewEdge(const QPoint &pos) NodeViewItem *&opposing_item = create_edge_from_output_ ? create_edge_input_item_ : create_edge_output_item_; // Filter out connecting to self - if (item_at_cursor == source_item) { + if (item_at_cursor && item_at_cursor->GetNode() == source_item->GetNode()) { item_at_cursor = nullptr; } @@ -1112,8 +1116,10 @@ void NodeView::PositionNewEdge(const QPoint &pos) create_edge_expanded_items_.resize(i + 1); // Expand item if possible - if (item_at_cursor && item_at_cursor->CanBeExpanded() && !item_at_cursor->IsExpanded() - && (create_edge_from_output_ || !item_at_cursor->IsOutputItem())) { + if (item_at_cursor + && item_at_cursor->CanBeExpanded() + && !item_at_cursor->IsExpanded() + && create_edge_from_output_) { ExpandItem(item_at_cursor); create_edge_expanded_items_.append(item_at_cursor); } @@ -1126,6 +1132,11 @@ void NodeView::PositionNewEdge(const QPoint &pos) item_at_cursor = nullptr; } + // Filter out "output node" of the context, we assume users won't want to fetch the output of this + if (item_at_cursor && !create_edge_from_output_ && item_at_cursor->IsLabelledAsOutputOfContext()) { + item_at_cursor = nullptr; + } + // If the item has changed if (item_at_cursor != opposing_item) { // If we had a destination active, disconnect from it since the item has changed diff --git a/app/widget/nodeview/nodeviewitem.h b/app/widget/nodeview/nodeviewitem.h index 898c49a41..e236de3ea 100644 --- a/app/widget/nodeview/nodeviewitem.h +++ b/app/widget/nodeview/nodeviewitem.h @@ -128,6 +128,11 @@ public: void AddEdge(NodeViewEdge* edge); void RemoveEdge(NodeViewEdge* edge); + bool IsLabelledAsOutputOfContext() const + { + return label_as_output_; + } + void SetLabelAsOutput(bool e); void SetHighlighted(bool e); From b6e6b894615da4910e747127b80bcb858fc73109 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 19 Dec 2021 16:38:29 +0000 Subject: [PATCH 54/94] saveotio: Make sure all tracks are the same length --- app/task/project/saveotio/saveotio.cpp | 25 +++++++++++++++++++++++-- app/task/project/saveotio/saveotio.h | 2 +- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/app/task/project/saveotio/saveotio.cpp b/app/task/project/saveotio/saveotio.cpp index f4d4b8ced..66ea55ca4 100644 --- a/app/task/project/saveotio/saveotio.cpp +++ b/app/task/project/saveotio/saveotio.cpp @@ -116,7 +116,7 @@ OTIO::Timeline *SaveOTIOTask::SerializeTimeline(Sequence *sequence) return otio_timeline; } -OTIO::Track *SaveOTIOTask::SerializeTrack(Track *track, double sequence_rate) +OTIO::Track *SaveOTIOTask::SerializeTrack(Track *track, double sequence_rate, rational max_track_length) { auto otio_track = new OTIO::Track(); @@ -192,6 +192,19 @@ OTIO::Track *SaveOTIOTask::SerializeTrack(Track *track, double sequence_rate) } } + // All OTIO tracks must have the same duration so we add a Gap to fill the remaining time + if (otio_track->duration(&es).to_seconds() < max_track_length.toDouble()) { + double time_left = max_track_length.toDouble() - otio_track->duration(&es).to_seconds(); + + OTIO::Gap* gap = new OTIO::Gap(OTIO::TimeRange(otio_track->duration(&es), + OTIO::RationalTime(time_left, 1.0))); + otio_track->append_child(gap, &es); + + if (es.outcome != OTIO::ErrorStatus::Outcome::OK) { + goto fail; + } + } + return otio_track; fail: @@ -204,8 +217,16 @@ bool SaveOTIOTask::SerializeTrackList(TrackList *list, OTIO::Timeline* otio_time { OTIO::ErrorStatus es; + rational max_track_length = RATIONAL_MIN; + foreach (Track* track, list->GetTracks()) { - auto otio_track = SerializeTrack(track, sequence_rate); + if (track->track_length() > max_track_length) { + max_track_length = track->track_length(); + } + } + + foreach (Track* track, list->GetTracks()) { + auto otio_track = SerializeTrack(track, sequence_rate, max_track_length); if (!otio_track) { return false; diff --git a/app/task/project/saveotio/saveotio.h b/app/task/project/saveotio/saveotio.h index 0239beb4b..4be81d178 100644 --- a/app/task/project/saveotio/saveotio.h +++ b/app/task/project/saveotio/saveotio.h @@ -44,7 +44,7 @@ protected: private: OTIO::Timeline* SerializeTimeline(Sequence* sequence); - OTIO::Track* SerializeTrack(Track* track, double sequence_rate); + OTIO::Track* SerializeTrack(Track* track, double sequence_rate, rational max_track_length); bool SerializeTrackList(TrackList* list, OTIO::Timeline *otio_timeline, double sequence_rate); From 21165cb2ff8d83122e642720eae934cab37f4c1c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 19 Dec 2021 13:19:31 -0800 Subject: [PATCH 55/94] implemented node flags --- app/node/block/block.cpp | 2 ++ app/node/math/merge/merge.cpp | 2 ++ app/node/node.cpp | 3 ++- app/node/node.h | 17 +++++++++++++++++ app/node/output/viewer/viewer.cpp | 2 ++ app/widget/nodeparamview/nodeparamview.cpp | 14 +++++++++++--- 6 files changed, 36 insertions(+), 4 deletions(-) diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index 34f87b2e1..a726d6b0f 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -47,6 +47,8 @@ Block::Block() : IgnoreHashingFrom(kLengthInput); AddInput(kEnabledInput, NodeValue::kBoolean, true, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + + SetFlags(kDontShowInParamView); } QVector Block::Category() const diff --git a/app/node/math/merge/merge.cpp b/app/node/math/merge/merge.cpp index f5bd9ed0d..5b1582c84 100644 --- a/app/node/math/merge/merge.cpp +++ b/app/node/math/merge/merge.cpp @@ -32,6 +32,8 @@ MergeNode::MergeNode() AddInput(kBaseIn, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); AddInput(kBlendIn, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + + SetFlags(kDontShowInParamView); } Node *MergeNode::copy() const diff --git a/app/node/node.cpp b/app/node/node.cpp index ad406a8bb..e76ece9e5 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -47,7 +47,8 @@ Node::Node() : override_color_(-1), folder_(nullptr), operation_stack_(0), - cache_result_(false) + cache_result_(false), + flags_(kNone) { uuid_ = QUuid::createUuid(); } diff --git a/app/node/node.h b/app/node/node.h index 92d06e63f..299e9b5ae 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -94,6 +94,11 @@ public: kCategoryCount }; + enum Flag { + kNone = 0, + kDontShowInParamView = 0x1 + }; + Node(); virtual ~Node() override; @@ -116,6 +121,11 @@ public: const QUuid &GetUUID() const {return uuid_;} void SetUUID(const QUuid &uuid) {uuid_ = uuid;} + const uint64_t &GetFlags() const + { + return flags_; + } + /** * @brief Clear current node variables and replace them with */ @@ -1036,6 +1046,11 @@ protected: tooltip_ = s; } + void SetFlags(const uint64_t &f) + { + flags_ = f; + } + signals: /** * @brief Signal emitted when SetLabel() is called @@ -1315,6 +1330,8 @@ private: QUuid uuid_; + uint64_t flags_; + private slots: /** * @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 8e257f6e9..82e5acdc6 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -66,6 +66,8 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream AddStream(Track::kAudio, QVariant()); set_default_parameters(); } + + SetFlags(kDontShowInParamView); } Node *ViewerOutput::copy() const diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index f4dee8b7c..268bf4af0 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -76,8 +76,16 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : for (int i=0; isetVisible(false); - static_cast(c->titleBarWidget())->SetAddEffectButtonVisible(i == Track::kVideo || i == Track::kAudio); - static_cast(c->titleBarWidget())->SetText(Footage::GetStreamTypeName(static_cast(i))); + + NodeParamViewItemTitleBar *title_bar = static_cast(c->titleBarWidget()); + + if (i == Track::kVideo || i == Track::kAudio) { + title_bar->SetAddEffectButtonVisible(true); + title_bar->SetText(tr("%1 Nodes").arg(Footage::GetStreamTypeName(static_cast(i)))); + } else { + title_bar->SetText(tr("Other")); + } + context_items_[i] = c; param_widget_area_->AddItem(c); } @@ -259,7 +267,7 @@ void NodeParamView::SetContexts(const QVector &contexts) item->setVisible(true); for (auto it=ctx->GetContextPositions().cbegin(); it!=ctx->GetContextPositions().cend(); it++) { - if (!dynamic_cast(it.key()) && !dynamic_cast(it.key())) { + if (!(it.key()->GetFlags() & Node::kDontShowInParamView)) { AddNode(it.key(), item); } } From a8ac7edd519a9568e13922651c366c34484492fb Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 25 Dec 2021 11:11:45 -0800 Subject: [PATCH 56/94] improved nodeparamview scroll behavior --- app/widget/nodeparamview/nodeparamview.cpp | 13 ++++-------- app/widget/nodeparamview/nodeparamview.h | 24 +--------------------- 2 files changed, 5 insertions(+), 32 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 268bf4af0..b04f41011 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -56,8 +56,7 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : splitter->addWidget(param_scroll_area_); // Param widget - param_widget_container_ = new NodeParamViewParamContainer(); - connect(param_widget_container_, &NodeParamViewParamContainer::Resized, this, &NodeParamView::UpdateGlobalScrollBar); + param_widget_container_ = new QWidget(); param_scroll_area_->setWidget(param_widget_container_); param_widget_area_ = new NodeParamViewDockArea(); @@ -100,6 +99,8 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : // Connect scrollbars together connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue); + connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::rangeChanged, vertical_scrollbar_, &QScrollBar::setRange); + connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::rangeChanged, this, &NodeParamView::UpdateGlobalScrollBar); connect(vertical_scrollbar_, &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue); if (create_keyframe_view) { @@ -287,8 +288,6 @@ void NodeParamView::resizeEvent(QResizeEvent *event) super::resizeEvent(event); vertical_scrollbar_->setPageStep(vertical_scrollbar_->height()); - - UpdateGlobalScrollBar(); } void NodeParamView::ScaleChangedEvent(const double &scale) @@ -457,13 +456,9 @@ void NodeParamView::SortItemsInContext(NodeParamViewContext *context_item) void NodeParamView::UpdateGlobalScrollBar() { - int height_offscreen = param_widget_container_->height() + scrollbar()->height(); - if (keyframe_view_) { - keyframe_view_->SetMaxScroll(height_offscreen + 2000); + keyframe_view_->SetMaxScroll(param_widget_container_->height() - ruler()->height()); } - - vertical_scrollbar_->setRange(0, height_offscreen - param_scroll_area_->height()); } void NodeParamView::PinNode(bool pin) diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index b7eb50edd..d9f7985de 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -33,28 +33,6 @@ namespace olive { -class NodeParamViewParamContainer : public QWidget -{ - Q_OBJECT -public: - NodeParamViewParamContainer(QWidget* parent = nullptr) : - QWidget(parent) - { - } - -protected: - virtual void resizeEvent(QResizeEvent *event) override - { - QWidget::resizeEvent(event); - - emit Resized(event->size().height()); - } - -signals: - void Resized(int new_height); - -}; - class NodeParamView : public TimeBasedWidget { Q_OBJECT @@ -129,7 +107,7 @@ private: QScrollArea* param_scroll_area_; - NodeParamViewParamContainer* param_widget_container_; + QWidget* param_widget_container_; NodeParamViewDockArea* param_widget_area_; From 5223483e8e1c8f513114605cda70bf6a286d3216 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 27 Dec 2021 15:16:49 -0800 Subject: [PATCH 57/94] work towards group editor --- app/dialog/nodegroup/nodegroupdialog.cpp | 270 +++++++++++++++++++-- app/dialog/nodegroup/nodegroupdialog.h | 23 +- app/node/graph.cpp | 4 - app/node/group/group.cpp | 42 +--- app/node/group/group.h | 70 ------ app/panel/node/node.cpp | 32 +-- app/panel/node/node.h | 41 ++-- app/panel/param/param.cpp | 4 +- app/panel/param/param.h | 5 + app/render/previewautocacher.cpp | 5 + app/undo/undocommand.cpp | 38 ++- app/undo/undocommand.h | 6 +- app/widget/nodeparamview/nodeparamview.cpp | 18 +- app/widget/nodeparamview/nodeparamview.h | 10 + app/widget/nodeview/CMakeLists.txt | 2 + app/widget/nodeview/nodeview.cpp | 38 ++- app/widget/nodeview/nodeview.h | 8 + app/widget/nodeview/nodeviewcontext.cpp | 13 +- app/widget/nodeview/nodeviewscene.cpp | 11 - app/widget/nodeview/nodeviewscene.h | 3 +- app/widget/nodeview/nodewidget.cpp | 51 ++++ app/widget/nodeview/nodewidget.h | 57 +++++ app/window/mainwindow/mainwindow.cpp | 16 -- 23 files changed, 500 insertions(+), 267 deletions(-) create mode 100644 app/widget/nodeview/nodewidget.cpp create mode 100644 app/widget/nodeview/nodewidget.h diff --git a/app/dialog/nodegroup/nodegroupdialog.cpp b/app/dialog/nodegroup/nodegroupdialog.cpp index 951c45339..90742f176 100644 --- a/app/dialog/nodegroup/nodegroupdialog.cpp +++ b/app/dialog/nodegroup/nodegroupdialog.cpp @@ -23,10 +23,12 @@ #include #include #include +#include #include -#include "widget/nodeparamview/nodeparamview.h" -#include "widget/nodeview/nodeview.h" +#include "panel/node/node.h" +#include "panel/panelmanager.h" +#include "panel/param/param.h" namespace olive { @@ -35,7 +37,7 @@ namespace olive { NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : super(parent), group_(group), - parent_undo_(nullptr) + prepend_undo_(nullptr) { QGridLayout *layout = new QGridLayout(this); @@ -44,29 +46,26 @@ NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : layout->addWidget(new QLabel(tr("Name:")), row, 0); name_edit_ = new QLineEdit(); + name_edit_->setText(group->GetLabel()); layout->addWidget(name_edit_, row, 1); row++; - QSplitter *splitter = new QSplitter(Qt::Horizontal); + QMainWindow *splitter = new QMainWindow(); + QWidget *cw = new QWidget(); + cw->setFixedSize(0, 0); + splitter->setCentralWidget(cw); layout->addWidget(splitter, row, 0, 1, 2); - NodeParamView *param_view = new NodeParamView(false); - param_view->SetCreateCheckBoxes(kCheckBoxesOnNonConnected); - splitter->addWidget(param_view); + ParamPanel *param_view = PanelManager::instance()->CreatePanel(splitter); + param_view->GetParamView()->SetIgnoreNodeFlags(true); + param_view->GetParamView()->SetCreateCheckBoxes(kCheckBoxesOnNonConnected); + splitter->addDockWidget(Qt::LeftDockWidgetArea, param_view); - NodeView *node_view = new NodeView(); - node_view->SetContexts({group}); - QMetaObject::invokeMethod(node_view, &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); - splitter->addWidget(node_view); - - splitter->setSizes({splitter->width() / 4, splitter->width() / 4 * 3}); - - for (auto it=group->GetInputPassthroughs().cbegin(); it!=group->GetInputPassthroughs().cend(); it++) { - param_view->SetInputChecked(it.value(), true); - } - - param_view->SetContexts({group}); + NodePanel *node_view = PanelManager::instance()->CreatePanel(splitter); + node_view->GetNodeWidget()->view()->OverrideUndoStack(&undo_stack_); + QMetaObject::invokeMethod(node_view->GetNodeWidget()->view(), &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); + splitter->addDockWidget(Qt::RightDockWidgetArea, node_view); row++; @@ -76,24 +75,245 @@ NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : connect(btns, &QDialogButtonBox::rejected, this, &NodeGroupDialog::reject); layout->addWidget(btns, row, 0, 1, 2); - setWindowTitle(tr("Group Editor")); + setWindowTitle(tr("Group Editor - %1").arg(group->GetLabelOrName())); + + // Create a project + copied_project_ = new Project(); + copied_project_->setParent(this); + + // Copy project nodes + // NOTE: I hate this. What might be better is to fold the "ColorManager" and "ProjectSettings" + // nodes into "Project" inputs and make "Project" a node too. + for (int i=0; inodes().size(); i++) { + Node *ours = copied_project_->nodes().at(i); + Node *theirs = group_->project()->nodes().at(i); + + copy_subnodes_.insert(theirs, ours); + Node::CopyInputs(ours, theirs, false); + } + + // Copy group and nodes + copy_group_ = static_cast(group_->copy()); + copy_group_->setParent(copied_project_); + + // Copy subnodes with positions + const Node::PositionMap &map = group_->GetContextPositions(); + for (auto it=map.cbegin(); it!=map.cend(); it++) { + Node *copy = it.key()->copy(); + copy->SetUUID(it.key()->GetUUID()); + copy->setParent(copied_project_); + + copy_group_->SetNodePositionInContext(copy, it.value()); + copy_subnodes_.insert(it.key(), copy); + } + + for (auto it=map.cbegin(); it!=map.cend(); it++) { + Node::CopyInputs(it.key(), copy_subnodes_.value(it.key()), false); + } + + // Copy edges + for (auto it=copy_subnodes_.cbegin(); it!=copy_subnodes_.cend(); it++) { + Node *src = it.key(); + Node *cpy = it.value(); + + for (auto jt=src->input_connections().cbegin(); jt!=src->input_connections().cend(); jt++) { + if (Node *cpy_output = copy_subnodes_.value(jt->second)) { + Node::ConnectEdge(cpy_output, NodeInput(cpy, jt->first.input(), jt->first.element())); + copied_edges_.append({cpy_output, NodeInput(cpy, jt->first.input(), jt->first.element())}); + } + } + } + + node_view->SetContexts({copy_group_}); + + for (auto it=group_->GetInputPassthroughs().cbegin(); it!=group_->GetInputPassthroughs().cend(); it++) { + const NodeInput &src_input = it.value(); + NodeInput copy_input(copy_subnodes_.value(src_input.node()), src_input.input(), src_input.element()); + param_view->GetParamView()->SetInputChecked(copy_input, true); + } + + param_view->SetContexts({copy_group_}); } void NodeGroupDialog::accept() { + // First, validate if a connection, deleted node, or disabled passthrough is going to disconnect + // something elsewhere in the group. Ask the user to confirm if so. + + // Detect removed nodes + QVector nodes_to_delete = GetNodesToDelete(); + + // Detect new connections + QVector edges_to_connect = GetNewConnections(); + + // Warn user if operation will disconnect a node outside the group + if (OperationWillAffectOutsideGroup(nodes_to_delete, edges_to_connect)) { + if (QMessageBox::question(this, QString(), tr("This operation will disconnect nodes outside of this group. Do you wish to continue?"), QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) { + return; + } + } + + NodeViewDeleteCommand *delete_command = new NodeViewDeleteCommand(); + + // Remove deleted nodes + foreach (Node *n, nodes_to_delete) { + delete_command->AddNode(n, group_); + } + + // Disconnect old edges + foreach (const Node::OutputConnection &edge, copied_edges_) { + bool found = false; + const NodeInput ©_input = edge.second; + + for (auto it=edge.first->output_connections().cbegin(); it!=edge.first->output_connections().cend(); it++) { + if (it->second == copy_input) { + found = true; + break; + } + } + + if (!found) { + // Edge has been disconnected + delete_command->AddEdge(copy_subnodes_.key(edge.first), NodeInput(copy_subnodes_.key(copy_input.node()), copy_input.input(), copy_input.element())); + } + } + + delete_command->redo_now(); + + // Add new nodes + MultiUndoCommand *add_command = new MultiUndoCommand(); + for (auto it=copy_group_->GetContextPositions().cbegin(); it!=copy_group_->GetContextPositions().cend(); it++) { + Node *n = it.key(); + Node *original = copy_subnodes_.key(n); + if (!original) { + // If it's a new node that isn't even in the project yet, add it + original = n->copy(); + Node::CopyInputs(n, original, false); + add_command->add_child(new NodeAddCommand(group_->parent(), original)); + copy_subnodes_.insert(original, n); + } + + // Update position in context + add_command->add_child(new NodeSetPositionCommand(original, group_, it.value())); + } + add_command->redo_now(); + + // Connect new edges + MultiUndoCommand *connect_command = new MultiUndoCommand(); + edges_to_connect = GetNewConnections(); // Update list with new nodes made above if necessary + foreach (const Node::OutputConnection &edge, edges_to_connect) { + connect_command->add_child(new NodeEdgeAddCommand(edge.first, edge.second)); + } + connect_command->redo_now(); + MultiUndoCommand *command = new MultiUndoCommand(); + if (prepend_undo_) { + command->add_child(prepend_undo_); + } + + command->add_child(delete_command); + command->add_child(add_command); + command->add_child(connect_command); + + // Update group name if (name_edit_->text() != group_->GetCustomName()) { command->add_child(new NodeGroupSetCustomNameCommand(group_, name_edit_->text())); } - if (parent_undo_) { - parent_undo_->add_child(command); - } else { - Core::instance()->undo_stack()->push(command); - } + Core::instance()->undo_stack()->push(command); super::accept(); } +void NodeGroupDialog::reject() +{ + if (prepend_undo_) { + prepend_undo_->undo_now(); + delete prepend_undo_; + } + + super::reject(); +} + +QVector NodeGroupDialog::GetNodesToDelete() +{ + QVector nodes_to_delete; + + for (auto it=group_->GetContextPositions().cbegin(); it!=group_->GetContextPositions().cend(); it++) { + Node *original = it.key(); + Node *copy = copy_subnodes_.value(original); + + if (!copy_group_->ContextContainsNode(copy)) { + nodes_to_delete.append(original); + } + } + + return nodes_to_delete; +} + +QVector NodeGroupDialog::GetNewConnections() +{ + QVector edges_to_connect; + + for (auto it=copy_group_->GetContextPositions().cbegin(); it!=copy_group_->GetContextPositions().cend(); it++) { + const Node::InputConnections &ic = it.key()->input_connections(); + + for (auto jt=ic.cbegin(); jt!=ic.cend(); jt++) { + // All connections inside this dialog will be valid and inside the group + const NodeInput &copied_input = jt->first; + Node *copied_output = jt->second; + + NodeInput original_input(copy_subnodes_.key(copied_input.node()), copied_input.input(), copied_input.element()); + Node *original_output = copy_subnodes_.key(copied_output); + + bool found = false; + + if (original_output) { + for (auto kt=original_output->output_connections().cbegin(); kt!=original_output->output_connections().cend(); kt++) { + if (kt->second == original_input) { + found = true; + break; + } + } + } + + if (!found) { + edges_to_connect.append({original_output, original_input}); + } + } + } + + return edges_to_connect; +} + +bool NodeGroupDialog::OperationWillAffectOutsideGroup(const QVector &deleted, const QVector &connections) +{ + // Check if a node to be deleted inputs from a node outside the group + foreach (Node *n, deleted) { + for (auto jt=n->input_connections().cbegin(); jt!=n->input_connections().cend(); jt++) { + if (!group_->ContextContainsNode(jt->second)) { + return true; + } + } + + for (auto jt=n->output_connections().cbegin(); jt!=n->output_connections().cend(); jt++) { + if (!group_->ContextContainsNode(jt->second.node())) { + return true; + } + } + } + + // Check if a new connection will overwrite a connection from outside the group + foreach (const Node::OutputConnection &edge, connections) { + if (Node *current_conn = edge.second.GetConnectedOutput()) { + if (!group_->ContextContainsNode(current_conn)) { + return true; + } + } + } + + return false; +} + } diff --git a/app/dialog/nodegroup/nodegroupdialog.h b/app/dialog/nodegroup/nodegroupdialog.h index 741f0ac55..d18b7fd10 100644 --- a/app/dialog/nodegroup/nodegroupdialog.h +++ b/app/dialog/nodegroup/nodegroupdialog.h @@ -23,8 +23,10 @@ #include #include +#include #include "node/group/group.h" +#include "undo/undostack.h" namespace olive { @@ -34,22 +36,37 @@ class NodeGroupDialog : public QDialog public: explicit NodeGroupDialog(NodeGroup *group, QWidget *parent = nullptr); - void SetParentUndoCommand(MultiUndoCommand *c) + void PrependUndoCommand(MultiUndoCommand *c) { - parent_undo_ = c; + prepend_undo_ = c; } public slots: virtual void accept() override; + virtual void reject() override; + signals: private: + QVector GetNodesToDelete(); + + QVector GetNewConnections(); + + bool OperationWillAffectOutsideGroup(const QVector &deleted, const QVector &connections); + NodeGroup *group_; QLineEdit *name_edit_; - MultiUndoCommand *parent_undo_; + MultiUndoCommand *prepend_undo_; + + Project *copied_project_; + NodeGroup *copy_group_; + QMap copy_subnodes_; + QVector copied_edges_; + + UndoStack undo_stack_; }; diff --git a/app/node/graph.cpp b/app/node/graph.cpp index 024cebf98..f9a68c500 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -105,10 +105,6 @@ void NodeGraph::childEvent(QChildEvent *event) // Remove from any contexts foreach (Node *context, node_children_) { context->RemoveNodeFromContext(node); - - if (NodeGroup *group = dynamic_cast(context)) { - group->RemoveNode(node); - } } } } diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp index f8e44790f..be92d9be8 100644 --- a/app/node/group/group.cpp +++ b/app/node/group/group.cpp @@ -55,28 +55,14 @@ QString NodeGroup::Description() const void NodeGroup::Retranslate() { - foreach (Node *n, nodes_) { - n->Retranslate(); - } -} - -void NodeGroup::AddNode(Node *node) -{ - nodes_.append(node); - - emit NodeAddedToGroup(node); -} - -void NodeGroup::RemoveNode(Node *node) -{ - if (nodes_.removeOne(node)) { - emit NodeRemovedFromGroup(node); + for (auto it=GetContextPositions().cbegin(); it!=GetContextPositions().cend(); it++) { + it.key()->Retranslate(); } } void NodeGroup::AddInputPassthrough(const NodeInput &input) { - Q_ASSERT(nodes_.contains(input.node())); + Q_ASSERT(ContextContainsNode(input.node())); for (auto it=input_passthroughs_.cbegin(); it!=input_passthroughs_.cend(); it++) { if (it.value() == input) { @@ -109,7 +95,7 @@ void NodeGroup::RemoveInputPassthrough(const NodeInput &input) void NodeGroup::SetOutputPassthrough(Node *node) { - Q_ASSERT(!node || nodes_.contains(node)); + Q_ASSERT(!node || ContextContainsNode(node)); output_passthrough_ = node; @@ -145,16 +131,6 @@ QString NodeGroup::GetInputName(const QString &id) const return input_passthroughs_.value(id).name(); } -void NodeAddToGroupCommand::redo() -{ - group_->AddNode(node_); -} - -void NodeAddToGroupCommand::undo() -{ - group_->RemoveNode(node_); -} - void NodeGroupSetCustomNameCommand::redo() { old_name_ = group_->GetCustomName(); @@ -183,16 +159,6 @@ void NodeGroupAddInputPassthrough::undo() } } -void NodeRemoveFromGroupCommand::redo() -{ - group_->RemoveNode(node_); -} - -void NodeRemoveFromGroupCommand::undo() -{ - group_->AddNode(node_); -} - void NodeGroupSetOutputPassthrough::redo() { old_output_ = group_->GetOutputPassthrough(); diff --git a/app/node/group/group.h b/app/node/group/group.h index 31e07ab07..d023a7416 100644 --- a/app/node/group/group.h +++ b/app/node/group/group.h @@ -41,20 +41,6 @@ public: virtual void Retranslate() override; - void AddNode(Node *node); - - void RemoveNode(Node *node); - - bool ContainsNode(Node *node) const - { - return nodes_.contains(node); - } - - const QVector &GetNodes() const - { - return nodes_; - } - void AddInputPassthrough(const NodeInput &input); void RemoveInputPassthrough(const NodeInput &input); @@ -96,10 +82,6 @@ public: virtual QString GetInputName(const QString& id) const override; signals: - void NodeAddedToGroup(Node *node); - - void NodeRemovedFromGroup(Node *node); - void InputPassthroughAdded(NodeGroup *group, const NodeInput &input); void InputPassthroughRemoved(NodeGroup *group, const NodeInput &input); @@ -107,8 +89,6 @@ signals: void OutputPassthroughChanged(NodeGroup *group, Node *output); private: - QVector nodes_; - QHash input_passthroughs_; Node *output_passthrough_; @@ -117,56 +97,6 @@ private: }; -class NodeAddToGroupCommand : public UndoCommand -{ -public: - NodeAddToGroupCommand(Node *node, NodeGroup *group) : - node_(node), - group_(group) - {} - - virtual Project * GetRelevantProject() const override - { - return node_->project(); - } - -protected: - virtual void redo() override; - - virtual void undo() override; - -private: - Node *node_; - - NodeGroup *group_; - -}; - -class NodeRemoveFromGroupCommand : public UndoCommand -{ -public: - NodeRemoveFromGroupCommand(Node *node, NodeGroup *group) : - node_(node), - group_(group) - {} - - virtual Project * GetRelevantProject() const override - { - return node_->project(); - } - -protected: - virtual void redo() override; - - virtual void undo() override; - -private: - Node *node_; - - NodeGroup *group_; - -}; - class NodeGroupSetCustomNameCommand : public UndoCommand { public: diff --git a/app/panel/node/node.cpp b/app/panel/node/node.cpp index 3f0e3a4a2..6e4b842e6 100644 --- a/app/panel/node/node.cpp +++ b/app/panel/node/node.cpp @@ -20,40 +20,20 @@ #include "node.h" -#include - namespace olive { NodePanel::NodePanel(QWidget *parent) : PanelWidget(QStringLiteral("NodePanel"), parent) { - QWidget *outer_widget = new QWidget(this); + node_widget_ = new NodeWidget(); + connect(this, &NodePanel::visibilityChanged, node_widget_->view(), &NodeView::CenterOnItemsBoundingRect); - QVBoxLayout *outer_layout = new QVBoxLayout(outer_widget); - outer_layout->setMargin(0); - - toolbar_ = new NodeViewToolBar(); - outer_layout->addWidget(toolbar_); - - // Create NodeView widget - node_view_ = new NodeView(this); - outer_layout->addWidget(node_view_); - connect(this, &NodePanel::visibilityChanged, node_view_, &NodeView::CenterOnItemsBoundingRect); - - // Connect toolbar to NodeView - connect(toolbar_, &NodeViewToolBar::MiniMapEnabledToggled, node_view_, &NodeView::SetMiniMapEnabled); - connect(toolbar_, &NodeViewToolBar::AddNodeClicked, node_view_, &NodeView::ShowAddMenu); - - // Set defaults - toolbar_->SetMiniMapEnabled(true); - node_view_->SetMiniMapEnabled(true); - - // Connect node view signals to this panel - connect(node_view_, &NodeView::NodesSelected, this, &NodePanel::NodesSelected); - connect(node_view_, &NodeView::NodesDeselected, this, &NodePanel::NodesDeselected); + // Connect node view signals to this panel - MAY REMOVE + connect(node_widget_->view(), &NodeView::NodesSelected, this, &NodePanel::NodesSelected); + connect(node_widget_->view(), &NodeView::NodesDeselected, this, &NodePanel::NodesDeselected); // Set it as the main widget of this panel - SetWidgetWithPadding(outer_widget); + SetWidgetWithPadding(node_widget_); // Set strings Retranslate(); diff --git a/app/panel/node/node.h b/app/panel/node/node.h index 20eccc79a..083445029 100644 --- a/app/panel/node/node.h +++ b/app/panel/node/node.h @@ -21,8 +21,7 @@ #ifndef NODEPANEL_H #define NODEPANEL_H -#include "widget/nodeview/nodeview.h" -#include "widget/nodeview/nodeviewtoolbar.h" +#include "widget/nodeview/nodewidget.h" #include "widget/panel/panel.h" namespace olive { @@ -36,76 +35,80 @@ class NodePanel : public PanelWidget public: NodePanel(QWidget* parent); + NodeWidget *GetNodeWidget() const + { + return node_widget_; + } + void SetContexts(const QVector &nodes) { - node_view_->SetContexts(nodes); - toolbar_->setEnabled(!nodes.isEmpty()); + node_widget_->SetContexts(nodes); } void CloseContextsBelongingToProject(Project *project) { - node_view_->CloseContextsBelongingToProject(project); + node_widget_->view()->CloseContextsBelongingToProject(project); } const QVector &GetCurrentContexts() const { - return node_view_->GetCurrentContexts(); + return node_widget_->view()->GetCurrentContexts(); } virtual void SelectAll() override { - node_view_->SelectAll(); + node_widget_->view()->SelectAll(); } virtual void DeselectAll() override { - node_view_->DeselectAll(); + node_widget_->view()->DeselectAll(); } virtual void DeleteSelected() override { - node_view_->DeleteSelected(); + node_widget_->view()->DeleteSelected(); } virtual void CutSelected() override { - node_view_->CopySelected(true); + node_widget_->view()->CopySelected(true); } virtual void CopySelected() override { - node_view_->CopySelected(false); + node_widget_->view()->CopySelected(false); } virtual void Paste() override { - node_view_->Paste(); + node_widget_->view()->Paste(); } virtual void Duplicate() override { - node_view_->Duplicate(); + node_widget_->view()->Duplicate(); } virtual void SetColorLabel(int index) override { - node_view_->SetColorLabel(index); + node_widget_->view()->SetColorLabel(index); } virtual void ZoomIn() override { - node_view_->ZoomIn(); + node_widget_->view()->ZoomIn(); } virtual void ZoomOut() override { - node_view_->ZoomOut(); + node_widget_->view()->ZoomOut(); } public slots: void Select(const QVector& nodes, bool center_view_on_item) { - node_view_->Select(nodes, center_view_on_item); + node_widget_->view()->Select(nodes, center_view_on_item); } signals: @@ -119,9 +122,7 @@ private: SetTitle(tr("Node Editor")); } - NodeView* node_view_; - - NodeViewToolBar *toolbar_; + NodeWidget *node_widget_; }; diff --git a/app/panel/param/param.cpp b/app/panel/param/param.cpp index bf44d5db3..90c2c1983 100644 --- a/app/panel/param/param.cpp +++ b/app/panel/param/param.cpp @@ -37,12 +37,12 @@ ParamPanel::ParamPanel(QWidget* parent) : void ParamPanel::SelectNodes(const QVector &nodes) { - //static_cast(GetTimeBasedWidget())->SelectNodes(nodes); + static_cast(GetTimeBasedWidget())->SelectNodes(nodes); } void ParamPanel::DeselectNodes(const QVector &nodes) { - //static_cast(GetTimeBasedWidget())->DeselectNodes(nodes); + static_cast(GetTimeBasedWidget())->DeselectNodes(nodes); } void ParamPanel::DeleteSelected() diff --git a/app/panel/param/param.h b/app/panel/param/param.h index e298ccf3e..4389bc15f 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -33,6 +33,11 @@ class ParamPanel : public TimeBasedPanel public: ParamPanel(QWidget* parent); + NodeParamView *GetParamView() const + { + return static_cast(GetTimeBasedWidget()); + } + public slots: void SelectNodes(const QVector& nodes); void DeselectNodes(const QVector& nodes); diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 75b78c3c5..887c8b578 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -367,6 +367,11 @@ void PreviewAutoCacher::ProcessUpdateQueue() void PreviewAutoCacher::AddNode(Node *node) { + if (dynamic_cast(node)) { + // Group nodes are just dummy nodes, no need to copy them + return; + } + // Copy node Node* copy = node->copy(); diff --git a/app/undo/undocommand.cpp b/app/undo/undocommand.cpp index 075348c15..f182f86f4 100644 --- a/app/undo/undocommand.cpp +++ b/app/undo/undocommand.cpp @@ -24,34 +24,24 @@ namespace olive { -MultiUndoCommand::MultiUndoCommand() : - done_(false) -{ -} - void MultiUndoCommand::redo() { - if (!done_) { - for (auto it=children_.cbegin(); it!=children_.cend(); it++) { - (*it)->redo_and_set_modified(); - } - done_ = true; + for (auto it=children_.cbegin(); it!=children_.cend(); it++) { + (*it)->redo_and_set_modified(); } } void MultiUndoCommand::undo() { - if (done_) { - for (auto it=children_.crbegin(); it!=children_.crend(); it++) { - (*it)->undo_and_set_modified(); - } - done_ = false; + for (auto it=children_.crbegin(); it!=children_.crend(); it++) { + (*it)->undo_and_set_modified(); } } UndoCommand::UndoCommand() { prepared_ = false; + done_ = false; } void UndoCommand::redo_and_set_modified() @@ -76,17 +66,23 @@ void UndoCommand::undo_and_set_modified() void UndoCommand::redo_now() { - if (!prepared_) { - prepare(); - prepared_ = true; - } + if (!done_) { + if (!prepared_) { + prepare(); + prepared_ = true; + } - redo(); + redo(); + done_ = true; + } } void UndoCommand::undo_now() { - undo(); + if (done_) { + undo(); + done_ = false; + } } } diff --git a/app/undo/undocommand.h b/app/undo/undocommand.h index 94d4d3e8f..bd6032da2 100644 --- a/app/undo/undocommand.h +++ b/app/undo/undocommand.h @@ -75,12 +75,14 @@ private: bool prepared_; + bool done_; + }; class MultiUndoCommand : public UndoCommand { public: - MultiUndoCommand(); + MultiUndoCommand() = default; virtual Project* GetRelevantProject() const override { @@ -109,8 +111,6 @@ protected: private: std::vector children_; - bool done_; - }; } diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index b04f41011..86732d37c 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -38,7 +38,8 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : last_scroll_val_(0), focused_node_(nullptr), create_checkboxes_(kNoCheckBoxes), - time_target_(nullptr) + time_target_(nullptr), + ignore_flags_(false) { // Create horizontal layout to place scroll area in (and keyframe editing eventually) QHBoxLayout* layout = new QHBoxLayout(this); @@ -268,7 +269,7 @@ void NodeParamView::SetContexts(const QVector &contexts) item->setVisible(true); for (auto it=ctx->GetContextPositions().cbegin(); it!=ctx->GetContextPositions().cend(); it++) { - if (!(it.key()->GetFlags() & Node::kDontShowInParamView)) { + if (!(it.key()->GetFlags() & Node::kDontShowInParamView) || ignore_flags_) { AddNode(it.key(), item); } } @@ -351,6 +352,16 @@ void NodeParamView::DeleteSelected() } } +void NodeParamView::SelectNodes(const QVector &nodes) +{ + // Do nothing, this is a placeholder if we ever need this to do anything in the future +} + +void NodeParamView::DeselectNodes(const QVector &nodes) +{ + // Do nothing, this is a placeholder if we ever need this to do anything in the future +} + void NodeParamView::UpdateItemTime(const rational &time) { foreach (NodeParamViewContext* item, context_items_) { @@ -543,6 +554,9 @@ void NodeParamView::UpdateElementY() int y = it.value()->GetElementY(ic); + // For some reason Qt's mapToGlobal doesn't seem to handle this, so we offset here + y += vertical_scrollbar_->value(); + const KeyframeView::InputConnections &input_con = connections.value(input); int use_index = i + 1; if (use_index < input_con.size()) { diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index d9f7985de..29110deca 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -69,6 +69,14 @@ public: keyframe_view_->DeselectAll(); } + void SetIgnoreNodeFlags(bool e) + { + ignore_flags_ = e; + } + + void SelectNodes(const QVector &nodes); + void DeselectNodes(const QVector &nodes); + public slots: void SetInputChecked(const NodeInput &input, bool e); @@ -123,6 +131,8 @@ private: QHash input_checked_; + bool ignore_flags_; + private slots: void UpdateGlobalScrollBar(); diff --git a/app/widget/nodeview/CMakeLists.txt b/app/widget/nodeview/CMakeLists.txt index 12512695f..cf3a2375e 100644 --- a/app/widget/nodeview/CMakeLists.txt +++ b/app/widget/nodeview/CMakeLists.txt @@ -35,5 +35,7 @@ set(OLIVE_SOURCES widget/nodeview/nodeviewtoolbar.h widget/nodeview/nodeviewundo.cpp widget/nodeview/nodeviewundo.h + widget/nodeview/nodewidget.cpp + widget/nodeview/nodewidget.h PARENT_SCOPE ) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index c6a3c9746..d0e85fc37 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -26,7 +26,6 @@ #include #include -#include "core.h" #include "dialog/nodegroup/nodegroupdialog.h" #include "nodeviewundo.h" #include "node/audio/volume/volume.h" @@ -50,6 +49,7 @@ NodeView::NodeView(QWidget *parent) : create_edge_output_item_(nullptr), create_edge_input_item_(nullptr), paste_command_(nullptr), + undo_stack_(Core::instance()->undo_stack()), scale_(1.0) { setScene(&scene_); @@ -127,7 +127,13 @@ void NodeView::ClearGraph() void NodeView::DeleteSelected() { - scene_.DeleteSelected(); + NodeViewDeleteCommand* command = new NodeViewDeleteCommand(); + + foreach (NodeViewContext *ctx, scene_.context_map()) { + ctx->DeleteSelected(command); + } + + undo_stack_->push(command); } void NodeView::SelectAll() @@ -221,7 +227,7 @@ void NodeView::SetColorLabel(int index) command->add_child(new NodeOverrideColorCommand(node, index)); } - Core::instance()->undo_stack()->push(command); + undo_stack_->push(command); } void NodeView::ZoomIn() @@ -276,7 +282,7 @@ void NodeView::keyPressEvent(QKeyEvent *event) } } } - Core::instance()->undo_stack()->pushIfHasChildren(pos_command); + undo_stack_->pushIfHasChildren(pos_command); break; } case Qt::Key_Escape: @@ -555,7 +561,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } create_edge_expanded_items_.clear(); - Core::instance()->undo_stack()->pushIfHasChildren(command); + undo_stack_->pushIfHasChildren(command); } MultiUndoCommand* command = new MultiUndoCommand(); @@ -639,7 +645,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } dragging_items_.clear(); - Core::instance()->undo_stack()->pushIfHasChildren(command); + undo_stack_->pushIfHasChildren(command); super::mouseReleaseEvent(event); } @@ -1199,7 +1205,6 @@ void NodeView::GroupNodes() DeselectAll(); foreach (Node *n, nodes_to_group) { command->add_child(new NodeRemovePositionFromContextCommand(n, context)); - command->add_child(new NodeAddToGroupCommand(n, group)); command->add_child(new NodeSetPositionCommand(n, group, context->GetNodePositionDataInContext(n))); for (auto it=n->inputs().cbegin(); it!=n->inputs().cend(); it++) { @@ -1232,14 +1237,8 @@ void NodeView::GroupNodes() command->redo_now(); NodeGroupDialog ngd(group, this); - if (ngd.exec() == QDialog::Accepted) { - // Push to stack so it can be undone (MultiUndoCommand will ignore the request to redo again) - Core::instance()->undo_stack()->push(command); - } else { - // Undo command and delete - command->undo_now(); - delete command; - } + ngd.PrependUndoCommand(command); + ngd.exec(); } void NodeView::UngroupNodes() @@ -1269,13 +1268,12 @@ void NodeView::UngroupNodes() command->add_child(new NodeRemovePositionFromContextCommand(group, context)); command->add_child(new NodeRemoveAndDisconnectCommand(group)); - foreach (Node *n, group->GetNodes()) { - command->add_child(new NodeRemovePositionFromContextCommand(n, group)); - command->add_child(new NodeRemoveFromGroupCommand(n, group)); - command->add_child(new NodeSetPositionCommand(n, context, group->GetNodePositionDataInContext(n))); + for (auto it=group->GetContextPositions().cbegin(); it!=group->GetContextPositions().cend(); it++) { + command->add_child(new NodeRemovePositionFromContextCommand(it.key(), group)); + command->add_child(new NodeSetPositionCommand(it.key(), context, group->GetNodePositionDataInContext(it.key()))); } - Core::instance()->undo_stack()->push(command); + undo_stack_->push(command); } void NodeView::ShowNodeProperties() diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 8e34bba99..02118ba45 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -24,6 +24,7 @@ #include #include +#include "core.h" #include "node/graph.h" #include "node/nodecopypaste.h" #include "nodeviewedge.h" @@ -76,6 +77,11 @@ public: void ZoomOut(); + void OverrideUndoStack(UndoStack *stack) + { + undo_stack_ = stack; + } + const QVector &GetCurrentContexts() const { return contexts_; @@ -189,6 +195,8 @@ private: QMap dragging_items_; + UndoStack *undo_stack_; + double scale_; static const double kMinimumScale; diff --git a/app/widget/nodeview/nodeviewcontext.cpp b/app/widget/nodeview/nodeviewcontext.cpp index 59c640634..8ded8fdc3 100644 --- a/app/widget/nodeview/nodeviewcontext.cpp +++ b/app/widget/nodeview/nodeviewcontext.cpp @@ -56,13 +56,13 @@ void NodeViewContext::AddChild(Node *node) AddNodeInternal(node, item); if (NodeGroup *group = dynamic_cast(node)) { - foreach (Node *n, group->GetNodes()) { + for (auto it=group->GetContextPositions().cbegin(); it!=group->GetContextPositions().cend(); it++) { // Use this item as the representative for all of these nodes too - AddNodeInternal(n, item); + AddNodeInternal(it.key(), item); } - connect(group, &NodeGroup::NodeAddedToGroup, this, &NodeViewContext::GroupAddedNode); - connect(group, &NodeGroup::NodeRemovedFromGroup, this, &NodeViewContext::GroupRemovedNode); + connect(group, &NodeGroup::NodeAddedToContext, this, &NodeViewContext::GroupAddedNode); + connect(group, &NodeGroup::NodeRemovedFromContext, this, &NodeViewContext::GroupRemovedNode); } UpdateRect(); @@ -78,6 +78,11 @@ void NodeViewContext::RemoveChild(Node *node) disconnect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected); disconnect(node, &Node::InputDisconnected, this, &NodeViewContext::ChildInputDisconnected); + if (NodeGroup *group = dynamic_cast(node)) { + disconnect(group, &NodeGroup::NodeAddedToContext, this, &NodeViewContext::GroupAddedNode); + disconnect(group, &NodeGroup::NodeRemovedFromContext, this, &NodeViewContext::GroupRemovedNode); + } + NodeViewItem *item = item_map_.take(node); // Delete edges first because the edge destructor will try to reference item (maybe that should diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index 37a226710..dc76509cc 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -58,17 +58,6 @@ void NodeViewScene::DeselectAll() } } -void NodeViewScene::DeleteSelected() -{ - NodeViewDeleteCommand* command = new NodeViewDeleteCommand(); - - foreach (NodeViewContext *ctx, context_map_) { - ctx->DeleteSelected(command); - } - - Core::instance()->undo_stack()->push(command); -} - QVector NodeViewScene::GetSelectedItems() const { QVector items; diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index 467109923..64a149bd3 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -28,6 +28,7 @@ #include "nodeviewcontext.h" #include "nodeviewedge.h" #include "nodeviewitem.h" +#include "undo/undostack.h" namespace olive { @@ -40,8 +41,6 @@ public: void SelectAll(); void DeselectAll(); - void DeleteSelected(); - QVector GetSelectedItems() const; const QHash &context_map() const diff --git a/app/widget/nodeview/nodewidget.cpp b/app/widget/nodeview/nodewidget.cpp new file mode 100644 index 000000000..4fdb2dd06 --- /dev/null +++ b/app/widget/nodeview/nodewidget.cpp @@ -0,0 +1,51 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "nodewidget.h" + +#include + +namespace olive { + +NodeWidget::NodeWidget(QWidget *parent) : + QWidget(parent) +{ + QVBoxLayout *outer_layout = new QVBoxLayout(this); + outer_layout->setMargin(0); + + toolbar_ = new NodeViewToolBar(); + outer_layout->addWidget(toolbar_); + + // Create NodeView widget + node_view_ = new NodeView(this); + outer_layout->addWidget(node_view_); + + // Connect toolbar to NodeView + connect(toolbar_, &NodeViewToolBar::MiniMapEnabledToggled, node_view_, &NodeView::SetMiniMapEnabled); + connect(toolbar_, &NodeViewToolBar::AddNodeClicked, node_view_, &NodeView::ShowAddMenu); + + // Set defaults + toolbar_->SetMiniMapEnabled(true); + node_view_->SetMiniMapEnabled(true); + + setSizePolicy(node_view_->sizePolicy()); +} + +} diff --git a/app/widget/nodeview/nodewidget.h b/app/widget/nodeview/nodewidget.h new file mode 100644 index 000000000..47762f499 --- /dev/null +++ b/app/widget/nodeview/nodewidget.h @@ -0,0 +1,57 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef NODEWIDGET_H +#define NODEWIDGET_H + +#include + +#include "nodeview.h" +#include "nodeviewtoolbar.h" + +namespace olive { + +class NodeWidget : public QWidget +{ + Q_OBJECT +public: + NodeWidget(QWidget *parent = nullptr); + + NodeView *view() const + { + return node_view_; + } + + void SetContexts(const QVector &nodes) + { + node_view_->SetContexts(nodes); + toolbar_->setEnabled(!nodes.isEmpty()); + } + +private: + NodeView *node_view_; + + NodeViewToolBar *toolbar_; + +}; + +} + +#endif // NODEWIDGET_H diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index bceb6a935..61eb7ef92 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -220,11 +220,6 @@ void MainWindow::FolderOpen(Project* p, Folder *i, bool floating) { ProjectPanel* panel = PanelManager::instance()->CreatePanel(this); - // Set custom name to distinguish it from regular ProjectPanels - panel->setObjectName(QStringLiteral("FolderPanel")); - - SetUniquePanelID(panel, folder_panels_); - panel->set_project(p); panel->set_root(i); @@ -823,8 +818,6 @@ T *MainWindow::AppendPanelInternal(QList& list) { T* panel = PanelManager::instance()->CreatePanel(this); - SetUniquePanelID(panel, list); - if (!list.isEmpty()) { tabifyDockWidget(list.last(), panel); } @@ -841,20 +834,11 @@ T *MainWindow::AppendPanelInternal(QList& list) return panel; } -template -void MainWindow::SetUniquePanelID(T *panel, const QList &list) -{ - // Set unique object name so it can be identified by QMainWindow's save and restore state functions - panel->setObjectName(panel->objectName().append(QString::number(list.size()))); -} - template T *MainWindow::AppendFloatingPanelInternal(QList &list) { T* panel = PanelManager::instance()->CreatePanel(this); - SetUniquePanelID(panel, list); - panel->setFloating(true); panel->show(); From f8ac3f4e40c19ab4de17cd73c413fc225537ebaf Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 27 Dec 2021 20:30:00 -0800 Subject: [PATCH 58/94] refactor node gizmo release --- app/node/distort/crop/cropdistortnode.cpp | 4 +- app/node/distort/crop/cropdistortnode.h | 2 +- .../transform/transformdistortnode.cpp | 4 +- .../distort/transform/transformdistortnode.h | 2 +- app/node/generator/shape/shapenodebase.cpp | 4 +- app/node/generator/shape/shapenodebase.h | 2 +- app/node/node.cpp | 2 +- app/node/node.h | 2 +- app/panel/panelmanager.cpp | 41 +++++++++++--- app/panel/panelmanager.h | 56 ++++--------------- app/widget/viewer/viewerdisplay.cpp | 4 +- app/window/mainwindow/mainwindow.cpp | 26 ++++----- 12 files changed, 68 insertions(+), 81 deletions(-) diff --git a/app/node/distort/crop/cropdistortnode.cpp b/app/node/distort/crop/cropdistortnode.cpp index 5018a8f5a..009e05310 100644 --- a/app/node/distort/crop/cropdistortnode.cpp +++ b/app/node/distort/crop/cropdistortnode.cpp @@ -233,13 +233,11 @@ void CropDistortNode::GizmoMove(const QPointF &p, const rational &time, const Qt } } -void CropDistortNode::GizmoRelease() +void CropDistortNode::GizmoRelease(MultiUndoCommand *command) { - MultiUndoCommand *command = new MultiUndoCommand(); for (NodeInputDragger& i : gizmo_dragger_) { i.End(command); } - Core::instance()->undo_stack()->push(command); gizmo_dragger_.clear(); gizmo_start_.clear(); diff --git a/app/node/distort/crop/cropdistortnode.h b/app/node/distort/crop/cropdistortnode.h index a20998a7d..24f2c0b50 100644 --- a/app/node/distort/crop/cropdistortnode.h +++ b/app/node/distort/crop/cropdistortnode.h @@ -76,7 +76,7 @@ public: virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p) override; virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override; - virtual void GizmoRelease() override; + virtual void GizmoRelease(MultiUndoCommand *command) override; static const QString kTextureInput; static const QString kLeftInput; diff --git a/app/node/distort/transform/transformdistortnode.cpp b/app/node/distort/transform/transformdistortnode.cpp index df6266a7a..483b9f001 100644 --- a/app/node/distort/transform/transformdistortnode.cpp +++ b/app/node/distort/transform/transformdistortnode.cpp @@ -301,13 +301,11 @@ void TransformDistortNode::GizmoMove(const QPointF &p, const rational &time, con } } -void TransformDistortNode::GizmoRelease() +void TransformDistortNode::GizmoRelease(MultiUndoCommand *command) { - MultiUndoCommand *command = new MultiUndoCommand(); for (NodeInputDragger& i : gizmo_dragger_) { i.End(command); } - Core::instance()->undo_stack()->push(command); gizmo_dragger_.clear(); gizmo_start_.clear(); diff --git a/app/node/distort/transform/transformdistortnode.h b/app/node/distort/transform/transformdistortnode.h index 17bf541a0..6bfe5e2f3 100644 --- a/app/node/distort/transform/transformdistortnode.h +++ b/app/node/distort/transform/transformdistortnode.h @@ -78,7 +78,7 @@ public: virtual bool GizmoPress(const NodeValueRow &row, const NodeGlobals &globals, const QPointF &p) override; virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override; - virtual void GizmoRelease() override; + virtual void GizmoRelease(MultiUndoCommand *command) override; enum AutoScaleType { kAutoScaleNone, diff --git a/app/node/generator/shape/shapenodebase.cpp b/app/node/generator/shape/shapenodebase.cpp index 8211bb180..d5c03b0ac 100644 --- a/app/node/generator/shape/shapenodebase.cpp +++ b/app/node/generator/shape/shapenodebase.cpp @@ -297,13 +297,11 @@ void ShapeNodeBase::GizmoMove(const QPointF &p, const rational &time, const Qt:: } } -void ShapeNodeBase::GizmoRelease() +void ShapeNodeBase::GizmoRelease(MultiUndoCommand *command) { - MultiUndoCommand *command = new MultiUndoCommand(); for (NodeInputDragger& i : gizmo_dragger_) { i.End(command); } - Core::instance()->undo_stack()->push(command); gizmo_dragger_.clear(); } diff --git a/app/node/generator/shape/shapenodebase.h b/app/node/generator/shape/shapenodebase.h index 819d4cb94..7337d5b6c 100644 --- a/app/node/generator/shape/shapenodebase.h +++ b/app/node/generator/shape/shapenodebase.h @@ -49,7 +49,7 @@ public: virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p) override; virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override; - virtual void GizmoRelease() override; + virtual void GizmoRelease(MultiUndoCommand *command) override; private: static QVector2D GenerateGizmoAnchor(const QVector2D &pos, const QVector2D &size, int drag, QVector2D *pt); diff --git a/app/node/node.cpp b/app/node/node.cpp index e76ece9e5..90ee197f9 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -1504,7 +1504,7 @@ void Node::GizmoMove(const QPointF &, const rational&, const Qt::KeyboardModifie { } -void Node::GizmoRelease() +void Node::GizmoRelease(MultiUndoCommand *) { } diff --git a/app/node/node.h b/app/node/node.h index 299e9b5ae..45516fbb8 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -855,7 +855,7 @@ public: virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF& p); virtual void GizmoMove(const QPointF& p, const rational &time, const Qt::KeyboardModifiers &modifiers); - virtual void GizmoRelease(); + virtual void GizmoRelease(MultiUndoCommand *command); const QString& GetLabel() const; void SetLabel(const QString& s); diff --git a/app/panel/panelmanager.cpp b/app/panel/panelmanager.cpp index 779c2be35..e3959bfbf 100644 --- a/app/panel/panelmanager.cpp +++ b/app/panel/panelmanager.cpp @@ -95,6 +95,40 @@ PanelManager *PanelManager::instance() return instance_; } +void PanelManager::RegisterPanel(PanelWidget *panel) +{ + // Add panel to the bottom of the focus history + focus_history_.append(panel); + + panel->SetMovementLocked(locked_); + + // Get panel parent (it's assumed it has one) + QWidget *parent = panel->parentWidget(); + + // Sane default for panel size + panel->resize(parent->size() / 3); + + // We're about to center the panel relative to the parent (usually the main window), but for some + // reason this requires the panel to be shown first. + panel->show(); + + // Center the panel relative to the parent + QPoint parent_center = panel->mapFromGlobal(parent->mapToGlobal(parent->rect().center())); + QPoint panel_center = panel->rect().center(); + panel->move(parent_center - panel_center); + + if (focus_history_.size() == 1) { + // This is the first panel, focus it + panel->SetBorderVisible(true); + emit FocusedPanelChanged(panel); + } +} + +void PanelManager::UnregisterPanel(PanelWidget *panel) +{ + focus_history_.removeOne(panel); +} + void PanelManager::FocusChanged(QWidget *old, QWidget *now) { Q_UNUSED(old) @@ -151,11 +185,4 @@ void PanelManager::SetPanelsLocked(bool locked) locked_ = locked; } -void PanelManager::PanelDestroyed() -{ - PanelWidget* panel = static_cast(sender()); - - focus_history_.removeOne(panel); -} - } diff --git a/app/panel/panelmanager.h b/app/panel/panelmanager.h index 50dfefce6..a71a312a3 100644 --- a/app/panel/panelmanager.h +++ b/app/panel/panelmanager.h @@ -84,12 +84,6 @@ public: */ T* MostRecentlyFocused(); - template - /** - * @brief Create a panel - */ - T* CreatePanel(QWidget* parent); - /** * @brief Get whether panels are currently prevented from moving */ @@ -118,6 +112,16 @@ public: */ QList GetPanelsOfType(); + /** + * @brief Panel should call this upon construction so it can be kept track of + */ + void RegisterPanel(PanelWidget *panel); + + /** + * @brief Panel should call this upon destruction so no invalid pointers will be kept for it + */ + void UnregisterPanel(PanelWidget *panel); + public slots: /** * @brief Connect this to a QApplication's SIGNAL(focusChanged()) @@ -153,48 +157,8 @@ private: */ static PanelManager* instance_; -private slots: - /** - * @brief Processing if a panel gets deleted - */ - void PanelDestroyed(); - }; -template -T *PanelManager::CreatePanel(QWidget *parent) -{ - T* panel = new T(parent); - - // Add panel to the bottom of the focus history - focus_history_.append(panel); - - panel->SetMovementLocked(locked_); - - // Sane default for panel size - panel->resize(parent->size() / 3); - - // We're about to center the panel relative to the parent (usually the main window), but for some - // reason this requires the panel to be shown first. - panel->show(); - - // Center the panel relative to the parent - QPoint parent_center = panel->mapFromGlobal(parent->mapToGlobal(parent->rect().center())); - QPoint panel_center = panel->rect().center(); - panel->move(parent_center - panel_center); - - // Connect destroy signal so we can remove it from focus history - connect(panel, &PanelWidget::destroyed, this, &PanelManager::PanelDestroyed, Qt::DirectConnection); - - if (focus_history_.size() == 1) { - // This is the first panel, focus it - panel->SetBorderVisible(true); - emit FocusedPanelChanged(panel); - } - - return panel; -} - template T* PanelManager::MostRecentlyFocused() { diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 3e21bf120..4c9b21654 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -271,7 +271,9 @@ void ViewerDisplayWidget::mouseReleaseEvent(QMouseEvent *event) } else if (gizmo_click_) { // Handle gizmo - gizmos_->GizmoRelease(); + MultiUndoCommand *command = new MultiUndoCommand(); + gizmos_->GizmoRelease(command); + undo_stack()->pushIfHasChildren(command); gizmo_click_ = false; } else { diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 61eb7ef92..6b4fd9883 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -78,17 +78,17 @@ MainWindow::MainWindow(QWidget *parent) : setStatusBar(status_bar); // Create standard panels - node_panel_ = PanelManager::instance()->CreatePanel(this); - footage_viewer_panel_ = PanelManager::instance()->CreatePanel(this); - param_panel_ = PanelManager::instance()->CreatePanel(this); - curve_panel_ = PanelManager::instance()->CreatePanel(this); - sequence_viewer_panel_ = PanelManager::instance()->CreatePanel(this); - pixel_sampler_panel_ = PanelManager::instance()->CreatePanel(this); + node_panel_ = new NodePanel(undo_stack_, this); + footage_viewer_panel_ = new FootageViewerPanel(undo_stack_, this); + param_panel_ = new ParamPanel(undo_stack_, this); + curve_panel_ = new CurvePanel(undo_stack_, this); + sequence_viewer_panel_ = new SequenceViewerPanel(undo_stack_, this); + pixel_sampler_panel_ = new PixelSamplerPanel(undo_stack_, this); AppendProjectPanel(); - tool_panel_ = PanelManager::instance()->CreatePanel(this); - task_man_panel_ = PanelManager::instance()->CreatePanel(this); + tool_panel_ = new ToolPanel(undo_stack_, this); + task_man_panel_ = new TaskManagerPanel(undo_stack_, this); AppendTimelinePanel(); - audio_monitor_panel_ = PanelManager::instance()->CreatePanel(this); + audio_monitor_panel_ = new AudioMonitorPanel(undo_stack_, this); // Make node-related connections connect(node_panel_, &NodePanel::NodesSelected, param_panel_, &ParamPanel::SelectNodes); @@ -218,7 +218,7 @@ bool MainWindow::IsSequenceOpen(Sequence *sequence) const void MainWindow::FolderOpen(Project* p, Folder *i, bool floating) { - ProjectPanel* panel = PanelManager::instance()->CreatePanel(this); + ProjectPanel* panel = new ProjectPanel(undo_stack_, this); panel->set_project(p); panel->set_root(i); @@ -256,7 +256,7 @@ void MainWindow::OpenNodeInViewer(ViewerOutput *node) viewer_panels_.value(node)->raise(); } else { // Create a viewer for this node - ViewerPanel* viewer = PanelManager::instance()->CreatePanel(this); + ViewerPanel* viewer = new ViewerPanel(undo_stack_, this); viewer->SetSignalInsteadOfClose(true); viewer->setFloating(true); @@ -816,7 +816,7 @@ void MainWindow::showEvent(QShowEvent *e) template T *MainWindow::AppendPanelInternal(QList& list) { - T* panel = PanelManager::instance()->CreatePanel(this); + T* panel = new T(undo_stack_, this); if (!list.isEmpty()) { tabifyDockWidget(list.last(), panel); @@ -837,7 +837,7 @@ T *MainWindow::AppendPanelInternal(QList& list) template T *MainWindow::AppendFloatingPanelInternal(QList &list) { - T* panel = PanelManager::instance()->CreatePanel(this); + T* panel = new T(undo_stack_, this); panel->setFloating(true); panel->show(); From 65a1c75933a809af28074a7b0845560724df2a31 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 27 Dec 2021 20:56:04 -0800 Subject: [PATCH 59/94] clean up and remove nodegroupdialog --- app/core.cpp | 14 +- app/core.h | 2 +- app/dialog/CMakeLists.txt | 1 - app/dialog/nodegroup/CMakeLists.txt | 22 -- app/dialog/nodegroup/nodegroupdialog.cpp | 319 ------------------- app/dialog/nodegroup/nodegroupdialog.h | 75 ----- app/panel/pixelsampler/pixelsamplerpanel.cpp | 2 +- app/panel/viewer/viewer.cpp | 25 +- app/panel/viewer/viewer.h | 8 +- app/widget/nodeview/nodeview.cpp | 23 +- app/widget/nodeview/nodeview.h | 7 - app/widget/panel/panel.cpp | 9 + app/widget/panel/panel.h | 2 + app/widget/viewer/viewerdisplay.cpp | 2 +- app/window/mainwindow/mainwindow.cpp | 26 +- 15 files changed, 58 insertions(+), 479 deletions(-) delete mode 100644 app/dialog/nodegroup/CMakeLists.txt delete mode 100644 app/dialog/nodegroup/nodegroupdialog.cpp delete mode 100644 app/dialog/nodegroup/nodegroupdialog.h diff --git a/app/core.cpp b/app/core.cpp index 1db15b4fa..241d57206 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -1358,10 +1358,10 @@ void Core::SetPreferenceForRenderMode(RenderMode::Mode mode, const QString &pref Config::Current()[GetRenderModePreferencePrefix(mode, preference)] = value; } -void Core::LabelNodes(const QVector &nodes) +bool Core::LabelNodes(const QVector &nodes, MultiUndoCommand *parent) { if (nodes.isEmpty()) { - return; + return false; } bool ok; @@ -1390,8 +1390,16 @@ void Core::LabelNodes(const QVector &nodes) rename_command->AddNode(n, s); } - undo_stack_.push(rename_command); + if (parent) { + parent->add_child(rename_command); + } else { + undo_stack_.push(rename_command); + } + + return true; } + + return false; } Sequence *Core::CreateNewSequenceForProject(Project* project) const diff --git a/app/core.h b/app/core.h index 3ac9b1e70..003e9e3f3 100644 --- a/app/core.h +++ b/app/core.h @@ -253,7 +253,7 @@ public: /** * @brief Show a dialog to the user to rename a set of nodes */ - void LabelNodes(const QVector &nodes); + bool LabelNodes(const QVector &nodes, MultiUndoCommand *parent = nullptr); /** * @brief Create a new sequence named appropriately for the active project diff --git a/app/dialog/CMakeLists.txt b/app/dialog/CMakeLists.txt index afe445704..64c718d2a 100644 --- a/app/dialog/CMakeLists.txt +++ b/app/dialog/CMakeLists.txt @@ -24,7 +24,6 @@ add_subdirectory(export) add_subdirectory(footageproperties) add_subdirectory(footagerelink) add_subdirectory(keyframeproperties) -add_subdirectory(nodegroup) add_subdirectory(preferences) add_subdirectory(progress) add_subdirectory(rendercancel) diff --git a/app/dialog/nodegroup/CMakeLists.txt b/app/dialog/nodegroup/CMakeLists.txt deleted file mode 100644 index 0f48edfc9..000000000 --- a/app/dialog/nodegroup/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -# Olive - Non-Linear Video Editor -# Copyright (C) 2021 Olive Team -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -set(OLIVE_SOURCES - ${OLIVE_SOURCES} - dialog/nodegroup/nodegroupdialog.cpp - dialog/nodegroup/nodegroupdialog.h - PARENT_SCOPE -) diff --git a/app/dialog/nodegroup/nodegroupdialog.cpp b/app/dialog/nodegroup/nodegroupdialog.cpp deleted file mode 100644 index 90742f176..000000000 --- a/app/dialog/nodegroup/nodegroupdialog.cpp +++ /dev/null @@ -1,319 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "nodegroupdialog.h" - -#include -#include -#include -#include -#include - -#include "panel/node/node.h" -#include "panel/panelmanager.h" -#include "panel/param/param.h" - -namespace olive { - -#define super QDialog - -NodeGroupDialog::NodeGroupDialog(NodeGroup *group, QWidget *parent) : - super(parent), - group_(group), - prepend_undo_(nullptr) -{ - QGridLayout *layout = new QGridLayout(this); - - int row = 0; - - layout->addWidget(new QLabel(tr("Name:")), row, 0); - - name_edit_ = new QLineEdit(); - name_edit_->setText(group->GetLabel()); - layout->addWidget(name_edit_, row, 1); - - row++; - - QMainWindow *splitter = new QMainWindow(); - QWidget *cw = new QWidget(); - cw->setFixedSize(0, 0); - splitter->setCentralWidget(cw); - layout->addWidget(splitter, row, 0, 1, 2); - - ParamPanel *param_view = PanelManager::instance()->CreatePanel(splitter); - param_view->GetParamView()->SetIgnoreNodeFlags(true); - param_view->GetParamView()->SetCreateCheckBoxes(kCheckBoxesOnNonConnected); - splitter->addDockWidget(Qt::LeftDockWidgetArea, param_view); - - NodePanel *node_view = PanelManager::instance()->CreatePanel(splitter); - node_view->GetNodeWidget()->view()->OverrideUndoStack(&undo_stack_); - QMetaObject::invokeMethod(node_view->GetNodeWidget()->view(), &NodeView::CenterOnItemsBoundingRect, Qt::QueuedConnection); - splitter->addDockWidget(Qt::RightDockWidgetArea, node_view); - - row++; - - QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - btns->setCenterButtons(true); - connect(btns, &QDialogButtonBox::accepted, this, &NodeGroupDialog::accept); - connect(btns, &QDialogButtonBox::rejected, this, &NodeGroupDialog::reject); - layout->addWidget(btns, row, 0, 1, 2); - - setWindowTitle(tr("Group Editor - %1").arg(group->GetLabelOrName())); - - // Create a project - copied_project_ = new Project(); - copied_project_->setParent(this); - - // Copy project nodes - // NOTE: I hate this. What might be better is to fold the "ColorManager" and "ProjectSettings" - // nodes into "Project" inputs and make "Project" a node too. - for (int i=0; inodes().size(); i++) { - Node *ours = copied_project_->nodes().at(i); - Node *theirs = group_->project()->nodes().at(i); - - copy_subnodes_.insert(theirs, ours); - Node::CopyInputs(ours, theirs, false); - } - - // Copy group and nodes - copy_group_ = static_cast(group_->copy()); - copy_group_->setParent(copied_project_); - - // Copy subnodes with positions - const Node::PositionMap &map = group_->GetContextPositions(); - for (auto it=map.cbegin(); it!=map.cend(); it++) { - Node *copy = it.key()->copy(); - copy->SetUUID(it.key()->GetUUID()); - copy->setParent(copied_project_); - - copy_group_->SetNodePositionInContext(copy, it.value()); - copy_subnodes_.insert(it.key(), copy); - } - - for (auto it=map.cbegin(); it!=map.cend(); it++) { - Node::CopyInputs(it.key(), copy_subnodes_.value(it.key()), false); - } - - // Copy edges - for (auto it=copy_subnodes_.cbegin(); it!=copy_subnodes_.cend(); it++) { - Node *src = it.key(); - Node *cpy = it.value(); - - for (auto jt=src->input_connections().cbegin(); jt!=src->input_connections().cend(); jt++) { - if (Node *cpy_output = copy_subnodes_.value(jt->second)) { - Node::ConnectEdge(cpy_output, NodeInput(cpy, jt->first.input(), jt->first.element())); - copied_edges_.append({cpy_output, NodeInput(cpy, jt->first.input(), jt->first.element())}); - } - } - } - - node_view->SetContexts({copy_group_}); - - for (auto it=group_->GetInputPassthroughs().cbegin(); it!=group_->GetInputPassthroughs().cend(); it++) { - const NodeInput &src_input = it.value(); - NodeInput copy_input(copy_subnodes_.value(src_input.node()), src_input.input(), src_input.element()); - param_view->GetParamView()->SetInputChecked(copy_input, true); - } - - param_view->SetContexts({copy_group_}); -} - -void NodeGroupDialog::accept() -{ - // First, validate if a connection, deleted node, or disabled passthrough is going to disconnect - // something elsewhere in the group. Ask the user to confirm if so. - - // Detect removed nodes - QVector nodes_to_delete = GetNodesToDelete(); - - // Detect new connections - QVector edges_to_connect = GetNewConnections(); - - // Warn user if operation will disconnect a node outside the group - if (OperationWillAffectOutsideGroup(nodes_to_delete, edges_to_connect)) { - if (QMessageBox::question(this, QString(), tr("This operation will disconnect nodes outside of this group. Do you wish to continue?"), QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) { - return; - } - } - - NodeViewDeleteCommand *delete_command = new NodeViewDeleteCommand(); - - // Remove deleted nodes - foreach (Node *n, nodes_to_delete) { - delete_command->AddNode(n, group_); - } - - // Disconnect old edges - foreach (const Node::OutputConnection &edge, copied_edges_) { - bool found = false; - const NodeInput ©_input = edge.second; - - for (auto it=edge.first->output_connections().cbegin(); it!=edge.first->output_connections().cend(); it++) { - if (it->second == copy_input) { - found = true; - break; - } - } - - if (!found) { - // Edge has been disconnected - delete_command->AddEdge(copy_subnodes_.key(edge.first), NodeInput(copy_subnodes_.key(copy_input.node()), copy_input.input(), copy_input.element())); - } - } - - delete_command->redo_now(); - - // Add new nodes - MultiUndoCommand *add_command = new MultiUndoCommand(); - for (auto it=copy_group_->GetContextPositions().cbegin(); it!=copy_group_->GetContextPositions().cend(); it++) { - Node *n = it.key(); - Node *original = copy_subnodes_.key(n); - if (!original) { - // If it's a new node that isn't even in the project yet, add it - original = n->copy(); - Node::CopyInputs(n, original, false); - add_command->add_child(new NodeAddCommand(group_->parent(), original)); - copy_subnodes_.insert(original, n); - } - - // Update position in context - add_command->add_child(new NodeSetPositionCommand(original, group_, it.value())); - } - add_command->redo_now(); - - // Connect new edges - MultiUndoCommand *connect_command = new MultiUndoCommand(); - edges_to_connect = GetNewConnections(); // Update list with new nodes made above if necessary - foreach (const Node::OutputConnection &edge, edges_to_connect) { - connect_command->add_child(new NodeEdgeAddCommand(edge.first, edge.second)); - } - connect_command->redo_now(); - - MultiUndoCommand *command = new MultiUndoCommand(); - - if (prepend_undo_) { - command->add_child(prepend_undo_); - } - - command->add_child(delete_command); - command->add_child(add_command); - command->add_child(connect_command); - - // Update group name - if (name_edit_->text() != group_->GetCustomName()) { - command->add_child(new NodeGroupSetCustomNameCommand(group_, name_edit_->text())); - } - - Core::instance()->undo_stack()->push(command); - - super::accept(); -} - -void NodeGroupDialog::reject() -{ - if (prepend_undo_) { - prepend_undo_->undo_now(); - delete prepend_undo_; - } - - super::reject(); -} - -QVector NodeGroupDialog::GetNodesToDelete() -{ - QVector nodes_to_delete; - - for (auto it=group_->GetContextPositions().cbegin(); it!=group_->GetContextPositions().cend(); it++) { - Node *original = it.key(); - Node *copy = copy_subnodes_.value(original); - - if (!copy_group_->ContextContainsNode(copy)) { - nodes_to_delete.append(original); - } - } - - return nodes_to_delete; -} - -QVector NodeGroupDialog::GetNewConnections() -{ - QVector edges_to_connect; - - for (auto it=copy_group_->GetContextPositions().cbegin(); it!=copy_group_->GetContextPositions().cend(); it++) { - const Node::InputConnections &ic = it.key()->input_connections(); - - for (auto jt=ic.cbegin(); jt!=ic.cend(); jt++) { - // All connections inside this dialog will be valid and inside the group - const NodeInput &copied_input = jt->first; - Node *copied_output = jt->second; - - NodeInput original_input(copy_subnodes_.key(copied_input.node()), copied_input.input(), copied_input.element()); - Node *original_output = copy_subnodes_.key(copied_output); - - bool found = false; - - if (original_output) { - for (auto kt=original_output->output_connections().cbegin(); kt!=original_output->output_connections().cend(); kt++) { - if (kt->second == original_input) { - found = true; - break; - } - } - } - - if (!found) { - edges_to_connect.append({original_output, original_input}); - } - } - } - - return edges_to_connect; -} - -bool NodeGroupDialog::OperationWillAffectOutsideGroup(const QVector &deleted, const QVector &connections) -{ - // Check if a node to be deleted inputs from a node outside the group - foreach (Node *n, deleted) { - for (auto jt=n->input_connections().cbegin(); jt!=n->input_connections().cend(); jt++) { - if (!group_->ContextContainsNode(jt->second)) { - return true; - } - } - - for (auto jt=n->output_connections().cbegin(); jt!=n->output_connections().cend(); jt++) { - if (!group_->ContextContainsNode(jt->second.node())) { - return true; - } - } - } - - // Check if a new connection will overwrite a connection from outside the group - foreach (const Node::OutputConnection &edge, connections) { - if (Node *current_conn = edge.second.GetConnectedOutput()) { - if (!group_->ContextContainsNode(current_conn)) { - return true; - } - } - } - - return false; -} - -} diff --git a/app/dialog/nodegroup/nodegroupdialog.h b/app/dialog/nodegroup/nodegroupdialog.h deleted file mode 100644 index d18b7fd10..000000000 --- a/app/dialog/nodegroup/nodegroupdialog.h +++ /dev/null @@ -1,75 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef NODEGROUPDIALOG_H -#define NODEGROUPDIALOG_H - -#include -#include -#include - -#include "node/group/group.h" -#include "undo/undostack.h" - -namespace olive { - -class NodeGroupDialog : public QDialog -{ - Q_OBJECT -public: - explicit NodeGroupDialog(NodeGroup *group, QWidget *parent = nullptr); - - void PrependUndoCommand(MultiUndoCommand *c) - { - prepend_undo_ = c; - } - -public slots: - virtual void accept() override; - - virtual void reject() override; - -signals: - -private: - QVector GetNodesToDelete(); - - QVector GetNewConnections(); - - bool OperationWillAffectOutsideGroup(const QVector &deleted, const QVector &connections); - - NodeGroup *group_; - - QLineEdit *name_edit_; - - MultiUndoCommand *prepend_undo_; - - Project *copied_project_; - NodeGroup *copy_group_; - QMap copy_subnodes_; - QVector copied_edges_; - - UndoStack undo_stack_; - -}; - -} - -#endif // NODEGROUPDIALOG_H diff --git a/app/panel/pixelsampler/pixelsamplerpanel.cpp b/app/panel/pixelsampler/pixelsamplerpanel.cpp index 2f17abb9e..dce9fd698 100644 --- a/app/panel/pixelsampler/pixelsamplerpanel.cpp +++ b/app/panel/pixelsampler/pixelsamplerpanel.cpp @@ -25,7 +25,7 @@ namespace olive { PixelSamplerPanel::PixelSamplerPanel(QWidget *parent) : - PanelWidget(QStringLiteral("ProjectPanel"), parent) + PanelWidget(QStringLiteral("PixelSamplerPanel"), parent) { sampler_widget_ = new ManagedPixelSamplerWidget(); SetWidgetWithPadding(sampler_widget_); diff --git a/app/panel/viewer/viewer.cpp b/app/panel/viewer/viewer.cpp index d23981aab..3d2bf8561 100644 --- a/app/panel/viewer/viewer.cpp +++ b/app/panel/viewer/viewer.cpp @@ -24,24 +24,6 @@ namespace olive { ViewerPanel::ViewerPanel(const QString &object_name, QWidget *parent) : ViewerPanelBase(object_name, parent) -{ - Init(); -} - -ViewerPanel::ViewerPanel(QWidget *parent) : - ViewerPanelBase(QStringLiteral("ViewerPanel"), parent) -{ - Init(); -} - -void ViewerPanel::Retranslate() -{ - ViewerPanelBase::Retranslate(); - - SetTitle(tr("Viewer")); -} - -void ViewerPanel::Init() { // Set ViewerWidget as the central widget ViewerWidget* vw = new ViewerWidget(); @@ -52,4 +34,11 @@ void ViewerPanel::Init() Retranslate(); } +void ViewerPanel::Retranslate() +{ + ViewerPanelBase::Retranslate(); + + SetTitle(tr("Viewer")); +} + } diff --git a/app/panel/viewer/viewer.h b/app/panel/viewer/viewer.h index a0f1d99bd..3b1cc4eb5 100644 --- a/app/panel/viewer/viewer.h +++ b/app/panel/viewer/viewer.h @@ -34,14 +34,14 @@ class ViewerPanel : public ViewerPanelBase { Q_OBJECT public: ViewerPanel(const QString& object_name, QWidget* parent); - ViewerPanel(QWidget* parent); + ViewerPanel(QWidget *parent) : + ViewerPanel(QStringLiteral("ViewerPanel"), parent) + { + } protected: virtual void Retranslate() override; -private: - void Init(); - }; } diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index d0e85fc37..c87b8ccd5 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -26,7 +26,6 @@ #include #include -#include "dialog/nodegroup/nodegroupdialog.h" #include "nodeviewundo.h" #include "node/audio/volume/volume.h" #include "node/distort/transform/transformdistortnode.h" @@ -49,7 +48,6 @@ NodeView::NodeView(QWidget *parent) : create_edge_output_item_(nullptr), create_edge_input_item_(nullptr), paste_command_(nullptr), - undo_stack_(Core::instance()->undo_stack()), scale_(1.0) { setScene(&scene_); @@ -133,7 +131,7 @@ void NodeView::DeleteSelected() ctx->DeleteSelected(command); } - undo_stack_->push(command); + Core::instance()->undo_stack()->push(command); } void NodeView::SelectAll() @@ -227,7 +225,7 @@ void NodeView::SetColorLabel(int index) command->add_child(new NodeOverrideColorCommand(node, index)); } - undo_stack_->push(command); + Core::instance()->undo_stack()->push(command); } void NodeView::ZoomIn() @@ -282,7 +280,7 @@ void NodeView::keyPressEvent(QKeyEvent *event) } } } - undo_stack_->pushIfHasChildren(pos_command); + Core::instance()->undo_stack()->pushIfHasChildren(pos_command); break; } case Qt::Key_Escape: @@ -561,7 +559,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } create_edge_expanded_items_.clear(); - undo_stack_->pushIfHasChildren(command); + Core::instance()->undo_stack()->pushIfHasChildren(command); } MultiUndoCommand* command = new MultiUndoCommand(); @@ -645,7 +643,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } dragging_items_.clear(); - undo_stack_->pushIfHasChildren(command); + Core::instance()->undo_stack()->pushIfHasChildren(command); super::mouseReleaseEvent(event); } @@ -1234,11 +1232,9 @@ void NodeView::GroupNodes() command->add_child(new NodeSetPositionCommand(group, context, avg_pos)); // Do command - command->redo_now(); + Core::instance()->LabelNodes({group}, command); - NodeGroupDialog ngd(group, this); - ngd.PrependUndoCommand(command); - ngd.exec(); + Core::instance()->undo_stack()->push(command); } void NodeView::UngroupNodes() @@ -1273,7 +1269,7 @@ void NodeView::UngroupNodes() command->add_child(new NodeSetPositionCommand(it.key(), context, group->GetNodePositionDataInContext(it.key()))); } - undo_stack_->push(command); + Core::instance()->undo_stack()->push(command); } void NodeView::ShowNodeProperties() @@ -1281,8 +1277,7 @@ void NodeView::ShowNodeProperties() Node *first_node = selected_nodes_.first(); if (NodeGroup *group = dynamic_cast(first_node)) { - NodeGroupDialog ngd(group, this); - ngd.exec(); + qDebug() << "STUB!"; } else { LabelSelectedNodes(); } diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 02118ba45..23e03964a 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -77,11 +77,6 @@ public: void ZoomOut(); - void OverrideUndoStack(UndoStack *stack) - { - undo_stack_ = stack; - } - const QVector &GetCurrentContexts() const { return contexts_; @@ -195,8 +190,6 @@ private: QMap dragging_items_; - UndoStack *undo_stack_; - double scale_; static const double kMinimumScale; diff --git a/app/widget/panel/panel.cpp b/app/widget/panel/panel.cpp index 3009a3fb3..4ce4cd405 100644 --- a/app/widget/panel/panel.cpp +++ b/app/widget/panel/panel.cpp @@ -28,6 +28,8 @@ #include #include +#include "panel/panelmanager.h" + namespace olive { PanelWidget::PanelWidget(const QString &object_name, QWidget *parent) : @@ -39,6 +41,13 @@ PanelWidget::PanelWidget(const QString &object_name, QWidget *parent) : setFocusPolicy(Qt::ClickFocus); connect(this, &PanelWidget::visibilityChanged, this, &PanelWidget::PanelVisibilityChanged); + + PanelManager::instance()->RegisterPanel(this); +} + +PanelWidget::~PanelWidget() +{ + PanelManager::instance()->UnregisterPanel(this); } void PanelWidget::SetMovementLocked(bool locked) diff --git a/app/widget/panel/panel.h b/app/widget/panel/panel.h index 87690bf0d..130e3eaf8 100644 --- a/app/widget/panel/panel.h +++ b/app/widget/panel/panel.h @@ -45,6 +45,8 @@ public: */ PanelWidget(const QString& object_name, QWidget* parent); + virtual ~PanelWidget() override; + /** * @brief Set whether panel movement is locked or not */ diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 4c9b21654..721903b2c 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -273,7 +273,7 @@ void ViewerDisplayWidget::mouseReleaseEvent(QMouseEvent *event) // Handle gizmo MultiUndoCommand *command = new MultiUndoCommand(); gizmos_->GizmoRelease(command); - undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->pushIfHasChildren(command); gizmo_click_ = false; } else { diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 6b4fd9883..1447895fe 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -78,17 +78,17 @@ MainWindow::MainWindow(QWidget *parent) : setStatusBar(status_bar); // Create standard panels - node_panel_ = new NodePanel(undo_stack_, this); - footage_viewer_panel_ = new FootageViewerPanel(undo_stack_, this); - param_panel_ = new ParamPanel(undo_stack_, this); - curve_panel_ = new CurvePanel(undo_stack_, this); - sequence_viewer_panel_ = new SequenceViewerPanel(undo_stack_, this); - pixel_sampler_panel_ = new PixelSamplerPanel(undo_stack_, this); + node_panel_ = new NodePanel(this); + footage_viewer_panel_ = new FootageViewerPanel(this); + param_panel_ = new ParamPanel(this); + curve_panel_ = new CurvePanel(this); + sequence_viewer_panel_ = new SequenceViewerPanel(this); + pixel_sampler_panel_ = new PixelSamplerPanel(this); AppendProjectPanel(); - tool_panel_ = new ToolPanel(undo_stack_, this); - task_man_panel_ = new TaskManagerPanel(undo_stack_, this); + tool_panel_ = new ToolPanel(this); + task_man_panel_ = new TaskManagerPanel(this); AppendTimelinePanel(); - audio_monitor_panel_ = new AudioMonitorPanel(undo_stack_, this); + audio_monitor_panel_ = new AudioMonitorPanel(this); // Make node-related connections connect(node_panel_, &NodePanel::NodesSelected, param_panel_, &ParamPanel::SelectNodes); @@ -218,7 +218,7 @@ bool MainWindow::IsSequenceOpen(Sequence *sequence) const void MainWindow::FolderOpen(Project* p, Folder *i, bool floating) { - ProjectPanel* panel = new ProjectPanel(undo_stack_, this); + ProjectPanel* panel = new ProjectPanel(this); panel->set_project(p); panel->set_root(i); @@ -256,7 +256,7 @@ void MainWindow::OpenNodeInViewer(ViewerOutput *node) viewer_panels_.value(node)->raise(); } else { // Create a viewer for this node - ViewerPanel* viewer = new ViewerPanel(undo_stack_, this); + ViewerPanel* viewer = new ViewerPanel(this); viewer->SetSignalInsteadOfClose(true); viewer->setFloating(true); @@ -816,7 +816,7 @@ void MainWindow::showEvent(QShowEvent *e) template T *MainWindow::AppendPanelInternal(QList& list) { - T* panel = new T(undo_stack_, this); + T* panel = new T(this); if (!list.isEmpty()) { tabifyDockWidget(list.last(), panel); @@ -837,7 +837,7 @@ T *MainWindow::AppendPanelInternal(QList& list) template T *MainWindow::AppendFloatingPanelInternal(QList &list) { - T* panel = new T(undo_stack_, this); + T* panel = new T(this); panel->setFloating(true); panel->show(); From 74d9c43ff5f301dbe8082046ae892710c857b755 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 28 Dec 2021 21:10:57 -0800 Subject: [PATCH 60/94] keyframeview: optimized keyframe drawing --- app/node/keyframe.h | 1 + app/widget/keyframeview/keyframeview.cpp | 82 ++++++++++++++++++++++-- 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/app/node/keyframe.h b/app/node/keyframe.h index c9462cefe..a142c5015 100644 --- a/app/node/keyframe.h +++ b/app/node/keyframe.h @@ -43,6 +43,7 @@ public: * @brief Methods of interpolation to use with this keyframe */ enum Type { + kInvalid = -1, kLinear, kHold, kBezier diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index 0e751265a..57d8f8c5d 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -244,6 +244,27 @@ void KeyframeView::mouseReleaseEvent(QMouseEvent *event) } } +int BinarySearchFirstKeyframeAfterOrAt(const QVector &keys, const rational &time) +{ + int low = 0; + int high = keys.size()-1; + + while (low <= high) { + int mid = low + (high-low)/2; + NodeKeyframe *test_key = keys.at(mid); + + if (test_key->time() == time || (test_key->time() > time && (mid == 0 || keys.at(mid-1)->time() < time))) { + return mid; + } else if (test_key->time() < time) { + low = mid + 1; + } else { + high = mid - 1; + } + } + + return -1; +} + void KeyframeView::drawForeground(QPainter *painter, const QRectF &rect) { int key_sz = QtUtils::QFontMetricsWidth(fontMetrics(), "Oi"); @@ -254,15 +275,64 @@ void KeyframeView::drawForeground(QPainter *painter, const QRectF &rect) painter->setRenderHint(QPainter::Antialiasing); foreach (KeyframeViewInputConnection *track, tracks_) { - foreach (NodeKeyframe *key, track->GetKeyframes()) { - QRectF key_rect(-key_rad, -key_rad, key_sz, key_sz); - key_rect.translate(GetKeyframeSceneX(key), GetKeyframeSceneY(track, key)); + const QVector &keys = track->GetKeyframes(); - if (!rect.intersects(key_rect)) { + if (keys.isEmpty()) { + continue; + } + + if (!IsYAxisEnabled()) { + // Filter out if the keyframes are offscreen Y + qreal y = GetKeyframeSceneY(track, keys.first()); + if (y + key_rad < rect.top() || y - key_rad >= rect.bottom()) { continue; } + } + + // Find first keyframe to show with binary search + rational left_time = SceneToTime(rect.left() - key_sz); + int using_index = BinarySearchFirstKeyframeAfterOrAt(keys, left_time); + + rational next_key = RATIONAL_MIN; + NodeKeyframe::Type last_type = NodeKeyframe::kInvalid; + for (int i=using_index; itime() < next_key && key->type() == last_type) { + // This key will be drawn at exactly the same location as the last one and therefore + // doesn't need to be drawn. See if the next one will be drawn. + i++; + if (i == keys.size()) { + break; + } + + key = keys.at(i); + + if (key->time() < next_key) { + // Next key still won't be drawn, so we'll switch to a binary search + i = BinarySearchFirstKeyframeAfterOrAt(keys, next_key); + + if (i == -1) { + break; + } + + key = keys.at(i); + } + } + + QRectF key_rect(-key_rad, -key_rad, key_sz, key_sz); + qreal key_x = GetKeyframeSceneX(key); + key_rect.translate(key_x, GetKeyframeSceneY(track, key)); + + if (key_rect.left() >= rect.right()) { + // Break after last keyframe + break; + } DrawKeyframe(painter, key, track, key_rect); + + next_key = SceneToTime(key_x + 1); + last_type = key->type(); } } @@ -282,6 +352,8 @@ void KeyframeView::DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeVi selection_manager_.DeclareDrawnObject(key, key_rect); switch (key->type()) { + case NodeKeyframe::kInvalid: + break; case NodeKeyframe::kLinear: { QPointF points[] = { @@ -403,6 +475,8 @@ void KeyframeView::ShowContextMenu() if (all_keys_are_same_type) { switch (type) { + case NodeKeyframe::kInvalid: + break; case NodeKeyframe::kLinear: linear_key_action->setChecked(true); break; From a4934f1d47f33032ed346d47acfb06346f41da6d Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 28 Dec 2021 21:52:28 -0800 Subject: [PATCH 61/94] implemented saving/loading for groups --- app/common/xmlutils.cpp | 65 +++++++++++++++++++--------- app/common/xmlutils.h | 16 +++++-- app/core.cpp | 2 +- app/node/factory.cpp | 11 +++++ app/node/factory.h | 3 ++ app/node/group/group.cpp | 62 +++++++++++++++++++++----- app/node/group/group.h | 52 +++------------------- app/node/nodecopypaste.cpp | 7 +-- app/node/project/project.cpp | 5 +-- app/panel/node/node.cpp | 1 + app/panel/node/node.h | 8 ++++ app/panel/param/param.h | 10 +++++ app/window/mainwindow/mainwindow.cpp | 20 ++++++++- app/window/mainwindow/mainwindow.h | 2 + 14 files changed, 170 insertions(+), 94 deletions(-) diff --git a/app/common/xmlutils.cpp b/app/common/xmlutils.cpp index f778b6440..97ec92057 100644 --- a/app/common/xmlutils.cpp +++ b/app/common/xmlutils.cpp @@ -24,15 +24,30 @@ #include "node/factory.h" #include "widget/nodeparamview/nodeparamviewundo.h" #include "widget/nodeview/nodeviewundo.h" +//#include "widget/timelinewidget/undo/timelineundogeneral.h" namespace olive { -void XMLConnectNodes(const XMLNodeData &xml_node_data, uint version, MultiUndoCommand *command) +bool XMLReadNextStartElement(QXmlStreamReader *reader) { - foreach (const XMLNodeData::SerializedConnection& con, xml_node_data.desired_connections) { - Node *out = xml_node_data.node_ptrs.value(con.output_node); + QXmlStreamReader::TokenType token; - if (out) { + while ((token = reader->readNext()) != QXmlStreamReader::Invalid + && token != QXmlStreamReader::EndDocument) { + if (reader->isEndElement()) { + return false; + } else if (reader->isStartElement()) { + return true; + } + } + + return false; +} + +void XMLNodeData::PostConnect(uint version, MultiUndoCommand *command) const +{ + foreach (const XMLNodeData::SerializedConnection& con, desired_connections) { + if (Node *out = node_ptrs.value(con.output_node)) { // Use output param as hint tag since we grandfathered those in Node::ValueHint hint(con.output_param); @@ -53,28 +68,36 @@ void XMLConnectNodes(const XMLNodeData &xml_node_data, uint version, MultiUndoCo } } } -} -bool XMLReadNextStartElement(QXmlStreamReader *reader) -{ - QXmlStreamReader::TokenType token; - - while ((token = reader->readNext()) != QXmlStreamReader::Invalid - && token != QXmlStreamReader::EndDocument) { - if (reader->isEndElement()) { - return false; - } else if (reader->isStartElement()) { - return true; + foreach (const XMLNodeData::BlockLink& l, block_links) { + Node *a = l.block; + Node *b = node_ptrs.value(l.link); + if (command) { + command->add_child(new NodeLinkCommand(a, b, true)); + } else { + Node::Link(a, b); } } - return false; -} + foreach (const XMLNodeData::GroupLink &l, group_input_links) { + if (Node *input_node = node_ptrs.value(l.input_node)) { + NodeInput resolved(input_node, l.input_id, l.input_element); + if (command) { + command->add_child(new NodeGroupAddInputPassthrough(l.group, resolved)); + } else { + l.group->AddInputPassthrough(resolved); + } + } + } -void XMLLinkBlocks(const XMLNodeData &xml_node_data) -{ - foreach (const XMLNodeData::BlockLink& l, xml_node_data.block_links) { - Block::Link(l.block, static_cast(xml_node_data.node_ptrs.value(l.link))); + for (auto it=group_output_links.cbegin(); it!=group_output_links.cend(); it++) { + if (Node *output_node = node_ptrs.value(it.value())) { + if (command) { + command->add_child(new NodeGroupSetOutputPassthrough(it.key(), output_node)); + } else { + it.key()->SetOutputPassthrough(output_node); + } + } } } diff --git a/app/common/xmlutils.h b/app/common/xmlutils.h index e8b88d6a3..d0488ccd0 100644 --- a/app/common/xmlutils.h +++ b/app/common/xmlutils.h @@ -31,6 +31,7 @@ namespace olive { class Block; class Node; class NodeInput; +class NodeGroup; #define XMLAttributeLoop(reader, item) \ foreach (const QXmlStreamAttribute& item, reader->attributes()) @@ -49,14 +50,23 @@ struct XMLNodeData { quintptr link; }; + struct GroupLink { + NodeGroup *group; + quintptr input_node; + QString input_id; + int input_element; + }; + QHash node_ptrs; QList desired_connections; QList block_links; + QVector group_input_links; + QHash group_output_links; + + void PostConnect(uint version, MultiUndoCommand *command = nullptr) const; }; -void XMLConnectNodes(const XMLNodeData& xml_node_data, uint version, MultiUndoCommand *command = nullptr); - /** * @brief Workaround for QXmlStreamReader::readNextStartElement not detecting the end of a document * @@ -68,8 +78,6 @@ void XMLConnectNodes(const XMLNodeData& xml_node_data, uint version, MultiUndoCo */ bool XMLReadNextStartElement(QXmlStreamReader* reader); -void XMLLinkBlocks(const XMLNodeData& xml_node_data); - } #endif // XMLREADLOOP_H diff --git a/app/core.cpp b/app/core.cpp index 241d57206..d0301a329 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -75,7 +75,7 @@ namespace olive { Core* Core::instance_ = nullptr; -const uint Core::kProjectVersion = 210907; +const uint Core::kProjectVersion = 211228; Core::Core(const CoreParams& params) : main_window_(nullptr), diff --git a/app/node/factory.cpp b/app/node/factory.cpp index f4368d898..447ba0dbd 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -54,6 +54,7 @@ namespace olive { QList NodeFactory::library_; +QVector NodeFactory::hidden_; void NodeFactory::Initialize() { @@ -65,6 +66,9 @@ void NodeFactory::Initialize() library_.append(created_node); } + + hidden_.append(kTextGeneratorLegacy); + hidden_.append(kGroupNode); } void NodeFactory::Destroy() @@ -86,6 +90,11 @@ Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item, Node::Cate continue; } + if (hidden_.contains(i)) { + // Skip this node + continue; + } + // Make sure nodes are up-to-date with the current translation n->Retranslate(); @@ -241,6 +250,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return new SubtitleBlock(); case kShapeGenerator: return new ShapeNode(); + case kGroupNode: + return new NodeGroup(); case kInternalNodeCount: break; diff --git a/app/node/factory.h b/app/node/factory.h index 1d8cd72f2..10101e648 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -61,6 +61,7 @@ public: kTimeRemapNode, kSubtitleBlock, kShapeGenerator, + kGroupNode, // Count value kInternalNodeCount @@ -87,6 +88,8 @@ public: private: static QList library_; + static QVector hidden_; + }; } diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp index be92d9be8..e64b551d8 100644 --- a/app/node/group/group.cpp +++ b/app/node/group/group.cpp @@ -24,6 +24,8 @@ namespace olive { +#define super Node + NodeGroup::NodeGroup() : output_passthrough_(nullptr) { @@ -31,11 +33,7 @@ NodeGroup::NodeGroup() : QString NodeGroup::Name() const { - if (custom_name_.isEmpty()) { - return tr("Group"); - } else { - return custom_name_; - } + return tr("Group"); } QString NodeGroup::id() const @@ -131,15 +129,59 @@ QString NodeGroup::GetInputName(const QString &id) const return input_passthroughs_.value(id).name(); } -void NodeGroupSetCustomNameCommand::redo() +bool NodeGroup::LoadCustom(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled) { - old_name_ = group_->GetCustomName(); - group_->SetCustomName(new_name_); + if (reader->name() == QStringLiteral("inputpassthroughs")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("inputpassthrough")) { + XMLNodeData::GroupLink link; + + link.group = this; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + link.input_node = reader->readElementText().toULongLong(); + } else if (reader->name() == QStringLiteral("input")) { + link.input_id = reader->readElementText(); + } else if (reader->name() == QStringLiteral("element")) { + link.input_element = reader->readElementText().toInt(); + } else { + reader->skipCurrentElement(); + } + } + + xml_node_data.group_input_links.append(link); + } else { + reader->skipCurrentElement(); + } + } + + return true; + } else if (reader->name() == QStringLiteral("outputpassthrough")) { + xml_node_data.group_output_links.insert(this, reader->readElementText().toULongLong()); + return true; + } else { + return super::LoadCustom(reader, xml_node_data, version, cancelled); + } } -void NodeGroupSetCustomNameCommand::undo() +void NodeGroup::SaveCustom(QXmlStreamWriter *writer) const { - group_->SetCustomName(old_name_); + super::SaveCustom(writer); + + writer->writeStartElement(QStringLiteral("inputpassthroughs")); + + foreach (const NodeInput &ip, input_passthroughs_) { + writer->writeStartElement(QStringLiteral("inputpassthrough")); + writer->writeTextElement(QStringLiteral("node"), QString::number(reinterpret_cast(ip.node()))); + writer->writeTextElement(QStringLiteral("input"), ip.input()); + writer->writeTextElement(QStringLiteral("element"), QString::number(ip.element())); + writer->writeEndElement(); // input + } + + writer->writeEndElement(); // inputpassthroughs + + writer->writeTextElement(QStringLiteral("outputpassthrough"), QString::number(reinterpret_cast(output_passthrough_))); } void NodeGroupAddInputPassthrough::redo() diff --git a/app/node/group/group.h b/app/node/group/group.h index d023a7416..979a52bc7 100644 --- a/app/node/group/group.h +++ b/app/node/group/group.h @@ -52,24 +52,6 @@ public: void SetOutputPassthrough(Node *node); - const QString &GetCustomName() const - { - return custom_name_; - } - - void SetCustomName(const QString &name) - { - custom_name_ = name; - - // NOTE: Not technically the right signal, but should achieve the right goal - emit LabelChanged(custom_name_); - } - - void ClearCustomName() - { - custom_name_.clear(); - } - static QString GetGroupInputIDFromInput(const NodeInput &input); const QHash &GetInputPassthroughs() const @@ -88,40 +70,16 @@ signals: void OutputPassthroughChanged(NodeGroup *group, Node *output); +protected: + virtual bool LoadCustom(QXmlStreamReader* reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt* cancelled) override; + + virtual void SaveCustom(QXmlStreamWriter* writer) const override; + private: QHash input_passthroughs_; Node *output_passthrough_; - QString custom_name_; - -}; - -class NodeGroupSetCustomNameCommand : public UndoCommand -{ -public: - NodeGroupSetCustomNameCommand(NodeGroup *group, const QString &name) : - group_(group), - new_name_(name) - {} - - virtual Project * GetRelevantProject() const override - { - return group_->project(); - } - -protected: - virtual void redo() override; - - virtual void undo() override; - -private: - NodeGroup *group_; - - QString old_name_; - - QString new_name_; - }; class NodeGroupAddInputPassthrough : public UndoCommand diff --git a/app/node/nodecopypaste.cpp b/app/node/nodecopypaste.cpp index e9c0a4d0e..2a54212af 100644 --- a/app/node/nodecopypaste.cpp +++ b/app/node/nodecopypaste.cpp @@ -205,12 +205,7 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, } // Make connections - if (!xml_node_data.desired_connections.isEmpty()) { - XMLConnectNodes(xml_node_data, data_version, command); - } - - // Link blocks - XMLLinkBlocks(xml_node_data); + xml_node_data.PostConnect(data_version, command); // Process contexts for (auto it=pasted_contexts.cbegin(); it!=pasted_contexts.cend(); it++) { diff --git a/app/node/project/project.cpp b/app/node/project/project.cpp index ba118ba50..0ca246374 100644 --- a/app/node/project/project.cpp +++ b/app/node/project/project.cpp @@ -193,10 +193,7 @@ void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, uint } // Make connections - XMLConnectNodes(xml_node_data, version); - - // Link blocks - XMLLinkBlocks(xml_node_data); + xml_node_data.PostConnect(version); } void Project::Save(QXmlStreamWriter *writer) const diff --git a/app/panel/node/node.cpp b/app/panel/node/node.cpp index 6e4b842e6..d6abc3a1d 100644 --- a/app/panel/node/node.cpp +++ b/app/panel/node/node.cpp @@ -31,6 +31,7 @@ NodePanel::NodePanel(QWidget *parent) : // Connect node view signals to this panel - MAY REMOVE connect(node_widget_->view(), &NodeView::NodesSelected, this, &NodePanel::NodesSelected); connect(node_widget_->view(), &NodeView::NodesDeselected, this, &NodePanel::NodesDeselected); + connect(node_widget_->view(), &NodeView::NodeGroupOpenRequested, this, &NodePanel::NodeGroupOpenRequested); // Set it as the main widget of this panel SetWidgetWithPadding(node_widget_); diff --git a/app/panel/node/node.h b/app/panel/node/node.h index 083445029..f1dae048f 100644 --- a/app/panel/node/node.h +++ b/app/panel/node/node.h @@ -40,6 +40,11 @@ public: return node_widget_; } + const QVector &GetContexts() const + { + return node_widget_->view()->GetContexts(); + } + void SetContexts(const QVector &nodes) { node_widget_->SetContexts(nodes); @@ -109,6 +114,7 @@ public slots: void Select(const QVector& nodes, bool center_view_on_item) { node_widget_->view()->Select(nodes, center_view_on_item); + this->raise(); } signals: @@ -116,6 +122,8 @@ signals: void NodesDeselected(const QVector& nodes); + void NodeGroupOpenRequested(NodeGroup *group); + private: virtual void Retranslate() override { diff --git a/app/panel/param/param.h b/app/panel/param/param.h index 4389bc15f..5d7928f04 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -38,6 +38,16 @@ public: return static_cast(GetTimeBasedWidget()); } + void SetCreateCheckBoxes(NodeParamViewCheckBoxBehavior e) + { + GetParamView()->SetCreateCheckBoxes(e); + } + + void SetIgnoreNodeFlags(bool e) + { + GetParamView()->SetIgnoreNodeFlags(e); + } + public slots: void SelectNodes(const QVector& nodes); void DeselectNodes(const QVector& nodes); diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 1447895fe..d17ff3a0f 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -93,6 +93,7 @@ MainWindow::MainWindow(QWidget *parent) : // Make node-related connections connect(node_panel_, &NodePanel::NodesSelected, param_panel_, &ParamPanel::SelectNodes); connect(node_panel_, &NodePanel::NodesDeselected, param_panel_, &ParamPanel::DeselectNodes); + connect(node_panel_, &NodePanel::NodeGroupOpenRequested, this, &MainWindow::NodeGroupRequested); connect(param_panel_, &ParamPanel::RequestSelectNode, this, [this](const QVector& target){ node_panel_->Select(target, true); }); @@ -451,6 +452,17 @@ void MainWindow::StatusBarDoubleClicked() task_man_panel_->raise(); } +void MainWindow::NodeGroupRequested(NodeGroup *group) +{ + NodePanel *panel = new NodePanel(this); + panel->setFloating(true); + panel->setVisible(true); + panel->SetContexts({group}); + panel->SetSignalInsteadOfClose(true); + addDockWidget(Qt::LeftDockWidgetArea, panel); + connect(panel, &NodePanel::CloseRequested, panel, &NodePanel::deleteLater); +} + void MainWindow::TimelinePanelSelectionChanged(const QVector &blocks) { TimelinePanel *panel = static_cast(sender()); @@ -723,7 +735,13 @@ void MainWindow::FocusedPanelChanged(PanelWidget *panel) UpdateAudioMonitorParams(tbp->GetConnectedViewer()); } - if (TimelinePanel* timeline = dynamic_cast(panel)) { + if (NodePanel *node_panel = dynamic_cast(panel)) { + // Set param view contexts to these + bool is_default_node_panel = node_panel == node_panel_; + param_panel_->SetIgnoreNodeFlags(!is_default_node_panel); + param_panel_->SetCreateCheckBoxes(is_default_node_panel ? kNoCheckBoxes : kCheckBoxesOnNonConnected); + param_panel_->SetContexts(node_panel->GetContexts()); + } else if (TimelinePanel* timeline = dynamic_cast(panel)) { // Signal timeline focus TimelineFocused(timeline->GetConnectedViewer()); diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 719652200..63dc4445f 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -187,6 +187,8 @@ private slots: void StatusBarDoubleClicked(); + void NodeGroupRequested(NodeGroup *group); + #ifdef Q_OS_LINUX void ShowNouveauWarning(); #endif From 82df30586d0ac1d24ce3fb6713817a70b61bbf06 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 29 Dec 2021 00:12:23 -0800 Subject: [PATCH 62/94] correctly handle unicode args on win32 Fixes #1810 --- CMakeLists.txt | 4 +++- app/common/commandlineparser.cpp | 8 ++++---- app/common/commandlineparser.h | 2 +- app/main.cpp | 18 +++++++++++++++++- app/window/mainwindow/mainwindow.cpp | 2 +- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e8c28db2a..67de32ebf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -162,7 +162,9 @@ else() endif() endif() -if (APPLE) +if (WIN32) + list(APPEND OLIVE_DEFINITIONS "-DUNICODE -D_UNICODE") +elseif (APPLE) list(APPEND OLIVE_LIBRARIES "-framework IOKit") elseif(UNIX) list(APPEND OLIVE_LIBRARIES Qt5::DBus) diff --git a/app/common/commandlineparser.cpp b/app/common/commandlineparser.cpp index 882ffcf24..150bdc244 100644 --- a/app/common/commandlineparser.cpp +++ b/app/common/commandlineparser.cpp @@ -52,16 +52,16 @@ const CommandLineParser::PositionalArgument *CommandLineParser::AddPositionalArg return a; } -void CommandLineParser::Process(int argc, char **argv) +void CommandLineParser::Process(const QVector &argv) { int positional_index = 0; - for (int i=1; iSet(); - if (o.takes_arg && i+1 < argc) { + if (o.takes_arg && i+1 < argv.size()) { o.option->SetSetting(argv[i+1]); i++; } diff --git a/app/common/commandlineparser.h b/app/common/commandlineparser.h index 7d131f357..4b32a9134 100644 --- a/app/common/commandlineparser.h +++ b/app/common/commandlineparser.h @@ -91,7 +91,7 @@ public: const PositionalArgument* AddPositionalArgument(const QString& name, const QString& description, bool required = false); - void Process(int argc, char** argv); + void Process(const QVector &argv); void PrintHelp(const char* filename); diff --git a/app/main.cpp b/app/main.cpp index 8a21126ff..7e729216f 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -71,6 +71,22 @@ int main(int argc, char *argv[]) // Parse command line arguments // + QVector args; +#if defined(_WIN32) && defined(UNICODE) + int wargc; + LPWSTR *wargv = CommandLineToArgvW(GetCommandLineW(), &wargc); + args.resize(wargc); + for (int i=0; i