From 4ce922536e648f2e7b8c85a1fe0878596fac0e92 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Wed, 11 Aug 2021 17:15:35 +0100 Subject: [PATCH 01/81] Create marker widget and attempt to add to timeline --- app/dialog/about/about.cpp | 5 +++++ app/widget/CMakeLists.txt | 1 + app/widget/timeruler/seekablewidget.cpp | 23 +++++++++++++++++++++++ app/widget/timeruler/seekablewidget.h | 9 +++++++++ app/widget/timeruler/timeruler.h | 1 + 5 files changed, 39 insertions(+) diff --git a/app/dialog/about/about.cpp b/app/dialog/about/about.cpp index e3500e217..792ca5173 100644 --- a/app/dialog/about/about.cpp +++ b/app/dialog/about/about.cpp @@ -30,6 +30,8 @@ #include "patreon.h" #include "scrollinglabel.h" +#include "widget/marker/marker.h" + namespace olive { AboutDialog::AboutDialog(bool welcome_dialog, QWidget *parent) : @@ -74,6 +76,9 @@ AboutDialog::AboutDialog(bool welcome_dialog, QWidget *parent) : label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); horiz_layout->addWidget(label); + Marker* marker = new Marker(); + horiz_layout->addWidget(marker); + layout->addLayout(horiz_layout); // Patrons where possible diff --git a/app/widget/CMakeLists.txt b/app/widget/CMakeLists.txt index 38fefccbd..18e5bb112 100644 --- a/app/widget/CMakeLists.txt +++ b/app/widget/CMakeLists.txt @@ -28,6 +28,7 @@ add_subdirectory(focusablelineedit) add_subdirectory(handmovableview) add_subdirectory(keyframeview) add_subdirectory(manageddisplay) +add_subdirectory(marker) add_subdirectory(menu) add_subdirectory(nodecombobox) add_subdirectory(nodeparamview) diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index a5845c25c..7e9b7ff36 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -44,6 +44,10 @@ SeekableWidget::SeekableWidget(QWidget* parent) : playhead_width_ = QtUtils::QFontMetricsWidth(fm, "H"); setContextMenuPolicy(Qt::CustomContextMenu); + + marker_layout_ = new QHBoxLayout(this); + + UpdateMarkers(); } void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) @@ -62,6 +66,8 @@ void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) connect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&SeekableWidget::update)); connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&SeekableWidget::update)); connect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&SeekableWidget::update)); + + connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&SeekableWidget::UpdateMarkers)); } update(); @@ -127,6 +133,23 @@ void SeekableWidget::SetScroll(int s) update(); } +void SeekableWidget::UpdateMarkers() +{ + printf("Test"); + if (timeline_points()) { + foreach(Marker * marker, marker_widgets) { + marker->deleteLater(); + } + marker_widgets.clear(); + + foreach(TimelineMarker * marker, timeline_points()->markers()->list()) { + Marker* marker_widget = new Marker(); + marker_widgets.append(marker_widget); + marker_layout_->addWidget(marker_widget); + } + } +} + int SeekableWidget::TimeToScreen(const rational &time) const { return qFloor(TimeToScene(time)) - scroll_; diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index 8cccaf64b..74f930d6d 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -21,10 +21,13 @@ #ifndef SEEKABLEWIDGET_H #define SEEKABLEWIDGET_H +#include + #include "common/rational.h" #include "timeline/timelinepoints.h" #include "widget/snapservice/snapservice.h" #include "widget/timebased/timescaledobject.h" +#include "widget/marker/marker.h" namespace olive { @@ -55,6 +58,8 @@ public slots: void SetScroll(int s); + void UpdateMarkers(); + protected: void SeekToScreenPoint(int screen); @@ -102,6 +107,10 @@ private: bool dragging_; + QHBoxLayout* marker_layout_; + + QVector marker_widgets; + }; } diff --git a/app/widget/timeruler/timeruler.h b/app/widget/timeruler/timeruler.h index 496117238..77a7b3b82 100644 --- a/app/widget/timeruler/timeruler.h +++ b/app/widget/timeruler/timeruler.h @@ -27,6 +27,7 @@ #include "common/timerange.h" #include "seekablewidget.h" #include "render/playbackcache.h" +#include "widget/marker/marker.h" namespace olive { From 5d1a854c3f885e1b3add1da606c7951814b9fa42 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Wed, 11 Aug 2021 17:17:10 +0100 Subject: [PATCH 02/81] Actually add marker source code --- app/widget/marker/CMakeLists.txt | 22 +++++++ app/widget/marker/marker.cpp | 100 +++++++++++++++++++++++++++++++ app/widget/marker/marker.h | 58 ++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 app/widget/marker/CMakeLists.txt create mode 100644 app/widget/marker/marker.cpp create mode 100644 app/widget/marker/marker.h diff --git a/app/widget/marker/CMakeLists.txt b/app/widget/marker/CMakeLists.txt new file mode 100644 index 000000000..4ec5cab0d --- /dev/null +++ b/app/widget/marker/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} + widget/marker/marker.h + widget/marker/marker.cpp + PARENT_SCOPE +) \ No newline at end of file diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp new file mode 100644 index 000000000..b6df9e423 --- /dev/null +++ b/app/widget/marker/marker.cpp @@ -0,0 +1,100 @@ +/*** + + 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 "marker.h" + +#include + +#include "common/qtutils.h" +#include "ui/colorcoding.h" +#include "widget/menu/menu.h" +#include "widget/menu/menushared.h" + +namespace olive { + +Marker::Marker(QWidget *parent) : + QWidget(parent), + marker_color_(Qt::green) +{ + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setMinimumSize(20, 20); + setContextMenuPolicy(Qt::CustomContextMenu); + + connect(this, &Marker::customContextMenuRequested, this, &Marker::ShowContextMenu); + + color_coding_menu_ = new ColorLabelMenu(); + connect(color_coding_menu_, &ColorLabelMenu::ColorSelected, this, &Marker::SetColor); +} + +void Marker::paintEvent(QPaintEvent *event) +{ + QFontMetrics fm = fontMetrics(); + + int text_height = fm.height(); + int marker_width_ = QtUtils::QFontMetricsWidth(fm, "H"); + + int y = text_height; //13 + + int half_width = marker_width_ / 2; + + int x = half_width; //3 + + if (x + half_width < 0 || x - half_width > width()) { + return; + } + + QPainter p(this); + p.setPen(Qt::black); + p.setBrush(marker_color_); + + p.setRenderHint(QPainter::Antialiasing); + + int half_text_height = text_height / 3; + + QPoint points[] = { + QPoint(x, y), + QPoint(x - half_width, y - half_text_height), + QPoint(x - half_width, y - text_height), + QPoint(x + 1 + half_width, y - text_height), + QPoint(x + 1 + half_width, y - half_text_height), + QPoint(x + 1, y), + }; + + p.drawPolygon(points, 6); + + p.setRenderHint(QPainter::Antialiasing, false); +} + +void Marker::ShowContextMenu() { + Menu m(this); + + m.addMenu(color_coding_menu_); + + m.exec(QCursor::pos()); +} + +void Marker::SetColor(int c) +{ + marker_color_ = ColorCoding::GetColor(c).toQColor(); + + update(); +} + +} // namespace olive \ No newline at end of file diff --git a/app/widget/marker/marker.h b/app/widget/marker/marker.h new file mode 100644 index 000000000..ed84f8d45 --- /dev/null +++ b/app/widget/marker/marker.h @@ -0,0 +1,58 @@ +/*** + + 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 MARKER_H +#define MARKER_H + +#include +#include + +#include "common/define.h" +#include "widget/colorlabelmenu/colorlabelmenu.h" + +namespace olive { + +class Marker : public QWidget { + Q_OBJECT + public: + Marker(QWidget* parent = nullptr); + + protected: + void paintEvent(QPaintEvent* event) override; + //virtual void mouseReleaseEvent(QMouseEvent* event) override; + //virtual void mouseDoubleClickEvent(QMouseEvent* event) override; + + signals: + //void MouseClicked(); + //void MouseDoubleClicked(); + + private: + ColorLabelMenu* color_coding_menu_; + + QColor marker_color_; + + private slots: + void ShowContextMenu(); + void SetColor(int c); +}; + +} // namespace olive + +#endif // MARKER_H From d068e9db11ea4a7e206923de3d11ef9400ba9c33 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Wed, 11 Aug 2021 17:23:54 +0100 Subject: [PATCH 03/81] Remove test in About dialoge --- app/dialog/about/about.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/dialog/about/about.cpp b/app/dialog/about/about.cpp index 792ca5173..e3500e217 100644 --- a/app/dialog/about/about.cpp +++ b/app/dialog/about/about.cpp @@ -30,8 +30,6 @@ #include "patreon.h" #include "scrollinglabel.h" -#include "widget/marker/marker.h" - namespace olive { AboutDialog::AboutDialog(bool welcome_dialog, QWidget *parent) : @@ -76,9 +74,6 @@ AboutDialog::AboutDialog(bool welcome_dialog, QWidget *parent) : label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); horiz_layout->addWidget(label); - Marker* marker = new Marker(); - horiz_layout->addWidget(marker); - layout->addLayout(horiz_layout); // Patrons where possible From 9a2b669c9f469eb69addde5ed65f4f22dc4f2660 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Fri, 13 Aug 2021 19:57:45 +0100 Subject: [PATCH 04/81] marker: Store color as int. --- app/widget/marker/marker.cpp | 6 +++--- app/widget/marker/marker.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index b6df9e423..7a1713c22 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -31,7 +31,7 @@ namespace olive { Marker::Marker(QWidget *parent) : QWidget(parent), - marker_color_(Qt::green) + marker_color_(7) //green FIXME: add default color to config { setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); setMinimumSize(20, 20); @@ -62,7 +62,7 @@ void Marker::paintEvent(QPaintEvent *event) QPainter p(this); p.setPen(Qt::black); - p.setBrush(marker_color_); + p.setBrush(ColorCoding::GetColor(marker_color_).toQColor()); p.setRenderHint(QPainter::Antialiasing); @@ -92,7 +92,7 @@ void Marker::ShowContextMenu() { void Marker::SetColor(int c) { - marker_color_ = ColorCoding::GetColor(c).toQColor(); + marker_color_ = c; update(); } diff --git a/app/widget/marker/marker.h b/app/widget/marker/marker.h index ed84f8d45..da42d28e0 100644 --- a/app/widget/marker/marker.h +++ b/app/widget/marker/marker.h @@ -46,7 +46,7 @@ class Marker : public QWidget { private: ColorLabelMenu* color_coding_menu_; - QColor marker_color_; + int marker_color_; private slots: void ShowContextMenu(); From 6fdfcb38d7e3047bbc8eea29b459c699afe54d89 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Fri, 13 Aug 2021 20:08:28 +0100 Subject: [PATCH 05/81] marker: create color menu on the stack --- app/widget/marker/marker.cpp | 8 ++++---- app/widget/marker/marker.h | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index 7a1713c22..b9c7a1390 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -38,9 +38,6 @@ Marker::Marker(QWidget *parent) : setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &Marker::customContextMenuRequested, this, &Marker::ShowContextMenu); - - color_coding_menu_ = new ColorLabelMenu(); - connect(color_coding_menu_, &ColorLabelMenu::ColorSelected, this, &Marker::SetColor); } void Marker::paintEvent(QPaintEvent *event) @@ -85,7 +82,10 @@ void Marker::paintEvent(QPaintEvent *event) void Marker::ShowContextMenu() { Menu m(this); - m.addMenu(color_coding_menu_); + // Color menu + ColorLabelMenu color_coding_menu; + connect(&color_coding_menu, &ColorLabelMenu::ColorSelected, this, &Marker::SetColor); + m.addMenu(&color_coding_menu); m.exec(QCursor::pos()); } diff --git a/app/widget/marker/marker.h b/app/widget/marker/marker.h index da42d28e0..8b52027bd 100644 --- a/app/widget/marker/marker.h +++ b/app/widget/marker/marker.h @@ -44,7 +44,6 @@ class Marker : public QWidget { //void MouseDoubleClicked(); private: - ColorLabelMenu* color_coding_menu_; int marker_color_; From 3ff59544b246839cd0e942646b03bb4720254d43 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Fri, 13 Aug 2021 23:40:33 +0100 Subject: [PATCH 06/81] Position markers correctly and update with scroll --- app/widget/timeruler/seekablewidget.cpp | 26 +++++++++++++++++++++++-- app/widget/timeruler/seekablewidget.h | 6 ++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 7e9b7ff36..33930fb82 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -67,7 +67,7 @@ void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&SeekableWidget::update)); connect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&SeekableWidget::update)); - connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&SeekableWidget::UpdateMarkers)); + connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, &SeekableWidget::addMarker); } update(); @@ -131,6 +131,8 @@ void SeekableWidget::SetScroll(int s) scroll_ = s; update(); + + updateMarkerPositions(); } void SeekableWidget::UpdateMarkers() @@ -150,6 +152,26 @@ void SeekableWidget::UpdateMarkers() } } +void SeekableWidget::addMarker(TimelineMarker* marker) +{ + if (!marker_map_.contains(marker)) { + Marker *marker_widget = new Marker(this); + marker_map_.insert(marker, marker_widget); + + //marker_widget->setGeometry(TimeToScreen(marker->time().in()), 20, 20, 20); + marker_widget->move(TimeToScreen(marker->time().in()), 20); + marker_widget->show(); + } +} + +void SeekableWidget::updateMarkerPositions() +{ + foreach (TimelineMarker *marker, marker_map_.keys()) { + Marker *m = marker_map_.value(marker); + m->move(TimeToScreen(marker->time().in()), 20); + } +} + int SeekableWidget::TimeToScreen(const rational &time) const { return qFloor(TimeToScene(time)) - scroll_; @@ -224,7 +246,7 @@ void SeekableWidget::DrawTimelinePoints(QPainter* p, int marker_bottom) if (marker->time().length() == 0) { // Single point in time marker - DrawPlayhead(p, marker_left, marker_bottom); + //DrawPlayhead(p, marker_left, marker_bottom); } else { // Marker range int rect_left = qMax(0, marker_left); diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index 74f930d6d..6a2d84171 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -60,6 +60,10 @@ public slots: void UpdateMarkers(); + void addMarker(TimelineMarker* marker); + + void updateMarkerPositions(); + protected: void SeekToScreenPoint(int screen); @@ -111,6 +115,8 @@ private: QVector marker_widgets; + QMap marker_map_; + }; } From e0a20a278e6b383ff0d508c5e5d362deba9a86ae Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Fri, 13 Aug 2021 23:47:57 +0100 Subject: [PATCH 07/81] Cleanup --- app/widget/marker/CMakeLists.txt | 2 +- app/widget/marker/marker.cpp | 8 +------- app/widget/timeruler/seekablewidget.cpp | 21 --------------------- app/widget/timeruler/seekablewidget.h | 6 ------ 4 files changed, 2 insertions(+), 35 deletions(-) diff --git a/app/widget/marker/CMakeLists.txt b/app/widget/marker/CMakeLists.txt index 4ec5cab0d..0f49bae01 100644 --- a/app/widget/marker/CMakeLists.txt +++ b/app/widget/marker/CMakeLists.txt @@ -19,4 +19,4 @@ set(OLIVE_SOURCES widget/marker/marker.h widget/marker/marker.cpp PARENT_SCOPE -) \ No newline at end of file +) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index b9c7a1390..c3e77c633 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -53,10 +53,6 @@ void Marker::paintEvent(QPaintEvent *event) int x = half_width; //3 - if (x + half_width < 0 || x - half_width > width()) { - return; - } - QPainter p(this); p.setPen(Qt::black); p.setBrush(ColorCoding::GetColor(marker_color_).toQColor()); @@ -75,8 +71,6 @@ void Marker::paintEvent(QPaintEvent *event) }; p.drawPolygon(points, 6); - - p.setRenderHint(QPainter::Antialiasing, false); } void Marker::ShowContextMenu() { @@ -97,4 +91,4 @@ void Marker::SetColor(int c) update(); } -} // namespace olive \ No newline at end of file +} // namespace olive diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 33930fb82..0227997bf 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -44,10 +44,6 @@ SeekableWidget::SeekableWidget(QWidget* parent) : playhead_width_ = QtUtils::QFontMetricsWidth(fm, "H"); setContextMenuPolicy(Qt::CustomContextMenu); - - marker_layout_ = new QHBoxLayout(this); - - UpdateMarkers(); } void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) @@ -135,23 +131,6 @@ void SeekableWidget::SetScroll(int s) updateMarkerPositions(); } -void SeekableWidget::UpdateMarkers() -{ - printf("Test"); - if (timeline_points()) { - foreach(Marker * marker, marker_widgets) { - marker->deleteLater(); - } - marker_widgets.clear(); - - foreach(TimelineMarker * marker, timeline_points()->markers()->list()) { - Marker* marker_widget = new Marker(); - marker_widgets.append(marker_widget); - marker_layout_->addWidget(marker_widget); - } - } -} - void SeekableWidget::addMarker(TimelineMarker* marker) { if (!marker_map_.contains(marker)) { diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index 6a2d84171..949345df5 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -58,8 +58,6 @@ public slots: void SetScroll(int s); - void UpdateMarkers(); - void addMarker(TimelineMarker* marker); void updateMarkerPositions(); @@ -111,10 +109,6 @@ private: bool dragging_; - QHBoxLayout* marker_layout_; - - QVector marker_widgets; - QMap marker_map_; }; From 562b0e3a7f44c36141a75eaf96e97522668fb165 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 15 Aug 2021 11:57:48 +0100 Subject: [PATCH 08/81] Make sure marker position is updated whenever the timeline changes --- app/widget/timeruler/seekablewidget.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 0227997bf..43122a516 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -66,6 +66,8 @@ void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, &SeekableWidget::addMarker); } + updateMarkerPositions(); + update(); } @@ -107,6 +109,8 @@ void SeekableWidget::mouseReleaseEvent(QMouseEvent *event) void SeekableWidget::ScaleChangedEvent(const double &) { + updateMarkerPositions(); + update(); } @@ -119,6 +123,8 @@ void SeekableWidget::SetTime(const rational &r) { time_ = r; + updateMarkerPositions(); + update(); } @@ -126,9 +132,9 @@ void SeekableWidget::SetScroll(int s) { scroll_ = s; - update(); - updateMarkerPositions(); + + update(); } void SeekableWidget::addMarker(TimelineMarker* marker) @@ -137,7 +143,6 @@ void SeekableWidget::addMarker(TimelineMarker* marker) Marker *marker_widget = new Marker(this); marker_map_.insert(marker, marker_widget); - //marker_widget->setGeometry(TimeToScreen(marker->time().in()), 20, 20, 20); marker_widget->move(TimeToScreen(marker->time().in()), 20); marker_widget->show(); } @@ -145,7 +150,7 @@ void SeekableWidget::addMarker(TimelineMarker* marker) void SeekableWidget::updateMarkerPositions() { - foreach (TimelineMarker *marker, marker_map_.keys()) { + foreach (TimelineMarker* marker, marker_map_.keys()) { Marker *m = marker_map_.value(marker); m->move(TimeToScreen(marker->time().in()), 20); } From 575ce396964c2cfe94235d3e2a8e5f2a029424e9 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 15 Aug 2021 13:04:50 +0100 Subject: [PATCH 09/81] Move color to TimelineMarker and make sure all instances of a marker update on color change --- app/timeline/timelinemarker.cpp | 15 ++++++++++++++- app/timeline/timelinemarker.h | 7 +++++++ app/widget/marker/marker.cpp | 2 +- app/widget/marker/marker.h | 5 ++++- app/widget/timeruler/seekablewidget.cpp | 3 +++ 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index 4086e6983..7d5d44884 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -27,7 +27,8 @@ namespace olive { TimelineMarker::TimelineMarker(const TimeRange &time, const QString &name, QObject *parent) : QObject(parent), time_(time), - name_(name) + name_(name), + color_(7) // FIXME: set via config { } @@ -53,6 +54,18 @@ void TimelineMarker::set_name(const QString &name) emit NameChanged(name_); } +int TimelineMarker::color() +{ + return color_; +} + +void TimelineMarker::set_color(int c) +{ + color_ = c; + + emit ColorChanged(color_); +} + void TimelineMarkerList::Save(QXmlStreamWriter *writer) const { foreach (TimelineMarker* marker, markers_) { diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index ebc88a289..be037367f 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -41,16 +41,23 @@ public: const QString& name() const; void set_name(const QString& name); + int color(); + void set_color(int c); + signals: void TimeChanged(const TimeRange& time); void NameChanged(const QString& name); + void ColorChanged(int c); + private: TimeRange time_; QString name_; + int color_; + }; class TimelineMarkerList : public QObject diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index c3e77c633..e7316d30f 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -78,7 +78,7 @@ void Marker::ShowContextMenu() { // Color menu ColorLabelMenu color_coding_menu; - connect(&color_coding_menu, &ColorLabelMenu::ColorSelected, this, &Marker::SetColor); + connect(&color_coding_menu, &ColorLabelMenu::ColorSelected, this, &Marker::ColorChanged); m.addMenu(&color_coding_menu); m.exec(QCursor::pos()); diff --git a/app/widget/marker/marker.h b/app/widget/marker/marker.h index 8b52027bd..cdb13b829 100644 --- a/app/widget/marker/marker.h +++ b/app/widget/marker/marker.h @@ -34,6 +34,9 @@ class Marker : public QWidget { public: Marker(QWidget* parent = nullptr); + public slots: + void SetColor(int c); + protected: void paintEvent(QPaintEvent* event) override; //virtual void mouseReleaseEvent(QMouseEvent* event) override; @@ -42,6 +45,7 @@ class Marker : public QWidget { signals: //void MouseClicked(); //void MouseDoubleClicked(); + void ColorChanged(int c); private: @@ -49,7 +53,6 @@ class Marker : public QWidget { private slots: void ShowContextMenu(); - void SetColor(int c); }; } // namespace olive diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 43122a516..0d62eed00 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -143,6 +143,9 @@ void SeekableWidget::addMarker(TimelineMarker* marker) Marker *marker_widget = new Marker(this); marker_map_.insert(marker, marker_widget); + connect(marker_widget, &Marker::ColorChanged, marker, &TimelineMarker::set_color); + connect(marker, &TimelineMarker::ColorChanged, marker_widget, &Marker::SetColor); + marker_widget->move(TimeToScreen(marker->time().in()), 20); marker_widget->show(); } From ca807b7b665738c76b6bbb1ad71a62f64e80a79c Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 15 Aug 2021 13:45:08 +0100 Subject: [PATCH 10/81] Marker color is saved to .ove file --- app/timeline/timelinemarker.cpp | 12 ++++++++++-- app/timeline/timelinemarker.h | 2 +- app/widget/timeruler/seekablewidget.cpp | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index 7d5d44884..11e5303dc 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -73,6 +73,8 @@ void TimelineMarkerList::Save(QXmlStreamWriter *writer) const writer->writeAttribute(QStringLiteral("name"), marker->name()); + writer->writeAttribute(QStringLiteral("color"), QString::number(marker->color())); + writer->writeAttribute(QStringLiteral("in"), marker->time().in().toString()); writer->writeAttribute(QStringLiteral("out"), marker->time().out().toString()); @@ -85,9 +87,12 @@ TimelineMarkerList::~TimelineMarkerList() qDeleteAll(markers_); } -TimelineMarker* TimelineMarkerList::AddMarker(const TimeRange &time, const QString &name) +TimelineMarker* TimelineMarkerList::AddMarker(const TimeRange &time, const QString &name, int color) { TimelineMarker* m = new TimelineMarker(time, name); + if (color >= 0) { + m->set_color(color); + } markers_.append(m); emit MarkerAdded(m); return m; @@ -117,11 +122,14 @@ void TimelineMarkerList::Load(QXmlStreamReader *reader) while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("marker")) { QString name; + int color = -1; rational in, out; XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("name")) { name = attr.value().toString(); + }else if(attr.name() == QStringLiteral("color")){ + color = attr.value().toInt(); } else if (attr.name() == QStringLiteral("in")) { in = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("out")) { @@ -129,7 +137,7 @@ void TimelineMarkerList::Load(QXmlStreamReader *reader) } } - AddMarker(TimeRange(in, out), name); + AddMarker(TimeRange(in, out), name, color); } reader->skipCurrentElement(); diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index be037367f..be1c62b77 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -68,7 +68,7 @@ public: virtual ~TimelineMarkerList() override; - TimelineMarker *AddMarker(const TimeRange& time = TimeRange(), const QString& name = QString()); + TimelineMarker *AddMarker(const TimeRange& time = TimeRange(), const QString& name = QString(), int color = -1); void RemoveMarker(TimelineMarker* marker); diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 0d62eed00..297bf6558 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -44,6 +44,7 @@ SeekableWidget::SeekableWidget(QWidget* parent) : playhead_width_ = QtUtils::QFontMetricsWidth(fm, "H"); setContextMenuPolicy(Qt::CustomContextMenu); + } void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) @@ -53,6 +54,10 @@ void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) disconnect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&SeekableWidget::update)); disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&SeekableWidget::update)); disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&SeekableWidget::update)); + + foreach(Marker* marker_widget, marker_map_.values()) { + marker_widget->deleteLater(); + } } timeline_points_ = points; @@ -66,6 +71,14 @@ void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, &SeekableWidget::addMarker); } + if (timeline_points() && !timeline_points()->markers()->list().isEmpty()) { + foreach (TimelineMarker *marker, timeline_points()->markers()->list()) { + if (!marker_map_.keys().contains(marker)) { + addMarker(marker); + } + } + } + updateMarkerPositions(); update(); @@ -147,6 +160,7 @@ void SeekableWidget::addMarker(TimelineMarker* marker) connect(marker, &TimelineMarker::ColorChanged, marker_widget, &Marker::SetColor); marker_widget->move(TimeToScreen(marker->time().in()), 20); + marker_widget->SetColor(marker->color()); marker_widget->show(); } } From 853dbc006651419dfc7aa6b0d5ff86e8d4eafd65 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 15 Aug 2021 17:42:30 +0100 Subject: [PATCH 11/81] Allow markers to be selected --- app/panel/timebased/timebased.cpp | 5 ++++ app/panel/timebased/timebased.h | 2 ++ app/panel/timeline/timeline.cpp | 2 ++ app/widget/marker/marker.cpp | 31 ++++++++++++++++++++++-- app/widget/marker/marker.h | 8 ++++++ app/widget/timebased/timebasedwidget.cpp | 7 ++++++ app/widget/timebased/timebasedwidget.h | 2 ++ app/widget/timeruler/seekablewidget.cpp | 23 ++++++++++++++++++ app/widget/timeruler/seekablewidget.h | 6 +++++ app/widget/timeruler/timeruler.cpp | 8 ++++++ app/widget/timeruler/timeruler.h | 2 ++ 11 files changed, 94 insertions(+), 2 deletions(-) diff --git a/app/panel/timebased/timebased.cpp b/app/panel/timebased/timebased.cpp index 44cc51281..b2fabbec9 100644 --- a/app/panel/timebased/timebased.cpp +++ b/app/panel/timebased/timebased.cpp @@ -211,4 +211,9 @@ void TimeBasedPanel::GoToOut() GetTimeBasedWidget()->GoToOut(); } +void TimeBasedPanel::DeleteSelected() +{ + GetTimeBasedWidget()->DeleteSelected(); +} + } diff --git a/app/panel/timebased/timebased.h b/app/panel/timebased/timebased.h index b85657e4d..380420bbc 100644 --- a/app/panel/timebased/timebased.h +++ b/app/panel/timebased/timebased.h @@ -98,6 +98,8 @@ public: virtual void GoToOut() override; + virtual void DeleteSelected() override; + public slots: void SetTimebase(const rational& timebase); diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index 461d94f16..155ab050d 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -20,6 +20,8 @@ #include "timeline.h" +#include + #include "panel/panelmanager.h" #include "panel/project/footagemanagementpanel.h" diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index e7316d30f..1824578ca 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -31,7 +31,8 @@ namespace olive { Marker::Marker(QWidget *parent) : QWidget(parent), - marker_color_(7) //green FIXME: add default color to config + marker_color_(7), //green FIXME: add default color to config + active_(false) { setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); setMinimumSize(20, 20); @@ -40,6 +41,16 @@ Marker::Marker(QWidget *parent) : connect(this, &Marker::customContextMenuRequested, this, &Marker::ShowContextMenu); } +void Marker::set_active(bool active) +{ + active_ = active; +} + +bool Marker::active() +{ + return active_; +} + void Marker::paintEvent(QPaintEvent *event) { QFontMetrics fm = fontMetrics(); @@ -54,7 +65,11 @@ void Marker::paintEvent(QPaintEvent *event) int x = half_width; //3 QPainter p(this); - p.setPen(Qt::black); + if (active_) { + p.setPen(Qt::white); + } else { + p.setPen(Qt::black); + } p.setBrush(ColorCoding::GetColor(marker_color_).toQColor()); p.setRenderHint(QPainter::Antialiasing); @@ -73,6 +88,15 @@ void Marker::paintEvent(QPaintEvent *event) p.drawPolygon(points, 6); } +void Marker::mousePressEvent(QMouseEvent* e) +{ + if (e->button() == Qt::LeftButton) { + active_ = !active_; + update(); + emit markerSelected(this); + } +} + void Marker::ShowContextMenu() { Menu m(this); @@ -81,6 +105,9 @@ void Marker::ShowContextMenu() { connect(&color_coding_menu, &ColorLabelMenu::ColorSelected, this, &Marker::ColorChanged); m.addMenu(&color_coding_menu); + m.addSeparator(); + MenuShared::instance()->AddItemsForEditMenu(&m, false); + m.exec(QCursor::pos()); } diff --git a/app/widget/marker/marker.h b/app/widget/marker/marker.h index cdb13b829..8d73e6a2a 100644 --- a/app/widget/marker/marker.h +++ b/app/widget/marker/marker.h @@ -22,6 +22,7 @@ #define MARKER_H #include +#include #include #include "common/define.h" @@ -34,11 +35,15 @@ class Marker : public QWidget { public: Marker(QWidget* parent = nullptr); + void set_active(bool active); + bool active(); + public slots: void SetColor(int c); protected: void paintEvent(QPaintEvent* event) override; + virtual void mousePressEvent(QMouseEvent* event) override; //virtual void mouseReleaseEvent(QMouseEvent* event) override; //virtual void mouseDoubleClickEvent(QMouseEvent* event) override; @@ -46,11 +51,14 @@ class Marker : public QWidget { //void MouseClicked(); //void MouseDoubleClicked(); void ColorChanged(int c); + void markerSelected(Marker* marker); private: int marker_color_; + bool active_; + private slots: void ShowContextMenu(); }; diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index d294ef466..7357b46c4 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -658,6 +658,13 @@ void TimeBasedWidget::GoToOut() } } +void TimeBasedWidget::DeleteSelected() +{ + if (ruler_->underMouse()) { + ruler_->DeleteSelected(); + } +} + TimeBasedWidget::MarkerAddCommand::MarkerAddCommand(Project *project, TimelineMarkerList *marker_list, const TimeRange &range, const QString &name) : project_(project), marker_list_(marker_list), diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index 2e479d84b..9e588dfe0 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -91,6 +91,8 @@ public slots: void GoToOut(); + void DeleteSelected(); + protected slots: void SetTimeAndSignal(const rational& t); diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 297bf6558..1d72926f3 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -150,6 +150,18 @@ void SeekableWidget::SetScroll(int s) update(); } +QMap SeekableWidget::GetActiveMarkers() { + QMap active_markers; + + foreach(TimelineMarker* marker, marker_map_.keys()) { + if (marker_map_.value(marker)->active()) { + active_markers.insert(marker, marker_map_.value(marker)); + } + } + + return active_markers; +} + void SeekableWidget::addMarker(TimelineMarker* marker) { if (!marker_map_.contains(marker)) { @@ -158,6 +170,7 @@ void SeekableWidget::addMarker(TimelineMarker* marker) connect(marker_widget, &Marker::ColorChanged, marker, &TimelineMarker::set_color); connect(marker, &TimelineMarker::ColorChanged, marker_widget, &Marker::SetColor); + connect(marker_widget, &Marker::markerSelected, this, &SeekableWidget::markerSelected); marker_widget->move(TimeToScreen(marker->time().in()), 20); marker_widget->SetColor(marker->color()); @@ -173,6 +186,16 @@ void SeekableWidget::updateMarkerPositions() } } +void SeekableWidget::markerSelected(Marker* marker) +{ + foreach(Marker * marker_widget, marker_map_.values()) { + if (marker_widget != marker) { + marker_widget->set_active(false); + marker_widget->update(); + } + } +} + int SeekableWidget::TimeToScreen(const rational &time) const { return qFloor(TimeToScene(time)) - scroll_; diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index 949345df5..aa708a97c 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -53,6 +53,10 @@ public: return dragging_; } + virtual void DeleteSelected(){}; + + QMap GetActiveMarkers(); + public slots: void SetTime(const rational &r); @@ -62,6 +66,8 @@ public slots: void updateMarkerPositions(); + void markerSelected(Marker* marker); + protected: void SeekToScreenPoint(int screen); diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp index db887fe7e..25ca69e82 100644 --- a/app/widget/timeruler/timeruler.cpp +++ b/app/widget/timeruler/timeruler.cpp @@ -83,6 +83,14 @@ void TimeRuler::SetPlaybackCache(PlaybackCache *cache) update(); } +void TimeRuler::DeleteSelected() +{ + foreach(TimelineMarker* marker, GetActiveMarkers().keys()) { + timeline_points()->markers()->RemoveMarker(marker); + GetActiveMarkers().value(marker)->deleteLater(); + } +} + void TimeRuler::paintEvent(QPaintEvent *) { // Nothing to paint if the timebase is invalid diff --git a/app/widget/timeruler/timeruler.h b/app/widget/timeruler/timeruler.h index 77a7b3b82..3c96df559 100644 --- a/app/widget/timeruler/timeruler.h +++ b/app/widget/timeruler/timeruler.h @@ -41,6 +41,8 @@ public: void SetPlaybackCache(PlaybackCache* cache); + virtual void DeleteSelected() override; + protected: virtual void paintEvent(QPaintEvent* e) override; From d8101869b9cf784e2be78fcf6b01eb019524f182 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Mon, 16 Aug 2021 20:47:59 +0100 Subject: [PATCH 12/81] Redo marker selection code --- app/timeline/timelinemarker.cpp | 12 +++++++ app/timeline/timelinemarker.h | 7 ++++ app/widget/marker/marker.cpp | 23 ++++++++++--- app/widget/marker/marker.h | 3 +- app/widget/timeruler/seekablewidget.cpp | 46 +++++++++++++++---------- app/widget/timeruler/seekablewidget.h | 11 ++++-- app/widget/timeruler/timeruler.cpp | 4 +-- 7 files changed, 76 insertions(+), 30 deletions(-) diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index 11e5303dc..047afc779 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -66,6 +66,18 @@ void TimelineMarker::set_color(int c) emit ColorChanged(color_); } +bool TimelineMarker::active() +{ + return active_; +} + +void TimelineMarker::set_active(bool active) +{ + active_ = active; + + emit ActiveChanged(active_); +} + void TimelineMarkerList::Save(QXmlStreamWriter *writer) const { foreach (TimelineMarker* marker, markers_) { diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index be1c62b77..1c921a311 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -44,6 +44,9 @@ public: int color(); void set_color(int c); + bool active(); + void set_active(bool active); + signals: void TimeChanged(const TimeRange& time); @@ -51,6 +54,8 @@ signals: void ColorChanged(int c); + void ActiveChanged(bool active); + private: TimeRange time_; @@ -58,6 +63,8 @@ private: int color_; + bool active_; + }; class TimelineMarkerList : public QObject diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index 1824578ca..985d15b94 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -26,6 +26,7 @@ #include "ui/colorcoding.h" #include "widget/menu/menu.h" #include "widget/menu/menushared.h" +#include "widget/timeruler/seekablewidget.h" namespace olive { @@ -34,16 +35,18 @@ Marker::Marker(QWidget *parent) : marker_color_(7), //green FIXME: add default color to config active_(false) { - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - setMinimumSize(20, 20); + //setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setMaximumSize(8, 20); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &Marker::customContextMenuRequested, this, &Marker::ShowContextMenu); } -void Marker::set_active(bool active) +void Marker::SetActive(bool active) { active_ = active; + + update(); } bool Marker::active() @@ -91,9 +94,19 @@ void Marker::paintEvent(QPaintEvent *event) void Marker::mousePressEvent(QMouseEvent* e) { if (e->button() == Qt::LeftButton) { - active_ = !active_; + static_cast(parent())->SeekToScreenPoint(e->pos().x() + this->x()); + if (!active_) { + if (e->modifiers() != Qt::ShiftModifier) { + static_cast(parent())->DeselectAllMarkers(); + } + emit ActiveChanged(true); + } else { + if (e->modifiers() == Qt::ShiftModifier) { + emit ActiveChanged(false); + } + } + update(); - emit markerSelected(this); } } diff --git a/app/widget/marker/marker.h b/app/widget/marker/marker.h index 8d73e6a2a..cb23cc39a 100644 --- a/app/widget/marker/marker.h +++ b/app/widget/marker/marker.h @@ -35,11 +35,11 @@ class Marker : public QWidget { public: Marker(QWidget* parent = nullptr); - void set_active(bool active); bool active(); public slots: void SetColor(int c); + void SetActive(bool active); protected: void paintEvent(QPaintEvent* event) override; @@ -52,6 +52,7 @@ class Marker : public QWidget { //void MouseDoubleClicked(); void ColorChanged(int c); void markerSelected(Marker* marker); + void ActiveChanged(bool active); private: diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 1d72926f3..8b0eff063 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -99,6 +99,8 @@ void SeekableWidget::mousePressEvent(QMouseEvent *event) if (event->button() == Qt::LeftButton) { SeekToScreenPoint(event->pos().x()); dragging_ = true; + + DeselectAllMarkers(); } } @@ -150,16 +152,23 @@ void SeekableWidget::SetScroll(int s) update(); } -QMap SeekableWidget::GetActiveMarkers() { - QMap active_markers; - - foreach(TimelineMarker* marker, marker_map_.keys()) { - if (marker_map_.value(marker)->active()) { - active_markers.insert(marker, marker_map_.value(marker)); +QVector SeekableWidget::GetActiveTimelineMarkers() { + QVector active_timelineMarkers; + foreach (TimelineMarker *marker, timeline_points()->markers()->list()) { + if (marker->active()) { + active_timelineMarkers.append(marker); } } - return active_markers; + return active_timelineMarkers; +} + + +void SeekableWidget::DeselectAllMarkers() +{ + foreach(TimelineMarker* marker, timeline_points()->markers()->list()) { + marker->set_active(false); + } } void SeekableWidget::addMarker(TimelineMarker* marker) @@ -168,9 +177,11 @@ void SeekableWidget::addMarker(TimelineMarker* marker) Marker *marker_widget = new Marker(this); marker_map_.insert(marker, marker_widget); - connect(marker_widget, &Marker::ColorChanged, marker, &TimelineMarker::set_color); + connect(marker_widget, &Marker::ColorChanged, this, &SeekableWidget::SetMarkerColor); connect(marker, &TimelineMarker::ColorChanged, marker_widget, &Marker::SetColor); - connect(marker_widget, &Marker::markerSelected, this, &SeekableWidget::markerSelected); + + connect(marker_widget, &Marker::ActiveChanged, marker, &TimelineMarker::set_active); + connect(marker, &TimelineMarker::ActiveChanged, marker_widget, &Marker::SetActive); marker_widget->move(TimeToScreen(marker->time().in()), 20); marker_widget->SetColor(marker->color()); @@ -178,6 +189,13 @@ void SeekableWidget::addMarker(TimelineMarker* marker) } } +void SeekableWidget::SetMarkerColor(int c) +{ + foreach(TimelineMarker* marker, GetActiveTimelineMarkers()) { + marker->set_color(c); + } +} + void SeekableWidget::updateMarkerPositions() { foreach (TimelineMarker* marker, marker_map_.keys()) { @@ -186,16 +204,6 @@ void SeekableWidget::updateMarkerPositions() } } -void SeekableWidget::markerSelected(Marker* marker) -{ - foreach(Marker * marker_widget, marker_map_.values()) { - if (marker_widget != marker) { - marker_widget->set_active(false); - marker_widget->update(); - } - } -} - int SeekableWidget::TimeToScreen(const rational &time) const { return qFloor(TimeToScene(time)) - scroll_; diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index aa708a97c..ae0664d62 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -55,7 +55,11 @@ public: virtual void DeleteSelected(){}; - QMap GetActiveMarkers(); + QVector SeekableWidget::GetActiveTimelineMarkers(); + + void DeselectAllMarkers(); + + void SeekToScreenPoint(int screen); public slots: void SetTime(const rational &r); @@ -66,10 +70,9 @@ public slots: void updateMarkerPositions(); - void markerSelected(Marker* marker); + void SetMarkerColor(int c); protected: - void SeekToScreenPoint(int screen); virtual void mousePressEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override; @@ -117,6 +120,8 @@ private: QMap marker_map_; + QMap active_markers_map_; + }; } diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp index 25ca69e82..52b9c7891 100644 --- a/app/widget/timeruler/timeruler.cpp +++ b/app/widget/timeruler/timeruler.cpp @@ -85,9 +85,9 @@ void TimeRuler::SetPlaybackCache(PlaybackCache *cache) void TimeRuler::DeleteSelected() { - foreach(TimelineMarker* marker, GetActiveMarkers().keys()) { + foreach(TimelineMarker* marker, GetActiveTimelineMarkers()) { timeline_points()->markers()->RemoveMarker(marker); - GetActiveMarkers().value(marker)->deleteLater(); + //GetActiveMarkers().value(marker)->deleteLater(); } } From 91ae2ecc44450b919bc2532d768a255e8731440b Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Mon, 16 Aug 2021 21:29:45 +0100 Subject: [PATCH 13/81] Make sure either clips are selected or markers are selected, never both. --- app/widget/marker/marker.cpp | 4 ++++ app/widget/timelinewidget/tool/pointer.cpp | 2 ++ 2 files changed, 6 insertions(+) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index 985d15b94..255a7934c 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -27,6 +27,7 @@ #include "widget/menu/menu.h" #include "widget/menu/menushared.h" #include "widget/timeruler/seekablewidget.h" +#include "widget/timelinewidget/timelinewidget.h" namespace olive { @@ -108,6 +109,9 @@ void Marker::mousePressEvent(QMouseEvent* e) update(); } + + // Feels very hacky, might it be better to write some access methods? + static_cast(parent()->parent())->DeselectAll(); } void Marker::ShowContextMenu() { diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 1ea1ba703..e29cd4fd4 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -135,6 +135,8 @@ void PointerTool::MousePress(TimelineViewMouseEvent *event) if (can_rubberband_select_) { drag_global_start_ = QCursor::pos(); } + + parent()->ruler()->DeselectAllMarkers(); } void PointerTool::MouseMove(TimelineViewMouseEvent *event) From 0044174f3d7b62247635ebae2b9b346d2aca7789 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 17 Aug 2021 13:26:33 +0100 Subject: [PATCH 14/81] Fix build issue. --- app/widget/timeruler/seekablewidget.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index ae0664d62..8bf0704ac 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -55,7 +55,7 @@ public: virtual void DeleteSelected(){}; - QVector SeekableWidget::GetActiveTimelineMarkers(); + QVector GetActiveTimelineMarkers(); void DeselectAllMarkers(); From c445dc46a9efae4f7951a8cd567fb757745bbcac Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 17 Aug 2021 14:38:26 +0100 Subject: [PATCH 15/81] Add basic marker deleting and fix selection code --- app/widget/marker/marker.cpp | 11 ++++++++--- app/widget/timelinewidget/timelinewidget.cpp | 4 ++++ app/widget/timeruler/seekablewidget.cpp | 16 ++++++++++++++++ app/widget/timeruler/seekablewidget.h | 4 +++- app/widget/timeruler/timeruler.cpp | 8 -------- app/widget/timeruler/timeruler.h | 2 -- 6 files changed, 31 insertions(+), 14 deletions(-) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index 255a7934c..0d49b70fa 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -47,6 +47,14 @@ void Marker::SetActive(bool active) { active_ = active; + // Feels very hacky, might it be better to write some access methods? + if (active) { + TimelineWidget *timeline = dynamic_cast(parent()->parent()); + if (timeline) { + static_cast(parent()->parent())->DeselectAll(); + } + } + update(); } @@ -109,9 +117,6 @@ void Marker::mousePressEvent(QMouseEvent* e) update(); } - - // Feels very hacky, might it be better to write some access methods? - static_cast(parent()->parent())->DeselectAll(); } void Marker::ShowContextMenu() { diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index cab49be8c..be6c68d1c 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -475,6 +475,10 @@ void TimelineWidget::ReplaceBlocksWithGaps(const QVector &blocks, void TimelineWidget::DeleteSelected(bool ripple) { + if (ruler()->GetActiveTimelineMarkers().size() > 0) { + ruler()->DeleteSelected(); + return; + } QVector selected_list = GetSelectedBlocks(); QVector blocks_to_delete; diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 8b0eff063..7a1b66726 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -69,6 +69,7 @@ void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) connect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&SeekableWidget::update)); connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, &SeekableWidget::addMarker); + connect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, &SeekableWidget::removeMarker); } if (timeline_points() && !timeline_points()->markers()->list().isEmpty()) { @@ -89,6 +90,15 @@ void SeekableWidget::SetSnapService(SnapService *service) snap_service_ = service; } +void SeekableWidget::DeleteSelected() { + foreach (TimelineMarker *marker, GetActiveTimelineMarkers()) { + //marker_map_.value(marker)->deleteLater(); + //marker_map_.take(marker); + timeline_points()->markers()->RemoveMarker(marker); + // GetActiveMarkers().value(marker)->deleteLater(); + } +} + const int &SeekableWidget::GetScroll() const { return scroll_; @@ -189,6 +199,12 @@ void SeekableWidget::addMarker(TimelineMarker* marker) } } +void SeekableWidget::removeMarker(TimelineMarker *marker) +{ + marker_map_.value(marker)->deleteLater(); + marker_map_.take(marker); +} + void SeekableWidget::SetMarkerColor(int c) { foreach(TimelineMarker* marker, GetActiveTimelineMarkers()) { diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index 8bf0704ac..05d9a81bb 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -53,7 +53,7 @@ public: return dragging_; } - virtual void DeleteSelected(){}; + void DeleteSelected(); QVector GetActiveTimelineMarkers(); @@ -68,6 +68,8 @@ public slots: void addMarker(TimelineMarker* marker); + void removeMarker(TimelineMarker* marker); + void updateMarkerPositions(); void SetMarkerColor(int c); diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp index 52b9c7891..db887fe7e 100644 --- a/app/widget/timeruler/timeruler.cpp +++ b/app/widget/timeruler/timeruler.cpp @@ -83,14 +83,6 @@ void TimeRuler::SetPlaybackCache(PlaybackCache *cache) update(); } -void TimeRuler::DeleteSelected() -{ - foreach(TimelineMarker* marker, GetActiveTimelineMarkers()) { - timeline_points()->markers()->RemoveMarker(marker); - //GetActiveMarkers().value(marker)->deleteLater(); - } -} - void TimeRuler::paintEvent(QPaintEvent *) { // Nothing to paint if the timebase is invalid diff --git a/app/widget/timeruler/timeruler.h b/app/widget/timeruler/timeruler.h index 3c96df559..77a7b3b82 100644 --- a/app/widget/timeruler/timeruler.h +++ b/app/widget/timeruler/timeruler.h @@ -41,8 +41,6 @@ public: void SetPlaybackCache(PlaybackCache* cache); - virtual void DeleteSelected() override; - protected: virtual void paintEvent(QPaintEvent* e) override; From 6e77606c3033f4b753d45fb82ad92ce5a3fd9053 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sat, 21 Aug 2021 19:16:27 +0100 Subject: [PATCH 16/81] Make widget deletion undoable --- app/widget/timebased/timebasedwidget.cpp | 25 ++++++++++++++++++++++++ app/widget/timebased/timebasedwidget.h | 22 +++++++++++++++++++++ app/widget/timeruler/seekablewidget.cpp | 10 ++++++---- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index 7357b46c4..fcc53161f 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -688,6 +688,31 @@ void TimeBasedWidget::MarkerAddCommand::undo() marker_list_->RemoveMarker(added_marker_); } +TimeBasedWidget::MarkerRemoveCommand::MarkerRemoveCommand(Project* project, TimelineMarker* marker, TimelineMarkerList* marker_list) : + project_(project), + marker_(marker), + marker_list_(marker_list), + range_(marker->time()), + name_(marker->name()), + color_(marker->color()) +{ +} + +Project *TimeBasedWidget::MarkerRemoveCommand::GetRelevantProject() const +{ + return project_; +} + +void TimeBasedWidget::MarkerRemoveCommand::redo() +{ + marker_list_->RemoveMarker(marker_); +} + +void TimeBasedWidget::MarkerRemoveCommand::undo() +{ + marker_list_->AddMarker(range_, name_, color_); +} + bool TimeBasedWidget::eventFilter(QObject *object, QEvent *event) { if (wheel_passthrough_objects_.contains(object) && event->type() == QEvent::Wheel) { diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index 9e588dfe0..ae6db3b56 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -54,6 +54,28 @@ public: virtual bool eventFilter(QObject* object, QEvent* event) override; + // Temp file location + class MarkerRemoveCommand : public UndoCommand { + public: + MarkerRemoveCommand(Project* project, TimelineMarker* marker, TimelineMarkerList* marker_list); + + virtual Project* GetRelevantProject() const override; + + protected: + virtual void redo() override; + virtual void undo() override; + + private: + Project* project_; + TimelineMarker* marker_; + TimelineMarkerList* marker_list_; + TimeRange range_; + QString name_; + int color_; + + TimelineMarker* removed_marker_; + }; + public slots: void SetTime(const rational &time); diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 7a1b66726..0764ee80b 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -26,6 +26,7 @@ #include "common/qtutils.h" #include "core.h" +#include "widget/timebased/timebasedwidget.h" namespace olive { @@ -91,12 +92,13 @@ void SeekableWidget::SetSnapService(SnapService *service) } void SeekableWidget::DeleteSelected() { + MultiUndoCommand* command = new MultiUndoCommand(); + foreach (TimelineMarker *marker, GetActiveTimelineMarkers()) { - //marker_map_.value(marker)->deleteLater(); - //marker_map_.take(marker); - timeline_points()->markers()->RemoveMarker(marker); - // GetActiveMarkers().value(marker)->deleteLater(); + command->add_child(new TimeBasedWidget::MarkerRemoveCommand(Core::instance()->GetActiveProject(), marker, timeline_points_->markers())); } + + Core::instance()->undo_stack()->pushIfHasChildren(command); } const int &SeekableWidget::GetScroll() const From 76e55a857f13ac5866b5ed789e0e5c4974373578 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sat, 21 Aug 2021 23:40:55 +0100 Subject: [PATCH 17/81] Make markers copy/paste-able --- app/timeline/timelinemarker.cpp | 25 ++-- app/timeline/timelinemarker.h | 3 + app/widget/marker/CMakeLists.txt | 2 + app/widget/marker/markercopypaste.cpp | 124 +++++++++++++++++++ app/widget/marker/markercopypaste.h | 40 ++++++ app/widget/timebased/timebasedwidget.cpp | 7 +- app/widget/timebased/timebasedwidget.h | 42 ++++--- app/widget/timelinewidget/timelinewidget.cpp | 22 +++- app/widget/timelinewidget/timelinewidget.h | 2 + app/widget/timeruler/seekablewidget.cpp | 15 +++ app/widget/timeruler/seekablewidget.h | 7 +- 11 files changed, 254 insertions(+), 35 deletions(-) create mode 100644 app/widget/marker/markercopypaste.cpp create mode 100644 app/widget/marker/markercopypaste.h diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index 047afc779..1a0403568 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -78,19 +78,24 @@ void TimelineMarker::set_active(bool active) emit ActiveChanged(active_); } +void TimelineMarker::Save(QXmlStreamWriter* writer) const +{ + writer->writeStartElement(QStringLiteral("marker")); + + writer->writeAttribute(QStringLiteral("name"), name_); + + writer->writeAttribute(QStringLiteral("color"), QString::number(color_)); + + writer->writeAttribute(QStringLiteral("in"), time_.in().toString()); + writer->writeAttribute(QStringLiteral("out"), time_.out().toString()); + + writer->writeEndElement(); // marker +} + void TimelineMarkerList::Save(QXmlStreamWriter *writer) const { foreach (TimelineMarker* marker, markers_) { - writer->writeStartElement(QStringLiteral("marker")); - - writer->writeAttribute(QStringLiteral("name"), marker->name()); - - writer->writeAttribute(QStringLiteral("color"), QString::number(marker->color())); - - writer->writeAttribute(QStringLiteral("in"), marker->time().in().toString()); - writer->writeAttribute(QStringLiteral("out"), marker->time().out().toString()); - - writer->writeEndElement(); // marker + marker->Save(writer); } } diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index 1c921a311..f63a1f3d3 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -47,6 +47,9 @@ public: bool active(); void set_active(bool active); + void Load(QXmlStreamReader* reader); + void Save(QXmlStreamWriter* writer) const; + signals: void TimeChanged(const TimeRange& time); diff --git a/app/widget/marker/CMakeLists.txt b/app/widget/marker/CMakeLists.txt index 0f49bae01..01ce2157d 100644 --- a/app/widget/marker/CMakeLists.txt +++ b/app/widget/marker/CMakeLists.txt @@ -18,5 +18,7 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} widget/marker/marker.h widget/marker/marker.cpp + widget/marker/markercopypaste.h + widget/marker/markercopypaste.cpp PARENT_SCOPE ) diff --git a/app/widget/marker/markercopypaste.cpp b/app/widget/marker/markercopypaste.cpp new file mode 100644 index 000000000..c5b48edb0 --- /dev/null +++ b/app/widget/marker/markercopypaste.cpp @@ -0,0 +1,124 @@ +/*** + 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 "markercopypaste.h" + +#include "core.h" +#include "widget/timebased/timebasedwidget.h" + +namespace olive { + +void MarkerCopyPasteService::CopyMarkersToClipboard(const QVector& markers, void* userdata) +{ + QString copy_str; + + QXmlStreamWriter writer(©_str); + writer.setAutoFormatting(true); + + writer.writeStartDocument(); + writer.writeStartElement(QStringLiteral("olive")); + + writer.writeTextElement(QStringLiteral("version"), QString::number(Core::kProjectVersion)); + + writer.writeStartElement(QStringLiteral("markers")); + + // Get earliest marker for offset + rational min_start = RATIONAL_MAX; + foreach (TimelineMarker* marker, markers) { + if (marker->time().in() < min_start) { + min_start = marker->time().in(); + } + } + + foreach(TimelineMarker * marker, markers) { + // Cache marker in/out points + rational in = marker->time().in(); + rational out = marker->time().out(); + + // Temporarily set in/out to be relative to the earliest selected marker + marker->set_time(TimeRange(in - min_start, marker->time().out() - min_start)); + marker->Save(&writer); + + // Reset in/out + marker->set_time(TimeRange(in, out)); + } + + + writer.writeEndElement(); // markers + writer.writeEndElement(); // olive + writer.writeEndDocument(); + + Core::CopyStringToClipboard(copy_str); +} + +void MarkerCopyPasteService::PasteMarkersFromClipboard(TimelineMarkerList* list, MultiUndoCommand* command, + rational offset) { + QString clipboard = Core::PasteStringFromClipboard(); + + if (clipboard.isEmpty()) { + return; + } + + QXmlStreamReader reader(clipboard); + uint data_version = 0; + + QVector pasted_markers; + + while (XMLReadNextStartElement(&reader)) { + if (reader.name() == QStringLiteral("olive")) { + while (XMLReadNextStartElement(&reader)) { + if (reader.name() == QStringLiteral("version")) { + data_version = reader.readElementText().toUInt(); + } else if (reader.name() == QStringLiteral("markers")) { + while (XMLReadNextStartElement(&reader)) { + if (data_version == 0) { + return; + } + QString name; + int color; + rational in, out; + + XMLAttributeLoop((&reader), attr) { + if (attr.name() == QStringLiteral("name")) { + name = attr.value().toString(); + } + + if (attr.name() == QStringLiteral("color")) { + color = attr.value().toInt(); + } + + if (attr.name() == QStringLiteral("in")) { + in = rational::fromString(attr.value().toString()); + } + + if (attr.name() == QStringLiteral("out")) { + out = rational::fromString(attr.value().toString()); + } + } + + command->add_child(new TimeBasedWidget::MarkerAddCommand(Core::instance()->GetActiveProject(), + list, + TimeRange(in + offset, out + offset), + name, + color)); + } + } + } + } + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); +} +} //namespace olive diff --git a/app/widget/marker/markercopypaste.h b/app/widget/marker/markercopypaste.h new file mode 100644 index 000000000..7566ad805 --- /dev/null +++ b/app/widget/marker/markercopypaste.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 MARKERCOPYPASTEWIDGET_H +#define MARKERCOPYPASTEWIDGET_H + +#include + +#include "widget/marker/marker.h" +#include "timeline/timelinemarker.h" +#include "undo/undocommand.h" + +namespace olive { + +class MarkerCopyPasteService +{ +public: + MarkerCopyPasteService() = default; + +protected: + void CopyMarkersToClipboard(const QVector &markers, void* userdata = nullptr); + + void PasteMarkersFromClipboard(TimelineMarkerList* list, MultiUndoCommand *command, rational offset); +}; + +} + +#endif // MARKERCOPYPASTEWIDGET_H diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index fcc53161f..28e8a7eab 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -665,11 +665,12 @@ void TimeBasedWidget::DeleteSelected() } } -TimeBasedWidget::MarkerAddCommand::MarkerAddCommand(Project *project, TimelineMarkerList *marker_list, const TimeRange &range, const QString &name) : +TimeBasedWidget::MarkerAddCommand::MarkerAddCommand(Project *project, TimelineMarkerList *marker_list, const TimeRange &range, const QString &name, int color) : project_(project), marker_list_(marker_list), range_(range), - name_(name) + name_(name), + color_(color) { } @@ -680,7 +681,7 @@ Project *TimeBasedWidget::MarkerAddCommand::GetRelevantProject() const void TimeBasedWidget::MarkerAddCommand::redo() { - added_marker_ = marker_list_->AddMarker(range_, name_); + added_marker_ = marker_list_->AddMarker(range_, name_, color_); } void TimeBasedWidget::MarkerAddCommand::undo() diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index ae6db3b56..1e6fb87fe 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -55,6 +55,27 @@ public: virtual bool eventFilter(QObject* object, QEvent* event) override; // Temp file location + + class MarkerAddCommand : public UndoCommand { + public: + MarkerAddCommand(Project* project, TimelineMarkerList* marker_list, const TimeRange& range, const QString& name, int color = -1); + + virtual Project* GetRelevantProject() const override; + + protected: + virtual void redo() override; + virtual void undo() override; + + private: + Project* project_; + TimelineMarkerList* marker_list_; + TimeRange range_; + QString name_; + int color_; + + TimelineMarker* added_marker_; + }; + class MarkerRemoveCommand : public UndoCommand { public: MarkerRemoveCommand(Project* project, TimelineMarker* marker, TimelineMarkerList* marker_list); @@ -163,26 +184,7 @@ signals: void ConnectedNodeChanged(ViewerOutput* old, ViewerOutput* now); private: - class MarkerAddCommand : public UndoCommand - { - public: - MarkerAddCommand(Project* project, TimelineMarkerList* marker_list, const TimeRange& range, const QString& name); - - virtual Project* GetRelevantProject() const override; - - protected: - virtual void redo() override; - virtual void undo() override; - - private: - Project* project_; - TimelineMarkerList* marker_list_; - TimeRange range_; - QString name_; - - TimelineMarker* added_marker_; - - }; + /** * @brief Set either in or out point to the current playhead diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index be6c68d1c..637203991 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -378,6 +378,15 @@ void TimelineWidget::DeselectAll() SignalDeselectedAllBlocks(); } +bool TimelineWidget::MarkersActive() +{ + if (ruler()->GetActiveTimelineMarkers().size() > 0) { + return true; + } else { + return false; + } +} + void TimelineWidget::RippleToIn() { RippleTo(Timeline::kTrimIn); @@ -475,7 +484,7 @@ void TimelineWidget::ReplaceBlocksWithGaps(const QVector &blocks, void TimelineWidget::DeleteSelected(bool ripple) { - if (ruler()->GetActiveTimelineMarkers().size() > 0) { + if (MarkersActive()) { ruler()->DeleteSelected(); return; } @@ -598,6 +607,12 @@ void TimelineWidget::CopySelected(bool cut) return; } + if (MarkersActive()) { + ruler()->CopySelected(cut); + return; + } + + QVector selected = GetSelectedBlocks(); if (selected.isEmpty()) { @@ -631,6 +646,11 @@ void TimelineWidget::Paste(bool insert) return; } + if (ruler()->underMouse()) { + ruler()->PasteMarkers(insert, GetTime()); + return; + } + MultiUndoCommand* command = new MultiUndoCommand(); QVector paste_data; diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 6082e2e51..080aa9fd5 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -59,6 +59,8 @@ public: void DeselectAll(); + bool MarkersActive(); + void RippleToIn(); void RippleToOut(); diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 0764ee80b..90fb36c79 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -106,6 +106,21 @@ const int &SeekableWidget::GetScroll() const return scroll_; } +void SeekableWidget::CopySelected(bool cut) +{ + CopyMarkersToClipboard(GetActiveTimelineMarkers()); + + if (cut) { + DeleteSelected(); + } +} + +void SeekableWidget::PasteMarkers(bool insert, rational insert_time) +{ + MultiUndoCommand *command = new MultiUndoCommand(); + PasteMarkersFromClipboard(timeline_points()->markers(), command, insert_time); +} + void SeekableWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index 05d9a81bb..7438245a3 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -28,10 +28,11 @@ #include "widget/snapservice/snapservice.h" #include "widget/timebased/timescaledobject.h" #include "widget/marker/marker.h" +#include "widget/marker/markercopypaste.h" namespace olive { -class SeekableWidget : public TimelineScaledWidget +class SeekableWidget : public TimelineScaledWidget, public MarkerCopyPasteService { Q_OBJECT public: @@ -55,6 +56,10 @@ public: void DeleteSelected(); + void CopySelected(bool cut); + + void PasteMarkers(bool insert, rational insert_time); + QVector GetActiveTimelineMarkers(); void DeselectAllMarkers(); From f037511cdbd71c1647526f4d8d5745a51216e5e4 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Thu, 26 Aug 2021 10:57:21 +0100 Subject: [PATCH 18/81] Fix build issue --- app/widget/timebased/timebasedwidget.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index 1e6fb87fe..4167858aa 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -93,8 +93,6 @@ public: TimeRange range_; QString name_; int color_; - - TimelineMarker* removed_marker_; }; public slots: From 59c8c45018c0884598a2fc3907fbd183d5b2cf5c Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Thu, 26 Aug 2021 11:40:34 +0100 Subject: [PATCH 19/81] Make sure color is definitely initialised --- app/widget/marker/markercopypaste.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/marker/markercopypaste.cpp b/app/widget/marker/markercopypaste.cpp index c5b48edb0..6dd7b7eee 100644 --- a/app/widget/marker/markercopypaste.cpp +++ b/app/widget/marker/markercopypaste.cpp @@ -87,7 +87,7 @@ void MarkerCopyPasteService::PasteMarkersFromClipboard(TimelineMarkerList* list, return; } QString name; - int color; + int color = -1; rational in, out; XMLAttributeLoop((&reader), attr) { From 374b97b6a5cb525baaab3936d048ee3c4c32f81e Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Mon, 18 Oct 2021 16:32:10 +0100 Subject: [PATCH 20/81] Add default marker color to config --- app/config/config.cpp | 2 ++ .../tabs/preferencesappearancetab.cpp | 18 ++++++++++++++++++ .../tabs/preferencesappearancetab.h | 2 ++ app/timeline/timelinemarker.cpp | 5 +++-- app/widget/marker/marker.cpp | 2 +- 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app/config/config.cpp b/app/config/config.cpp index 797cb6d9f..beb8c867d 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -134,6 +134,8 @@ void Config::SetDefaults() // Online/offline settings SetEntryInternal(QStringLiteral("OnlinePixelFormat"), NodeValue::kInt, VideoParams::kFormatFloat32); SetEntryInternal(QStringLiteral("OfflinePixelFormat"), NodeValue::kInt, VideoParams::kFormatFloat16); + + SetEntryInternal(QStringLiteral("MarkerColor"), NodeValue::kInt, ColorCoding::kRed); } void Config::Load() diff --git a/app/dialog/preferences/tabs/preferencesappearancetab.cpp b/app/dialog/preferences/tabs/preferencesappearancetab.cpp index bec5431ad..157f0a37a 100644 --- a/app/dialog/preferences/tabs/preferencesappearancetab.cpp +++ b/app/dialog/preferences/tabs/preferencesappearancetab.cpp @@ -82,6 +82,22 @@ PreferencesAppearanceTab::PreferencesAppearanceTab() appearance_layout->addWidget(color_group, row, 0, 1, 2); } + row++; + { + QGroupBox* marker_group = new QGroupBox(); + marker_group->setTitle(tr("Default Marker Color")); + + QGridLayout* marker_layout = new QGridLayout(marker_group); + + marker_layout->addWidget(new QLabel("Marker"), 0, 0); + + marker_btn_ = new ColorCodingComboBox(); + marker_btn_->SetColor(Config::Current()[QStringLiteral("MarkerColor")].toInt()); + marker_layout->addWidget(marker_btn_, 0, 1); + + appearance_layout->addWidget(marker_group, row, 0, 1, 2); + } + layout->addStretch(); } @@ -99,6 +115,8 @@ void PreferencesAppearanceTab::Accept(MultiUndoCommand *command) for (int i=0; iGetSelectedColor(); } + + Config::Current()[QStringLiteral("MarkerColor")] = marker_btn_->GetSelectedColor(); } } diff --git a/app/dialog/preferences/tabs/preferencesappearancetab.h b/app/dialog/preferences/tabs/preferencesappearancetab.h index 3ee33a0ad..286c79e03 100644 --- a/app/dialog/preferences/tabs/preferencesappearancetab.h +++ b/app/dialog/preferences/tabs/preferencesappearancetab.h @@ -47,6 +47,8 @@ private: QVector color_btns_; + ColorCodingComboBox* marker_btn_; + }; } diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index 1a0403568..79a31623c 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -21,15 +21,16 @@ #include "timelinemarker.h" #include "common/xmlutils.h" +#include "config/config.h" namespace olive { TimelineMarker::TimelineMarker(const TimeRange &time, const QString &name, QObject *parent) : QObject(parent), time_(time), - name_(name), - color_(7) // FIXME: set via config + name_(name) { + color_ = Config::Current()[QStringLiteral("MarkerColor")].toInt(); } const TimeRange &TimelineMarker::time() const diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index 0d49b70fa..f7689e59a 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -23,6 +23,7 @@ #include #include "common/qtutils.h" +#include "config/config.h" #include "ui/colorcoding.h" #include "widget/menu/menu.h" #include "widget/menu/menushared.h" @@ -33,7 +34,6 @@ namespace olive { Marker::Marker(QWidget *parent) : QWidget(parent), - marker_color_(7), //green FIXME: add default color to config active_(false) { //setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); From bf4459cbc7dc8b497887f86236ad933548eb5a22 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Mon, 18 Oct 2021 21:24:55 +0100 Subject: [PATCH 21/81] Add labels to makers --- app/widget/marker/marker.cpp | 30 ++++++++++++++++++++++++- app/widget/marker/marker.h | 6 +++++ app/widget/timeruler/seekablewidget.cpp | 7 ++++-- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index f7689e59a..cf9710c5b 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -24,6 +24,7 @@ #include "common/qtutils.h" #include "config/config.h" +#include "dialog/text/text.h" #include "ui/colorcoding.h" #include "widget/menu/menu.h" #include "widget/menu/menushared.h" @@ -37,7 +38,7 @@ Marker::Marker(QWidget *parent) : active_(false) { //setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - setMaximumSize(8, 20); + setMinimumSize(8, 20); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &Marker::customContextMenuRequested, this, &Marker::ShowContextMenu); @@ -98,6 +99,10 @@ void Marker::paintEvent(QPaintEvent *event) }; p.drawPolygon(points, 6); + + if (!name_.isEmpty()) { + p.drawText(x + marker_width_, y - half_text_height, name_); + } } void Marker::mousePressEvent(QMouseEvent* e) @@ -130,9 +135,24 @@ void Marker::ShowContextMenu() { m.addSeparator(); MenuShared::instance()->AddItemsForEditMenu(&m, false); + m.addSeparator(); + QAction rename; + rename.setText(tr("Rename")); + m.addAction(&rename); + connect(&rename, &QAction::triggered, this, &Marker::Rename); + m.exec(QCursor::pos()); } +void Marker::Rename() +{ + TextDialog d(this->name_, this); + if (d.exec() == QDialog::Accepted) { + QString s = d.text(); + emit NameChanged(s); + } +} + void Marker::SetColor(int c) { marker_color_ = c; @@ -140,4 +160,12 @@ void Marker::SetColor(int c) update(); } +void Marker::SetName(QString s) +{ + name_ = s; + qDebug() << name_; + + update(); +} + } // namespace olive diff --git a/app/widget/marker/marker.h b/app/widget/marker/marker.h index cb23cc39a..f86528450 100644 --- a/app/widget/marker/marker.h +++ b/app/widget/marker/marker.h @@ -37,9 +37,12 @@ class Marker : public QWidget { bool active(); + void Rename(); + public slots: void SetColor(int c); void SetActive(bool active); + void SetName(QString s); protected: void paintEvent(QPaintEvent* event) override; @@ -53,6 +56,7 @@ class Marker : public QWidget { void ColorChanged(int c); void markerSelected(Marker* marker); void ActiveChanged(bool active); + void NameChanged(QString name); private: @@ -60,6 +64,8 @@ class Marker : public QWidget { bool active_; + QString name_; + private slots: void ShowContextMenu(); }; diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 90fb36c79..18f8630a8 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -210,7 +210,10 @@ void SeekableWidget::addMarker(TimelineMarker* marker) connect(marker_widget, &Marker::ActiveChanged, marker, &TimelineMarker::set_active); connect(marker, &TimelineMarker::ActiveChanged, marker_widget, &Marker::SetActive); - marker_widget->move(TimeToScreen(marker->time().in()), 20); + connect(marker_widget, &Marker::NameChanged, marker, &TimelineMarker::set_name); + connect(marker, &TimelineMarker::NameChanged, marker_widget, &Marker::SetName); + + marker_widget->move(TimeToScreen(marker->time().in()), text_height_); marker_widget->SetColor(marker->color()); marker_widget->show(); } @@ -233,7 +236,7 @@ void SeekableWidget::updateMarkerPositions() { foreach (TimelineMarker* marker, marker_map_.keys()) { Marker *m = marker_map_.value(marker); - m->move(TimeToScreen(marker->time().in()), 20); + m->move(TimeToScreen(marker->time().in()), text_height_); } } From 3840c5158da52c18897b3d905d0eb21cdd99b065 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 19 Oct 2021 13:24:27 +0100 Subject: [PATCH 22/81] Improve widget resizing --- app/widget/marker/marker.cpp | 17 ++++++++++------- app/widget/timeruler/seekablewidget.cpp | 3 ++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index cf9710c5b..7b8db781b 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -20,11 +20,11 @@ #include "marker.h" +#include #include #include "common/qtutils.h" #include "config/config.h" -#include "dialog/text/text.h" #include "ui/colorcoding.h" #include "widget/menu/menu.h" #include "widget/menu/menushared.h" @@ -37,8 +37,8 @@ Marker::Marker(QWidget *parent) : QWidget(parent), active_(false) { - //setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - setMinimumSize(8, 20); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + resize(7, 14); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &Marker::customContextMenuRequested, this, &Marker::ShowContextMenu); @@ -101,7 +101,10 @@ void Marker::paintEvent(QPaintEvent *event) p.drawPolygon(points, 6); if (!name_.isEmpty()) { + resize(fm.horizontalAdvance(name_)+10, 14); p.drawText(x + marker_width_, y - half_text_height, name_); + } else { + resize(7, 14); } } @@ -146,10 +149,10 @@ void Marker::ShowContextMenu() { void Marker::Rename() { - TextDialog d(this->name_, this); - if (d.exec() == QDialog::Accepted) { - QString s = d.text(); - emit NameChanged(s); + bool ok; + QString marker_name = QInputDialog::getText(this, tr("Set Marker"), tr("Marker name:"), QLineEdit::Normal, QString(), &ok); + if (ok) { + emit NameChanged(marker_name); } } diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 18f8630a8..c2cc7cb2e 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -213,8 +213,9 @@ void SeekableWidget::addMarker(TimelineMarker* marker) connect(marker_widget, &Marker::NameChanged, marker, &TimelineMarker::set_name); connect(marker, &TimelineMarker::NameChanged, marker_widget, &Marker::SetName); - marker_widget->move(TimeToScreen(marker->time().in()), text_height_); + marker_widget->move(TimeToScreen(marker->time().in())-2, text_height_); marker_widget->SetColor(marker->color()); + marker_widget->SetName(marker->name()); marker_widget->show(); } } From f3e025c729e5f2601da06c5cd0593bc9c0d17ddb Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 19 Oct 2021 13:59:28 +0100 Subject: [PATCH 23/81] Improve marker selecting --- app/widget/marker/marker.cpp | 9 ++++++--- app/widget/timeruler/seekablewidget.cpp | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index 7b8db781b..2ab2b7306 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -110,8 +110,12 @@ void Marker::paintEvent(QPaintEvent *event) void Marker::mousePressEvent(QMouseEvent* e) { - if (e->button() == Qt::LeftButton) { - static_cast(parent())->SeekToScreenPoint(e->pos().x() + this->x()); + // Only select if clicking on the icon and not the label + if (e->pos().x() > 7) { + return; + } + if (e->button() == Qt::LeftButton || e->button() == Qt::RightButton) { + static_cast(parent())->SeekToScreenPoint(this->x() + 4); if (!active_) { if (e->modifiers() != Qt::ShiftModifier) { static_cast(parent())->DeselectAllMarkers(); @@ -166,7 +170,6 @@ void Marker::SetColor(int c) void Marker::SetName(QString s) { name_ = s; - qDebug() << name_; update(); } diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index c2cc7cb2e..37b9cc823 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -237,7 +237,7 @@ void SeekableWidget::updateMarkerPositions() { foreach (TimelineMarker* marker, marker_map_.keys()) { Marker *m = marker_map_.value(marker); - m->move(TimeToScreen(marker->time().in()), text_height_); + m->move(TimeToScreen(marker->time().in())-2, text_height_); } } From 8432b10c84b83adcdd75682190a1e73c265b9793 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 19 Oct 2021 14:24:28 +0100 Subject: [PATCH 24/81] Improve right click and renaming --- app/widget/marker/marker.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index 2ab2b7306..1e3bb1269 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -131,7 +131,14 @@ void Marker::mousePressEvent(QMouseEvent* e) } } -void Marker::ShowContextMenu() { +void Marker::ShowContextMenu() +{ + // Only show context menu if we clicked on the icon and not the label + QRect marker_icon(0, 0, 7, 14); + if (!marker_icon.contains(this->mapFromGlobal(QCursor::pos()))) { + return; + } + Menu m(this); // Color menu @@ -154,7 +161,7 @@ void Marker::ShowContextMenu() { void Marker::Rename() { bool ok; - QString marker_name = QInputDialog::getText(this, tr("Set Marker"), tr("Marker name:"), QLineEdit::Normal, QString(), &ok); + QString marker_name = QInputDialog::getText(this, tr("Set Marker"), tr("Marker name:"), QLineEdit::Normal, name_, &ok); if (ok) { emit NameChanged(marker_name); } From 1a7f821d82f42a87ee85f914baacf2ee83eb8fe2 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 19 Oct 2021 17:40:01 +0100 Subject: [PATCH 25/81] Make markers movable --- app/widget/marker/marker.cpp | 38 ++++++++++++++++++++++++- app/widget/marker/marker.h | 12 +++++++- app/widget/timeruler/seekablewidget.cpp | 3 ++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index 1e3bb1269..ad571339f 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -35,7 +35,8 @@ namespace olive { Marker::Marker(QWidget *parent) : QWidget(parent), - active_(false) + active_(false), + dragging_(false) { setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); resize(7, 14); @@ -129,6 +130,36 @@ void Marker::mousePressEvent(QMouseEvent* e) update(); } + + click_position_ = e->globalPos(); + marker_start_x_ = x(); +} + +void Marker::mouseMoveEvent(QMouseEvent* e) +{ + if (active_) { + dragging_ = true; + + QPoint old_pos = mapToGlobal(pos()); + + int new_pos = marker_start_x_ + e->globalPos().x() - click_position_.x(); + + if (new_pos > -3 && new_pos < static_cast(parent())->width() - 3) { + this->move(new_pos-2, this->pos().y()); + repaint(); + } + } +} + +void Marker::mouseReleaseEvent(QMouseEvent* e) +{ + if (dragging_) { + rational time = static_cast(parent())->SceneToTime(this->x()+4); + time = time < 0.0 ? 0.0 : time; + emit TimeChanged(TimeRange(time, time)); + + dragging_ = false; + } } void Marker::ShowContextMenu() @@ -181,4 +212,9 @@ void Marker::SetName(QString s) update(); } +void Marker::SetTime(TimeRange time) +{ + move(static_cast(parent())->TimeToScene(time.in())-2, y()); +} + } // namespace olive diff --git a/app/widget/marker/marker.h b/app/widget/marker/marker.h index f86528450..b4b17ed67 100644 --- a/app/widget/marker/marker.h +++ b/app/widget/marker/marker.h @@ -26,6 +26,7 @@ #include #include "common/define.h" +#include "common/timerange.h" #include "widget/colorlabelmenu/colorlabelmenu.h" namespace olive { @@ -43,11 +44,13 @@ class Marker : public QWidget { void SetColor(int c); void SetActive(bool active); void SetName(QString s); + void SetTime(TimeRange t); protected: void paintEvent(QPaintEvent* event) override; virtual void mousePressEvent(QMouseEvent* event) override; - //virtual void mouseReleaseEvent(QMouseEvent* event) override; + virtual void mouseMoveEvent(QMouseEvent* event) override; + virtual void mouseReleaseEvent(QMouseEvent* event) override; //virtual void mouseDoubleClickEvent(QMouseEvent* event) override; signals: @@ -57,6 +60,7 @@ class Marker : public QWidget { void markerSelected(Marker* marker); void ActiveChanged(bool active); void NameChanged(QString name); + void TimeChanged(TimeRange time); private: @@ -66,6 +70,12 @@ class Marker : public QWidget { QString name_; + QPointF click_position_; + + int marker_start_x_; + + bool dragging_; + private slots: void ShowContextMenu(); }; diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 37b9cc823..3b052b842 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -213,6 +213,9 @@ void SeekableWidget::addMarker(TimelineMarker* marker) connect(marker_widget, &Marker::NameChanged, marker, &TimelineMarker::set_name); connect(marker, &TimelineMarker::NameChanged, marker_widget, &Marker::SetName); + connect(marker_widget, &Marker::TimeChanged, marker, &TimelineMarker::set_time); + connect(marker, &TimelineMarker::TimeChanged, marker_widget, &Marker::SetTime); + marker_widget->move(TimeToScreen(marker->time().in())-2, text_height_); marker_widget->SetColor(marker->color()); marker_widget->SetName(marker->name()); From 2d35bcb1db9e8edb3d6f35ab5c291eef2e88a6eb Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 19 Oct 2021 19:17:45 +0100 Subject: [PATCH 26/81] cleanup --- app/widget/marker/marker.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index ad571339f..f6c9b26e7 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -140,8 +140,6 @@ void Marker::mouseMoveEvent(QMouseEvent* e) if (active_) { dragging_ = true; - QPoint old_pos = mapToGlobal(pos()); - int new_pos = marker_start_x_ + e->globalPos().x() - click_position_.x(); if (new_pos > -3 && new_pos < static_cast(parent())->width() - 3) { From 630950299320cbd0f7ed8075cc6db01596c4ac34 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 19 Oct 2021 19:41:33 +0100 Subject: [PATCH 27/81] Make sure scroll bars markers use correct color --- app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp b/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp index f928779bf..e45b3d1fc 100644 --- a/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp +++ b/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp @@ -25,6 +25,8 @@ #include #include +#include "ui/colorcoding.h" + namespace olive { ResizableTimelineScrollBar::ResizableTimelineScrollBar(QWidget* parent) : @@ -109,8 +111,8 @@ void ResizableTimelineScrollBar::paintEvent(QPaintEvent *event) } if (!points_->markers()->list().isEmpty()) { - QColor marker_color(0, 255, 0, 128); foreach (TimelineMarker* marker, points_->markers()->list()) { + QColor marker_color = ColorCoding::GetColor(marker->color()).toQColor(); int64_t in = qRound64(ratio * TimeToScene(marker->time().in())); int64_t out = qRound64(ratio * TimeToScene(marker->time().out())); int64_t length = qMax(int64_t(1), out-in); From ba5a984f14bf266dd251625d417691d42c5ad40f Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Thu, 21 Oct 2021 15:21:32 +0100 Subject: [PATCH 28/81] Remove hard coded values. Tweak dragging logic --- app/widget/marker/marker.cpp | 28 +++++++++++++++++++--------- app/widget/marker/marker.h | 6 ++++++ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index f6c9b26e7..5cb46dda1 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -36,10 +36,17 @@ namespace olive { Marker::Marker(QWidget *parent) : QWidget(parent), active_(false), + drag_allowed_(false), dragging_(false) { setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - resize(7, 14); + + QFontMetrics fm = fontMetrics(); + marker_height_ = fm.height(); + marker_width_ = QtUtils::QFontMetricsWidth(fm, "H"); + resize(marker_width_, marker_height_); + + setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &Marker::customContextMenuRequested, this, &Marker::ShowContextMenu); @@ -72,11 +79,11 @@ void Marker::paintEvent(QPaintEvent *event) int text_height = fm.height(); int marker_width_ = QtUtils::QFontMetricsWidth(fm, "H"); - int y = text_height; //13 + int y = text_height; int half_width = marker_width_ / 2; - int x = half_width; //3 + int x = half_width; QPainter p(this); if (active_) { @@ -102,17 +109,17 @@ void Marker::paintEvent(QPaintEvent *event) p.drawPolygon(points, 6); if (!name_.isEmpty()) { - resize(fm.horizontalAdvance(name_)+10, 14); + resize(marker_width_ + fm.horizontalAdvance(name_) + fm.horizontalAdvance(" "), marker_height_); p.drawText(x + marker_width_, y - half_text_height, name_); } else { - resize(7, 14); + resize(marker_width_, marker_height_); } } void Marker::mousePressEvent(QMouseEvent* e) { // Only select if clicking on the icon and not the label - if (e->pos().x() > 7) { + if (e->pos().x() > marker_width_) { return; } if (e->button() == Qt::LeftButton || e->button() == Qt::RightButton) { @@ -133,16 +140,17 @@ void Marker::mousePressEvent(QMouseEvent* e) click_position_ = e->globalPos(); marker_start_x_ = x(); + drag_allowed_ = true; } void Marker::mouseMoveEvent(QMouseEvent* e) { - if (active_) { + if (drag_allowed_) { dragging_ = true; int new_pos = marker_start_x_ + e->globalPos().x() - click_position_.x(); - if (new_pos > -3 && new_pos < static_cast(parent())->width() - 3) { + if (new_pos > -marker_width_ / 2 && new_pos < static_cast(parent())->width() - marker_width_ / 2) { this->move(new_pos-2, this->pos().y()); repaint(); } @@ -158,12 +166,14 @@ void Marker::mouseReleaseEvent(QMouseEvent* e) dragging_ = false; } + + drag_allowed_ = false; } void Marker::ShowContextMenu() { // Only show context menu if we clicked on the icon and not the label - QRect marker_icon(0, 0, 7, 14); + QRect marker_icon(0, 0, marker_width_, marker_height_); if (!marker_icon.contains(this->mapFromGlobal(QCursor::pos()))) { return; } diff --git a/app/widget/marker/marker.h b/app/widget/marker/marker.h index b4b17ed67..764931e3b 100644 --- a/app/widget/marker/marker.h +++ b/app/widget/marker/marker.h @@ -74,8 +74,14 @@ class Marker : public QWidget { int marker_start_x_; + bool drag_allowed_; + bool dragging_; + int marker_height_; + + int marker_width_; + private slots: void ShowContextMenu(); }; From 2840f957bdcd123a03d6eb8bc311fa7f4868c5f4 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Thu, 21 Oct 2021 15:30:31 +0100 Subject: [PATCH 29/81] Fix shadow vairable --- app/widget/marker/marker.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index 5cb46dda1..e5d3a7ca9 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -76,10 +76,10 @@ void Marker::paintEvent(QPaintEvent *event) { QFontMetrics fm = fontMetrics(); - int text_height = fm.height(); - int marker_width_ = QtUtils::QFontMetricsWidth(fm, "H"); + //int text_height = fm.height(); + //int marker_width_ = QtUtils::QFontMetricsWidth(fm, "H"); - int y = text_height; + int y = marker_height_; int half_width = marker_width_ / 2; @@ -95,14 +95,14 @@ void Marker::paintEvent(QPaintEvent *event) p.setRenderHint(QPainter::Antialiasing); - int half_text_height = text_height / 3; + int half_marker_height = marker_height_ / 3; QPoint points[] = { QPoint(x, y), - QPoint(x - half_width, y - half_text_height), - QPoint(x - half_width, y - text_height), - QPoint(x + 1 + half_width, y - text_height), - QPoint(x + 1 + half_width, y - half_text_height), + QPoint(x - half_width, y - half_marker_height), + QPoint(x - half_width, y - marker_height_), + QPoint(x + 1 + half_width, y - marker_height_), + QPoint(x + 1 + half_width, y - half_marker_height), QPoint(x + 1, y), }; @@ -110,7 +110,7 @@ void Marker::paintEvent(QPaintEvent *event) if (!name_.isEmpty()) { resize(marker_width_ + fm.horizontalAdvance(name_) + fm.horizontalAdvance(" "), marker_height_); - p.drawText(x + marker_width_, y - half_text_height, name_); + p.drawText(x + marker_width_, y - half_marker_height, name_); } else { resize(marker_width_, marker_height_); } From 7af6f85a7c17c188b32278dab1d198140fd44bb1 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Thu, 21 Oct 2021 20:21:22 +0100 Subject: [PATCH 30/81] Move marker undo code to seperate file --- app/widget/marker/CMakeLists.txt | 2 + app/widget/marker/markercopypaste.cpp | 3 +- app/widget/marker/markerundo.cpp | 69 ++++++++++++++++++++++++ app/widget/marker/markerundo.h | 66 +++++++++++++++++++++++ app/widget/timebased/timebasedwidget.cpp | 50 +---------------- app/widget/timebased/timebasedwidget.h | 41 -------------- app/widget/timeruler/seekablewidget.cpp | 3 +- 7 files changed, 142 insertions(+), 92 deletions(-) create mode 100644 app/widget/marker/markerundo.cpp create mode 100644 app/widget/marker/markerundo.h diff --git a/app/widget/marker/CMakeLists.txt b/app/widget/marker/CMakeLists.txt index 01ce2157d..73922308e 100644 --- a/app/widget/marker/CMakeLists.txt +++ b/app/widget/marker/CMakeLists.txt @@ -20,5 +20,7 @@ set(OLIVE_SOURCES widget/marker/marker.cpp widget/marker/markercopypaste.h widget/marker/markercopypaste.cpp + widget/marker/markerundo.h + widget/marker/markerundo.cpp PARENT_SCOPE ) diff --git a/app/widget/marker/markercopypaste.cpp b/app/widget/marker/markercopypaste.cpp index 6dd7b7eee..4a2eddbe3 100644 --- a/app/widget/marker/markercopypaste.cpp +++ b/app/widget/marker/markercopypaste.cpp @@ -16,6 +16,7 @@ #include "markercopypaste.h" #include "core.h" +#include "markerundo.h" #include "widget/timebased/timebasedwidget.h" namespace olive { @@ -108,7 +109,7 @@ void MarkerCopyPasteService::PasteMarkersFromClipboard(TimelineMarkerList* list, } } - command->add_child(new TimeBasedWidget::MarkerAddCommand(Core::instance()->GetActiveProject(), + command->add_child(new MarkerAddCommand(Core::instance()->GetActiveProject(), list, TimeRange(in + offset, out + offset), name, diff --git a/app/widget/marker/markerundo.cpp b/app/widget/marker/markerundo.cpp new file mode 100644 index 000000000..ba338b031 --- /dev/null +++ b/app/widget/marker/markerundo.cpp @@ -0,0 +1,69 @@ +/*** + 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 "markerundo.h" + +namespace olive { + +MarkerAddCommand::MarkerAddCommand(Project *project, TimelineMarkerList *marker_list, const TimeRange &range, const QString &name, int color) : + project_(project), + marker_list_(marker_list), + range_(range), + name_(name), + color_(color) +{ +} + +Project* MarkerAddCommand::GetRelevantProject() const +{ + return project_; +} + +void MarkerAddCommand::redo() +{ + added_marker_ = marker_list_->AddMarker(range_, name_, color_); +} + +void MarkerAddCommand::undo() +{ + marker_list_->RemoveMarker(added_marker_); +} + +MarkerRemoveCommand::MarkerRemoveCommand(Project *project, TimelineMarker *marker, TimelineMarkerList *marker_list) : + project_(project), + marker_(marker), + marker_list_(marker_list), + range_(marker->time()), + name_(marker->name()), + color_(marker->color()) +{ +} + +Project* MarkerRemoveCommand::GetRelevantProject() const +{ + return project_; +} + +void MarkerRemoveCommand::redo() +{ + marker_list_->RemoveMarker(marker_); +} + +void MarkerRemoveCommand::undo() +{ + marker_list_->AddMarker(range_, name_, color_); +} + +} diff --git a/app/widget/marker/markerundo.h b/app/widget/marker/markerundo.h new file mode 100644 index 000000000..5390b8ead --- /dev/null +++ b/app/widget/marker/markerundo.h @@ -0,0 +1,66 @@ +/*** + 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 MARKERUNDO_H +#define MARKERUNDO_H + +#include "undo/undocommand.h" +#include "timeline/timelinemarker.h" + +namespace olive { + +class MarkerAddCommand : public UndoCommand { + public: + MarkerAddCommand(Project* project, TimelineMarkerList* marker_list, const TimeRange& range, const QString& name, + int color = -1); + + virtual Project* GetRelevantProject() const override; + + protected: + virtual void redo() override; + virtual void undo() override; + + private: + Project* project_; + TimelineMarkerList* marker_list_; + TimeRange range_; + QString name_; + int color_; + + TimelineMarker* added_marker_; +}; + +class MarkerRemoveCommand : public UndoCommand { + public: + MarkerRemoveCommand(Project* project, TimelineMarker* marker, TimelineMarkerList* marker_list); + + virtual Project* GetRelevantProject() const override; + + protected: + virtual void redo() override; + virtual void undo() override; + + private: + Project* project_; + TimelineMarker* marker_; + TimelineMarkerList* marker_list_; + TimeRange range_; + QString name_; + int color_; +}; + +} + +#endif // TIMELINEUNDOTRACK_H diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index 28e8a7eab..351803553 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -27,6 +27,7 @@ #include "config/config.h" #include "core.h" #include "node/project/sequence/sequence.h" +#include "widget/marker/markerundo.h" #include "widget/timelinewidget/undo/timelineundoworkarea.h" namespace olive { @@ -665,55 +666,6 @@ void TimeBasedWidget::DeleteSelected() } } -TimeBasedWidget::MarkerAddCommand::MarkerAddCommand(Project *project, TimelineMarkerList *marker_list, const TimeRange &range, const QString &name, int color) : - project_(project), - marker_list_(marker_list), - range_(range), - name_(name), - color_(color) -{ -} - -Project *TimeBasedWidget::MarkerAddCommand::GetRelevantProject() const -{ - return project_; -} - -void TimeBasedWidget::MarkerAddCommand::redo() -{ - added_marker_ = marker_list_->AddMarker(range_, name_, color_); -} - -void TimeBasedWidget::MarkerAddCommand::undo() -{ - marker_list_->RemoveMarker(added_marker_); -} - -TimeBasedWidget::MarkerRemoveCommand::MarkerRemoveCommand(Project* project, TimelineMarker* marker, TimelineMarkerList* marker_list) : - project_(project), - marker_(marker), - marker_list_(marker_list), - range_(marker->time()), - name_(marker->name()), - color_(marker->color()) -{ -} - -Project *TimeBasedWidget::MarkerRemoveCommand::GetRelevantProject() const -{ - return project_; -} - -void TimeBasedWidget::MarkerRemoveCommand::redo() -{ - marker_list_->RemoveMarker(marker_); -} - -void TimeBasedWidget::MarkerRemoveCommand::undo() -{ - marker_list_->AddMarker(range_, name_, color_); -} - bool TimeBasedWidget::eventFilter(QObject *object, QEvent *event) { if (wheel_passthrough_objects_.contains(object) && event->type() == QEvent::Wheel) { diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index 4167858aa..e8b6bee76 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -54,47 +54,6 @@ public: virtual bool eventFilter(QObject* object, QEvent* event) override; - // Temp file location - - class MarkerAddCommand : public UndoCommand { - public: - MarkerAddCommand(Project* project, TimelineMarkerList* marker_list, const TimeRange& range, const QString& name, int color = -1); - - virtual Project* GetRelevantProject() const override; - - protected: - virtual void redo() override; - virtual void undo() override; - - private: - Project* project_; - TimelineMarkerList* marker_list_; - TimeRange range_; - QString name_; - int color_; - - TimelineMarker* added_marker_; - }; - - class MarkerRemoveCommand : public UndoCommand { - public: - MarkerRemoveCommand(Project* project, TimelineMarker* marker, TimelineMarkerList* marker_list); - - virtual Project* GetRelevantProject() const override; - - protected: - virtual void redo() override; - virtual void undo() override; - - private: - Project* project_; - TimelineMarker* marker_; - TimelineMarkerList* marker_list_; - TimeRange range_; - QString name_; - int color_; - }; - public slots: void SetTime(const rational &time); diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 3b052b842..fe803c408 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -27,6 +27,7 @@ #include "common/qtutils.h" #include "core.h" #include "widget/timebased/timebasedwidget.h" +#include "widget/marker/markerundo.h" namespace olive { @@ -95,7 +96,7 @@ void SeekableWidget::DeleteSelected() { MultiUndoCommand* command = new MultiUndoCommand(); foreach (TimelineMarker *marker, GetActiveTimelineMarkers()) { - command->add_child(new TimeBasedWidget::MarkerRemoveCommand(Core::instance()->GetActiveProject(), marker, timeline_points_->markers())); + command->add_child(new MarkerRemoveCommand(Core::instance()->GetActiveProject(), marker, timeline_points_->markers())); } Core::instance()->undo_stack()->pushIfHasChildren(command); From 5c7073eab6b8578d8f010f69e82c8dfe85f5d549 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Thu, 21 Oct 2021 21:47:22 +0100 Subject: [PATCH 31/81] Make changing marker color undoable --- app/widget/marker/markerundo.cpp | 23 +++++++++++++++++++++++ app/widget/marker/markerundo.h | 17 +++++++++++++++++ app/widget/timeruler/seekablewidget.cpp | 6 +++++- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/app/widget/marker/markerundo.cpp b/app/widget/marker/markerundo.cpp index ba338b031..8a78efcb0 100644 --- a/app/widget/marker/markerundo.cpp +++ b/app/widget/marker/markerundo.cpp @@ -66,4 +66,27 @@ void MarkerRemoveCommand::undo() marker_list_->AddMarker(range_, name_, color_); } +MarkerChangeColorCommand::MarkerChangeColorCommand(Project *project, TimelineMarker *marker, int new_color) : + project_(project), + marker_(marker), + old_color_(marker->color()), + new_color_(new_color) +{ +} + +Project* MarkerChangeColorCommand::GetRelevantProject() const +{ + return project_; +} + +void MarkerChangeColorCommand::redo() +{ + marker_->set_color(new_color_); +} + +void MarkerChangeColorCommand::undo() +{ + marker_->set_color(old_color_); +} + } diff --git a/app/widget/marker/markerundo.h b/app/widget/marker/markerundo.h index 5390b8ead..370723b8a 100644 --- a/app/widget/marker/markerundo.h +++ b/app/widget/marker/markerundo.h @@ -61,6 +61,23 @@ class MarkerRemoveCommand : public UndoCommand { int color_; }; +class MarkerChangeColorCommand : public UndoCommand { + public: + MarkerChangeColorCommand(Project* project, TimelineMarker* marker, int new_color); + + virtual Project* GetRelevantProject() const override; + + protected: + virtual void redo() override; + virtual void undo() override; + + private: + Project* project_; + TimelineMarker* marker_; + int old_color_; + int new_color_; +}; + } #endif // TIMELINEUNDOTRACK_H diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index fe803c408..77b04cd7b 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -232,9 +232,13 @@ void SeekableWidget::removeMarker(TimelineMarker *marker) void SeekableWidget::SetMarkerColor(int c) { + MultiUndoCommand *command = new MultiUndoCommand(); + foreach(TimelineMarker* marker, GetActiveTimelineMarkers()) { - marker->set_color(c); + command->add_child(new MarkerChangeColorCommand(Core::instance()->GetActiveProject(), marker, c)); } + + Core::instance()->undo_stack()->pushIfHasChildren(command); } void SeekableWidget::updateMarkerPositions() From 87b66e0cbba3517dd62cc5048bf61d1b2a93c28a Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Thu, 21 Oct 2021 22:54:42 +0100 Subject: [PATCH 32/81] Make renaming markers undoable --- app/timeline/timelinemarker.cpp | 9 +++++++++ app/timeline/timelinemarker.h | 3 +++ app/widget/marker/markerundo.cpp | 23 +++++++++++++++++++++++ app/widget/marker/markerundo.h | 17 +++++++++++++++++ app/widget/timeruler/seekablewidget.cpp | 2 +- 5 files changed, 53 insertions(+), 1 deletion(-) diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index 79a31623c..bece44e0f 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -22,6 +22,8 @@ #include "common/xmlutils.h" #include "config/config.h" +#include "core.h" +#include "widget/marker/markerundo.h" namespace olive { @@ -55,6 +57,13 @@ void TimelineMarker::set_name(const QString &name) emit NameChanged(name_); } +void TimelineMarker::set_name_undo(QString name) +{ + UndoCommand *command = new MarkerChangeNameCommand(Core::instance()->GetActiveProject(), this, name); + + Core::instance()->undo_stack()->push(command); +} + int TimelineMarker::color() { return color_; diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index f63a1f3d3..70b3a53b2 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -50,6 +50,9 @@ public: void Load(QXmlStreamReader* reader); void Save(QXmlStreamWriter* writer) const; +public slots: + void set_name_undo(QString name); + signals: void TimeChanged(const TimeRange& time); diff --git a/app/widget/marker/markerundo.cpp b/app/widget/marker/markerundo.cpp index 8a78efcb0..17ff46df1 100644 --- a/app/widget/marker/markerundo.cpp +++ b/app/widget/marker/markerundo.cpp @@ -89,4 +89,27 @@ void MarkerChangeColorCommand::undo() marker_->set_color(old_color_); } +MarkerChangeNameCommand::MarkerChangeNameCommand(Project *project, TimelineMarker *marker, QString new_name) : + project_(project), + marker_(marker), + old_name_(marker->name()), + new_name_(new_name) +{ +} + +Project* MarkerChangeNameCommand::GetRelevantProject() const +{ + return project_; +} + +void MarkerChangeNameCommand::redo() +{ + marker_->set_name(new_name_); +} + +void MarkerChangeNameCommand::undo() +{ + marker_->set_name(old_name_); +} + } diff --git a/app/widget/marker/markerundo.h b/app/widget/marker/markerundo.h index 370723b8a..5dd4d4cbf 100644 --- a/app/widget/marker/markerundo.h +++ b/app/widget/marker/markerundo.h @@ -78,6 +78,23 @@ class MarkerChangeColorCommand : public UndoCommand { int new_color_; }; +class MarkerChangeNameCommand : public UndoCommand { + public: + MarkerChangeNameCommand(Project* project, TimelineMarker* marker, QString name); + + virtual Project* GetRelevantProject() const override; + + protected: + virtual void redo() override; + virtual void undo() override; + + private: + Project* project_; + TimelineMarker* marker_; + QString old_name_; + QString new_name_; +}; + } #endif // TIMELINEUNDOTRACK_H diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 77b04cd7b..3602c4d50 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -211,7 +211,7 @@ void SeekableWidget::addMarker(TimelineMarker* marker) connect(marker_widget, &Marker::ActiveChanged, marker, &TimelineMarker::set_active); connect(marker, &TimelineMarker::ActiveChanged, marker_widget, &Marker::SetActive); - connect(marker_widget, &Marker::NameChanged, marker, &TimelineMarker::set_name); + connect(marker_widget, &Marker::NameChanged, marker, &TimelineMarker::set_name_undo); connect(marker, &TimelineMarker::NameChanged, marker_widget, &Marker::SetName); connect(marker_widget, &Marker::TimeChanged, marker, &TimelineMarker::set_time); From d04d16545105ac70403348c1e4db020f0788d152 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Thu, 21 Oct 2021 23:00:34 +0100 Subject: [PATCH 33/81] Add safety checks to DeslectAllMarkers() --- app/widget/timeruler/seekablewidget.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 3602c4d50..b62d60414 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -194,6 +194,12 @@ QVector SeekableWidget::GetActiveTimelineMarkers() { void SeekableWidget::DeselectAllMarkers() { + if (timebase().isNull()) { + return; + } + if (!timeline_points()) { + return; + } foreach(TimelineMarker* marker, timeline_points()->markers()->list()) { marker->set_active(false); } From c8552ad184f911211945ec61ad7cd8166ea1ec0b Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Fri, 22 Oct 2021 21:55:35 +0100 Subject: [PATCH 34/81] Make moving a marker undoable --- app/timeline/timelinemarker.cpp | 6 ++++++ app/timeline/timelinemarker.h | 1 + app/widget/marker/markerundo.cpp | 23 +++++++++++++++++++++++ app/widget/marker/markerundo.h | 19 +++++++++++++++++++ app/widget/timeruler/seekablewidget.cpp | 2 +- 5 files changed, 50 insertions(+), 1 deletion(-) diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index bece44e0f..18b7a19a7 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -46,6 +46,12 @@ void TimelineMarker::set_time(const TimeRange &time) emit TimeChanged(time_); } +void TimelineMarker::set_time_undo(TimeRange time) { + UndoCommand *command = new MarkerChangeTimeCommand(Core::instance()->GetActiveProject(), this, time); + + Core::instance()->undo_stack()->push(command); +} + const QString &TimelineMarker::name() const { return name_; diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index 70b3a53b2..71e651f37 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -52,6 +52,7 @@ public: public slots: void set_name_undo(QString name); + void set_time_undo(TimeRange time); signals: void TimeChanged(const TimeRange& time); diff --git a/app/widget/marker/markerundo.cpp b/app/widget/marker/markerundo.cpp index 17ff46df1..e75a0a68e 100644 --- a/app/widget/marker/markerundo.cpp +++ b/app/widget/marker/markerundo.cpp @@ -112,4 +112,27 @@ void MarkerChangeNameCommand::undo() marker_->set_name(old_name_); } +MarkerChangeTimeCommand::MarkerChangeTimeCommand(Project* project, TimelineMarker* marker, TimeRange time) : + project_(project), + marker_(marker), + old_time_(marker->time()), + new_time_(time) +{ +} + +Project* MarkerChangeTimeCommand::GetRelevantProject() const +{ + return project_; +} + +void MarkerChangeTimeCommand::redo() +{ + marker_->set_time(new_time_); +} + +void MarkerChangeTimeCommand::undo() +{ + marker_->set_time(old_time_); +} + } diff --git a/app/widget/marker/markerundo.h b/app/widget/marker/markerundo.h index 5dd4d4cbf..d5ef865bc 100644 --- a/app/widget/marker/markerundo.h +++ b/app/widget/marker/markerundo.h @@ -95,6 +95,25 @@ class MarkerChangeNameCommand : public UndoCommand { QString new_name_; }; +class MarkerChangeTimeCommand : public UndoCommand { + public: + MarkerChangeTimeCommand(Project* project, TimelineMarker* marker, TimeRange time); + + virtual Project* GetRelevantProject() const override; + + protected: + virtual void redo() override; + virtual void undo() override; + + private: + Project* project_; + TimelineMarker* marker_; + TimeRange old_time_; + TimeRange new_time_; + + +}; + } #endif // TIMELINEUNDOTRACK_H diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index b62d60414..da712cfc9 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -220,7 +220,7 @@ void SeekableWidget::addMarker(TimelineMarker* marker) connect(marker_widget, &Marker::NameChanged, marker, &TimelineMarker::set_name_undo); connect(marker, &TimelineMarker::NameChanged, marker_widget, &Marker::SetName); - connect(marker_widget, &Marker::TimeChanged, marker, &TimelineMarker::set_time); + connect(marker_widget, &Marker::TimeChanged, marker, &TimelineMarker::set_time_undo); connect(marker, &TimelineMarker::TimeChanged, marker_widget, &Marker::SetTime); marker_widget->move(TimeToScreen(marker->time().in())-2, text_height_); From a331104d4c42032bb1f0264c64f63dc820f65b72 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sat, 23 Oct 2021 19:45:17 +0100 Subject: [PATCH 35/81] Draw clip markers on timeline --- .../timelinewidget/view/timelineview.cpp | 58 +++++++++++++++++++ app/widget/timelinewidget/view/timelineview.h | 2 + 2 files changed, 60 insertions(+) diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 97424c59a..cd2572095 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -512,6 +512,25 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q } } } + + // Draw markers + if (clip->connected_viewer() && !clip->connected_viewer()->GetLength().isNull()) { + if (clip->connected_viewer()->GetTimelinePoints()->markers()->list().size() > 0) { + QList marker_list = clip->connected_viewer()->GetTimelinePoints()->markers()->list(); + + QFontMetrics fm = fontMetrics(); + int marker_width = QtUtils::QFontMetricsWidth(fm, "H"); + + if (marker_list.length() * marker_width < block_right - block_left) { + foreach (TimelineMarker *marker, marker_list) { + if (marker->time().in() >= clip->media_in() && marker->time().out() <= clip->media_in() + clip->length()) { + DrawClipMarker(painter, TimeToScene(clip->in() - clip->media_in() + marker->time().in()), + block_top + block_height, marker->color(), marker->name()); + } + } + } + } + } } // For transitions, show lines representing a transition @@ -581,6 +600,45 @@ void TimelineView::DrawZebraStripes(QPainter *painter, const QRectF &r) painter->setClipping(false); } +void TimelineView::DrawClipMarker(QPainter* painter, double marker_x, qreal marker_y, int marker_color, QString name) +{ + QFontMetrics fm = fontMetrics(); + + int marker_height = fm.height(); + int marker_width = QtUtils::QFontMetricsWidth(fm, "H"); + + int y = marker_y - 1; + + int half_width = marker_width / 2; + + int x = marker_x + half_width; + + painter->setPen(Qt::black); + + painter->setBrush(ColorCoding::GetColor(marker_color).toQColor()); + + painter->setRenderHint(QPainter::Antialiasing); + + int half_marker_height = marker_height / 3; + + qDebug() << x << y; + + QPoint points[] = { + QPoint(x, y), + QPoint(x - half_width, y - half_marker_height), + QPoint(x - half_width, y - marker_height), + QPoint(x + 1 + half_width, y - marker_height), + QPoint(x + 1 + half_width, y - half_marker_height), + QPoint(x + 1, y), + }; + + painter->drawPolygon(points, 6); + + if (!name.isEmpty()) { + painter->drawText(x + marker_width, y - half_marker_height, name); + } +} + int TimelineView::GetHeightOfAllTracks() const { if (connected_track_list_) { diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index 878efca76..1c17dabce 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -132,6 +132,8 @@ private: void DrawZebraStripes(QPainter *painter, const QRectF &r); + void DrawClipMarker(QPainter* painter, double narker_x, qreal marker_y, int marker_color, QString name = QString()); + int GetHeightOfAllTracks() const; void UpdatePlayheadRect(); From fe07c40ce9c0b1221b27c152bcc36aea001ed08b Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sat, 23 Oct 2021 22:26:07 +0100 Subject: [PATCH 36/81] Fix shadow variable --- app/widget/timelinewidget/view/timelineview.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index cd2572095..4b4808a0a 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -518,7 +518,6 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q if (clip->connected_viewer()->GetTimelinePoints()->markers()->list().size() > 0) { QList marker_list = clip->connected_viewer()->GetTimelinePoints()->markers()->list(); - QFontMetrics fm = fontMetrics(); int marker_width = QtUtils::QFontMetricsWidth(fm, "H"); if (marker_list.length() * marker_width < block_right - block_left) { From 82df680c52b383b4373a8d56b1c13527a1a4bf2c Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Mon, 25 Oct 2021 21:33:58 +0100 Subject: [PATCH 37/81] Only draw clip marker names if there is space --- .../timelinewidget/view/timelineview.cpp | 25 ++++++++++++++++--- app/widget/timelinewidget/view/timelineview.h | 2 +- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 4b4808a0a..e4b3b38e2 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -520,11 +520,28 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q int marker_width = QtUtils::QFontMetricsWidth(fm, "H"); + + // Only draw markers if the block UI is large enough to draw all the markers if (marker_list.length() * marker_width < block_right - block_left) { - foreach (TimelineMarker *marker, marker_list) { + + QListIterator iterator(marker_list); + while(iterator.hasNext()) { + TimelineMarker *marker = iterator.next(); + // Make sure marker is within In/Out points of the clip if (marker->time().in() >= clip->media_in() && marker->time().out() <= clip->media_in() + clip->length()) { + // Only draw names that we have room for + bool draw_name = true; + if (!marker->name().isEmpty()) { + int length = fm.horizontalAdvance(marker->name()); + if (iterator.hasNext()) { + if (TimeToScene(iterator.peekNext()->time().in()) - TimeToScene(marker->time().out()) < + (double)length) { + draw_name = false; + } + } + } DrawClipMarker(painter, TimeToScene(clip->in() - clip->media_in() + marker->time().in()), - block_top + block_height, marker->color(), marker->name()); + block_top + block_height, marker->color(), draw_name, marker->name()); } } } @@ -599,7 +616,7 @@ void TimelineView::DrawZebraStripes(QPainter *painter, const QRectF &r) painter->setClipping(false); } -void TimelineView::DrawClipMarker(QPainter* painter, double marker_x, qreal marker_y, int marker_color, QString name) +void TimelineView::DrawClipMarker(QPainter* painter, double marker_x, qreal marker_y, int marker_color, bool draw_name, QString name) { QFontMetrics fm = fontMetrics(); @@ -633,7 +650,7 @@ void TimelineView::DrawClipMarker(QPainter* painter, double marker_x, qreal mark painter->drawPolygon(points, 6); - if (!name.isEmpty()) { + if (!name.isEmpty() && draw_name) { painter->drawText(x + marker_width, y - half_marker_height, name); } } diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index 1c17dabce..36a964ed5 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -132,7 +132,7 @@ private: void DrawZebraStripes(QPainter *painter, const QRectF &r); - void DrawClipMarker(QPainter* painter, double narker_x, qreal marker_y, int marker_color, QString name = QString()); + void DrawClipMarker(QPainter* painter, double narker_x, qreal marker_y, int marker_color, bool draw_name, QString name = QString()); int GetHeightOfAllTracks() const; From 9eef02b446be2ee420be96b98a4dc8a82c7bee83 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 26 Oct 2021 00:37:53 +0100 Subject: [PATCH 38/81] make sure clip markers in timeline refresh as soon as marker is edited --- app/widget/marker/marker.cpp | 7 +++++++ app/widget/timelinewidget/timelinewidget.h | 5 +++++ app/widget/timelinewidget/view/timelineview.cpp | 2 -- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index e5d3a7ca9..e716385ac 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -25,6 +25,8 @@ #include "common/qtutils.h" #include "config/config.h" +#include "panel/panelmanager.h" +#include "panel/timeline/timeline.h" #include "ui/colorcoding.h" #include "widget/menu/menu.h" #include "widget/menu/menushared.h" @@ -114,6 +116,11 @@ void Marker::paintEvent(QPaintEvent *event) } else { resize(marker_width_, marker_height_); } + + TimelinePanel* timeline = PanelManager::instance()->MostRecentlyFocused(); + if (timeline) { + timeline->timeline_widget()->Refresh(); + } } void Marker::mousePressEvent(QMouseEvent* e) diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 080aa9fd5..55236255d 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -218,6 +218,11 @@ public: */ void SignalDeselectedAllBlocks(); + void Refresh() + { + UpdateViewports(); + } + MultiUndoCommand *TakeSubtitleSectionCommand() { // Copy pointer diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index e4b3b38e2..a7eb9420b 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -637,8 +637,6 @@ void TimelineView::DrawClipMarker(QPainter* painter, double marker_x, qreal mark int half_marker_height = marker_height / 3; - qDebug() << x << y; - QPoint points[] = { QPoint(x, y), QPoint(x - half_width, y - half_marker_height), From 0ccd1eca4813adb9cafc9bc589f05fbcdabf3ca9 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 26 Oct 2021 00:39:56 +0100 Subject: [PATCH 39/81] Replace hacky code --- app/widget/marker/marker.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index e716385ac..1b7683acc 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -60,9 +60,9 @@ void Marker::SetActive(bool active) // Feels very hacky, might it be better to write some access methods? if (active) { - TimelineWidget *timeline = dynamic_cast(parent()->parent()); + TimelinePanel *timeline = PanelManager::instance()->MostRecentlyFocused(); if (timeline) { - static_cast(parent()->parent())->DeselectAll(); + timeline->timeline_widget()->DeselectAll(); } } @@ -117,6 +117,7 @@ void Marker::paintEvent(QPaintEvent *event) resize(marker_width_, marker_height_); } + // In case this is a clip marker, redraw the timeline to update the clip marker TimelinePanel* timeline = PanelManager::instance()->MostRecentlyFocused(); if (timeline) { timeline->timeline_widget()->Refresh(); From 631e5d8ac873e19c51a35362d7f603e4fd54fe0a Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 26 Oct 2021 13:16:42 +0100 Subject: [PATCH 40/81] Ensure only drag on left click --- app/widget/marker/marker.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index 1b7683acc..b39216fa6 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -146,9 +146,11 @@ void Marker::mousePressEvent(QMouseEvent* e) update(); } - click_position_ = e->globalPos(); - marker_start_x_ = x(); - drag_allowed_ = true; + if (e->button() == Qt::LeftButton) { + click_position_ = e->globalPos(); + marker_start_x_ = x(); + drag_allowed_ = true; + } } void Marker::mouseMoveEvent(QMouseEvent* e) From 8e40e033b9abd488b837b4f3e61b27f130a99a24 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 26 Oct 2021 18:12:59 +0100 Subject: [PATCH 41/81] Clicking on a clip marker moves the play head to that timesatmp --- .../timelinewidget/view/timelineview.cpp | 27 ++++++++++++++----- app/widget/timelinewidget/view/timelineview.h | 4 ++- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index a7eb9420b..eb57d5f02 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -32,7 +32,10 @@ #include "common/qtutils.h" #include "common/timecodefunctions.h" #include "node/project/footage/footage.h" +#include "panel/panelmanager.h" +#include "panel/timeline/timeline.h" #include "ui/colorcoding.h" +#include "widget/timelinewidget/timelinewidget.h" namespace olive { @@ -59,6 +62,15 @@ TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent) : void TimelineView::mousePressEvent(QMouseEvent *event) { + foreach (QRectF rect, clip_marker_positions_.values()) { + if (rect.contains(mapToScene(event->pos()))) { + TimelinePanel *timeline = PanelManager::instance()->MostRecentlyFocused(); + if (timeline) { + timeline->timeline_widget()->SetTime(clip_marker_positions_.key(rect)->time().in()); + } + } + } + TimelineViewMouseEvent timeline_event = CreateMouseEvent(event); if (HandPress(event) @@ -519,7 +531,7 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q QList marker_list = clip->connected_viewer()->GetTimelinePoints()->markers()->list(); int marker_width = QtUtils::QFontMetricsWidth(fm, "H"); - + clip_marker_positions_.clear(); // Only draw markers if the block UI is large enough to draw all the markers if (marker_list.length() * marker_width < block_right - block_left) { @@ -541,7 +553,7 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q } } DrawClipMarker(painter, TimeToScene(clip->in() - clip->media_in() + marker->time().in()), - block_top + block_height, marker->color(), draw_name, marker->name()); + block_top + block_height, marker, draw_name); } } } @@ -616,7 +628,7 @@ void TimelineView::DrawZebraStripes(QPainter *painter, const QRectF &r) painter->setClipping(false); } -void TimelineView::DrawClipMarker(QPainter* painter, double marker_x, qreal marker_y, int marker_color, bool draw_name, QString name) +void TimelineView::DrawClipMarker(QPainter* painter, double marker_x, qreal marker_y, TimelineMarker* marker, bool draw_name) { QFontMetrics fm = fontMetrics(); @@ -631,7 +643,7 @@ void TimelineView::DrawClipMarker(QPainter* painter, double marker_x, qreal mark painter->setPen(Qt::black); - painter->setBrush(ColorCoding::GetColor(marker_color).toQColor()); + painter->setBrush(ColorCoding::GetColor(marker->color()).toQColor()); painter->setRenderHint(QPainter::Antialiasing); @@ -648,9 +660,12 @@ void TimelineView::DrawClipMarker(QPainter* painter, double marker_x, qreal mark painter->drawPolygon(points, 6); - if (!name.isEmpty() && draw_name) { - painter->drawText(x + marker_width, y - half_marker_height, name); + if (!marker->name().isEmpty() && draw_name) { + painter->drawText(x + marker_width, y - half_marker_height, marker->name()); } + + QPointF scenePos = QPoint(x - half_width, y); + clip_marker_positions_.insert(marker, (QRectF(scenePos, QSize(marker_width, -marker_height)))); } int TimelineView::GetHeightOfAllTracks() const diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index 36a964ed5..c22c5caba 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -132,7 +132,7 @@ private: void DrawZebraStripes(QPainter *painter, const QRectF &r); - void DrawClipMarker(QPainter* painter, double narker_x, qreal marker_y, int marker_color, bool draw_name, QString name = QString()); + void DrawClipMarker(QPainter* painter, double marker_x, qreal marker_y, TimelineMarker* marker, bool draw_name); int GetHeightOfAllTracks() const; @@ -157,6 +157,8 @@ private: ClipBlock *transition_overlay_out_; ClipBlock *transition_overlay_in_; + QMap clip_marker_positions_; + private slots: void TrackListChanged(); From b4bfee119399337ae141ac3df830f1c98344ee1e Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Mon, 8 Nov 2021 20:20:44 +0000 Subject: [PATCH 42/81] Add warning for malformed XML data --- app/widget/marker/markercopypaste.cpp | 13 +++++++++++++ app/widget/marker/markercopypaste.h | 1 + 2 files changed, 14 insertions(+) diff --git a/app/widget/marker/markercopypaste.cpp b/app/widget/marker/markercopypaste.cpp index 4a2eddbe3..d5710ef27 100644 --- a/app/widget/marker/markercopypaste.cpp +++ b/app/widget/marker/markercopypaste.cpp @@ -15,6 +15,8 @@ #include "markercopypaste.h" +#include + #include "core.h" #include "markerundo.h" #include "widget/timebased/timebasedwidget.h" @@ -120,6 +122,17 @@ void MarkerCopyPasteService::PasteMarkersFromClipboard(TimelineMarkerList* list, } } + if (reader.hasError()) { + // If this was NOT an internal error, we assume it's an XML error that the user needs to know about + QMessageBox::critical((QWidget*)Core::instance()->main_window(), + QCoreApplication::translate("MarkerCopyPasteWidget", "Error pasting markers"), + QCoreApplication::translate("MarkerCopyPasteWidget", "Failed to paste markers: %1").arg(reader.errorString()), + QMessageBox::Ok); + + return; + } + Core::instance()->undo_stack()->pushIfHasChildren(command); + } } //namespace olive diff --git a/app/widget/marker/markercopypaste.h b/app/widget/marker/markercopypaste.h index 7566ad805..36ca175a7 100644 --- a/app/widget/marker/markercopypaste.h +++ b/app/widget/marker/markercopypaste.h @@ -16,6 +16,7 @@ #ifndef MARKERCOPYPASTEWIDGET_H #define MARKERCOPYPASTEWIDGET_H +#include #include #include "widget/marker/marker.h" From 3a5f0134afc6d76711bd227f41edcee1ce54aab4 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Mon, 8 Nov 2021 20:51:38 +0000 Subject: [PATCH 43/81] Enable marker snapping --- app/widget/marker/marker.cpp | 18 +++++++++++++++--- app/widget/timeruler/seekablewidget.h | 8 +++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index b39216fa6..cd503470a 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -158,10 +158,22 @@ void Marker::mouseMoveEvent(QMouseEvent* e) if (drag_allowed_) { dragging_ = true; - int new_pos = marker_start_x_ + e->globalPos().x() - click_position_.x(); + int new_position = marker_start_x_ + e->globalPos().x() - click_position_.x(); - if (new_pos > -marker_width_ / 2 && new_pos < static_cast(parent())->width() - marker_width_ / 2) { - this->move(new_pos-2, this->pos().y()); + rational marker_time = static_cast(parent())->ScreenToTime(new_position); + + if (Core::instance()->snapping()) { + rational movement; + static_cast(parent())->GetSnapService()->SnapPoint({marker_time}, &movement); + if (!movement.isNull()) { + marker_time += movement; + } + + new_position = static_cast(parent())->TimeToScene(marker_time); + } + + if (new_position > -marker_width_ / 2 && new_position < static_cast(parent())->width() - marker_width_ / 2) { + this->move(new_position-2, this->pos().y()); repaint(); } } diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index 7438245a3..cbbcf939f 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -49,6 +49,8 @@ public: void SetSnapService(SnapService* service); + SnapService* GetSnapService() { return snap_service_; }; + bool IsDraggingPlayhead() const { return dragging_; @@ -66,6 +68,9 @@ public: void SeekToScreenPoint(int screen); + int TimeToScreen(const rational& time) const; + rational ScreenToTime(int x) const; + public slots: void SetTime(const rational &r); @@ -91,9 +96,6 @@ protected: TimelinePoints* timeline_points() const; - int TimeToScreen(const rational& time) const; - rational ScreenToTime(int x) const; - void DrawPlayhead(QPainter* p, int x, int y); inline const int& text_height() const { From bb1aa136b3df4264064660d90a03640f405cccf1 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Mon, 8 Nov 2021 21:33:19 +0000 Subject: [PATCH 44/81] Code cleanup --- app/widget/marker/marker.cpp | 21 +++++++++++++-------- app/widget/marker/marker.h | 5 ++++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index cd503470a..68514bbe8 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -131,10 +131,10 @@ void Marker::mousePressEvent(QMouseEvent* e) return; } if (e->button() == Qt::LeftButton || e->button() == Qt::RightButton) { - static_cast(parent())->SeekToScreenPoint(this->x() + 4); + SeekableParent()->SeekToScreenPoint(this->x() + 4); if (!active_) { if (e->modifiers() != Qt::ShiftModifier) { - static_cast(parent())->DeselectAllMarkers(); + SeekableParent()->DeselectAllMarkers(); } emit ActiveChanged(true); } else { @@ -160,19 +160,19 @@ void Marker::mouseMoveEvent(QMouseEvent* e) int new_position = marker_start_x_ + e->globalPos().x() - click_position_.x(); - rational marker_time = static_cast(parent())->ScreenToTime(new_position); + rational marker_time = SeekableParent()->ScreenToTime(new_position); if (Core::instance()->snapping()) { rational movement; - static_cast(parent())->GetSnapService()->SnapPoint({marker_time}, &movement); + SeekableParent()->GetSnapService()->SnapPoint({marker_time}, &movement); if (!movement.isNull()) { marker_time += movement; } - new_position = static_cast(parent())->TimeToScene(marker_time); + new_position = SeekableParent()->TimeToScene(marker_time); } - if (new_position > -marker_width_ / 2 && new_position < static_cast(parent())->width() - marker_width_ / 2) { + if (new_position > -marker_width_ / 2 && new_position < SeekableParent()->width() - marker_width_ / 2) { this->move(new_position-2, this->pos().y()); repaint(); } @@ -182,7 +182,7 @@ void Marker::mouseMoveEvent(QMouseEvent* e) void Marker::mouseReleaseEvent(QMouseEvent* e) { if (dragging_) { - rational time = static_cast(parent())->SceneToTime(this->x()+4); + rational time = SeekableParent()->SceneToTime(this->x() + 4); time = time < 0.0 ? 0.0 : time; emit TimeChanged(TimeRange(time, time)); @@ -192,6 +192,11 @@ void Marker::mouseReleaseEvent(QMouseEvent* e) drag_allowed_ = false; } +SeekableWidget* Marker::SeekableParent() +{ + return static_cast(parent()); +} + void Marker::ShowContextMenu() { // Only show context menu if we clicked on the icon and not the label @@ -244,7 +249,7 @@ void Marker::SetName(QString s) void Marker::SetTime(TimeRange time) { - move(static_cast(parent())->TimeToScene(time.in())-2, y()); + move(SeekableParent()->TimeToScene(time.in())-2, y()); } } // namespace olive diff --git a/app/widget/marker/marker.h b/app/widget/marker/marker.h index 764931e3b..f061b75f0 100644 --- a/app/widget/marker/marker.h +++ b/app/widget/marker/marker.h @@ -31,6 +31,8 @@ namespace olive { +class SeekableWidget; + class Marker : public QWidget { Q_OBJECT public: @@ -40,6 +42,8 @@ class Marker : public QWidget { void Rename(); + SeekableWidget* SeekableParent(); + public slots: void SetColor(int c); void SetActive(bool active); @@ -51,7 +55,6 @@ class Marker : public QWidget { virtual void mousePressEvent(QMouseEvent* event) override; virtual void mouseMoveEvent(QMouseEvent* event) override; virtual void mouseReleaseEvent(QMouseEvent* event) override; - //virtual void mouseDoubleClickEvent(QMouseEvent* event) override; signals: //void MouseClicked(); From 7b14b7f23273332ec3e7ba0474ef6fbd1134bc0c Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Mon, 8 Nov 2021 22:32:42 +0000 Subject: [PATCH 45/81] Add background color to marker label --- app/widget/marker/marker.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index 68514bbe8..771246a3f 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -93,7 +93,9 @@ void Marker::paintEvent(QPaintEvent *event) } else { p.setPen(Qt::black); } - p.setBrush(ColorCoding::GetColor(marker_color_).toQColor()); + + QColor color = ColorCoding::GetColor(marker_color_).toQColor(); + p.setBrush(color); p.setRenderHint(QPainter::Antialiasing); @@ -112,6 +114,8 @@ void Marker::paintEvent(QPaintEvent *event) if (!name_.isEmpty()) { resize(marker_width_ + fm.horizontalAdvance(name_) + fm.horizontalAdvance(" "), marker_height_); + color.setAlphaF(0.5); + p.fillRect(x, y, fm.horizontalAdvance(name_) + fm.horizontalAdvance(" ") * 2 + half_width, -marker_height_, color); p.drawText(x + marker_width_, y - half_marker_height, name_); } else { resize(marker_width_, marker_height_); From e606c156c5b423976371e8a0ab1fbac8c936dd3d Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 9 Nov 2021 11:59:29 +0000 Subject: [PATCH 46/81] Cleanup for review --- app/config/config.cpp | 2 +- app/panel/timeline/timeline.cpp | 2 -- app/widget/marker/marker.cpp | 30 +++++++++++-------- app/widget/marker/marker.h | 2 -- app/widget/marker/markercopypaste.cpp | 2 -- app/widget/timelinewidget/tool/pointer.cpp | 1 + .../timelinewidget/view/timelineview.cpp | 4 +-- app/widget/timeruler/seekablewidget.cpp | 16 ++++++---- app/widget/timeruler/timeruler.h | 1 - 9 files changed, 31 insertions(+), 29 deletions(-) diff --git a/app/config/config.cpp b/app/config/config.cpp index beb8c867d..30c0924f3 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -135,7 +135,7 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("OnlinePixelFormat"), NodeValue::kInt, VideoParams::kFormatFloat32); SetEntryInternal(QStringLiteral("OfflinePixelFormat"), NodeValue::kInt, VideoParams::kFormatFloat16); - SetEntryInternal(QStringLiteral("MarkerColor"), NodeValue::kInt, ColorCoding::kRed); + SetEntryInternal(QStringLiteral("MarkerColor"), NodeValue::kInt, ColorCoding::kGreen); } void Config::Load() diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index 155ab050d..461d94f16 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -20,8 +20,6 @@ #include "timeline.h" -#include - #include "panel/panelmanager.h" #include "panel/project/footagemanagementpanel.h" diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp index 771246a3f..eb058a3f2 100644 --- a/app/widget/marker/marker.cpp +++ b/app/widget/marker/marker.cpp @@ -23,8 +23,8 @@ #include #include -#include "common/qtutils.h" #include "config/config.h" +#include "common/qtutils.h" #include "panel/panelmanager.h" #include "panel/timeline/timeline.h" #include "ui/colorcoding.h" @@ -58,7 +58,8 @@ void Marker::SetActive(bool active) { active_ = active; - // Feels very hacky, might it be better to write some access methods? + // Generaly the same functions delete both markers and timeline blocks. This helps ensure that + // markers and timeline blocks can never be selected at the same time if (active) { TimelinePanel *timeline = PanelManager::instance()->MostRecentlyFocused(); if (timeline) { @@ -78,9 +79,6 @@ void Marker::paintEvent(QPaintEvent *event) { QFontMetrics fm = fontMetrics(); - //int text_height = fm.height(); - //int marker_width_ = QtUtils::QFontMetricsWidth(fm, "H"); - int y = marker_height_; int half_width = marker_width_ / 2; @@ -114,8 +112,12 @@ void Marker::paintEvent(QPaintEvent *event) if (!name_.isEmpty()) { resize(marker_width_ + fm.horizontalAdvance(name_) + fm.horizontalAdvance(" "), marker_height_); + + // Draw background rectangle color.setAlphaF(0.5); p.fillRect(x, y, fm.horizontalAdvance(name_) + fm.horizontalAdvance(" ") * 2 + half_width, -marker_height_, color); + + // Draw text p.drawText(x + marker_width_, y - half_marker_height, name_); } else { resize(marker_width_, marker_height_); @@ -166,14 +168,16 @@ void Marker::mouseMoveEvent(QMouseEvent* e) rational marker_time = SeekableParent()->ScreenToTime(new_position); - if (Core::instance()->snapping()) { - rational movement; - SeekableParent()->GetSnapService()->SnapPoint({marker_time}, &movement); - if (!movement.isNull()) { - marker_time += movement; - } + if (SeekableParent()->GetSnapService()) { + if (Core::instance()->snapping()) { + rational movement; + SeekableParent()->GetSnapService()->SnapPoint({marker_time}, &movement); + if (!movement.isNull()) { + marker_time += movement; + } - new_position = SeekableParent()->TimeToScene(marker_time); + new_position = SeekableParent()->TimeToScene(marker_time); + } } if (new_position > -marker_width_ / 2 && new_position < SeekableParent()->width() - marker_width_ / 2) { @@ -198,7 +202,7 @@ void Marker::mouseReleaseEvent(QMouseEvent* e) SeekableWidget* Marker::SeekableParent() { - return static_cast(parent()); + return dynamic_cast(parent()); } void Marker::ShowContextMenu() diff --git a/app/widget/marker/marker.h b/app/widget/marker/marker.h index f061b75f0..972dbb05a 100644 --- a/app/widget/marker/marker.h +++ b/app/widget/marker/marker.h @@ -57,8 +57,6 @@ class Marker : public QWidget { virtual void mouseReleaseEvent(QMouseEvent* event) override; signals: - //void MouseClicked(); - //void MouseDoubleClicked(); void ColorChanged(int c); void markerSelected(Marker* marker); void ActiveChanged(bool active); diff --git a/app/widget/marker/markercopypaste.cpp b/app/widget/marker/markercopypaste.cpp index d5710ef27..92a15d575 100644 --- a/app/widget/marker/markercopypaste.cpp +++ b/app/widget/marker/markercopypaste.cpp @@ -77,8 +77,6 @@ void MarkerCopyPasteService::PasteMarkersFromClipboard(TimelineMarkerList* list, QXmlStreamReader reader(clipboard); uint data_version = 0; - QVector pasted_markers; - while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("olive")) { while (XMLReadNextStartElement(&reader)) { diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index e29cd4fd4..3e1f46e01 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -136,6 +136,7 @@ void PointerTool::MousePress(TimelineViewMouseEvent *event) drag_global_start_ = QCursor::pos(); } + // If we click anywhere other than a marker, deselect all markers parent()->ruler()->DeselectAllMarkers(); } diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index eb57d5f02..6761fe25e 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -62,6 +62,7 @@ TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent) : void TimelineView::mousePressEvent(QMouseEvent *event) { + // If we click on marker, jump to that point in the timeline foreach (QRectF rect, clip_marker_positions_.values()) { if (rect.contains(mapToScene(event->pos()))) { TimelinePanel *timeline = PanelManager::instance()->MostRecentlyFocused(); @@ -546,8 +547,7 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q if (!marker->name().isEmpty()) { int length = fm.horizontalAdvance(marker->name()); if (iterator.hasNext()) { - if (TimeToScene(iterator.peekNext()->time().in()) - TimeToScene(marker->time().out()) < - (double)length) { + if (TimeToScene(iterator.peekNext()->time().in()) - TimeToScene(marker->time().out()) < (double)length) { draw_name = false; } } diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index da712cfc9..1e46da75e 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -26,8 +26,8 @@ #include "common/qtutils.h" #include "core.h" -#include "widget/timebased/timebasedwidget.h" #include "widget/marker/markerundo.h" +#include "widget/timebased/timebasedwidget.h" namespace olive { @@ -46,7 +46,6 @@ SeekableWidget::SeekableWidget(QWidget* parent) : playhead_width_ = QtUtils::QFontMetricsWidth(fm, "H"); setContextMenuPolicy(Qt::CustomContextMenu); - } void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) @@ -211,6 +210,14 @@ void SeekableWidget::addMarker(TimelineMarker* marker) Marker *marker_widget = new Marker(this); marker_map_.insert(marker, marker_widget); + /* + Markers are stored as TimelineMarkers and represented in the UI as Markers. As a single + TimelineMarker can be represented in various views it is necessary to make sure all instances + of the TimelineMarker's (UI) Markers are kept in sync. To do this, whenever a Marker is updated + in some way, it signals that change to the relevant TimelineMarker which in turn broadcasts + that update out to all the relevant Markers. + */ + connect(marker_widget, &Marker::ColorChanged, this, &SeekableWidget::SetMarkerColor); connect(marker, &TimelineMarker::ColorChanged, marker_widget, &Marker::SetColor); @@ -327,10 +334,7 @@ void SeekableWidget::DrawTimelinePoints(QPainter* p, int marker_bottom) continue; } - if (marker->time().length() == 0) { - // Single point in time marker - //DrawPlayhead(p, marker_left, marker_bottom); - } else { + if (marker->time().length() != 0) { // Marker range int rect_left = qMax(0, marker_left); int rect_right = qMin(width(), marker_right); diff --git a/app/widget/timeruler/timeruler.h b/app/widget/timeruler/timeruler.h index 77a7b3b82..496117238 100644 --- a/app/widget/timeruler/timeruler.h +++ b/app/widget/timeruler/timeruler.h @@ -27,7 +27,6 @@ #include "common/timerange.h" #include "seekablewidget.h" #include "render/playbackcache.h" -#include "widget/marker/marker.h" namespace olive { From fed62246e61aa0a880640eb96f5824a706f9787f Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 30 Jan 2022 13:36:11 +0000 Subject: [PATCH 47/81] Move marker load/save code to Serializer and fix build issues. Copy/paste not working --- .../project/serializer/serializer211228.cpp | 31 ++++++++---- .../project/serializer/serializer211228.h | 4 ++ app/timeline/timelinemarker.cpp | 48 ------------------- app/timeline/timelinemarker.h | 3 +- app/widget/marker/markercopypaste.cpp | 4 +- app/widget/marker/markercopypaste.h | 1 + 6 files changed, 29 insertions(+), 62 deletions(-) diff --git a/app/node/project/serializer/serializer211228.cpp b/app/node/project/serializer/serializer211228.cpp index a10d34bae..fbdcd780d 100644 --- a/app/node/project/serializer/serializer211228.cpp +++ b/app/node/project/serializer/serializer211228.cpp @@ -21,6 +21,7 @@ #include "serializer211228.h" #include "node/factory.h" +#include "timeline/timelinemarker.h" namespace olive { @@ -913,16 +914,33 @@ void ProjectSerializer211228::SaveWorkArea(QXmlStreamWriter *writer, TimelineWor writer->writeAttribute(QStringLiteral("out"), workarea->out().toString()); } +void ProjectSerializer211228::SaveMarker(QXmlStreamWriter* writer, TimelineMarker* marker) const +{ + writer->writeStartElement(QStringLiteral("marker")); + + writer->writeAttribute(QStringLiteral("name"), marker->name()); + + writer->writeAttribute(QStringLiteral("color"), QString::number(marker->color())); + + writer->writeAttribute(QStringLiteral("in"), marker->time().in().toString()); + writer->writeAttribute(QStringLiteral("out"), marker->time().out().toString()); + + writer->writeEndElement(); // marker +} + void ProjectSerializer211228::LoadMarkerList(QXmlStreamReader *reader, TimelineMarkerList *markers) const { - while (XMLReadNextStartElement(reader)) { + while(XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("marker")) { QString name; + int color = -1; rational in, out; XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("name")) { name = attr.value().toString(); + } else if (attr.name() == QStringLiteral("color")) { + color = attr.value().toInt(); } else if (attr.name() == QStringLiteral("in")) { in = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("out")) { @@ -930,7 +948,7 @@ void ProjectSerializer211228::LoadMarkerList(QXmlStreamReader *reader, TimelineM } } - markers->AddMarker(TimeRange(in, out), name); + markers->AddMarker(TimeRange(in, out), name, color); } reader->skipCurrentElement(); @@ -940,14 +958,7 @@ void ProjectSerializer211228::LoadMarkerList(QXmlStreamReader *reader, TimelineM void ProjectSerializer211228::SaveMarkerList(QXmlStreamWriter *writer, TimelineMarkerList *markers) const { foreach (TimelineMarker* marker, markers->list()) { - writer->writeStartElement(QStringLiteral("marker")); - - writer->writeAttribute(QStringLiteral("name"), marker->name()); - - writer->writeAttribute(QStringLiteral("in"), marker->time().in().toString()); - writer->writeAttribute(QStringLiteral("out"), marker->time().out().toString()); - - writer->writeEndElement(); // marker + SaveMarker(writer, marker); } } diff --git a/app/node/project/serializer/serializer211228.h b/app/node/project/serializer/serializer211228.h index 543a45498..10512ab46 100644 --- a/app/node/project/serializer/serializer211228.h +++ b/app/node/project/serializer/serializer211228.h @@ -97,6 +97,10 @@ private: void SaveWorkArea(QXmlStreamWriter *writer, TimelineWorkArea *workarea) const; + void LoadMarker(QXmlStreamReader *reader); + + void SaveMarker(QXmlStreamWriter *writer, TimelineMarker *marker) const; + void LoadMarkerList(QXmlStreamReader *reader, TimelineMarkerList *markers) const; void SaveMarkerList(QXmlStreamWriter *writer, TimelineMarkerList *markers) const; diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index e60b40947..e75a625ba 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -93,27 +93,6 @@ void TimelineMarker::set_active(bool active) emit ActiveChanged(active_); } - -void TimelineMarker::Save(QXmlStreamWriter* writer) const -{ - writer->writeStartElement(QStringLiteral("marker")); - - writer->writeAttribute(QStringLiteral("name"), name_); - - writer->writeAttribute(QStringLiteral("color"), QString::number(color_)); - - writer->writeAttribute(QStringLiteral("in"), time_.in().toString()); - writer->writeAttribute(QStringLiteral("out"), time_.out().toString()); - - writer->writeEndElement(); // marker -} - -void TimelineMarkerList::Save(QXmlStreamWriter *writer) const -{ - foreach (TimelineMarker* marker, markers_) { - marker->Save(writer); - } -} TimelineMarkerList::~TimelineMarkerList() { @@ -150,31 +129,4 @@ const QList &TimelineMarkerList::list() const return markers_; } -void TimelineMarkerList::Load(QXmlStreamReader *reader) -{ - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("marker")) { - QString name; - int color = -1; - rational in, out; - - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("name")) { - name = attr.value().toString(); - }else if(attr.name() == QStringLiteral("color")){ - color = attr.value().toInt(); - } else if (attr.name() == QStringLiteral("in")) { - in = rational::fromString(attr.value().toString()); - } else if (attr.name() == QStringLiteral("out")) { - out = rational::fromString(attr.value().toString()); - } - } - - AddMarker(TimeRange(in, out), name, color); - } - - reader->skipCurrentElement(); - } -} - } diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index f1bb60c7e..e33afce04 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -48,7 +48,6 @@ public: void set_active(bool active); void Load(QXmlStreamReader* reader); - void Save(QXmlStreamWriter* writer) const; public slots: void set_name_undo(QString name); @@ -82,7 +81,7 @@ public: virtual ~TimelineMarkerList() override; - TimelineMarker *AddMarker(const TimeRange& time = TimeRange(), const QString& name = QString(), int color = -1); + TimelineMarker* AddMarker(const TimeRange& time = TimeRange(), const QString& name = QString(), int color = -1); void RemoveMarker(TimelineMarker* marker); diff --git a/app/widget/marker/markercopypaste.cpp b/app/widget/marker/markercopypaste.cpp index 92a15d575..f5352907e 100644 --- a/app/widget/marker/markercopypaste.cpp +++ b/app/widget/marker/markercopypaste.cpp @@ -33,7 +33,7 @@ void MarkerCopyPasteService::CopyMarkersToClipboard(const QVectorset_time(TimeRange(in - min_start, marker->time().out() - min_start)); - marker->Save(&writer); + //marker->Save(&writer); // Reset in/out marker->set_time(TimeRange(in, out)); diff --git a/app/widget/marker/markercopypaste.h b/app/widget/marker/markercopypaste.h index 36ca175a7..e0715fb40 100644 --- a/app/widget/marker/markercopypaste.h +++ b/app/widget/marker/markercopypaste.h @@ -20,6 +20,7 @@ #include #include "widget/marker/marker.h" +#include "node/project/serializer/serializer.h" #include "timeline/timelinemarker.h" #include "undo/undocommand.h" From fdcee7cd0734ff2c723ae0df6e8daf9ca0199fc7 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 2 Feb 2022 10:46:08 -0800 Subject: [PATCH 48/81] Very minor serializer refactoring --- .../project/serializer/serializer211228.cpp | 52 +++++++++++-------- .../project/serializer/serializer211228.h | 4 +- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/app/node/project/serializer/serializer211228.cpp b/app/node/project/serializer/serializer211228.cpp index fbdcd780d..320c06cad 100644 --- a/app/node/project/serializer/serializer211228.cpp +++ b/app/node/project/serializer/serializer211228.cpp @@ -914,51 +914,57 @@ void ProjectSerializer211228::SaveWorkArea(QXmlStreamWriter *writer, TimelineWor writer->writeAttribute(QStringLiteral("out"), workarea->out().toString()); } +void ProjectSerializer211228::LoadMarker(QXmlStreamReader *reader, TimelineMarkerList *markers) const +{ + QString name; + int color = -1; + rational in, out; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("name")) { + name = attr.value().toString(); + } else if (attr.name() == QStringLiteral("color")) { + color = attr.value().toInt(); + } else if (attr.name() == QStringLiteral("in")) { + in = rational::fromString(attr.value().toString()); + } else if (attr.name() == QStringLiteral("out")) { + out = rational::fromString(attr.value().toString()); + } + } + + markers->AddMarker(TimeRange(in, out), name, color); + + // Required to move XML reader to the end of element + reader->readElementText(); +} + void ProjectSerializer211228::SaveMarker(QXmlStreamWriter* writer, TimelineMarker* marker) const { - writer->writeStartElement(QStringLiteral("marker")); - writer->writeAttribute(QStringLiteral("name"), marker->name()); writer->writeAttribute(QStringLiteral("color"), QString::number(marker->color())); writer->writeAttribute(QStringLiteral("in"), marker->time().in().toString()); writer->writeAttribute(QStringLiteral("out"), marker->time().out().toString()); - - writer->writeEndElement(); // marker } void ProjectSerializer211228::LoadMarkerList(QXmlStreamReader *reader, TimelineMarkerList *markers) const { while(XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("marker")) { - QString name; - int color = -1; - rational in, out; - - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("name")) { - name = attr.value().toString(); - } else if (attr.name() == QStringLiteral("color")) { - color = attr.value().toInt(); - } else if (attr.name() == QStringLiteral("in")) { - in = rational::fromString(attr.value().toString()); - } else if (attr.name() == QStringLiteral("out")) { - out = rational::fromString(attr.value().toString()); - } - } - - markers->AddMarker(TimeRange(in, out), name, color); + LoadMarker(reader, markers); + } else { + reader->skipCurrentElement(); } - - reader->skipCurrentElement(); } } void ProjectSerializer211228::SaveMarkerList(QXmlStreamWriter *writer, TimelineMarkerList *markers) const { foreach (TimelineMarker* marker, markers->list()) { + writer->writeStartElement(QStringLiteral("marker")); SaveMarker(writer, marker); + writer->writeEndElement(); // marker } } diff --git a/app/node/project/serializer/serializer211228.h b/app/node/project/serializer/serializer211228.h index 10512ab46..96ca65956 100644 --- a/app/node/project/serializer/serializer211228.h +++ b/app/node/project/serializer/serializer211228.h @@ -97,8 +97,8 @@ private: void SaveWorkArea(QXmlStreamWriter *writer, TimelineWorkArea *workarea) const; - void LoadMarker(QXmlStreamReader *reader); - + void LoadMarker(QXmlStreamReader *reader, TimelineMarkerList *markers) const; + void SaveMarker(QXmlStreamWriter *writer, TimelineMarker *marker) const; void LoadMarkerList(QXmlStreamReader *reader, TimelineMarkerList *markers) const; From ad61954aa22b027deac080c2477a3da40bb5f87e Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 17 Apr 2022 23:35:45 -0700 Subject: [PATCH 49/81] rational: don't try to reduce when denom == 1 Fixes #1895 (and probably several other issues when using -O2 on ARM64 systems) --- app/common/rational.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/common/rational.cpp b/app/common/rational.cpp index 9aa20426c..34d627572 100644 --- a/app/common/rational.cpp +++ b/app/common/rational.cpp @@ -72,7 +72,7 @@ void rational::fix_signs() void rational::reduce() { - if (!isNull()) { + if (!isNull() && denom_ != 1) { // Euclidean often fails if numbers are negative, we abs it and re-neg it later if necessary bool neg = numer_ < 0; From 3661599213e47404ea0d5021e2bbfa16bcc80790 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 18 Apr 2022 00:05:35 -0700 Subject: [PATCH 50/81] timeline/viewer: set minimum on rationalslider --- app/widget/playbackcontrols/playbackcontrols.cpp | 1 + app/widget/timelinewidget/timelinewidget.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/app/widget/playbackcontrols/playbackcontrols.cpp b/app/widget/playbackcontrols/playbackcontrols.cpp index db318ab98..41f771a17 100644 --- a/app/widget/playbackcontrols/playbackcontrols.cpp +++ b/app/widget/playbackcontrols/playbackcontrols.cpp @@ -55,6 +55,7 @@ PlaybackControls::PlaybackControls(QWidget *parent) : cur_tc_lbl_ = new RationalSlider(); cur_tc_lbl_->SetDisplayType(RationalSlider::kTime); + cur_tc_lbl_->SetMinimum(0); connect(cur_tc_lbl_, &RationalSlider::ValueChanged, this, &PlaybackControls::TimeChanged); lower_left_layout->addWidget(cur_tc_lbl_); lower_left_layout->addStretch(); diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 07c3fe52c..547f11cc9 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -76,6 +76,7 @@ TimelineWidget::TimelineWidget(QWidget *parent) : timecode_label_->SetAlignment(Qt::AlignCenter); timecode_label_->SetDisplayType(RationalSlider::kTime); timecode_label_->setVisible(false); + timecode_label_->SetMinimum(0); connect(timecode_label_, &RationalSlider::ValueChanged, this, &TimelineWidget::SetTimeAndSignal); ruler_and_time_layout->addWidget(timecode_label_); From 812885461e44a4851721510df44ccbd120eb2f7d Mon Sep 17 00:00:00 2001 From: Pablo Gil Date: Mon, 25 Apr 2022 21:15:20 +0200 Subject: [PATCH 51/81] Icon "text-small-caps" (#1904) * initial design * Dark SVG and all PNGs --- .../png/text-small-caps.128.disabled.png | Bin 0 -> 662 bytes .../olive-dark/png/text-small-caps.128.png | Bin 0 -> 517 bytes .../png/text-small-caps.16.disabled.png | Bin 0 -> 511 bytes .../olive-dark/png/text-small-caps.16.png | Bin 0 -> 355 bytes .../png/text-small-caps.32.disabled.png | Bin 0 -> 526 bytes .../olive-dark/png/text-small-caps.32.png | Bin 0 -> 370 bytes .../png/text-small-caps.64.disabled.png | Bin 0 -> 504 bytes .../olive-dark/png/text-small-caps.64.png | Bin 0 -> 385 bytes .../style/olive-dark/svg/text-small-caps.svg | 176 ++++++++++++++++++ .../png/text-small-caps.128.disabled.png | Bin 0 -> 665 bytes .../olive-light/png/text-small-caps.128.png | Bin 0 -> 519 bytes .../png/text-small-caps.16.disabled.png | Bin 0 -> 528 bytes .../olive-light/png/text-small-caps.16.png | Bin 0 -> 378 bytes .../png/text-small-caps.32.disabled.png | Bin 0 -> 531 bytes .../olive-light/png/text-small-caps.32.png | Bin 0 -> 372 bytes .../png/text-small-caps.64.disabled.png | Bin 0 -> 508 bytes .../olive-light/png/text-small-caps.64.png | Bin 0 -> 387 bytes .../style/olive-light/svg/text-small-caps.svg | 176 ++++++++++++++++++ 18 files changed, 352 insertions(+) create mode 100644 app/ui/style/olive-dark/png/text-small-caps.128.disabled.png create mode 100644 app/ui/style/olive-dark/png/text-small-caps.128.png create mode 100644 app/ui/style/olive-dark/png/text-small-caps.16.disabled.png create mode 100644 app/ui/style/olive-dark/png/text-small-caps.16.png create mode 100644 app/ui/style/olive-dark/png/text-small-caps.32.disabled.png create mode 100644 app/ui/style/olive-dark/png/text-small-caps.32.png create mode 100644 app/ui/style/olive-dark/png/text-small-caps.64.disabled.png create mode 100644 app/ui/style/olive-dark/png/text-small-caps.64.png create mode 100644 app/ui/style/olive-dark/svg/text-small-caps.svg create mode 100644 app/ui/style/olive-light/png/text-small-caps.128.disabled.png create mode 100644 app/ui/style/olive-light/png/text-small-caps.128.png create mode 100644 app/ui/style/olive-light/png/text-small-caps.16.disabled.png create mode 100644 app/ui/style/olive-light/png/text-small-caps.16.png create mode 100644 app/ui/style/olive-light/png/text-small-caps.32.disabled.png create mode 100644 app/ui/style/olive-light/png/text-small-caps.32.png create mode 100644 app/ui/style/olive-light/png/text-small-caps.64.disabled.png create mode 100644 app/ui/style/olive-light/png/text-small-caps.64.png create mode 100644 app/ui/style/olive-light/svg/text-small-caps.svg diff --git a/app/ui/style/olive-dark/png/text-small-caps.128.disabled.png b/app/ui/style/olive-dark/png/text-small-caps.128.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..2a9a47157cb86466682d952c038ec16071cfc545 GIT binary patch literal 662 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H0wgodS2_SGmUKs7M+SzC{oH>NS%G|oWRDy)vIg-4nJ$hLzpWB=2SsX#&Y5>H=O_Gc`jV%!|rTz4}W7#LYST^vIyZoR!_$aTnp!S$fa zp5S-!Ra(JIIayj>*d?ZGY1h_mGuU*R38n{*1E>r4Hyg zckh3(>*oa**PXv__?OebK{mjc0It5q7+zN!|M18DqF=%Z4OR=%(D1kI=b7f;(fx63 zZ_7*n2F6znv#5hXeqvydNM~<<%~>i?v8)Kl6De_xC~+(;$;dBa@J%c#$;?v-DlN{= zFWY|l%uJxXDo8#hu_V9nO2EgLyXM?P^3$OYzWRzD=AMbN@XZ7FW1Y=%Pvk%EJ)SMFG`>N&PEET$QYui zv?w{1A)}4zld zrRT?ICgEaktG3V_aL%u@>0t^QwK8pPNukqvcf$Jkt2)G@n4=9x9>EaloaenE9ja&@|JTCD| z0!j;nS^oal57yXGY7{K9RVw7eHg5gnsvp9WCRWT``67|Y_AzV3jJ++49)3+4TnWM+ z`^q`K2VV4&U68T0;(*t(G=+^jITx*PJgg?UYP~mSp4?G597Hm1O2A1eF$N=a+3ieP$+5UKJ#tl30>zm0Xkxq!^40 zjEr;*OmvM5LyU~93=OReOtlRRtPBi%ws}^fXvob^$xN%nt-<#7$D=?Ek{}y`^V3So z6N^$A%FE03GV`*FlM@S4_413-XTP(N0xB|wC@L*VPG!g_DJihh*H6yR&CSm%)+^7< q&dg0s$xH+(*9Y~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3AkesC}ZRi(`mI@6ibxc@H@Vusj#oDBZx&c|!C8yQ~AF$YUm-XA|u1 zv@-ARQ#>%U@@LBgn@g{otS23+>G1cu=eUM kgVk5MqyGMVIKPtlER%_KNn_%9pyL=kUHx3vIVCg!0L{RJ6#xJL literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/png/text-small-caps.32.disabled.png b/app/ui/style/olive-dark/png/text-small-caps.32.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..59d7ebaf3ae3500448ec09b6d83054faa70974af GIT binary patch literal 526 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-GzZ+Rj;xUkjGiz z5n0T@z@7-gj8hNq*aj42FY)wsWq-yZD#p#$v*|+~P^i|^#WBR=_}fX=d<_Z$tnAK< z*f%u)p3lR#U}{*zPsNnUUxl|m5Eapp{FJcZSs9PYdJC~7ovd>nDG6S;#gXekzd5%n^;tmnWqp`TAZCv@$T$HZZU08-nxGO3D+9QW?t2%k?tzvWt@w3sUv+i_&MmvylQSGKMHBElN&h$S5f( zu+rC0&d<%w&nwm|&&~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3AkesC|K_i(^Pc>)WZmT!#z<-0pk4U&#Ibv2WFkJ10`#Uud28q}Vti z^Qc*wiNa?EmBv3dMu%A@Eiq(MSjlgBW=)Nj0UPI>?C&y4diR{4|J}XZnn_*$e#39^ z+G!rIRD16^6{UK87Fgo2eP4?8R?DO`)ewdizL!45D<(wxhh=-LIPgdR!<-#IzWjQ% zK0~fa#G_FpLvO=U0bU#JI{}sd=I!NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VW5Sk?NMQuI$fPM8&w-e{VhT11Oa4>EalYaqsOlL#_q`9*2uA zdnSLEuhxoXZ4csHa`<<#ZhwNJ;G`wbjFcYj>(Y~ceP8CTFayJhpA)}J|NJ~Z-;NPz zM#JV$o38FvI?e`^(Wqs;pPRVPdy79%0sn?EJFrr_am;%BzCpQxZ#3t&)pUffR$0fsv7}fr+k>VTh5Dm7$@P zfw{JUft7*5ZOw=P6b-rgDVb@NxHYUQdGH3PK@wy`aDG}zd16s2LwR|*US?i)adKio zs$PCk`s{Z$Qb0w<5Jja$$*Bw(B_#z``ufTFxw-jy#d_tL*_pYiDVd2N<@z9=en?_o adVYLna(-Uie2ob}?F^o-elF{r5}E*>R-DQJ literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/png/text-small-caps.64.png b/app/ui/style/olive-dark/png/text-small-caps.64.png new file mode 100644 index 0000000000000000000000000000000000000000..9dbdc46e23b5b1852830be974974e8180db70561 GIT binary patch literal 385 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3AkesC~Vsi(^Q|oVPa)avpMEa6PytqU&#XivGtb-2zH0DjVloRtkLj zq2pE-$-t1{mHd0dy4Ra|D$S2o%=tLywj}>fBjziNra-*4@Xyrb>ih|t>rU=B{L5+J zARAzegm+(Oto~PPzwxj4+Y59z;CTxQS6wt_i}0A^rlIKsVI WPoVB=mY^ok=?tE(elF{r5}E*t*of%> literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/svg/text-small-caps.svg b/app/ui/style/olive-dark/svg/text-small-caps.svg new file mode 100644 index 000000000..b9ec71364 --- /dev/null +++ b/app/ui/style/olive-dark/svg/text-small-caps.svg @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + Martin Ruskov + + + http://commons.wikimedia.org/wiki/Tango_icon + + + + + + + + + + + + + + + + + + + diff --git a/app/ui/style/olive-light/png/text-small-caps.128.disabled.png b/app/ui/style/olive-light/png/text-small-caps.128.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..a8eba72159387c747fc2b24811b88c55d55d4fed GIT binary patch literal 665 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H0wgodS2_SGmUKs7M+SzC{oH>NS%G|oWRDy)vIg-4nJ$hLzpWB=2SsX#&Y5>H=O_Gc`jV%)5vyBA(zU|{6%ba4!+xb^ndM&3gP46X-R z3_Z3b{GX&N(W>Rz<>A3-uW~;$n z?EJFrr_am;%BzCpQxZ#3t&)pUffR$0fsv7}fr+k>VTh5Dm7$@PfvL8Eft7)Q&o<9W z6b-rgDVb@NxHZ_m{&*CqK@wy`aDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$Qb0w< z5Jja$$*Bw(B_#z``ufTFxw-jy#d_tL*_pYiDVd2N<@z9=en?_odVYLna(-Uie2ob} O?F^o-elF{r5}E*AFxz?n literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/text-small-caps.128.png b/app/ui/style/olive-light/png/text-small-caps.128.png new file mode 100644 index 0000000000000000000000000000000000000000..e4abf3abb289419dd226617a160a6d34304b2abc GIT binary patch literal 519 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSoCO|{#S9Fx?I6t9|MX)jP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IXpdt~FBFEB_jQk=7-^8Ml%shpl z(&Fs=vhAnO%mm6CLkuY`N={|SC@Cqh($`PU&&|!xE7mK|%+AbBP036I8K)28>4zld zrRT?ICgEaktG3V`_jl72(1Q;A0L>@K#(^e|be4@4~gT1m;l zSIqxk=a~cTf`WwH?~mJaZ{HJcUXyhH`}Hq8;Z;8T2Xq*Za4PsQ6f$)PJIrBt#3G>H zU;z?(_gXLE)J^M-`rqpIM>%1lSQKIi*)C^~IQ#0ia9_UWTugIe%6B(RUU$p;gZ6KM oIk*)exd*0?G$EMZfwazBW|!^4`3?)_EeDBuy85}Sb4q9e0Mf0d*#H0l literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/text-small-caps.16.disabled.png b/app/ui/style/olive-light/png/text-small-caps.16.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..7cf071540d66627332308fa33c4c0a1a71059321 GIT binary patch literal 528 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+uenMVO6iP5s=4O z;1OBOz`(o#gc;S3@UI05vX^-Jy0SlG5f$TR>b`GM4iu{Qba4#PIKOnlMy?hI9+&w- z7k)P|Kb)nz=Fk87jT<*uIBvM@#M5HU{qV-o26n&c4^xd;-mS{eUR~r}av^1t`g)lS zo#jj?k^~pzJTR8$e7E*O>$%TQbC#}kkyXe|oy>e9Y2vK5nP+_^M0h_WNGneX@88A$ z$ExP|nYgvnzh?cke|t1T<o>zjC2i5bd3x{jEt-d4Xq4}v<(cb3=BF2%fF*&$jwj5 zOsmAL;fK$8Z=eQAkPX54X(i=}MX3zs<>h*rdD+Fui3O>8`9 literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/text-small-caps.16.png b/app/ui/style/olive-light/png/text-small-caps.16.png new file mode 100644 index 0000000000000000000000000000000000000000..1f3c05233d2967b0119d1efe334cf69aeaee3fe2 GIT binary patch literal 378 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9G08$g&*{RsbBprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3AkesC|W}i(`mI@6m}Hc@H@Vu(V$3xs)l- z-1BX*fRhD-&*hTXxCz`96L*`xyEo<6+9Ok5ojh|)$zhecXQ^Pq#mUU>515WT;+E5w zxg(pveMBOeTfmFK@~`}dYX@!4KVNcl&EzfSo8R4jaQ`LWs>FVdQ I&MBb@0K6-YTmS$7 literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/text-small-caps.32.disabled.png b/app/ui/style/olive-light/png/text-small-caps.32.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..4ef9f9eac52fcd6b1d526b57f5e9085378e2507a GIT binary patch literal 531 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-GzZ+Rj;xUkjGiz z5n0T@z@7-gj8hNq*aj42FY)wsWq-yZD#p$H=LX9|piq;ii(`n#@wbz#`5F`iSlOKy zv2U2%^n3m(CjJFe!y+VA(;olojQYSe>n=~F!GvOKwmsHy+07;!9TxBKU=b zL*{=BH@Z!7{QUfi*QK)Y@ybl(mbSA1x<;hLHKN3^v?L?Hh`~3ps3bE_A*i%CJHKrE z=`%Bd@~R;Dl*E!$tK_0oAjM#0U}U6gV4`be7-D2(WoT$+V61ImU}a#SpH}0Hq9Hdw zB{QuOw}v?DBmaRKBtbR==ckpFCl;kLl$V$5W#(lUCnpx9>g5-u&wghk1yp1VQB+!# zoXU_ont`Fkrha~2u=f`Iz=jX-E*O&m*&fw|l K=d#Wzp$PyuWT{&K literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/text-small-caps.32.png b/app/ui/style/olive-light/png/text-small-caps.32.png new file mode 100644 index 0000000000000000000000000000000000000000..fce1b400286ecf2832ac0b65e266bb455d221fe0 GIT binary patch literal 372 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?5GL=a}2dU(e+prB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3AkesC|*Ai(^Pc>)R=(c@HZHxTNd;;*i}bEbi=XdFkk~NheuPPf9($ zW;0jNmiJ8TcK$q4Kwl{{Oel`C;S0W+ba(Qt7fK8VbPOzGYi56C^*ihO#L;N`&Pg?u zTqmwJeY3yTacb@ydqbNh0gvn6zczmBTqPFHc;okyKjADlzIyt%@8Ei4z36YJB!h=t zzg0>Fi=qINqCw1pmdKI;Vst0AqlQ A2><{9 literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/text-small-caps.64.disabled.png b/app/ui/style/olive-light/png/text-small-caps.64.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..d32d95d976c07ba687981e79e8ba35956ffcfca1 GIT binary patch literal 508 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;mUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VW5Sk?NMQuI$fPM8&vSlJ-n=0t#h&x;Tbp+8G>Iy&TyprY8;or}rBu8ABA67A2=LWR#Q?Sn2C0=jZ0;=N0RfXJ%*Srlw>jf|Ton gc={oUdFlD_naTNiaq~4M0JSrCy85}Sb4q9e0AYWj^Z)<= literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/text-small-caps.64.png b/app/ui/style/olive-light/png/text-small-caps.64.png new file mode 100644 index 0000000000000000000000000000000000000000..e7f72cb7015303f546cbeb3d2b0afac7290f0557 GIT binary patch literal 387 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3AkesC}cSi(^Q|oVPa&xehrnxE^$|@!)!2uhcDbRI67&X+>q@+?6^l zHg??;=c+Lt73x73ZbfJUPV^zbP0l+XkK&g+b@ literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/svg/text-small-caps.svg b/app/ui/style/olive-light/svg/text-small-caps.svg new file mode 100644 index 000000000..429f5b250 --- /dev/null +++ b/app/ui/style/olive-light/svg/text-small-caps.svg @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + Martin Ruskov + + + http://commons.wikimedia.org/wiki/Tango_icon + + + + + + + + + + + + + + + + + + + From c04cad8ef30934c8612817535eb7505f7aefbb60 Mon Sep 17 00:00:00 2001 From: Pablo Gil Date: Mon, 25 Apr 2022 21:15:42 +0200 Subject: [PATCH 52/81] Icon "text-strikethrough" (#1902) * initial design * Dark SVG and all PNGs --- .../png/text-strikethrough.128.disabled.png | Bin 0 -> 2190 bytes .../olive-dark/png/text-strikethrough.128.png | Bin 0 -> 2820 bytes .../png/text-strikethrough.16.disabled.png | Bin 0 -> 611 bytes .../olive-dark/png/text-strikethrough.16.png | Bin 0 -> 511 bytes .../png/text-strikethrough.32.disabled.png | Bin 0 -> 815 bytes .../olive-dark/png/text-strikethrough.32.png | Bin 0 -> 810 bytes .../png/text-strikethrough.64.disabled.png | Bin 0 -> 1242 bytes .../olive-dark/png/text-strikethrough.64.png | Bin 0 -> 1486 bytes .../olive-dark/svg/text-strikethrough.svg | 185 ++++++++++++++++++ .../png/text-strikethrough.128.disabled.png | Bin 0 -> 2523 bytes .../png/text-strikethrough.128.png | Bin 0 -> 3619 bytes .../png/text-strikethrough.16.disabled.png | Bin 0 -> 648 bytes .../olive-light/png/text-strikethrough.16.png | Bin 0 -> 617 bytes .../png/text-strikethrough.32.disabled.png | Bin 0 -> 888 bytes .../olive-light/png/text-strikethrough.32.png | Bin 0 -> 995 bytes .../png/text-strikethrough.64.disabled.png | Bin 0 -> 1432 bytes .../olive-light/png/text-strikethrough.64.png | Bin 0 -> 1936 bytes .../olive-light/svg/text-strikethrough.svg | 185 ++++++++++++++++++ 18 files changed, 370 insertions(+) create mode 100644 app/ui/style/olive-dark/png/text-strikethrough.128.disabled.png create mode 100644 app/ui/style/olive-dark/png/text-strikethrough.128.png create mode 100644 app/ui/style/olive-dark/png/text-strikethrough.16.disabled.png create mode 100644 app/ui/style/olive-dark/png/text-strikethrough.16.png create mode 100644 app/ui/style/olive-dark/png/text-strikethrough.32.disabled.png create mode 100644 app/ui/style/olive-dark/png/text-strikethrough.32.png create mode 100644 app/ui/style/olive-dark/png/text-strikethrough.64.disabled.png create mode 100644 app/ui/style/olive-dark/png/text-strikethrough.64.png create mode 100644 app/ui/style/olive-dark/svg/text-strikethrough.svg create mode 100644 app/ui/style/olive-light/png/text-strikethrough.128.disabled.png create mode 100644 app/ui/style/olive-light/png/text-strikethrough.128.png create mode 100644 app/ui/style/olive-light/png/text-strikethrough.16.disabled.png create mode 100644 app/ui/style/olive-light/png/text-strikethrough.16.png create mode 100644 app/ui/style/olive-light/png/text-strikethrough.32.disabled.png create mode 100644 app/ui/style/olive-light/png/text-strikethrough.32.png create mode 100644 app/ui/style/olive-light/png/text-strikethrough.64.disabled.png create mode 100644 app/ui/style/olive-light/png/text-strikethrough.64.png create mode 100644 app/ui/style/olive-light/svg/text-strikethrough.svg diff --git a/app/ui/style/olive-dark/png/text-strikethrough.128.disabled.png b/app/ui/style/olive-dark/png/text-strikethrough.128.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..1f02133cef29ea2b7ae203fe95db15af6d8244fb GIT binary patch literal 2190 zcmZ{lX*kqfAIHxO#+EU*ER7JOZj5D+U1-L>j$LG#85xWjW32aG{$-75EVsz6vgH<1 zN|sPjC=$9$)I=nrNSpMG>v{FOcwYR@@B02e-|sosbzYov*45cYL`Y5u03c##izjkB zv|9qa+?8t3V8Ly?CoG*T0cbrhydBKP)rurrq7wi~s$5?x06(}RXbFH=Z2*>o05C2F zK$=n3>1N6W@DK+ZJOGV3$~dkPWZ3$~03e^STd?yBpD5fRKg-UE!2ga{O3Fa)JtBh3 z5Wnp3mL3T&R?B_e_t~JJ-fWt&CKhY32|{_WxR4aeT7bx~?etS@El{NBe~Wjl|El=7;hJ`e10uei<3%b22#c z5VSu)-(LX0`LZN^l@>R~bOOwy@7qkeV1Qdw+kI7ssX95vLyUAdTMo3{J1jN#WX8eR ze~efuj7x8sM#P-*CaxR-Rj3& zd2reM*(y>LK;&;qPotBM|M;uj#1eJwuI_O4T9Ohu?qDoNhledrnG=d%8mo4w)FJ7Y z>B23(cGT{CljuJJ|u`FO-~rC-K$Smr>ckz7UY?=M!!$M;V=vjO{jx~|0>HM2)}3C)SaJD&Oc7DWR6~{){^6Xw zpRr#Tgd58JgFrW`|2*bNlGD3KDOE#`wa5c3EVHI&ylG&~GAgpcd~KmvKHRbkcx5a7 z8#I00tuk=(kl~lb=8wg_#ElS=K%z^s1hU^v;f&iH9MU#GzrrX)dM%Gtn}#Z4ckf+K>nLXn2#Q)*9>_I6D+ygpKr(&3xLe}E z(#XcC_-tvU1%dNI^i^q9P)>WCRmkzuHU&xMx25yf-;c>a%MsXk^p;9) z{pocW6dGietAOc~s1X4HgXG+kb}NNpFpJn5CpTn|4^`ZXR{!aVjYJmdZ=KYuWQ_FM zy*zZIeR=`2quOacs8_(-w-*|A>G9q%%cWxY&V0#oYV5I6F&}0d*S$HxgNK3b9z-R@ zDtqEJr-uXwoEDe9Z|j;)%fYu@m0^I>MwdEd@a9}++n$7MF))PtIaQr+CO7^y?e5Kg z$R)2pNZO#DQO?tbQ^F9rzKx)ChMbW=w#wKWtKV_Sh~I*_;#aKc)q0Khbo-vFJI^%ea7wCt#*>G1=iIL4K{9+$mWKv$ez9Sm18x zg@PvHdE&u|)h=1+huJbuxo_<__i`Bw7Zl5|m^L_bI8Dm)k#Lld0ew;S{_?;DH036C z?^7C*)-e?#8iFF^sbL088m@Ny!OuuhviZAlJh$J3B9&i`0-)yk<##5{_yk%3yFd|4 zzi{QWx?JxXR9}$4UWnYq9FxG{xWVNCI1I2c6>SZ3Wr2<#&Z@8;cpr@&3Y6jo0$jn;n^wL^$YX8X=;?8emoCR2c$d z_Wd&ex`Y>HeNw3K*E9(^mGCQqkaK#!70>!jerYrmk88GU3%v*u=(94B=eX=^x27Rj zvwN&aGIIi5A^|Bf1A4IaX21G7r;DsXjo$K<46jcu0+h-X%y72AY56DCJQ8yj_Mqj8 z4&1-E>^U5&uO}YB47%rpT2rQINp}%PhBwxgG6x)TVrTIYbL+yb4V$fLHCBWdTZE6j z)u)OAuon`Co9;)aqQIjqY`bcxgj?iD&eYb4$>Ymn)UI|#vo^K#d?ypXbul2_9Dt;`FD>+(%JK*il zAPfdZRUPv3shihzt!+4`jCT4H@@900tiEtpK5ER8#ohXz@=2rJ3C(?u^9Lv;QkP`z z_k7X)%#hApTa2vpB<8WAQ zJuFVg1E*uGuWyVytgfwXtgUU5^5NS51ZZ?JH8k=62kd9vCAb0E-4opDp{)2|CI!UD z$7@ofB4S9v42mY5d7|{AsT|j&`(INmlSBbwEEdC9ON&IO(dbbzn(@>KDvd&>2JhOn gc9oV#aMTHUAeBUqI$n8*&5Z*)f-}C^D(K9A02)N0v;Y7A literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/png/text-strikethrough.128.png b/app/ui/style/olive-dark/png/text-strikethrough.128.png new file mode 100644 index 0000000000000000000000000000000000000000..160d5a1db489465d9652f573f3c120c389c28f4a GIT binary patch literal 2820 zcmX9=dpOhW8~@JO#Lx&E33EskbDTt`mO~fdUXoJ3*kGp#;(5{h|Yb`1p8Ta%5PHzaKSF zml7RZylk!@Ano}>vWlgJP@(|`zi1jcQpF`UCX5o-(m!xraBujZNo=%#AP_>MQO)%9 z{3#I;l*ky}cybsyA~1mL_s34}&q>eKFEW_oOZKNk`jl(s0)Uu-9nQ*?Ub0jgk&W+0 z_9RQ`DHiB*2=LuCgwKyg?~Hc{%hX3Sio8)dX5t!N+k7ch@$XT+@5l3|tcy(?j2s+% z(PZ!=VJnuK|A9SW!zVq%DkrDvv_!>FD>dAP#)i|D0|!&u)7uxz<_1a>2i3{3=KRmz z(K8MN@TJ0u;1H-au3?Zk3EA33$Kv8}4dPB}5T~&qz9vz%`St3BdC)#^0kijvzK+Xz zaLqz6;|L>$G;v#HA2jJ8%9FIm$3uXk`EEpzMng4R2^EdczL?&v^-zIrCP%oGkRK-O z)~(HlEd#SYp1&pa?`Hg+`07W%$am*mgBkViUbBUCUjPlEfB%@HX?mb zCF(@R0YzNZgBL61mzaBb$bhrsPisSUB2^b$l1nYQoAr8~$D9}U5mmpZc3j|+h}W5W z4aSdNVYY#`#(ZlQgo$RWho_@yXJj*KQlI)%zHGd^fFN4vqB}3fQ@=surPd-_c$P;S zx-6!Sp7@uTfT2ESu3c*I^d)@<#2dr5O1bg4S$DN|uZ^Cblf>KQlbhO+^XDJ##vYnx<7 z0rnJbdw$NP-Dbk^UAM75-c+pp zz=5$=v60_KjUIT4>)3mdd}j>K;prWA7PXa}MS`x{6Y-g-LCr@XO`+QRZ`99g`+$-_ z<|WxwgJBsodm%B?UV6CDf*VaF4-9w(R>B~=y)YJP#QQc;_bTmA%svJ6A5t2Y0D4*+ zEwS4UYN>vPt5C1!cQB}H%K0t`;7YKlw^iHLjyEUq*pDpc7%g`GKn@6SjTLKz6?ehU z(UFDEJ|Bgj(~0&LyX^`AoW{OUk;nVdPm>Wp=mZ6w(uCoX5g=YKD$(f&LnY#}jXYki^5w`Kr#oH2&0gb!`~`os9g|Q%u`AG3O6L<;ll z46v3Pj4>UCCv{Flu3LvI|6ow>;0+_$X(?1C>Z?{{BVEVIRqN+tE=GK0xo{K=1Mn6y z9B`34V@scC{i(CcMG6hzW*N+BSfYIKwfOxb!ioKa2ikX`2zB-pWJ|1wr1eJPlss!f z!uXfQI*^LP`E;F_BsgA|>QO=?vjZ4=nY~GTGp<2E;+5oIqQ?xdj$3g}#SBc2`u-1& z8dAp$7$8)W#74)>*Khl=nbsuZE%HiegK2CQJBKRfpggE;616` zi~NxPww-R^92F~0AJ9!?xdmNHvBqBoWxmf%vChpt_y>f$ms}s|z9G;z+Fp1={?7_Q zsesR3TN8k$udtn==N=2nWj-SrQ;Pqu&;C*zT8*@A5C6jk5`u&dLCoax?C^FFbXU*U ze~2p0I-}c+0KhW{q-;3!nphF#fzxNOCBNC-{0zY>Nf9E}8z^9)5FF;ZUcF9TsAfOn zn`C6`=YAI24yuUp=rZ7rapp(jh^1^@7km~ghUyv%YZTt7Va=+cNcRh<9>P*$_%Ua$ zWuMQ|RA&FH9UJ&-*_7Oy9?%rl|ivIuBkARtB1t*RII+Ox9Q?jm)v@Xt9#k5}V= zAL5)s6&wiMRz*!HF5m_IrC=udYl*H6qnYc^v2=Q<=C$R#?#o+x_rRxN3G|U^JG7Q) zB~ihi13n|SCT~IMa&+lQ(Qs*2yGe6x*Z$m1Q7Urv+|C8(X+6|C9Vg2?zm)!BhmMpv zCw|_x5ElRA18MaUUoy@7%T#(o$=<<}Js*42)V9Vl?P12qmLfyhLOD%b$aoyvQchfz zCe;Yp!1fya8p?Q+`VMz)5W4_r5PWo(mN`MHf-NuI^5tgYYe(5aP2H0pj!&Lr90v8X zWtaD`ev#Z%n4m3Us2j5>lv$qDYjIx(z5u5p(I!srfpZ9IF_RFEdg-7Au4;XEU!LQR zE6ea^`CM!hsR`AgT13{H_ZC!3e+}kCQ7*1ceOB;I<3?EmkSI|+H19#gtR9EE%eSK3 zaecdIa?r{};w8$rBtfR4)2T=3n@f}Vhh>M)bZy8sKjsjgoJVLab2TzH+L?PZyswAi zFjzgdnJ@=|nL8?j(eTuvf&coTu)Pa@*R=dz-8CL?!FjBY+jE*TC7N)<#`tZQ@#Za# zwNz7H&y5j<3V~EQ_@arI(CxSPR2{#6;zf+oC0i68!zcItYEu>uEaloalZ88X+I`M5ta*= zefaxRT#{>CQZpYN`~P3ad{<$kwx~+uf=^lE4*bqZxx0#27p)F5KjCq(b6rMmp4g=_ zDWlk(*H6A`UB01Z$Ku887mqBTx?=tIrJE1z%y^c%y&^<OY}j_sZ2ihxuXLZiKg8s?w7>UdApg$(O9w>f9e!fq{B|eLdb@(V zJU5Ss-{N)5GSym9aOFvFw~5jAFpJX8@5>fkD4cG-hWGG~AG2D$!V{lfH_7?-*?tG7 zl~?cepje>qL`qyEN*qf|GV+TUd=raGGV>IIN{h4e%eJ3BGZQGU3X)GrEJ?LWE=mPb z3`Pb~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3Al}fq_xi)5S5wqPKUlp;xn`KA!W2gC z13Uq2b`8=Ca?|cBUKe^~wSnJ9eUE(OvYEF3)i!y=u<`AEC0f9>hiUQwAp`NQ;I-?$ zcTFx)%1qHPbnuz=sY3pI)RhH~3qym}b{v%VkeXcbFnNnguGgw3nLa_xH)v} literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/png/text-strikethrough.32.disabled.png b/app/ui/style/olive-dark/png/text-strikethrough.32.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..69ddfaf84d5547840f9fd1aa9905406c3e5792f3 GIT binary patch literal 815 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-GzZ+Rj;xUkjGiz z5n0T@z@7-gj8hNq*aj42FY)wsWq-yZDrzoSz;9*Cz`!`q)5S5wm-@iy?H#5RoQKqNP*S$X_D_Z*Y~es z)2ML!yKI4lW!ZWWhX>mO%1*g`_{V+WZc)SW2YJeWnJ4~f=rX1%zpXDOE=|#z&bL7IuCQiF(d&Z?bB~G}^?YU3Hxj+p!DaXN zN86#TJd^KVXX06Mym6*W%n2pQq=`16?<8h)9^W=MIk2)rKVr3rgNa`8dJ%?4Ns~4; z?2=q8%CI)`p7+I+(<|gXt9Pl-+pzGw&FL#eVpcLUFQo5z%CyCg`Niey9fpF&Vj(Lo zdac>*8Rlr!l((jGi?OlfiSFDDjQt#U@6=g5GyA(H#Z+XW=VZ1}n;4mAXMQ9d@=*KJ z{)d74T*2zNyB~G{BTuBnHKN3^v?L?Hh`~3ps3bE_A*i%CJHKrE=`%Bd@~R;Dl*E!$ ztK_0oAjM#0U}U6gV4`be7-D2-Wol|=WU6goU}a#ymHiQvL=YNs^HVa@DsgM5HuLNQ zYLEok5S*V@Ql40p%1~Zju9umYU7Va)kgAtols@~NjTBIkF+@>mQF1CnMoCG5mA-y* zer|4lUa?+zW_D(7YD#7zNVz_Uryr7-m!2P=nVg>&H(z4{P&VJ>fSXD@eYYiTc3VQyz{Uuk1+ZeE`#kN^MzSV=@dR9J<@mQRR| zQ5431&kSSKBnGp>EU?flm|~KJf3agqWM@IFl+-ADDYBcbrY1JZiddLL{-kWCW{Mg` z7|IyTf@z}6JQnXeHFLf9`|kJoeDl=1xaXepo?qwQ_q+!dSgOR?_Hy6=&;@J)T7hN2 z6fg;l0WW|d;IX8MngG~t0?q)(fhCE&)4&tpvZOav1K4f>t^u196Nfr1X*jmIDB%UY z1<8K{gTN^8iL5HEzy_cLXv+OYN+mA~V0#a+D=^;!E=U@W#54mtfum$cP`zyT+I~If z>9?JHaZ`L7%It3q)_szk3XtL(1<2`F2Idc`$x~IX0$~YP*9{;q-`Bu=q;3GYo`(Ro zkJS>OgOB<-6$+vMg_Yt21^gTT@Xx4AzV54X`m-f2j(0!bot1Pq zmZ#KMeyG6KM%H>$WU`qdn!Fsqb+X&rP35!T5O7h_P&vnxfus@WBxUffr7H0P8il;HryWng9R*07*qoM6N<$f(EH*GXMYp literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/png/text-strikethrough.64.disabled.png b/app/ui/style/olive-dark/png/text-strikethrough.64.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..9e4a15cdcce7320834685c3309ea30d30d36b353 GIT binary patch literal 1242 zcmZ`&X;9J$6n~;JbDWS4nQiNl?vzdA&;XhaxqlX*5gZ_lPvHRU2mvq@0!$;6!3Llu0gPq> zP$~hul_%SIR75~+O=K|u8p?vn2y#`%r>Ozvb6#7s5IIb;k6tcEtOlH25qJSDmtsCZ9f= z9q;JqELHPrf&>x>l<<@ ztTG>ES6N}A6IXD|`KRUibj#K9XLTSc9pLww`xPVIrQ_O&!=E_m`Rcaf`Y&1(Jh%2x zizj*Q9|q9p^(JHI_4F5LOXSOq4qgX?0{w03?h<`f!9tvsSDRFXNg%!%}h~a-T%i@xizyP0fzxUh0;*w&A0terKX4s2f}Kc!L{0vSFJA?1`Z{kotW} z8gXvs+KG?Wp=(f`l*NzUJl*nS+1#wt`=ch%q~J7q>x7eyWlL{A;cGZ>HRi`fqfM4@ z_nU`DBvrb@F@eW4qT9EvNzL!qzqyqI_a!6#N@TjAh$53>#SHJ&~Axcr3fXRG68 z=yTN3xQu{?fuzPQgz~T+n zKHE*2;jmqg7wo3H4xc<)lJv7>_y>U6WIp|z(HHI-6nj;87MzjsxgB@%D#=hzvSx*M zrDc1%%ja@(cSrcb<&s{tdr9c%*1t~V!h=f-pIII_-!x;l9_>fqyKw>wHb?eNeVS5= z(7#pNrT0Uc-HPv+_vGvK=dav%z+A7z>y|8-lrSHA7%O_`aUJ(XNo?hW4Ik3EaidYe zS=WsIZ)?In8jSg?YOoO^#?3<4R37(MXH8NSCMIY_)TfojhS`oGf9k5Gjtj%a?9V?PGyKj^L9cDACaratxkCqL4_jd&V076ObuH(yZWKt=W;diNvNSq@N(?%k zjv$risD%Qhn4nN)pRiN0h$-@4lU5}ZgG8fIQiw#MLMBtl)dZb1M=BGGq=H#HaTXEz b0(rJ#i&UtPXP9v1NF3PAbqvdz%z}Ra5fuC* literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/png/text-strikethrough.64.png b/app/ui/style/olive-dark/png/text-strikethrough.64.png new file mode 100644 index 0000000000000000000000000000000000000000..a5009da5e72ea377e96eb6689a7a04d7fc9588cb GIT binary patch literal 1486 zcmV;<1u^=GP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H102FjZ zSad;kbZBpK08L?XbZKrNQgw4{Z+5rK%$WcH05fz&Saefwb#h~60BCe{a5^t9V{dJ3 zZ*FrgcWG;BZDnL>VJ>fSXD@eYYiTc3VQyz{Uuk1+ZeE`#kN^M#{7FPXRCt{2n{9|y zMHt6_|J~Jgtt~BjQ!;y7l%*&sC^TvdLo0H($o$#|DWVqzBkD^KQ6Gv;K_sO<6-gyo zNwCB!_C`^(SOi77A|;D%hNPyuuI|%^Id{7*?mg$soOABIwSTyr%bjQb&&>bMnK|c~ zXJ8WlZ>R_-W;PAD5x5R$11}KzaudMJrT|NT z`+$W&Qy}F4umjizd?RT%kPpZ~05fX@9s(Z2{nL5i7_c39OValtM41X;W^;hofNQhl zjX{s3$9*e@4$Co^*+O6)a86d732`)}awYL1EvF41M`4efGZ~8d)K@1 z5rQT`Gg}6{l)!X9Zj^i=DfPc?X0w62am#9Z^z#W|fuuue`O4uffSC#C1ul;<{R((e z(qF#5#`&F#TVhX1S{2F{BEU_+rWmuGz;a0^ef?DM-{>=%#v^1Bye+1D1X!6z=K-?l zJcJ1_Kc;+}q${fmb53WkD3|*;~}XB z7gmc4z&23!aFYf#EdYR|9l)CjOmFw%_o-Ag%&Zxg9#?6vVHB7zDOJu;&5r;8NkhQq z7_$aoc3SSb5FoP6&BVPeR>D3>gK4?F1u(N2X12o2&i3U<=%)#InfPo}ubiZi z5pWOi4Dh*`tv0iX(pkM>X05;*z=8zk@B8=&NtE0STtDvTK3vGDH)QWGI%2a7ml2vI zQuKwShkbl`2@oBO=&fXeONQPVUH9NJN)7@?N=c5Xl4K>1N@ic1>eqt#~cuatvY=jmLZGIcS{O7FyYgP8%Xw0 zk|#!h=R-RWeyhNbz_Y;bq0T+=6VR1K=OJtrXaerUEr%Vr;AEcY$DK>s8l~xFf~@ib zT7Wxnvv)i0bZ8a_s1D#FL0j|6B*`Tt7UdeW;l#KGmt<8 literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/svg/text-strikethrough.svg b/app/ui/style/olive-dark/svg/text-strikethrough.svg new file mode 100644 index 000000000..c45927743 --- /dev/null +++ b/app/ui/style/olive-dark/svg/text-strikethrough.svg @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Martin Ruskov + + + http://commons.wikimedia.org/wiki/Tango_icon + + + + + + + + + + + + + + + + diff --git a/app/ui/style/olive-light/png/text-strikethrough.128.disabled.png b/app/ui/style/olive-light/png/text-strikethrough.128.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..9480bbfd8f1cefcb37c7aec1c3331409e4d14c54 GIT binary patch literal 2523 zcmZ`*X*d*W8-8a-){*68SEMXq?6M6F*=27bgBimxm>J7h(=kbwLPDE$knA!DSsF*S zLWpGPM6wjhktKt}H(lSa@5lG!dG70e?)$!;U+;T8$qsfFyxij4003SqOH(HnQhygG z8>2SBGD`>RMF@Z7K=GNEUj#@X{Djs>tRdBcfy%4qH8;y^SZgK12X&>e>^euWH9^q}PX`T@HWU@#r$6Esv z*YOXC#AI)z;E}J6;Q}c7*JQw0J=#=2@5#feuh@7ucw3HYrZ*JD5fz)KnEQ_8cl+f` zQ*^=8WC%vyhlmaRrYFK1*=M-1J4zgY=+-SwPs9t@t$ z=Y7M2M^<~`)T@t|C2J?nS=anx_FJ~Mh|RnFDZA>J3L`pO2H^i9mS1U#lX&e|!PNZO z=JW$Xoz{O2gU$ABd*Fx*A7MyF8U%1mG6VbywyejP6(tDvx+_vonYuFDe~GV1ahgKI z;cErPl?Ly$^cW)n(tykI{k;$VRP|mooy3#fTGPlTse8~<2OmvO6#dzkd!b!>ZS3%% zH#sW6Dci|%?`&OP(){g-=6A;#Ph}_=CPSINdwrRv`7L&1{`Lpu;@O7|ri)~5N)BTM zN;?B-Lku3kMYb0>tj5>M^ejZ&G3SW-FK^CymfG0Enol9PCh7;8oT8<^c<~=LimB)eUP;@XH zJtMlpw`mZDY+kfzbx=eA@9)|6ys^n$A2PrK1Zq( zE5(^`9RbP|l-IX1du*?3KY@3U%>BOR=DM2>dL#kSIVOYC>tPxKWC`r^QHt<(31eH} z#`;Z*K`LVBbNa5FLfrwp7>@`9raI5l^Xt5z9myx13Uz6r`cJ_}?iEL<9pD8nF68cK zuu67R95g04=_HuvvGMkvO3pt9sf`EHt^RA7_n|?{cgE0-+o6t{A~HZ@35f_m*a-t= zcFcy9K)Y}?8{iy72R;ce#;ckFm(BsJoISfScmg}hdd@b@II&gz+|m16&(oFytrZLs zzUkITWVoxbUa9=8xQl%E=WJCecZWEE!axe*g;7fC&#~0<>nIrYpbZ^|xRm8^Mr54a zqTVJG@b~0x+*FoITSCRnSAQKAE$Ui==+Tg)KUT--Gx^A-a^HJS~Xv-`QYnu;T8UJ=95ie^H)4Oh3@LW4*Szqs6{yv-(}+VTZ27naVxF6b+GT`GvGmT%+T^8ESjYte!jZ zzPU-Y`T1viqRkpxa-`BRIVrJQBlD6&?{AwYLjV}w_;@gV=Tp*Z3BpFUbje?L!JNT% zV8U^9O*sl7;dZEZsZq7p6#ApwAbh%n?ua(AYKm!o6Ls2)1Axs0rqZZK=Y+>ZvlB1v zwk$x+L>QEmJ7%DZx)N55&HzBuscKfRiq6H`e9zB-cZmX;L4TOC-d1kkm1zzy65kH4 zhjB0W9(qJceOwTL8qXit^Ik<&){IITUl$q*XnKoJFX*XMHsrH@^o;JWBt70Z>>}?B!<7 z4+Jj<+(K2eL1vTx2uA+x41jXs2HcPcV%CZF#&mmrkUw#jh7q;l2M;2v>?OeZ$x=aj zzBD);7|(5#zy+mtyYdKWT$W|0#tL*r1ss!9{sV?;k};YeC{>2+XO^+qUJ6N-XQS@6 zL~_jLTlb5Ded|3X>nCWNVmTEvYkV+orJ6xcPTlvE&NLoFbzecf%GdTfHA1NHdtUwiQDsv>diy?n`-`cFr3JKa+02e@waAvY&N-x+x`V0urOJpG0b5%p zp5dC8g)W_)7uKf7)=Xsyq{Qczx*k3Q*W?RRCJ#@NeV-aND*qC&2pvtuJI*FG!b%BH{O@AAyfmU3}`P<7=# zYi~Yh+BWgtl6AO~Xs_p7EPx7+Is*I**2GV%9}e{VT%k7A&<&v z_|-OIpjn1?i1lDXJj1dNe7r$bB$nyQD)5D;kz*#|T5Iu*^%TP)iLxq;TP{tzr45c0 zuvhyu*YHqB^5W@wZiEe7q#Cc9dtu>5OTW~V2P8^Sx>)#d9X2@fh4hJc*$r9<`zO!i z@i(~7J>!IW?mIWtdjL#a%lz|FDW4%`u1j+2CGp9mT#mCErb9ZDOYX)J5H-SU_3r<0 zZtd9#%!qH;SKTyG-PHPOAM!l2=O?-M8tc$FMlo}t7>7~($RWTM8$uzFq@BV-1IXb` z{R59#y6kV=8%y!g$A$Q?0BERbXdu+I5E|+iHPrRBwe>YLmDJSq)zk>BU7Y_DAd~Fj3Z>D+?OY$Xq5^!XaM}a~*D-Nv8>`ZG+Fme9@ D<(OQJ literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/text-strikethrough.128.png b/app/ui/style/olive-light/png/text-strikethrough.128.png new file mode 100644 index 0000000000000000000000000000000000000000..789c9ab2f900ff2692f57c57ea18b79fb12d56c6 GIT binary patch literal 3619 zcmXX}2{@Er`+lb}GZB8qItJm*QnD{uvo*?+C2Nyp8(WNFD3N6@b~fjDxX@ex0Pt9tW9-?M z^tT^|v&T+D&px){3^B)s0f0o}-wsKe-|%5CiiVqA40j;-hDUf3eSnCF2<3p_z%Xym z5Fcd%(J!B2fMz=h{&h0B7Vb|V0@j|y@PJ?``)gr=gzL?HA9LCJ8vg^iM)dXp{KLaT z^i)*533xmqI7~SrATR*$gA4Hd3#amTrsC)s>_>19@FoObd2~FTP56QZ#>DYP!Ouds zAo20YgqaB6oUFy{S6Mik+?L;Uiwn-DYOeO(<@r*{DIYrA5sEw9R8Sh=aOSB>^5Yho zH3VUHs8L%f8NP49C3L~Alftk2{76q-MmKp{$4J#fKPe#p7cnBIw6wSBy=;wJ@?3xP zo52q+&62H%{U0aX`k{P$!uo4l7Z&Dob6G42De%9|>zL^@CkJhCaB%GI-MiBXo~v@X z94{O&O_@`&;CT8J2yzTUrQpT9#b00TxdFJc&eRI8`lsm;qNMwoJtiuo!aNoXCmi z`qCNguyv~eR#CI!N(G%xCoJ^`$bKCLXgFfN5B3o>#OU_H$t@Ud+-y#FMDr0^QZmjg z!q8>d8ID4uEyq?iRREpC9+`~jF)oX9n70J%Sj3UL9a)TM(kV4#_1vi-?ARHA_X&^K zA4~IAkjYJYl1ksR4^-B>QwyJLT9ev#LN1LLS{WOxv@&LFY;00j4hJtCZbhFxdseY0 z^eu+@uDj||1u@YFs}G5`KewKvNo5D?Why;df@+8yDzup;_ilxE1?xh&d3a>q#!qum z#|kVjDzcf$hr~Q3A%7`(-gUHrCdK<3dw|LccBH2t-73FEXgQJCQ6t zF4`^|MZ&4KOkA6egAP8m-Er}Lc3C+vh9`&<`rfOqmfUNYazgP?rPiNmF|R2)(8fns zGIH5w$FKAOXy*G8nPpK|!l-U0>J! z)v5-nWDZ4xD zkH2+hZb!E{SH+lO7_!^DAaQ2Ow1)Yu|cOgHW|*nK-4`G8-2n)yv3K(hnc>sIiaBJ)#JD~`h^Vt3U{nOLa z({XZOqEg?^TtX3V6MmN$I4Ci`+QDkOBOo9!!qsRh{b-;^>@>!zI8bKYAr@xdpIg4Y z@bmL;>dofF9Fs(dIr9_@JkT*TykbWgJS!ItEwz7IaJwr%v&(D4)sp|~Y7Q#s2 z|4_40Mk1L(RW&X(HkI(+1(w@-uzJ*vi+LrSkMfC2GdlnH z_k)03vywom68&2?pd&QBz@fNqEL$6SlJ6Nrv5$!dfSW@_r(LZ0m*a5W8}pAPd5}we zICVW2z6e3Rgxj_!1_$dY^#YvJ0I0s?a%V0E3Wa{{dl!C4k5MKN3&Y^T6oq#)l5wZ> z^rA=)oEFm!9@20O$C8&6_9Jx{S7AbWplQZIx@FnpaZ}0oV3KKxYT0*_7w?4o>Qj9i zu3iyImm zMv^e!odGD9k2k=ul;PJ7o_V|9wY0Q^z57+-o-h=kj@7tIWNg8t3LzntwY38h;^KRo zxf*_rjCR7cMo|x6-yi2e%5Of>Wq_#$G=KW^Nn&wjrOH_$TMa054-S&>ulXnayd1)- z?u!Fg#9{Z8dh%Xa%jG`=OdrjN)N~X8eEs}ptY!Fx0F!?fWz4^2o6GRZ@+Sp;-}s%+ z=BMB*H(BIdNwt`4I)G8#q`M0c>!qHkG{JjaGx4bAd4CwRxV^o-qN-};H4u$QW94B$ z8dT?71exu<$>m_!JiyQGd>HvUK&sC%yTW`N(4ifJ0H> zR|bQ;E~P6E64Ij|S5z!~SzKJ~sGo6VbOxPzeT0fcPKf#EAWMvyT0o*z*>HySODnlM z=*i~C-D^^4d}QfRlcYxyLxv-a)Ia((6{C2dhb;oS2-qB6G9_o~aolHwM1`rS=g{4aR|Nn4)fBTKHC`8b0e9jq!#QFOA*4sbbD{gMyQp*qla1f3+c0xdGMQv>@n?bCQ zX|+#M6v+oADq8npo+)`qM-kj6bt+{<7dt8hsF~nM*`CS|cFE-E3z*=;sx}k#)kc>X zdmY6|!4T_*MMcW&Ec1Tz=8Zst=M=XT`K}u00ISWijDJV1o&s@B{P^)>+tIWhNeyca z9*^TBls4l_9X*M)Fp$^^S5v2=9~KrCRyiuzZvOgZBbpGZ|2Z|~Az0sH|J2}#UBihX z4XPe(p4bU8YeBD*S#E+9{l0mo=-s@N{MLxtOs~4%-5nhr(p@Y$wT!Kkp93v%fMkK* zwdDeGbZY4bSvrExYd~C-DxXSs#LP(p8p$#aEtiAa!J2Xm+>S)oNhKRpTiRmIItFMR zQzwD!_jm9Ymt7Mp;c&6Q&yd1iq_(@J)XYIPqB0MI|sMB zztU=z<5uG!|J1ZXlmZddP*YR09Nwrpqh2Fb$Ufw7g8OeFK4i9*V67#N1lVcny>hyB z)&YcakXBk{Vd^E~X(FOh0Q=rLU1mkhOun3lWKRtSZo1q$e_ROrKR<;rtfK3%nHB|d zh+3~GbTT=ROEZt3?T4AugP}_bGLDK$q3XFb9j#Sr&HsB!{=)G=e|9jsR%hCc;IqQ* zdNTL8h6DbtoVT@&6E~i!O%2FXfM>E{jFki_1G~b~ajwS&%LV5~vU&k7nr}nIz23F$ zQ6y!-X|4g?i1NVU-haTUD->n9vK2)wv=~>VO&jDc_VG(6-$x~cDpJf#cXoEZRXZw_ zv@to%3?HSbC@UX@c2nq6yC~j6zbW`h_h$U$R%Qe5@KU%t zyXLrNBb*kKdNaMfy%m1&JDV-KcK%wOXz+=R{j&h%e7sIkq(n!DhHkXW$;;a&7EWGb zv)l!ut>?e|eiFy-Dyn6Lnnayv@ijkNE&uGdV zRKe;)`X2VF0dO17Nts zkl+^MtTyO8QTH=Cr35XNZMo^1vnhbYjyE7ITrgL_ZHEjMu>>{ez&oZVw4j~WE)H9*dMCF$av(}f5#Yq1dZ#tAZ-|kBp(>a${ zO73+D*D~b$u0smQLv$pMDwBI>^ix^*Lfv>DzyZVHJ>l$X%zPx?vz@>&Fl~W-X^PP& z1$L50;fm>d#HWt-bxEJKOHib_=^En9=*LNrDUwQWtJJ6T#jA^Enaf-OLXgxeK*{P& zf|mK8;w!arY?Z8@m2FrhyE;ydCmZ;qx*sK*qq-it-fECJD0fuYH;(}OO3G}C@spgu zKk02Q82Gh0K24blNc0)Z(NOV)$BgLb3BOD!6G7J>k*AK#(iiU|x=sK+KYsl9!(u0l z(=<)<%xlQEM2bxXL6e*A{R)?m#fTrHf+RnPc$oNtE@0Ka5a@J?21t5@gs9H9GOU4M zFj9c@uc|HUB1{7)5~3U=_0R`WQd1M^GtX%$1(HrKt*xysEia?xbh$mWl|{X@Nrlq! z?(fS|(CvrT`DqWV%IqO|1G^VYCQ-9j=UCe}YMjN^4G2`#`w(_X>q>$8Ap!R`eE~O_ zW-=Kj^$m5Wz3sWj+l{cIxkz9HBp#)T0;!&;GOxNDToQv{kpJHvY#XJXZ}T2nUf^c` Pn*kPPHkjw89=HDkD^-tk literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/text-strikethrough.16.disabled.png b/app/ui/style/olive-light/png/text-strikethrough.16.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..1027e6ac34a14a48154fb9ac3a951897d6af310c GIT binary patch literal 648 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+uenMVO6iP5s=4O z;1OBOz`(o#gc;S3@UI05vX^-Jy0SlG5fwETt=w5A1{C`4>EaloaXxjTum7O{0oU`n z@*-Q56f=@?l+@Jz@c#dQt5;7%Ij<*!nPuq~S@FLM)|MMr=KI~7yVL*5uIrC_82x6J zosV9#BlPOL);oeB>Z&IX|7JWeHFx<=%cS?lzEAf|jM40EU^r z(i%@&5woIA{H4k&EietrG9{BwVuPbhq!nl(RXT4_Z67uDb%?~PlY0fRuK#5JPC zv9u&3zlgy%v8W_7Pa&wZI6J>=`{^??f%2*#`IN+xRIB8oR3OD*WME{ZYha>lWEf&( zXk}_@Wn`#rU|?ln@K5EcB8rCG{FKbJO57UyD|6=pHAsSN2+mI{DNig)WhgH%*UQYy zE>2D?NY%?PN}v7CMhd9N7^0}OC^?lOqokz3N?$)YKQ}i&uUM}~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3Al}fq`+lr;B5VMQ?A=?k?d(3EStzOhRsdHyTc8oVj3vTY)&s#N43F z-5g@yvNazS-^h5+eIr7nqxXbe!|tbLlQ~s(aXd0u)Ohk~goW~Cd*Kfu_rKPJFJfJM zQRBp>H)ahI4az@fKZ%^ROr7D{*@#~!Jtw_ktYB35RI9RoT56@m?6do>Z#&>!rV{U2 z#L##4&Do-zXFTH!H}9U5XEJ#^hsV9iPdw)J9e;d0Zuk4!$4veAvoTBxs=pTdf%DvI zKes@Uum9U0I0e5f+Z`#u@?G`o**A^F=MEY8bT69mUMWH5_-~c@lh){8nq0D1Fqp|g zb!U#+EB&}UosDwMPbx)47;Z2ZxC?olW1RD6wO`h2{u-CSlC76t+D!h^u|!2SHL~o^ zu4Xe4SHtt4|4c4k$NoT#Vfyo*w{N|VPgFniCRz2H*&AO~&q>`GmH7-4w#0YdpH=o> zxSy^0;KC*4D(AyiU)7&9{p=ft1BbQVeD!1URBW(oP@j~LwpEs)&a299rjK2Y$k%9D zzV_*2-A8p9K347J^ZcjqbE(eh^Os+iyk7W^p*LfG*6l#^Tfi7*@O1TaS?83{1OOK8 B2>$>8 literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/text-strikethrough.32.disabled.png b/app/ui/style/olive-light/png/text-strikethrough.32.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..8bfde72afd32f0315830b44ce268dfb6c497e035 GIT binary patch literal 888 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-GzZ+Rj;xUkjGiz z5n0T@z@7-gj8hNq*aj42FY)wsWq-yZDrzqFV1xP^1_s9Go-U3d9>$%xyM0l>vnH;+Or@-Ai^1(KWDoJy#YIvA53euw)nOUbUwHCaef9TL^j-VF@?|)=_ z?tFuR;s3kr%~~Pvv-FmK3E%vov|--eH!D>Y_6M%n&QhQzaxr`M!TmY!EYr_2h(@mH z@cXZ{7%wJxgOS_=CuVYrn-ZqvO(rt&E+fqRqPRGVA!*AYEtKgig)5%+OrrLzMF4iQ{J4B&%>ZRedj$s zhEqq5ToG#UW9!<+AoKWh+abPP7Ln^NWnPT?x$;v%%>$bYhjVu}p4eEq!DsHH8``;| z_V>!S_}?@-)T4C7BeL1?S#fpe>)RiX@jQ7w)u|-neAdIOnI-Xj>p4!w86@NgiJ$#( zYVMlI>wooj-FccUVJ>fSXD@eYYiTc3VQyz{Uuk1+ZeE`#kN^M!5lKWrR9J<@m(Ob) zWf;dl-`O#(Hpon(p4`sNst3uT)Yalav=w{u2M9sDcrbVnL=oy=;6<;w7d(2cC}5_hL@AE!CKD_h3&-)JCWvPyH9LIsA zmw;2i<7PG|>3*PVW_!RE@PnDH_WS+sHa9n~PX%CMVPU4*?Y?JbZvsaqiILO?F7-ArQFI1)E^m|cmFIaMuC1+YkBW)o_^703f!BabRrz7e9*E<3 z8F(>gx}2uz5@6#J=kizjj@tuI<_Ckp;CzkZOyXMwAYeXk@O^)yM)8SMxdM`I`$WB7 z@4>?aFf+S$o3rOF9wq>izAuaylO%a#ngETe$sh=}B`xy*X%TQb48wL11Xr^xyH*i? z=KxujZ3RJaAJ8sL9hdZa5Cl(zVR$3UvXpwKCm60X&nlA2YMn-Q8U`DroGBsIHuIp8%VAo1{}^{!<44`arRTKX8}; zNZQMf&Fr2-1ppo^0R3?wfFwzxcDr3lKT1&)odllF+XjQd%7FmPY!H=UT1S zxK`arlH?Uh=ks@pr0e~D|J$;l(mv2^HXms;8W#)ZZQ#mqN&f`?F|%2qDd{OQ3yXO? z&wGD;ef^8FI;GEY-}hfN+jm7g#LZiCpMTLN$s~PLRU9fB(fs`U&%>lm;8<0Xq9m=F z*}KlUFUR^$kgw5dwN9GZiQ!*Q1k6dA1CE;6o}?W!bHI<1E;{Fassnsi^)If&6T#W% ROmzSN002ovPDHLkV1n#D$z%Wk literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/text-strikethrough.64.disabled.png b/app/ui/style/olive-light/png/text-strikethrough.64.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..b41aa1fb283a08997ddb82695d73fa9304527bb7 GIT binary patch literal 1432 zcmZ`(Yd8~n9R6FFa>*sv!cG?)lG%i_kg>VXWriWL%w;muHd{DmlF(5&=;U&q!$Bgq zZi+;7aqXf~WUfhb+hGb#oHNh)bUvI9@9%xy-~0Za_uDVi)7?>im)0%-fV{I41}|pY zZOBN9CkScw5l_hoG!6~G&1~84Bq_0mg*xGJ03_%Ez&#IuNW9|m0f>VGFc$&<>M8*0 zu_Y~DR$>Ek%GD7AK+UBC2(j83>*UV>K;iK=B)VwV$>JrH>5RidXCzfsEz~9&^=rk2 zP-hI95IMH!KhBskSKRVtt&<=cc6{w-$zRRh7mnFiV~&JT3{E zOvo$ll9#Zo>d;xeU%MB}u(UGkq893bsCU>@r0I-ayg@ss%|pu8C|%bpL#0j~ntG$% zXLH8T!M#@~=b*d>Hs-ceff@CiO;31|^eewL*~JCpGuK!iwR~p7(L`I76xkM=PlsTX z6p8USW-IFh(6P+g9%;A2Du_!-V&yu{0xi#-PinGz@tGeyIyKn(K$!s^S%Z>kazV`r z@JPtQf$!$i9L!>0T2%ihIqNS0YTEE*-zZ2?=vjRJap+t9=j|Nty7*TuzJAR+hZW2< z7BvKA?1mkla&D1BmpgML_9DrGHyG!;(tAcbyVdOr)CK+2fyBzLqUuzubMMdz3pDJo9 z85WF=_7J^iDWoysY zE9I?T_P-yXsq-GRc?A*~e`Tw^Sf99IQO*y4q4f zRVl{cdd5QN%`PMsq% z+~|xeo89|4$VAv)x{5QV*#F$CuekmunF+XS2V!g4n~cIkED_n`TZ(zLCf!yF59?3^ zBA1oHq!__lqwsi}faR&>yfbS$pK|hEz;BJ=+w^XgP!@a~hva z@!!~gF~Fi<_fG!Co!g^QB&whN7Tg8hQL19E@k?trBh%GYoghjcyFU<2P?D+ekv>e6 zJGZ;^>sySna?KjD#kbjy)~Ut()0mH0b6E+|)l3ODb<;OZt7cSho>YwA#rf)x-RQef zb%&s{bn_#BFdyAo>Ylb5!Z^`FADHLW34Pn}{#)*3!&`1@fku%pd&)-o5+4p~fUN?? zT%dzQJlWD!Z?EFPvuGPvp=noEkQ?gu;Nqr>t|p$s}eNDwG~3CV+q=5XNvbV}vOIVTw9>6oo(-!Qm)4 z{AK^k*8dz(X=F-x{Qn=c<+8pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H102FjZ zSad;kbZBpK08L?XbZKrNQgw4{Z+5rK%$WcH05fz&Saefwb#h~60BCe{a5^t9V{dJ3 zZ*FrgcWG;BZDnL>VJ>fSXD@eYYiTc3VQyz{Uuk1+ZeE`#kN^M%zDYzuRCt{2n_Y|( z*A>VA=gzFX3;Qv4XrX4=nenzukoy6cs;xkZp(273g)~tp>O-VHM6D32f)c4xULy6O zeL)nY3JIxws4BH8MI{n1sDf(Ss-$WKqE)1T;Y z?A=ZHz1Z@cd(OFk-}yRo26)1!8)`?8L?W>&8jWrP@MQo)0M;>c4}e}G>S5+p0A>N) z129EIw*lN_<`E&p`_*do%IN6mSSxaCnE>f@x?^&3axZ}AnRy2QGl)MT8UrxQ%ok15 z{ChT=y%$EFtq{PlZTmYyh(pZ0t_A)fG>sV zw;n`vKA+DY)*UaxN+g*~?f~#M+BkUtVCI=1;{)xN3)r^3Qz>;4Kt~w52N@rD z1tgQnAtA&G1eiP#d74ABg+k$qZd@SX3dm$KW}#3xT04#TLPRA1e`Mx=5Ye?rBrhxcps^{DFW6LDzK;F!N7*gAm;Sa3Y`2 z{{cYL|8_E&98gMqi-`6zbE@|9|CLhPi^XE8S$~h@7G$jBI2Qoeb&ZJrMnpf%<#Lq( zvF_tI&eKG6h?zYp<&mKNv;{~elV4Rz{kE=iqN}!T9~>SYp3{v}2Ll?b>7!^L1ey66 zACszK7_YTx@<0fa2Z9N(wcd6jx|q#oKhX8xE`po}9iQ!g4&b|O=n5c+=rcYxH#B{> z6)gc0i9{ze*FAtLr9#<9TTN*90*_&B3w?)%mH=a8W3xoWbsY`8(fmT2b9vmFJc=ztzufV4`vIubi z&oJ{Vu~@9XudnaFl}hDy2zfQc{S}Z-r#mJlCXN7j0l;V5l|Y-hMMP)g@%Zt}moHa= zhgLvmXXonxz7JsaQf1c$Rs+~JJ3HG^E|)I>4_yZ^^S75Qtv18VFD`s<<=x?#TXbENE(Lm5`dSP zIo|AeW!;BizcmAqwHr-?`q(N_5XNgV)s%jNO~ zop{S_1w4Y|IC}sb1<+Z~j);`!d3%b*Vy@X3%lbM%N_if@VZQ<_GYi8oUaV)c7|U7! z0FY7+GxIMSFwN-rU-kmjI1b>ZkIhgjmFjNR@rn>YN;wPQOg$TB7Sq$ye&xB3u_6Sh z@s6K?X_}vF*6E57!1KJKpN&%Ller53fKtl0HC71Gbko<05g;0kKILcQd0x|%<`p9V zfDL{ILWqfGovs)Glv2<7*$5$Ivwl~E02?-JND$FpAF~feMn;;hWoQd9Ffb5vUH8?s zYuC24h?>b{Ow%-f+CVp|R!@?)0ES`gW9A=qb#?v4b=@~?+g_}lRhx|)H}*&=f5FV# z8_4JPIx)2G23^-Z!_1o>aYl57na`T0c_Ew4`ZZ82N+OY16^TR+)H*^NsTBQHO8GsV zSXu(qUW{1u{=0Vfb`8K)BD&^zUd}YlanJK^ODU)Epvh4rlgS<-M9szQ5kaS_j!C0GpT@L~vyCRXuE~V6VB1$py5W;zZ>Lw9gB%*VVdg0!nqlVu5z!3hAInK*ejmWUq?EE1`90wY5BLyF WPDiA!_i#=C0000 + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Martin Ruskov + + + http://commons.wikimedia.org/wiki/Tango_icon + + + + + + + + + + + + + + + + From 006d4e76a093a8f0dc9326fe648ea1837c78a743 Mon Sep 17 00:00:00 2001 From: Pablo Gil Date: Mon, 25 Apr 2022 21:15:56 +0200 Subject: [PATCH 53/81] Updated icon "text-underline" (#1903) * initial redesign * updated Dark SVG and all PNGs --- .../png/text-underline.128.disabled.png | Bin 1346 -> 1454 bytes .../olive-dark/png/text-underline.128.png | Bin 1623 -> 1772 bytes .../png/text-underline.16.disabled.png | Bin 529 -> 530 bytes .../olive-dark/png/text-underline.16.png | Bin 402 -> 414 bytes .../png/text-underline.32.disabled.png | Bin 624 -> 640 bytes .../olive-dark/png/text-underline.32.png | Bin 518 -> 548 bytes .../png/text-underline.64.disabled.png | Bin 824 -> 862 bytes .../olive-dark/png/text-underline.64.png | Bin 896 -> 952 bytes .../style/olive-dark/svg/text-underline.svg | 279 ++++++++++-------- .../png/text-underline.128.disabled.png | Bin 1491 -> 1632 bytes .../olive-light/png/text-underline.128.png | Bin 1975 -> 2172 bytes .../png/text-underline.16.disabled.png | Bin 554 -> 556 bytes .../olive-light/png/text-underline.16.png | Bin 450 -> 467 bytes .../png/text-underline.32.disabled.png | Bin 664 -> 681 bytes .../olive-light/png/text-underline.32.png | Bin 611 -> 642 bytes .../png/text-underline.64.disabled.png | Bin 889 -> 942 bytes .../olive-light/png/text-underline.64.png | Bin 1042 -> 1134 bytes .../style/olive-light/svg/text-underline.svg | 279 ++++++++++-------- 18 files changed, 300 insertions(+), 258 deletions(-) diff --git a/app/ui/style/olive-dark/png/text-underline.128.disabled.png b/app/ui/style/olive-dark/png/text-underline.128.disabled.png index 15bab9d97244a3024fcdb82f59e0df4d8d7e12e6..06bb1cb3d11026139aaa267a419778f633ecefa6 100644 GIT binary patch delta 1149 zcmX@awT^p2S3QfUsI^f6TTdYa1BXtu!i?sH#|y&bE?%MgPH1_SF|vgf1CHAPKZ&~|Mphy zJ#P-l$KLz7=-7VFCCAn+|CyU{w}5e9u-Q+oU5$O^lh!IcICcA%@11+yj}Kh7;gezYcb}X+WEa8S)wiot#TK4g``~Zm|9jR`_HNoc-S6B}`-i17e=TL% z5qQq@fyK1HL3iJO(s(|>*jzJ2tCit_NxAH^0J(PF_D4@T+AZ08Ew!&VUdrCFDL!F? z^rzXc^&`q>E5Cax_wCMHagAvq4UgU57yqiS-}89N_M?94y5II1|6lX$e5$oTqG>v5fvygj!=0;N4f7dk+ z1h*R!5$i zD}I`U$6P3XJcU!3`$za;HR~;z;&qitM?}N-6gEoF^Hxu&S6k%Stv1^vGhlh!Vm=>% zWj+!g8x@_|LQC!#@EkVad3?^rBdcVmfA-UTt^rdtne|@ojGk?|oyDVBy*2Dd_*_Qy zn}@Hy**xvmtP=Ssn~9P8H+S#dt#J2jXuI!s=8%vpkEW0)vxxfI zPcQ!4S8X|^b-Fn~OJTNPhfbv0eDQMM^FI2Y+a6Wth49Dk|9i#f@?~Epjjp2EhI;!| zN^R!fsz|tZyj$CUgVolLQ+J+=-zcL0=Bxwj2IdWcjBglnLKy0;8KgxI@EveN5~HKg z&iYKIId-pO)->$!N_S}xku)s6=UK><6FW73cY}2xYe-tWgKDv;_^ROOB@T>k21Okb zO)sn5^CtJdo?{#95V`0sbI{f&vb(O9h$g(w)0@U{##*gQA;F-SiEZX;7Quql%xo9o zgw-{XYZ#)-%T+MyBF@Ro0dOBIKhZhiL}PMLkqC!8B3qpfQMTpcc7Dod2f zxBebJy*T>H2mZz>8*(!3ir2o@J8&(P_j{9cMwIwX7RK7_*M~PO7H{rbTKUF`wTi<) zdG*&e9ahr~O|zD6eQw44BR76dMD%W!Z4pYNXfjrn^`BR|USH3`o9&(d!9buY_|#8b8j z8x(WbKis}yX3@z;Pot?_iVO$pUfG?Qm9x{gJ!whz*D2k{EIvp#e3x6d#n`8exyE4c zoKo?NN1a}!-hO7`ym18+&%DU{m+K4qYUiZB_Iq}5+qysXS8M7YTQ@N5{;{cHx#a7Z zhsIUMKB;Mj>Lgk|cCtE{J^lSeUOk2hVXrp-vR1A+s>&vj)Wg{zbZW(-HcOqT>(%w2 zug&?D&R^1}EdAkM^YqEFo5dITteCtUMvva zQ`8hIvn#(wM1z4Lg|C0j>=~A)A4*go`}m<-iGg8BpIO+R>JEz^Czu!%PZ;qOpUaG3}!iZl7$&S^xloVz%J` diff --git a/app/ui/style/olive-dark/png/text-underline.128.png b/app/ui/style/olive-dark/png/text-underline.128.png index 8ebd4ab8d0d949c8b55c77ab2696d32d94cdf7be..8dd2db5276dfb1d01c4d48b885088a46d5fe8d63 100644 GIT binary patch delta 1603 zcmb`H`8Sjc7{})wON}fu(zV6ZjBT=)>|_~kmKiPc+LffFRJPF=>7BtnmRmA}PJ_bO zCd;HtBzY}KILdl;g*lBHG>Ay9rR&~vI`<#wIp3eY&vQQK`~BtlQ(7dG@eGnwaUt4M zXnAw_0VyD;@DkD;te6&S8|oHsH^Fk`@VKG`9?m{7bGbFyp*ERLz@asaboT3Hrl}Vt z)4TTR{@!NGQ>@@5z~SYhM0d9IiQc7M0XCwzOZVcM1)|3b6T)8KBTACzz1O#d^OH6q z_nTKO#L@-k=>l(JWOD|@?Gv}XsT1!)RQJf&-obq;)u3Gz%}m+2Yw2M|ezk}r$43A+ z1xTfr5=QB}0{1Gy<_3%ie}td`(fKnL&`)Ijj~WyQYB%G?Y>%yrNazPBWsi=e0l*nA zWItq4#avEKX_~GtatQQ{lMD#YqVCQr?Q*L`BxjaR+mnHy@8yRwOS^hPS=8jLA5PTv zNlz?`y5nDG9&gv%csX)*pK>aOue4a!W1K}oU@Rm>w{&i8DhxC4isFbGF|hUd$kRb{ zspS@apz1Z1W5ZvVgLqenlwYFQD4~>A$<8)$QvV1MM>cazUg%8y5_m0mT*M(Pq1mxs z0}90%evGU5Pu1@CaB^NwBQjbfV<{4buL4$WKBKFG`3QeCpXWB`a{9aUVY3br%A?Rn zz|dtJ!q3VAM>P5dvMP}&T{rk;T}!{AikI0=Eb5qVZqHktM+09&gU5@s@+2u#2F>RQ z#OaMZXyU?9hN*` zYh1cxw{@zIgZYUUJi_W4wtKEV_OZj&+I#X^va;k^;t#2+1N<512fnfD7boZ9tdF)*Y&O-H0^L3(5t#mNKK# z?rCA~m3Xd1ZVE*i43M^V*+>pO65gEBp^ni#9`>>-MH;B_x)6lqd4gUVX(s0>D+XMu zE!L4>4G@STbW9? zH$#`LIk_qubp!(hgWD%|3V*|H^p+ZIm4wP!z(bFlj!rS9HZ4XT{(O7}9UpA-`T7jHy&@Zdi&UU77748n;n{)2=d7Vw zg*TaCcp1VFHfODaw z%l>Rv!)Z!kKT`TvFa&9a{DNAQC%bIOJHo=G^vQR=ufnVa&J0%hY;>T3ky=CfO1#RM zKIp;9@bmajh%*6vqcdnTagl~-cnlSWHB_75er-P#KsLv4xLJ&INueWeDJ`+>uX^m; z92-sL=WRrPe|uYvEQe+(e5W?|dxO^{nR{>pi|B4y!1w-!3{803Op t2Swi}Ls%`S+d_zKwC7#_+J9w6KVUa6SbCv9_tqn}$Hj?Etab=Y_y+pSFDwd&|tJoNXz*OWFkm105qKF5sP% z^I$&9#29F6Y0l z*?aC*_3yv+1v?k#zx{vnb@}(W>a|2Aa|kyOvKbr#uOj8jA&I3(S@vPSdNB&`Gc>epzp1q3x*;V-QMDzP-(m{)&I zgjqw=;Z?hpdUn^QIkO|Sn}c-iV_I_O_OhpZK@&t6`6kxwmYJ2rG~=dXLb_AttN1`^ zkCXcjp9x|=;~e};Fx%bfY~;IpJ8w7ap3gsLTEMUBcRI})x(6zf?#=;PYhb?dUH=+Q zwS`+4ZVT7H);l?C5zoq*+f#iFIhV5jP~7-#6;M$@Y0};D$Y39*aF!j{?nc|MzjyrZ za)#?F)AG;FOf~Z2{$QIKeCYDaYZec@9`v;Asy*O8b2HD96%1+Hq<^SxtVukku{bR- z>F%<7kBbavMP&ZA*_an7p2z&o&TZEk>7q1Cxm_v;bRPVgQ}5_@B%RPzpLIL; zb6M}L(0BKCsV0@rXS0b~+*w|nD%rM-Qk8-z38Y&>;0o#Az3u+VG^mNf>Ot(2}F zyDQG@Kl$JBEz&yJzk07q?G9-BmfD%@ z$-jG7pTSNg_il}nd_XL3*Bp=X`Cg`ckzt!x&ON|nd^^-WRI2ygPV>!g56r6P@x9Bp z=l#3wX&KoH@9J2uPFAT{_-z%T} zES|GnzaiC^_unrQJ%)ze=b7WwYaJ>~wlTa9^xg1h#*A#M0^}DI zKE9#Eslo1Do#6+AtCzA@vu!xwxJvb%Y1*_;d@0H$UO*|k?yRUX&a?IW=NTX9KKOZ$ zD}DKkB@DGy!CSdkK70OL)aFa#gX~qgF|TLE)n8cYyqtAL$L{lNc}&lO_j+5{epWoz zsK3&xt~OtF^XEAROfsFj&$H+;r!k3ttvkP9)@S+Gf4{`vzvaADZ^Qg70du~brMFgZ z7rjxlsAPwL69=et<4}BL^wMR*?tD!_mmW4_=^uB!a-#!|-Lcr?I@3jV z@g5$(8rOC5d2-uvMEYD~qZlY^s7-D2-Wol_N=_~X;_vpC zNQyNjDc<^_A=CYWU$AHE_FZ?1XCyHMx=Qa1SL!!gwQm3MTS}!x+@6p E0KpMaE&u=k diff --git a/app/ui/style/olive-dark/png/text-underline.16.png b/app/ui/style/olive-dark/png/text-underline.16.png index d48167781fbf07085174d0fc3b9ae819933cea7b..3f94216a0ea494a16ea21e9d2f0a6ef13963dfaa 100644 GIT binary patch delta 233 zcmVn2Z5Ir=HWc>0;#Kg{uDmo+ zU)4@c*U4^D=11SseyD%I9{|?r?V7v0Y_kvWmVQwK=%&B#17fVw$7*XAs!iY~$3?kt zQVtREq&@&6V6HBKPoM{!1NRZ}{AH*5PBnn5JeL{20FS^dBCPq|?-l9*xC4$4-2DJ< jLp=kA!0Ca24KUpTj_p!IA~zC600000NkvXXu0mjf9&Bsx delta 221 zcmV<303!dM1Cj%fy??+-L_t(IjqQ*zYC=&EMb8AmA`+}q25}iF+=N9IpoLqpaR(M5 zjT^8?k;)AS7Fva@!A9`d%!ilHSHcIoTzFjO-kJO6Jt&}#)QQ@q2DO{ssP2_bbM>J1 z8@zsc^HP7fn)XNh2izgxnEcexTWP0T0pH0l4FSXCpIZT&q?m}U7{4s^;Q zBDU%~@CK~aFW?Nk0?)uQB6h!K>U*jIjPndq-UA=NDk5xnUVB8|dT2EqF{u7v(RPCi XZthHdL&=L)00000NkvXXu0mjfy>@8W diff --git a/app/ui/style/olive-dark/png/text-underline.32.disabled.png b/app/ui/style/olive-dark/png/text-underline.32.disabled.png index f8f958b5def459f3aa2a514a4428b995130ee29d..f3db85a0dd83ed6289e071c301fcf18879c76551 100644 GIT binary patch delta 328 zcmeys(!e^QtDZ$v)Y@Pk$JZE{-7{$KOt}?LOon&^Etw9-C-Sgv7%Z?LQjt z|NrOl-Z<%Tfb`9v$Fau_iTZ9@Eg-1#$?WjCeV;e{Hu|vS$+rc9#thFk=|`D9k#1<6 zk!@b_ja763Q%J*fhAUx(RjSX682;t1wPu|DZod448@Gy>^zJUM7gy-q`^JjVmVs|l z1!DuF!lkmhC~L9ocdZ=J($`%MpQ$)u@mNEpdTX5_gF@=*KIRK5O3T;|xP|pIFb4ax zB}91rVqnm!5fZnXzUf)G$@{YPJhvA3PoKd5;Dp>^z1jbMdpNlB)W+}BPSv$%6qqi> zo>&mKgg1(7!Ox{yOND=aW;1L3bLWclevM%JcPwk_ucYcLOvx<1!)d|X z9q^4QpuuLI$@0gUhs5@`o-jFDD!lnt#j{<{GE{eOi`&UCb>&Rfz9(wm4!Bp`Ws%tV z_GH5$?-gbY9S4_szAl6OmX{8<18 zfVsFG*fO(n%J30j1CT3y0!L{Bl(y)at^#vj=@mExPJkO%8wO^8d7#hp?nzo}7N7^% z1}3t+flh%XNons*0XT>bU>R5dx-;x=zz(n}De?Qy1>k`U0keD`vNAB}mL7p?-~u>K zJM~ea1Yl+>z#`CD(A*lZD`~wBa0&DlG`y}CNtODKXx~v4rJ9@h|M~)==52Y*>`7Mu O0000U|1cn5Vy?-=GL_t(oh3%HXDn(HghQEbENis3f)h$IPUVtu{dIJLXcAOz;RM2E=pR43xw`Nl}X5z)riBbB=wlbjtqg)LLu*d#&$u_URm`t0c7~wWF(% zu(#p%wu-zxzk}PkGC&5%02v?yWPn-&yt%wTp|AJ)UOxhOa)0@70>FsNA3FiKcllHT zz_iQnk^#)@)e>2qpK^IZHAZey*vZPsHJ}RA;`Ktg90fo31*p<{Q z89o7Q0~Si}z;W3CD_aarH-VL?^b8yUr@(cnjRQ--Dlimz56o<)zACjmqtW56<>hpY*VhNTDK3b+JL%1(Wj*ViME k)c(`KW|L6!ui^jD2Q&3!LU@VVnE(I)07*qoM6N<$g3||`MF0Q* diff --git a/app/ui/style/olive-dark/png/text-underline.64.disabled.png b/app/ui/style/olive-dark/png/text-underline.64.disabled.png index 74eb19fddf01e25a01b5a40ae8a1314b323b8ad2..aadca969fee1fda613f1f2625751a8f62826bf80 100644 GIT binary patch delta 599 zcmdnNc8_gBS3QfUsI}os{=26c7#Pobx;Tbp+A#F-%?#UoOir{!_Ixj2DapeZ4C@!fzV#e$RYY(hz;6qTlus_wr|-@=wL4F-WS# zt$W!x`9Z@f+4`2A%Wu}HewK>Nrf4pJH$FedqGy69fa2dUaRL*9kozbF##f;X|4Nhhp0PzWu^By*KNpmeIsekrd0w%-N;DS0E>o%qo+PkKEYG~0DN%$*8l(j delta 514 zcmcb|wu5a#S3RRFgR%+F@p4H92FB%{E{-7?_uk&{_CMqxaNy&ACMGS9B!M0;g=5Ty z_x}EKw{PfgW*3(43{+mE@c!chL!Z{N=w7$EJ10ag+igE>AusoT+dF@@FL5l@vMuEQ zciF4+SxD8}bI(GT)m5?cBrq^b9AIEGXkdgfKd>LEUtAU5F4`A5t&jD@ikvAt3-r6$ zFINVSGE8n5mzD_>$2_r+~BFWE@ z49tD8`n%3>GfYZtPwIIpZ^8Ti-wXx;_g3HawGsvA*1kV-kAcB6&^}T3%ghf)c((`k zPM7#_>_qSqk9jhhmk!L}tgW)S)R$+)-JP(m0Bbb*wV0|!&RVj0Y_8B&5WroH47Xa zr8f%duyAkmP(7kFtx18S>qbk)(z*NwVqBU>8k86pw=`tbG`vsVc(au0-t3bzBl``% zXP4b9{(k@ezcY6%%gYoDw+o)09`co^KqJ6TN9p^UX^Zyn=G{>*q7g4Pao)@VTclw-V}20BJ+?h$30AReSimIuk6A&9HIHb!pSj7vxs%-O3TgEYWwxY{jaV z7iUYXy5(TgkS-P*%%Jp1s6tp{J43G6-OLR2JqP?4*6S>C)oXU17*)=AS72fLCI-2V zjwqLImJbF&uLG`5W4aX_a5XF;J>`V#>avLpc}(_9Th$(vFus+V{QO^idYvQR)5KN3CJmp}EX#4V4oU!yDuTIc0Sf-?BXr zDNr$bW4*!I#Vu{o+@%-d99-HOS6}9N&Bhi|S{)Y~dY36i-LA5s+f|L9wV#0@G?(pH zqp4-veuj5U%?Bi=daq>iZVXGP7oK|9*5CLm%MEp<`Mle*r(D{w;#JfGsi|@g@(uXj zh)%q?$Z2U#yXzCnf(Zr1A!_RznNxk_y!OZ|DET+?JqV1PRsX>5Lg1G@=Y!VItF6s!DjB?D)sdJxqx+n=Q+QzXeOSypQ1xwft%T@Z1LvDSu6a2{* z%xu9qWn=AJ-WY+2aWiglmRZjHm=ON;Y|P*M59wch%^K?1mE*2Fv$Fr3c>hVZ`2YV( h758p?Jc@rP?#VW_Iy>oL3Ih;$y85}Sb4p-r0s!7cQhERY delta 719 zcmdnN-oQR#Z~Zz?7srr_Id5m}&K3z2Y2UwDNotYAMKuMDNlG0q0W6Mda<)Wq%Vz{! zaLZYvdqin!YkiXfN7s#(t6tXf2|6M{9gNK^Jwi;2J~C>Z+9)CH{^j18dDgR=-~8QG zkXQHf>~WiSIbue4dn4*RL-iTdnI2D8`g9;p^H=$IhH$3GtCi}FF6E^uuf6o&U+h8b zf!R6-b~l(uHk32wZ(?}QbT5VR9_yZyOnbO1CbL$Ee^BH8p!dKRzrxF$jM`QcEYF|X z#k`iu&OL2)&&<#W;hD8tSfW_ZxTdY{of+DZx_>)MnbU!)zUHPTQy#ND(`LB` zHBR4{_OJWfec;o9N$U^D)@SdFZ0I<{RIcpJx8hZ1c3r3Ofp;hVOzWuG(58NOZD^@L z2IFa^)9V~P%39>JB?cv#73u2jhB*Bd%~n2vMi_q}?;Td=0Dxn0VhUtr>M2jPmK z@MrcF945i*e;ducwOO9^oKSsrcDv*Qp$nVd{cyUXmmnAVmXVK^GvyW3_@v7Mka}SgyC~shV@$~y?kF)As{xg>~YCh032)kZ7?_`RR;=f&-t6nW} tpSaY?h3%C6^Zz`Ru&BSzeWJwLp0RpydzAX#ZSo92;OXk;vd$@?2>?T!K`#IR diff --git a/app/ui/style/olive-dark/svg/text-underline.svg b/app/ui/style/olive-dark/svg/text-underline.svg index dc1cc0eba..1ce678e44 100644 --- a/app/ui/style/olive-dark/svg/text-underline.svg +++ b/app/ui/style/olive-dark/svg/text-underline.svg @@ -1,7 +1,7 @@ - - - - - - - - - - - - - - image/svg+xml - - - - Martin Ruskov - - - http://commons.wikimedia.org/wiki/Tango_icon - - - - - - - - - - - - - + + + + + + + + + + + + + + + + image/svg+xml + + + + Martin Ruskov + + + http://commons.wikimedia.org/wiki/Tango_icon + + + + + + + + + + + + + - + y="-62.000004" + transform="rotate(90)" + inkscape:path-effect="#path-effect2895" + d="m 43,-62.000004 c 1.656881,0 3,1.343146 3,3 V -5 c 0,1.6568542 -1.343146,3 -3,3 -1.656881,0 -3,-1.3431458 -3,-3 v -54.000004 c 0,-1.656854 1.343146,-3 3,-3 z" + sodipodi:type="rect" /> diff --git a/app/ui/style/olive-light/png/text-underline.128.disabled.png b/app/ui/style/olive-light/png/text-underline.128.disabled.png index c418f8363bf55d04412191dc19f0d3576922043b..91450be780437a66ed4f4f05606203dd04c62b97 100644 GIT binary patch delta 1349 zcmcJ|eK-?%0KoCz7?ZcraS!Ucbk!d3v%J(?%&E+zysMP=>2_{t-nKT|dhs@dimbdB zCl$KMoY$dTip2m}J{JN4j>0oE6S-Bw(tQS7qQPHQI-b4nNU|AdTx zzpH4NKgX-5bi~1ZY3+u*`2njyRBC0jBv_? z(~U}}O&q0z7j+6rMf`6%PLDeFw?}T2e-DsHCc@h+HD||7qEVX2w6IB%i*`Ee$Ju3M zB*npcMCVy^LYGZt)5+ zEjqS|TX#V215pJ3MnAHh6j!fSHC4{pufs}dzp_2NAns{FAs1~S9fmdqKJbH|`h^Rv zZdem!Ss(dp{J=DfHyWC)z(atXy4-Zp$GOJ*{sa54BQuv1R%b}~Tk7E_&Yg+z^b7a0 ztm&DSXsp>;%neT|C)$>=XgV0u6tic%U`x*$;WaZ90_X-^K{VRsq3C6X9R8 ztCYMWe@NQHFfTF;pIMx>lS!0maT__V^$N*QUg7?-^pAg8C~qdHr0D`~@;BYDtya;O ze(xg9GXzPKEIhHF#OirL-12U*WbbacO34o#whogH2Bgu_0W9$V?-jC zOY?i4{K!+7Zy0VKO5a4l!V*M!E)NL@Nx72uCshO9R{)5j_O@mZb*)%MS&40j=p0*&J4au>VnesEZjv>H1!&n0AvdkQ{7>?JbK ziVU@P%PLCH=glw5SHd|n!{hCUd&=$M`pgL8$O?>^v#@gfd|&nMEBDeI!7<9DlERuZ z5aw7VTM@0po=Bg?fOje99b=iQSIVf6Zb!j;+>Owyp|UDYW6>a70qOI0^O{L+$#knT zzd6heERS6;aeZLzFh@Vs%;)r>R}hI{S8H9o$OGK!6-+x^8vJziwzQq sMk1wrjRpX~p47u9Qsbgi@n~XF93h?pP)HQY4*4ITtgzGKTC7&`Ki^$_DF6Tf delta 1186 zcmaFBbD4WWS3RRFgNo6rr!KD<7+9Pkb+o+qUGov)WA0AluWI*2T@(_x{+Lzu&gcSB>1= z&8pG$ss7lL%HZ)ZNYhkv=#S}V1pp5Z0Kr4<85)T7wm z@P6Nd$FKa~a4d+~U%E}N!S`Xv%EVpgt{1K=KRV@J+JECybIdh;84P1*c1>Z>@;@-? zWtZ6;t`*D7bEl|3u=2D&=UdwAl-b?gA+4k2NKiYy{ z`+AE$I6FgnU)K4Po)>OA{_+hgn)g`JiIrtf=Yf(LGCKqube9!!or*cp#=!91CtACB z>X|u}Tjt-(diUwtSzU#PyT15;%i->bevr56w77u(jm-PSryrYNNt9Y#ch0*$fss+% zJ84a@a?UN8`U;NunQ~9}GKoa*+woM&_r^vu!@9tu{WCx?Q+#s)9GuyT=V1UM)-4w#v{uw%Lc(Uih8OzKFti%nJ@~IPfvmk>SJX zFM0=7nJ(a!F)43;YVml^-z9P~vkSgHnzd72q^@CgYv`YQWuJnNSs8yWH>o-!-i$>D+t&^Et+~TnvZI{H|5}6q5h&;Os^NLDhzji@H`y&fm7-@V)o7qBfPb zGUDg+GUe6ZEc_sTZ`MKmU(?NAMcg`R{o?Wbp!Waw&VA60VX-f2y?k-ayFAM;vmbuX zy}HRPap$3zFD|<8tyn7B-2QWO{>O)Rr+qIvZ02;^bbp8R<@%oqDVrBP-FxR_$@hD2 z4qat?X;NvcWAOR6zD!M2dfXMq<5f>3#G7CL;g(nBVrY20_LShawM$itAII)HyYr{2 z5(7ik>@UoG-2abr%#_|GVovjjK9nInD#d`xv;G%SIxYQG-p<&`_*m$xiclyD5eR zu7M)1fm8DHS`OV`qP#Qp!>7~x=ilI0d$X8n39|pZ_b&wX9@bKlQ(-PiL_Z&5$%DWqNE ziE|^-^QQ&^BTcEsYh)@jX+TT__dG$Pa*}_XHsImc(zsT?1cxG|k=WaEO4{H?q+iG3 z7=!=(Ec{uFPus8c0Y#40+~`s0+Dvp@d*8>cRFd4?eAI+WHG1+WTFY!-?9)o>*f8j@ zr9!U{TH)u;tsLN$GVN$s8nzlbx9)DX55dxCk9hOub!lMRP>quX=u*-XaU=os6|Ih> zc<1hk{0!z(zfw5=#RE34nLtVihnb4tG5LlKv1CB{ZyM-dGyIbh4==S$$Wo%AhF<}|dHuF8 zV?Dj&2)5px^$Y>#^#0e9Nw4_R0#%9JQl2XG5ueS}BnUgzzKheu08tB8wC60m7)q+Ywu|D{aE3Y^_6qdx9}$^akcrI5JgoS=9>fo9~GSJ zm`T*N@!4fKBS98xLdYAfvB0YMp(Xekd!+U4{EfkLiKU^RCK2qP4zKaN;-;TpZFa_j zWfYQ-@O21OP`Lq!+}TSjMH0Kd59(!2z+=6%9h5J*WDO2D)Z3e(7MkTv>2?=b^vK9h ze!vM^zC~gyB|q;L0am#4+wSDj9o3+1_N!6zYE$v?pkOFCN#k$zavwpc;QZl=`7uZ1 z^_w?nHnTN880S4-w-S(-`%bQ2wggm?$_!5w9Uk+nJNl6UTLf%5q1v&K<0Fo*Z^3&o zhg`fB9S(+-zte{53jB4u3%jSuKQzroI2X8R$_MqlwZ)pElqltK*e z6@Sev{6TEo)saA;Zp)QW3MM5wI|62CXjne-Wnr!B6AOCu&BBjdwQ8n7w`rDhBQo&w zrIlxMj3qTN9eyDIRM5-()Ol(Ctv)U_5JUN~<3J60>KGU&61vy9Dzx47vD(+P{aPrW zHvoE?jyu4pmG^k(ty+!Ltj@+E=-mBU#a^M$c)4RHv?ipYrNx`Oc;3LBN(~FUp7i7O z%uMr0d*s&b3p&8o%1ZgM-E@IUyn0`;c+j4LCr7Kj6l#>8p|y4D_!dNT17ln3;;6qG6rBeO_}m>iaa-c zCT7a?tnHj^G(}U}TXX)>yM0b+Bd7GWhP?Jm8MzF``RHi0xtZD0ADx|@e1H%>R>>MU zlDci)%E$R*|4v^x04*Q8%y^b$0t61N_c`R_SD%ArU5{dJJ1GTB7V034hG83*quExR zYoKB@qK4rxwJWggJuP|exeXR$V>63w(`#HL1d@YU!}IsZWnU@p+$7xbA>i!G(Kz(g zLSMi2vJNzBG~CLY*vbDb{BUsErTf{mmv?RZx@&EYPi$mTceVzCK;!rdgfT&$3b4s2>t!Db^qx)n8OX2soA#aUFla&C?&aDkG# z(!(5fyH@&RwPfLleVrZIRtS_eFdw83X%VgOMLxdozk@Pv-+v%zS z)?Fe^yrC&3Ha=cCwi=ZaC>7V#mFFxNwc_;R1=|;z8XFtCe0_a&!>QkWlM)uSC+^!? T%z_azV6{Bmy>aCSLoWXVnBut> delta 1808 zcmbW2`&ZJ38^=F*MBTikrjjeO7?f7t!Xb)zDRkZtZPVs0WkfE^OX^ZY`*5_(EEUMh zSw@sj*JREqnxz<~7Unf?Gt>lA@d|>KMPiQq1K)G@{pI=PIj?h`=lwqC{Z<(^@1XNQ zt$AOZ=lAJ%q^}7V_QaZN$rpYp5FTH4oGdo^aU8ohaU}V{N0O#a2;8{pc#RL^CofFW zy=&FlwkG?7s!smyb9J|df>6^1MfBYFc!pFN`2=}#PI|T5>i9&XZB}_aEqG=A#caa=_48z7XPjV3;YVl5<%HEeL@MfGF zose#vK&2b{|C?$1}WxhSj9qv2EM#^=RYsN z|Bn1$#N!FrKAr60&(sLf^Zit*E=|$>5xnkG2`=kxT1%fHtqUvhb)J5>VQZ?P=GC<$ ztuoFZ7W0%RvZDv#JD+fiyPJ4fb=Dg};SgNcQW0E<7KQnx7WC5dBG)a>I8~X(kVu_N zMaGPktXS)z$*5d|J(#~F*zN?s!|gpie(bq+0h;M_6HYM4oe^u$`(S2fcMQsGdtAZJ z0SM1|k=}db2N)u%fe&eae}xi52A^eg5+yI+y?gh{LQCxe)@!cTpt5(mbM^kJ22Xa> z(ZOLzVR2^EXZj7M<;^$UZhkhjJe~xTF?F79Oi%4O0L@z*s zP1<1XO$L=)3C)x5S+RCiW-&w}(bQv*{Lm`DRaI?nVdT7o`J`bBM%m&`PfdJY#iSXN zl6Eboy5Nm29ySwSKoM~+jN1j@~nXNRmkO38e!q#<%BuKISrt^>3`K ztQ2)@z2c8bFM-={P%lereg*vf{m&!VsVo@dJL0a(1F_Y_E5!7#+F2(qy!oOy9h%hI zlkmudA%I|_KO9h&SmnFTJ1}qA>#ZG7iVe$uIF>FK7qvmZ3L>Y~R-enLAB%gfBSs7G znt<%5rttIUZhsylH6Aw0o%zl8auhCCdF|atAvbngYy*zc4z>M>sHcpy4pq_MHwBdw+p;BYvw@Y+zV zm{-ud<=i8iT{E56%0?WIEqpzh&|#lR;ET51XxQA>rFpJQ#0!yYS8}Lwc=`Iyg9ZrS zbzLLy@z}@#Hd2|R)-zYIlOq%gBY>$L;$QRhwptjO2MT8gD=KdBw9`4{m$sMAQ3v9J0YIly zjsl*bEBe{m@-)Cs+f#msX3AXpdkg%4C=V;eGkJ`;;=ELVw_C=^XoZKFcwKbyWr{YZ zAKsWS0e}vBl@^U!GvMHd;F}8+X`F%js?Z&$_c|xf5dnU!i{Jr06J9vG7Q!O6h97Hd z;qfB-`un%y;^U>EK=2qP6Ry04Ddgwp6Fyz434zeIOYVu*M4Ij6PGC+Cy|_OSS#SUHYz-K$8wTW+`6YKQTyG?VGA=44J=L zVI}it&ONTlS*`k;4Me!G0#UsUBfwsos3|-65g?9G)89T2YZZAGctIZa_4VNjyk|Xu zGy@9ctO?r3%^cKx+C0d@F0AXFK|Zem)Dqt|Vr)XDB637CVF#5cBV}OScuK}by&oxz zW>fCpls2<&Gy7**iMDrxA!7lezBQn3+`o6CIgu1ald7ZQ$cvqb1t&Jrbvb1d5e;K3 z__(pj!Zo!~QI@H-C7fyi`?Cvc$#qbKgpUAV0^C_&Yym%dz1Mwu6hW~8^18i%G9VI8 zfs|R2qD>f8`*h5l(CE`G;nPW^oBx)DdMq}r9S_=-t4r2&s2o~(zoCk+YKaO)A^&$1 g*#8R$FS?%!6>FB%p3DIN;0wUlD-ieR@tEv?03I%6j{pDw diff --git a/app/ui/style/olive-light/png/text-underline.16.disabled.png b/app/ui/style/olive-light/png/text-underline.16.disabled.png index 2f76da2f8fe4ec68f170ead2cc14f19e6b379028..6e64c9f873f3cf8ba65e76f836516cc66f432238 100644 GIT binary patch delta 265 zcmZ3*vW8_sS3QfUsI^WP=SCq028P+5E{-7@=dbqpayKOiw9JoH{S?L~)v9r2?f?Jz zEFXp3+FvlQnNV(>(bQOx=eOJV`~ePzCElEWw94OZzn+#`TFH3f+NB9gUgl*?aXGA) z=v2>Y5wPF9)A5}&jG z_`meO!VwXtBT5;_U^J0z~e->WZ z^@*8b_9QQ!eG`idC9C&MZ@63A-(SrLRle-zEu*n5#2(F#Hhp_+v8`oas diff --git a/app/ui/style/olive-light/png/text-underline.16.png b/app/ui/style/olive-light/png/text-underline.16.png index 19c1473a2d9dc5c2557fb01f889bc3fa20fa75d9..6dd6ca8d5924a9d7e59ed3ba0140676dc0c13e75 100644 GIT binary patch delta 288 zcmV+*0pI?@1JeVLyng`!NklZtB5Ew3=9ld{vR1E^9}}sK_6g) z)nG7K_5s8PFq%ykU%_CqX|Wl85Q}ACie+c-wfu|2xGs!f^Q6nU=X_^`j+}FQS(feR zdH!72^{o3V%W^GAlH)W@)23;{+?Q@hL=FK=+O}OsPtlvi%zsy>9~`QGz#jlU06;{N zZ^7u9*>oA;6#!Iqd%@Ta0FZq95g;ND@jR)jYIE)vMX{l(rxAYF?XW-Ny*~$V1mGRO zJxK+yYi3&{uR{nIO924ry&nS@lPm$i%w9;|gb;24*n-aQMd#dsh+LZ4Xi$-ysp{DZ m$wxE0BDp#QU>Rmpl24x-yG*buBmO)900001$YJsA?z+Wwg(_0FFyiQ^*)ZLbzN^S{GuqfLI`IOemm^Q_>6Pz95@DEfjbci zz`mL7h{%=q{$eEn5a-+sn2AUQU}ldZa_zmp0c=TUx+K2+G^N}$O|>59%c&oH0zcJ1 VWyU5DrT_o{00>D%PDHLkV1oZQc{cz6 diff --git a/app/ui/style/olive-light/png/text-underline.32.disabled.png b/app/ui/style/olive-light/png/text-underline.32.disabled.png index 729a56f4bc3b646ffd454653608cf542ae1cc228..98542afd77b5c09e0e8d435db42ebdd8b44278b1 100644 GIT binary patch delta 391 zcmbQix{`H5S3QfUsI~52zQz^?21W@_7sn8f<8LSV`Zqg>xSpRKq?*KZ%=LEbEva7b zzyJRqTFNb&vq<$ATkKcn=Q|nJUdR+TQPbNoyK#Cozwz`&S=IgVZGzzp&%PyY%d`Fd z)|8>VaNDd|vE>(-q8zw?a5r514Q_*mQ$a) zIc!ScR9Glnz@qT^_t9-a#tfZ)%~dre5ecTZgOqNBFo+gcJ_~=eFHS>Y#+93Iybdec zGHje>boTj$yVpDHWEtkFbnd<>|I~k;KS!2?!H0&c@so8VuK%6;>w$Cry?d<-_L*>d5_4Z9Tn^mk_4H|BPD9;d3y!^ylzcC^ zF>K9T91|M9R>`lh2Jfp=Geu^Ms*Ez^h%d9TTjc=x+0WZx?;utYpfcI2FU`3~7H1VzE@#P1Dsuw)x;!rDB+Z`~e zfJLGAna?Xpo-)&C68sMFo6gLA)BnodUV+J!NAlN!N7t5c%+J}ME^hbb*V|Z*x0^i= z7th+a>TS8bq?@%&mI@>D0=Y|?t>%$__0=j2C(quWb@_Jgk8^Xsvifnm6~FJf@?!FK zMk^0PT_ej70|P5#Gb>XAZ6L$IK%sP{AOiz~YKdz^NlIc#s#R`&N@iLmgOP!ek*)z2 O4S^bhYbRSW6#xJ|!j>!m diff --git a/app/ui/style/olive-light/png/text-underline.32.png b/app/ui/style/olive-light/png/text-underline.32.png index 04bec5131b097df7ab9a052627ae0546459eba29..c6f100182ef491d1f52fecc87c03b3e1be4f056d 100644 GIT binary patch delta 445 zcmV;u0Yd)c1cC*Sy#cV1z7~H8$xa$Y1oHp}|61Qbh(%gUd$F;zurZAaL45;lEF$_2 zDYMxPh?szo2nuGMYqN=)knCm=qs5-?&YU^tD~4f!KNbz!FbrcAz*%P<s3(SsNuZ zolYl}QYFt)-m2E0lhyWsh2p|H80RAt45aP3MX(B{!k9U7SRxkiFzj|lK zai&86%=FF8&;UxQx_2g``BB@Md7d^Cxs-C5h_W7-J3qfKfKuv$h}aF%gb*jGR7(1kre*v6c0D3_2JAi^^S^JGf!wCcpGI%AWoCC1M z%vmCm0A}5-7iKmAJZPGB<9F*L^@{*NO1a0(c>u97^|p!Vv|2H(9(CS<+!)K@cIANY nho}gkJw|}GQmQz{zTx@;QYwtY_4DaR00000NkvXXu0mjffsM>J delta 414 zcmV;P0b%}v1>*#ey#bMtz7~I3HnTxcL@*Cv@UQg^EG%q&2eGlYF_kvz8)#z@(RWB^ zmf3)a2?&XxAnRP4By2*mlSR~kd%8Pw=9~|9UTZx%j7s`bN&!-U6d(mi0aAeR1rS1f_AN~$$nEhC$TEKeF!O744#RLd z0l-Y(+)NChwXR2JB3c-=otYQ=&ORpw2!h}VKo^3D))D}0M9w`{tJV52ujvwYpHW1# z?z--hsoe9t6(TA|U~c35u>e}@b0Xq)kR^mT$>nlhOnHu(kC~a;&{_y_8P_rNQQDD| z@({pI_W;owGhYC>3B!M|1^}5%#$o0y0J{LDI$mZz3xeS2TL6yZOxm_x04^ zv)Sx^rBeBb^I4{#R;v{j3Wcj?vuOiZ24EYC-vN}AQU{GjBQ%1V3{iQWHwR#wnTtf^ z0hn!Xy)d&6;NG&V>$qE=s$bntAfT zqLndTe;VnBJ+JZ0O!IpbZF7o4uYd)x02aUkSO5!P0W5$8uzvs+zyeqR3!n`^W~kb3 zg^?E7R_Yi5xKeALk5n2)MgZ(=kCYRD3nl<0xp}eOsSSXN2>?8)mE-~tKczW&&=;|U zTX@I?@Nr5Ra(08VZvL=>>>ikZnC1cia4%U~(A6_(ImRlh8bBU;9J38H=UK$x(EQ?(4dWD7`=Axsf2od=Pj=aQK^mR zrj>3_IX0+HC?2nlV?Oz-$yZutJ><6`KLSwi5c#S92;g_9tNM8O14^jtcMdV_e^0*v ztqP7WViqPflZOI2NHi@nF;p@!IyE^uG&ULDYZc#^PYWk0Y@NEj zmD%ae&;Raq4h!2aNhz*arS@d8$Rvx81syE+TyFOCvNWBm-kA3H-I>j=zpl%9u&{gE z{Y_$f=Y0H=rQ=_H>52Hx4{Dyu9hDSY2Es zv+{B;7h}N&+h9+Ibs~%k$IqUA>u1Oy<;oyn85qTNpr-hkh@)N-`}R+JMGpLye0svN z=k=vu+Zz8!MZ}!(Vr1A+zQ)$$>y?>(o#$V%7wne1A;5IB|3aaZ+fv3e4L`z!)C$UcYW=hx_#fiI9@*~Ue9`n?`zX9?RB{&vy)te zXXytqwXc}fF?)8j=-tn^Ret+2Jh&P6ZIbKjdf&{7XSeGa!-QlE54#rUSP3mR-eh?% z_MMJC6T`90YzM(rtgrevf95Y1Tv6k(Jl1Z?oKBPH{hsl2wjEeekfIf9lX!pT2hr`l z|031=B6$*2qZilSbMIZ1qrp(&DS6&t>F$J0)fZQWy#BS5Ka*w7#LU?n^~C}XagAr1`4Gs1t;?|OJS1>)DT=d I*^apY00_tfT>t<8 diff --git a/app/ui/style/olive-light/png/text-underline.64.png b/app/ui/style/olive-light/png/text-underline.64.png index 372aac28698878d3e9154e26b5a5fd4cff1fd2c6..5e5601379e5dce21448327882fe3fc7a5969a239 100644 GIT binary patch delta 959 zcmbQl@s4A{-ugM7E{-7;bKcJK&yWd~Illk?O`T;oyNrVkx3E0RT@>LOWWd!_@#Vtg z`U_nguKrEz3p>OG9sT84ULSPcqcQKuX{D|Q7wxZ9bzE?ED46WC!HXxhOpJGl*}3c9 zyVsZ+OW*hTG-coU%m?4^eXsq#JOBQlHrWHk$ufQPnG13cm{QK%%I@%K*)CJ}8Ec|1XfaHR zcHL@If1dyLZOPNRs$1{pu;i8>Sg^w3Q=0eLjUHN;&*hk8^E+HxJ8f37V&#&QyH5V8 zH`oHEeY2f5t2%Mtl55)qw{R(NG|6{msrVoGbai&&X)i6F1vhj4WvFHuXQrk;RiCu? zZq}qjDxQ6}UrbBvnaLhda)58w-FN5Bc7^6Oi*va)KbTq1wafL*uOKmo34g>Ia$Huk ze2DdYE12vP;rKgW#j{A{z{yF9@wc*~*#%e*E?4<~XRX>xfs^66TmSD2`JmR2=J>a0 z@x>p9!uBLuZ@m9L-!-sAy}_ERO@{yQhoWhz-rKSn9CqKWbG)>2lH%N}SFfJW{i?n7 z(4>31w^+j~Z%TZ&dQ;!TH%X<^_0r5qhSm&Xx?9>K-ZLim*RNl0u}!}(Z9aeQoSp8=qr1wOPx5-^FU|bLsbjs*>8;gKx0y3% zT3*R9o6qv0Wyx;cR6T|tJFaGxPMbdcfBlM!_a@YGCx|5XC|2%YQq8EMlCHAVLynEX zbN}Q!lS%{~ygvPQTU@xW&-vWrj}@mR;Q|7@SL z#Sb%Bc$D3#u&i*ie{IlU(RY4Q49kX>N3I^+;8|qY_qaSp`s1JQAKX7&|8fdxo?v7E N0#8>zmvv4FO#t!j)*b)= delta 866 zcmaFIF^OZs-g*a57srr_Id5kb~KT`GReh?U)Q62C?F`!`bo`k6yy1TpC)*Sh{XUe8q zXL4$czxtp2efQA+v+rwbX6~t-z$)}x`k2j=z4hz|UdFaQSuHJHFa2D4>8b_y-_M^? zvMlLOB*(+O|6i5x8sGiAglP?P#AL1wdl_D%hEiNrV32J56I)F@oC6!k297ud7;E0cUp?KJyBzgyYQzA1s&1pWJ^?duq`0xV#5zldkKhGBTX(u4fQY z&J8*MG`RTQ?=_9TMs6Zj5!u9o3$*Uz(l z%RiT{*(3b>L4n1cbD#eRGVq7%SWZj5dG=z)m)JLQ3>V&ikDpw!D(&Q{U%!5ZZ~W&L zp6Dqbx|el(#pUY%dXKuwTm$#0zU-Z4YF*D@*H>^~?H4aY!^Mme+tZtz8Tzj4L<0?- zxpU{v__o6jkHzh|_j~zgoBsk|>%S}f^yENcO;X z75#~KZr!}O(=ljL5kvdjVt+5IM%$QW9_zfS3|l_9PyVwuCSuyOX?@dDf9_mz&F`P) zA1h#x6@xW^xpN`4NuNb@@p4xlHTW*en92%#~)SGQlIC= zw)Ss%`d3{1)TWx$;^N}fx({P_Zr0b++o$sK>$ZDrlcFctaVXYwo{)}Ty4&}r3O6@5 z`>wn1xEbb6{}Qp}8CQdT)!uujUcGuXU-#JVv$5I - - - - - - - - - - - - - - image/svg+xml - - - - Martin Ruskov - - - http://commons.wikimedia.org/wiki/Tango_icon - - - - - - - - - - - - - + + + + + + + + + + + + + + + + image/svg+xml + + + + Martin Ruskov + + + http://commons.wikimedia.org/wiki/Tango_icon + + + + + + + + + + + + + - + y="-62.000004" + transform="rotate(90)" + inkscape:path-effect="#path-effect2895" + d="m 43,-62.000004 c 1.656881,0 3,1.343146 3,3 V -5 c 0,1.6568542 -1.343146,3 -3,3 -1.656881,0 -3,-1.3431458 -3,-3 v -54.000004 c 0,-1.656854 1.343146,-3 3,-3 z" + sodipodi:type="rect" /> From 27cb53cc44394395e481b5da2b90f7300b19e7e6 Mon Sep 17 00:00:00 2001 From: Pablo Gil Date: Mon, 25 Apr 2022 21:16:16 +0200 Subject: [PATCH 54/81] Icon track tool (#1901) * initial icon design * Dark SVG and all PNGs --- .../png/track-tool.128.disabled.png | Bin 0 -> 900 bytes .../style/olive-dark/png/track-tool.128.png | Bin 0 -> 895 bytes .../olive-dark/png/track-tool.16.disabled.png | Bin 0 -> 515 bytes app/ui/style/olive-dark/png/track-tool.16.png | Bin 0 -> 360 bytes .../olive-dark/png/track-tool.32.disabled.png | Bin 0 -> 534 bytes app/ui/style/olive-dark/png/track-tool.32.png | Bin 0 -> 375 bytes .../olive-dark/png/track-tool.64.disabled.png | Bin 0 -> 620 bytes app/ui/style/olive-dark/png/track-tool.64.png | Bin 0 -> 525 bytes app/ui/style/olive-dark/svg/track-tool.svg | 208 ++++++++++++++++++ .../png/track-tool.128.disabled.png | Bin 0 -> 969 bytes .../style/olive-light/png/track-tool.128.png | Bin 0 -> 985 bytes .../png/track-tool.16.disabled.png | Bin 0 -> 520 bytes .../style/olive-light/png/track-tool.16.png | Bin 0 -> 374 bytes .../png/track-tool.32.disabled.png | Bin 0 -> 538 bytes .../style/olive-light/png/track-tool.32.png | Bin 0 -> 376 bytes .../png/track-tool.64.disabled.png | Bin 0 -> 643 bytes .../style/olive-light/png/track-tool.64.png | Bin 0 -> 564 bytes app/ui/style/olive-light/svg/track-tool.svg | 208 ++++++++++++++++++ 18 files changed, 416 insertions(+) create mode 100644 app/ui/style/olive-dark/png/track-tool.128.disabled.png create mode 100644 app/ui/style/olive-dark/png/track-tool.128.png create mode 100644 app/ui/style/olive-dark/png/track-tool.16.disabled.png create mode 100644 app/ui/style/olive-dark/png/track-tool.16.png create mode 100644 app/ui/style/olive-dark/png/track-tool.32.disabled.png create mode 100644 app/ui/style/olive-dark/png/track-tool.32.png create mode 100644 app/ui/style/olive-dark/png/track-tool.64.disabled.png create mode 100644 app/ui/style/olive-dark/png/track-tool.64.png create mode 100644 app/ui/style/olive-dark/svg/track-tool.svg create mode 100644 app/ui/style/olive-light/png/track-tool.128.disabled.png create mode 100644 app/ui/style/olive-light/png/track-tool.128.png create mode 100644 app/ui/style/olive-light/png/track-tool.16.disabled.png create mode 100644 app/ui/style/olive-light/png/track-tool.16.png create mode 100644 app/ui/style/olive-light/png/track-tool.32.disabled.png create mode 100644 app/ui/style/olive-light/png/track-tool.32.png create mode 100644 app/ui/style/olive-light/png/track-tool.64.disabled.png create mode 100644 app/ui/style/olive-light/png/track-tool.64.png create mode 100644 app/ui/style/olive-light/svg/track-tool.svg diff --git a/app/ui/style/olive-dark/png/track-tool.128.disabled.png b/app/ui/style/olive-dark/png/track-tool.128.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..b1e9aad3a709566f6b59ef63d8239691fbd35f05 GIT binary patch literal 900 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H0wgodS2_SGmUKs7M+SzC{oH>NS%G|oWRDy)vIg-4nJ$hLzpWB=2SsX#&Y5>H=O_Gc`jqPo_iPK`_q42+*WT^vIyZoQpzFpJ4ifVKL& zKu37<{s;f-`}S{mQOex7!?GoVgR%4OlALX?mP~)>`*wrE=QkXJk;aF=>z|g`edCy# zo7FYmbB4z?mpzQpN~r9r&3Uo+Z=)sO;pno7r#HtPOS|2F<4B$7{Hw2-cBkF`AF^S! z@CT>&&-AvHGR=Fr_m1nYD}DQGUb#H9PFSkZz~``-!HS`m=>>xc*8=7XSOtnj0}4(v zeTY*}SoXb0e0?dC-KH3iRe8_l^J}V)ThwRFF7NrwGz(71{XJW+TzdXTD^|-foHtqg!k*+g z;uluO{5-l8!~pYvgxH0>$$R#_F0?+T1Oh@=UamT1Py!az_hE-3Pp`_0H8B_Yhb;sYn={mvufpKn0 z+79MLS9F0XLZrksqQtSZBqP6w!8fs}Br{JTsI)jczij*IGc$qmsv!B4#FA92rSNz6;nkIzib&x@O{F#)KZ!PC{xWt~$(6971{Rxbbm literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/png/track-tool.128.png b/app/ui/style/olive-dark/png/track-tool.128.png new file mode 100644 index 0000000000000000000000000000000000000000..f3901941e76a0649e0b13b216bd2f21d6437088c GIT binary patch literal 895 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSoCO|{#S9Fx?I6t9|MX)jP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IXpdt~FBFEB_jQk=7-^8Ml%shpl z(&Fs=vhAnO%mm6CLkuY`N={|SC@Cqh($`PU&&|!xE7mK|%+AbBP036I8K)28>4zld zrRT?ICgxt zCX6!oLaqA+WiIQTJf0f)ugC7mw(E!G)BeA^_4-Xbzx+R;!}agfPF{ager@o`yKc0^sRaSZi}ytD?^^J{M~0}A3reMQG2)Sj@rD3D-*VN{?^#una*(_ z_fg%f+((j31+#a|Pn%uQ#!z!*&HQKCw$2PvCm0^^Fq|Wj$ZG?czWk=zJVgee;hEde z4DTOqAekp{%C16|i>9;Ynv=}axs{`);>=YH9{ouZ|E*d2kiRuchsa0( zN7fE(X5HR<7U%#`hSc5L@0?qj$<&bZJrC%BJKw5)tjztsYp%BEq>>NfL3#E6^Zq;J Q0@FQ%r>mdKI;Vst012>m?*IS* literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/png/track-tool.16.disabled.png b/app/ui/style/olive-dark/png/track-tool.16.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..bd3c9bf7e7d8ce17d71f7394d7d733b5627c86de GIT binary patch literal 515 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+uenMVO6iP5s=4O z;1OBOz`(o#gc;S3@UI05vX^-Jy0SlG5f#<7{O$8V4=7aR>EaloalUtgBUgg~4~zOz z4%=??`w#xxKjuENxuT0*bz$y-cPlw}zcgATZ0xi;`rP-4kr6MK*0hN%VZ5(qVsgW* zSmEH*?!d?}bFU8<=jCW5?op|G(zaq6-;}9rTNTUrOtYNd$Yy!SZW1eH-{*RFYaPSl zYZ7MDCd*m?Z5JtVjVN&}Ey>6)V(?8YD#^@K2r4bk&M(`3`pitAyeddOC9x#cD!C{X zNHG{07#ZmrnCKcAh8P)InV4Fcnrj;vSQ!|E?KeAwq9HdwB{QuOw+8h`eRV($k{}y` z^V3So6N^$A%FE03GV`*FlM@S4_413-XTP(N0xB|wC@L*VPG!g_DJihh*H6yR&CSm% u)+^7<&dg0s$xH+(*9Y~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3AkesC}BJi(`mI@6t(zTug={F85vbHgDCbkn&Gbop(BHPuGSU6@4Fc zw?vpFKG)}4BKgn9>0hwRn?~V)4^`f~{CQoENbXQM#1ML^FiRw1GSB7Nx(=~{7tZ80 zN^kfryZm|A>7q+_Iy%@5R5pq3_!c&u^YC|P{+W06SA`p>Z5FNg`mdKI;Vst03xi0Y5)KL literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-dark/png/track-tool.32.disabled.png b/app/ui/style/olive-dark/png/track-tool.32.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..4600502574fbc0f0d42ce5df494505a35a4266f1 GIT binary patch literal 534 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-GzZ+Rj;xUkjGiz z5n0T@z@7-gj8hNq*aj42FY)wsWq-yZDynOho>zjC2i5bd3x{j0~+zOs!1KwG9lc3=G2dn;k;Y zkei>9nO2EggZiVsI-mwgkPX54X(i=}MX3zs<>h*rdD+Fui3O>8`9~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3AkesC}uYi(^Pc>)UA?c@G=#u=KM{N;C>9J0T{iIPdiCh}JtUYkzKV zxnc05cNTl;)rSwCNI0zh%DkuX_<`H3GtRzQ&7jk4pC5Vl&DQW4|MHJKXYgNdP}%xI z#erGF@q4&v0$+p1f&33=&-|DX-f?XUlfbIXqs$9u?d$0fO3+_8D~uuN@^`=UTGPVT za}|VafA{L^rQKb3udZgAGmYg%?9q3xLK*vBe3P@f%bNS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VW5Sk?NMQuI$fPL`8M2l3X`V0Sevrba4#Hxc7F3HD9xV0PFFN zI}IWe7v1?Ex4`5f%R&WKSF5tr&r53VU0MD0M(g*?hxauZvX4(~ynJx4c-pZ|X)Jj$ zF5-Kp7w%ucoFE{}|9nM)u*I*B_gm&#-@NFllfcLwksutA@^`*vW2Blu**nG=4n}?Q zKATocem0)6!eQ==WX8gzzVt>5!{bbkj{H%)e1S=9Lj&tK0rh7Q4s&Mw-f*V#ri7vH z?nhVjEYt($*Bp*5aCyDX;tuO`mRJ9GK9m2%y=)2G`bRqQj+n<6Ey5haeLB^mie48Dm)C7F2&L8Zmn`DNQrpP31iR|UzZB$lLF zB^RXvDF!10BO_e{6I~<25FanMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3Al}fq~K5)5S5QV$R!H8-1M}1zgf~yZIJ~uWC@!Xy{PlFi;G=5gWk0 zjHSt+>5}WVqjUa5%$WX%+i&$x{oPkR9@YonZ%>WXQ@QKCYhp>Os#WjaC3BZ7U-hei zt(Qqo%<_j(ivJGh>8;%G4nPXmlKqKKfAM=e}%-0TAe*U^QwEy{suuI0WDlGy|>92lF zJzR2M?YzK+s@XQq4zF`R^&Ah_K7V@KnM&SEj2Wng9%zwVyZxjw%a(g;>-q0mP0X3> z%%Nz~`ijf{^{-tQViR}2e(hA=`gYxV?z^JL%uiu6p+Ifh?y~FGTi + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Martin Ruskov + + + http://commons.wikimedia.org/wiki/Tango_icon + + + + + + + + + + + + + + + + + + diff --git a/app/ui/style/olive-light/png/track-tool.128.disabled.png b/app/ui/style/olive-light/png/track-tool.128.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..b96770b4cf68e6c5e3521b386c390b5b3b1c55c5 GIT binary patch literal 969 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H0wgodS2_SGmUKs7M+SzC{oH>NS%G|oWRDy)vIg-4nJ$hLzpWB=2SsX#&Y5>H=O_Gc`jqPiA6-WPTNHCcGNIEGZ*dOK%t7E_{t>+{d* z0=hg>IXag(6yN{X;fzoX%VX{>m}H2B;9 z8QWfM?Dblod2u6O%RQ5+wO^i}pB=!+Z~pXR)t3Tym-Vx5-roLw%D4CZnX4;Lne3Zc zdOBfKjLAOdZQ|9oerEe-n(obdW1C#Q{r=_0bCx8Cs4cj2>F^wuwv+CxxkpA(!l4S3{*1fuJ^@;^*Ri% z4H$i0&lhn$)%Izqy>NKW5hEXm`nXH6%uA)t?M=_w&#O0Ucc^5J@sQEepkXha_oPQN#*eyKr(j!m7M)R!&oQ5 z47<;^8N|F|CPUG?_BP0Sowqxl+^vL zZamClFgN~x+yetfpXbx_r5+=K25u?#&~tt-dW<{!LtG+QbHMZKd&Mn$7%u+oy}|v4 z^^Ug8W+3-~rS<+-Z@3ioTIGZ8&f9kDCR-pdS&5XmMwB>~mSp4?G597Hm1O2A1eF$N z=a+3ieP$+5UKJ#tl30>zm0Xkxq!^40jEr;*OmvM5LyQcqOiZmz&9n^+tPBi3oUkoJ z(U6;;l9^VCTSMd2)>A+Yk{}y`^V3So6N^$A%FE03GV`*FlM@S4_413-XTP(N0xB|w zC@L*VPG!g_DJihh*H6yR&CSm%)+^7<&dg0s$xH+(*9Y4zld zrRT?ICgEaktG3V`_jaeaqBCQYiiU_7VBx7DF~>n}Wh1$+IJ5B$|GPFd=-c(z zU%TH}S24OAar~_9Gc{;l>irAc4wq~m3%E~`O9!$SOffoXapa=!kErYSm=anT&ImSG zG8wotJmWYZ!#G2kp_nD152xVHTh0q#_c;5^O_vX-*7y>AOtDX;_WZtu-^2>;{Nak9 zznT99tHE5qeidD#zCxu;!`Wx;mfR1lF3it=zi>(U(kXwhv0Lu=!C1XFMz22Ye$no4 zk8a+)`SaYlbH~@cuJf!?cv0)ULhE?4U=qmmz_331q>_d&s6K+)&F~x!re;9K-D+xjebT%m3#m zu-ra!dt2u09lJ#DNZmeiy+1bz=5T?H57##)-+s6ERoS}PSq#bd-`(9Z_w_RIhSO#5 zF93z^2|McD-s^mOXI@r+%gvnc*R~bk{lZjm>)qPi=eu6AKG<^i^_QJ|p7mN1JBf)X zq$pi&EAnc~-P^a%l?GR9TuIx#eH+l;Z(=|bL1LG=52Wrc&#=Dk`*pta@qKr9X9Ag} l+7&&w_vSojVqp05oj)j7AY*;bN>`9qJYD@<);T3K0RYKLvBv-a literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/track-tool.16.disabled.png b/app/ui/style/olive-light/png/track-tool.16.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..7cbeecef8f572b859d36b9e216d3508120cf6409 GIT binary patch literal 520 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+uenMVO6iP5s=4O z;1OBOz`(o#gc;S3@UI05vX^-Jy0SlG5f#-n%l`a#KTxRL)5S4F<9zRgy<80jJT1iw zIp!WcyU_o^@BKZ^n@hx&7Yj(=5_#o&a^n2Q&a(`JFLivMcwZ~wp_ZT5$#~Y6Ct_Hu znV+%EWBQ>z{ZHsk-`OtzIjS{czuab7+!uGlL0xRc;|(hUr5_zy=$6}YcBMk)2i`3H zeXehV>_r_^6KZoy?g5=3QsNp>;#gXekzd5%n^;tmnWqp`TAZC08-nxGO3D+9QW?t2%k?tzvWt@w3sUv+i_&MmvylQSGKMHBElN&h$S5f(u+rC0 z&d<%w&nwm|&&~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3AkesC|j2i(`mI@6t(*Tuz1p&XYI(=hI{VXtmC!vqEpX{CcI102}vC z?${Uqml`KbyR0I6%f0;mBpVMSmPudMY&~~>v(qPymm;l<2hK<}%x9XsH*?waUAB`qHDK2k*ieej9k0O?rOGlKb$1-;Szp5AE;2qP-)%NoDp- z+kdePG7IlGm#k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-GzZ+Rj;xUkjGiz z5n0T@z@7-gj8hNq*aj42FY)wsWq-yZDynNP^X052P^i<>#WBR=_|qvHc^eFPSnlUc zY-;-3zG%S-34ce|97{vT6GHJ(=S~Ls@%y+nfCZs(^iZoU0uZO)br zj0I2McFnzbgq`91o^?~}wCfdLbGjI5X`EcnNW}ZS& zX>oRb+4j?CW&-6^LGmeyC8<`)MX5lF!N|bKNY}tb*T^u$$k58f)XLOE+rYrez#u(b zCmuyZZhlH;S|x4`Vq8-m12ss3YzWRzD=AMbN@XZ7FW1Y=%Pvk%EJ)SMFG`>N&PEET z$QYuiv?w{1A)}~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3AkesC}8Ii(^Pc>)UA?c^eFPSnhjla1Tpn-eAggR=ZUzY%_J*%_qf<2x&+QjCTYJDDW&uaa z;=kLy8afs=x}-f@{n>B1y$Wx{@hv~vt{JX9$#{9jKVN~J4!oCVh_Tu%TfTg$=<2BT zF5cQ@YBly3TD#A&%lzgctX07)2y`)nr>mdKI;Vst E0Ha%pGynhq literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/track-tool.64.disabled.png b/app/ui/style/olive-light/png/track-tool.64.disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..425f6805356d084a775a2b717e1efe382b740736 GIT binary patch literal 643 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;mUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VW5Sk?NMQuI$fPL`8MYWxkx%1PXofba4#Hxc7F3A=e=X5tnrH z2v+Id)&=Sx{vH1y!j>D?ds#V-AruQDa`Od;Jvj;gvhGB^bP`Cxvy&9{eXss4ZFyE1qC%#;{8Jsg-8 zJG|faKU4U{o^$*q#VifGjvjp|_NyRnYutXdBmV_%G?XVTi@afN_<8#QOT%Ku=STji zu3yNaqQJ=M^Kj0LoejlF%b2{*I8{u(EINBvf}3A>&Z{QLnX!iNn{t_6o;0^M{(m4_ z187l#9Mj90{bdKu8lOBly*pvDL(-?vO}dA;e@(D{*?nf_KcMeLN?apK97{_w@{1UJ z6N^eR^Av(gi?j2~wx2#T6DY3=l21u2NwrEYN(E93Mg~Skx&|h?Mus6qhE^t~R;H%f z1_o9J1_u??U!!Qq%}>cptHiBgL+$)Bu8ABA67A2=LWR#Q?Sn2C0=jZ0;=N0RfXJ%*Srlw>jf|Tonc={oUdFlD_ YnaTNiaq~4M0JSrCy85}Sb4q9e0G4#)`Tzg` literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/png/track-tool.64.png b/app/ui/style/olive-light/png/track-tool.64.png new file mode 100644 index 0000000000000000000000000000000000000000..672e0fab25bff475a46488de1ac8fb1e7ab962db GIT binary patch literal 564 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt&=TMUJH<8TmyFzKKO8nRyC9 zrN!C#W!q1mnF*9Ph8R*>l$^?tQBqQ1rLUiypPQSXSFBf_nVp%Nnv$6aGEN`F(+^3^ zOV5wbOwP}Xo3Al}fq^l})5S5QV$R!HhFOOlL|oEy=cHM8?hv$WJSC%Yu>1of&rbIb zvUhGT_Yf}O6AaB_VQPGODs8pK6Md^I;Y#$y)I*jV^~u; zt|cxoFl?;n?aiOfY|{z#tpn1IGHmt^yd&eKwV?^<_0$JmKOG3cmo{U_U%+fSbElvnfI>)Thz zt#ViGIETVZv&icT$Yw*Vb71LRa{Gw{BT&)o^^RLl0*$!Tx>dAq+t;m!ZgIKa{uQ+_ zuQfTRcI}4khrYOBH{wIumfg?Rt`~h3W^wG=b*^mfHDzIUIK_iLX4ZQ~Ki>$9ECx?k KKbLh*2~7Z$0^<$< literal 0 HcmV?d00001 diff --git a/app/ui/style/olive-light/svg/track-tool.svg b/app/ui/style/olive-light/svg/track-tool.svg new file mode 100644 index 000000000..68fb8ec52 --- /dev/null +++ b/app/ui/style/olive-light/svg/track-tool.svg @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Martin Ruskov + + + http://commons.wikimedia.org/wiki/Tango_icon + + + + + + + + + + + + + + + + + + From fe7444e762b6635c253a1f4d16f03c2ce78b5b8c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 25 Apr 2022 12:40:55 -0700 Subject: [PATCH 55/81] implemented underline, strikethrough, trackselect, and smallcaps icons --- app/ui/icons/icons.cpp | 6 ++++++ app/ui/icons/icons.h | 3 +++ app/widget/toolbar/toolbar.cpp | 2 +- app/widget/viewer/viewertexteditor.cpp | 5 +++-- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/ui/icons/icons.cpp b/app/ui/icons/icons.cpp index 5c16000a5..cc79d834a 100644 --- a/app/ui/icons/icons.cpp +++ b/app/ui/icons/icons.cpp @@ -55,6 +55,7 @@ QIcon icon::ToolSlip; QIcon icon::ToolSlide; QIcon icon::ToolHand; QIcon icon::ToolTransition; +QIcon icon::ToolTrackSelect; QIcon icon::Folder; QIcon icon::Sequence; QIcon icon::Video; @@ -68,6 +69,8 @@ QIcon icon::TriRight; QIcon icon::TextBold; QIcon icon::TextItalic; QIcon icon::TextUnderline; +QIcon icon::TextStrikethrough; +QIcon icon::TextSmallCaps; QIcon icon::TextAlignLeft; QIcon icon::TextAlignRight; QIcon icon::TextAlignCenter; @@ -116,6 +119,7 @@ void icon::LoadAll(const QString& theme) ToolSlide = Create(theme, "slide"); ToolHand = Create(theme, "hand"); ToolTransition = Create(theme, "transition-tool"); + ToolTrackSelect = Create(theme, "track-tool"); Folder = Create(theme, "folder"); Sequence = Create(theme, "sequence"); @@ -133,6 +137,8 @@ void icon::LoadAll(const QString& theme) TextBold = Create(theme, "text-bold"); TextItalic = Create(theme, "text-italic"); TextUnderline = Create(theme, "text-underline"); + TextStrikethrough = Create(theme, "text-strikethrough"); + TextSmallCaps = Create(theme, "text-small-caps"); TextAlignLeft = Create(theme, "align-left"); TextAlignRight = Create(theme, "align-right"); TextAlignCenter = Create(theme, "align-center"); diff --git a/app/ui/icons/icons.h b/app/ui/icons/icons.h index c3e330685..987ab2b3b 100644 --- a/app/ui/icons/icons.h +++ b/app/ui/icons/icons.h @@ -57,6 +57,7 @@ extern QIcon ToolSlip; extern QIcon ToolSlide; extern QIcon ToolHand; extern QIcon ToolTransition; +extern QIcon ToolTrackSelect; // Project Icons extern QIcon Folder; @@ -78,6 +79,8 @@ extern QIcon TriRight; extern QIcon TextBold; extern QIcon TextItalic; extern QIcon TextUnderline; +extern QIcon TextStrikethrough; +extern QIcon TextSmallCaps; extern QIcon TextAlignLeft; extern QIcon TextAlignRight; extern QIcon TextAlignCenter; diff --git a/app/widget/toolbar/toolbar.cpp b/app/widget/toolbar/toolbar.cpp index dd30d4372..ddce86c7a 100644 --- a/app/widget/toolbar/toolbar.cpp +++ b/app/widget/toolbar/toolbar.cpp @@ -126,7 +126,7 @@ void Toolbar::Retranslate() void Toolbar::UpdateIcons() { btn_pointer_tool_->setIcon(icon::ToolPointer); - btn_trackselect_tool_->setIcon(icon::TriRight); // FIXME: Procure real icon + btn_trackselect_tool_->setIcon(icon::ToolTrackSelect); btn_edit_tool_->setIcon(icon::ToolEdit); btn_ripple_tool_->setIcon(icon::ToolRipple); btn_rolling_tool_->setIcon(icon::ToolRolling); diff --git a/app/widget/viewer/viewertexteditor.cpp b/app/widget/viewer/viewertexteditor.cpp index a3480a67c..cdcc769ee 100644 --- a/app/widget/viewer/viewertexteditor.cpp +++ b/app/widget/viewer/viewertexteditor.cpp @@ -359,7 +359,7 @@ ViewerTextEditorToolBar::ViewerTextEditorToolBar(QWidget *parent) : strikethrough_btn_ = new QPushButton(); connect(strikethrough_btn_, &QPushButton::clicked, this, &ViewerTextEditorToolBar::StrikethroughChanged); strikethrough_btn_->setCheckable(true); - strikethrough_btn_->setText(tr("S")); // FIXME: Source icon + strikethrough_btn_->setIcon(icon::TextStrikethrough); basic_layout->addWidget(strikethrough_btn_); basic_layout->addWidget(QtUtils::CreateVerticalLine()); @@ -441,7 +441,8 @@ ViewerTextEditorToolBar::ViewerTextEditorToolBar(QWidget *parent) : connect(line_height_slider_, &FloatSlider::ValueChanged, this, &ViewerTextEditorToolBar::LineHeightChanged); advanced_layout->addWidget(line_height_slider_); - small_caps_btn_ = new QPushButton(tr("Small Caps")); // FIXME: Procure icon + small_caps_btn_ = new QPushButton(); + small_caps_btn_->setIcon(icon::TextSmallCaps); small_caps_btn_->setCheckable(true); connect(small_caps_btn_, &QPushButton::clicked, this, &ViewerTextEditorToolBar::SmallCapsChanged); advanced_layout->addWidget(small_caps_btn_); From fb53931f6816bfb1a4ee536fcf6dcf0c2f5cb1a4 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 25 Apr 2022 13:16:27 -0700 Subject: [PATCH 56/81] implement "type" for xml node data --- app/node/nodecopypaste.cpp | 4 ++-- app/node/nodecopypaste.h | 7 ++++++- app/node/project/serializer/serializer.cpp | 18 +++++++++--------- app/node/project/serializer/serializer.h | 8 ++++---- app/task/project/load/load.cpp | 2 +- app/task/project/save/save.cpp | 2 +- app/widget/nodeview/nodeview.cpp | 1 + app/widget/timelinewidget/timelinewidget.cpp | 1 + 8 files changed, 25 insertions(+), 18 deletions(-) diff --git a/app/node/nodecopypaste.cpp b/app/node/nodecopypaste.cpp index 9b4aec472..7741f9940 100644 --- a/app/node/nodecopypaste.cpp +++ b/app/node/nodecopypaste.cpp @@ -51,7 +51,7 @@ void NodeCopyPasteService::CopyNodesToClipboard(QVector nodes, void *use CopyNodesToClipboardCallback(nodes, &data, userdata); - ProjectSerializer::Save(&writer, data); + ProjectSerializer::Save(&writer, data, type_); Core::CopyStringToClipboard(copy_str); } @@ -66,7 +66,7 @@ void NodeCopyPasteService::PasteNodesFromClipboard(void *userdata) QXmlStreamReader reader(clipboard); Project temp; - ProjectSerializer::Result res = ProjectSerializer::Load(&temp, &reader); + ProjectSerializer::Result res = ProjectSerializer::Load(&temp, &reader, type_); if (res.code() != ProjectSerializer::kSuccess) { return; diff --git a/app/node/nodecopypaste.h b/app/node/nodecopypaste.h index 28c53bed9..eaf3c2b86 100644 --- a/app/node/nodecopypaste.h +++ b/app/node/nodecopypaste.h @@ -34,7 +34,9 @@ namespace olive { class NodeCopyPasteService { public: - NodeCopyPasteService() = default; + NodeCopyPasteService(const QString &type) : + type_(type) + {} protected: void CopyNodesToClipboard(QVector nodes, void* userdata = nullptr); @@ -45,6 +47,9 @@ protected: virtual void PasteNodesToClipboardCallback(const QVector &nodes, const ProjectSerializer::LoadData &load_data, void *userdata){} +private: + QString type_; + }; } diff --git a/app/node/project/serializer/serializer.cpp b/app/node/project/serializer/serializer.cpp index 91164db0e..ca2bee88e 100644 --- a/app/node/project/serializer/serializer.cpp +++ b/app/node/project/serializer/serializer.cpp @@ -55,14 +55,14 @@ void ProjectSerializer::Destroy() instances_.clear(); } -ProjectSerializer::Result ProjectSerializer::Load(Project *project, const QString &filename) +ProjectSerializer::Result ProjectSerializer::Load(Project *project, const QString &filename, const QString &type) { QFile project_file(filename); if (project_file.open(QFile::ReadOnly | QFile::Text)) { QXmlStreamReader reader(&project_file); - Result inner_result = Load(project, &reader); + Result inner_result = Load(project, &reader, type); project_file.close(); @@ -82,13 +82,13 @@ ProjectSerializer::Result ProjectSerializer::Load(Project *project, const QStrin } } -ProjectSerializer::Result ProjectSerializer::Load(Project *project, QXmlStreamReader *reader) +ProjectSerializer::Result ProjectSerializer::Load(Project *project, QXmlStreamReader *reader, const QString &type) { // Determine project version uint version = 0; while (!version && XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("olive") || reader->name() == QStringLiteral("project")) { + if (reader->name() == QStringLiteral("olive") || reader->name() == type) { while(!version && XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("version")) { version = reader->readElementText().toUInt(); @@ -135,7 +135,7 @@ ProjectSerializer::Result ProjectSerializer::Load(Project *project, QXmlStreamRe } } -ProjectSerializer::Result ProjectSerializer::Save(const SaveData &data) +ProjectSerializer::Result ProjectSerializer::Save(const SaveData &data, const QString &type) { QString temp_save = FileFunctions::GetSafeTemporaryFilename(data.GetFilename()); @@ -144,7 +144,7 @@ ProjectSerializer::Result ProjectSerializer::Save(const SaveData &data) if (project_file.open(QFile::WriteOnly | QFile::Text)) { QXmlStreamWriter writer(&project_file); - Result inner_result = Save(&writer, data); + Result inner_result = Save(&writer, data, type); project_file.close(); @@ -167,7 +167,7 @@ ProjectSerializer::Result ProjectSerializer::Save(const SaveData &data) } } -ProjectSerializer::Result ProjectSerializer::Save(QXmlStreamWriter *writer, const SaveData &data) +ProjectSerializer::Result ProjectSerializer::Save(QXmlStreamWriter *writer, const SaveData &data, const QString &type) { writer->setAutoFormatting(true); @@ -187,11 +187,11 @@ ProjectSerializer::Result ProjectSerializer::Save(QXmlStreamWriter *writer, cons writer->writeTextElement("url", data.GetFilename()); } - writer->writeStartElement(QStringLiteral("project")); + writer->writeStartElement(type); serializer->Save(writer, data, nullptr); - writer->writeEndElement(); // project + writer->writeEndElement(); // [type] writer->writeEndElement(); // olive diff --git a/app/node/project/serializer/serializer.h b/app/node/project/serializer/serializer.h index 0417f440c..58f9505cc 100644 --- a/app/node/project/serializer/serializer.h +++ b/app/node/project/serializer/serializer.h @@ -137,11 +137,11 @@ public: static void Destroy(); - static Result Load(Project *project, const QString &filename); - static Result Load(Project *project, QXmlStreamReader *read_device); + static Result Load(Project *project, const QString &filename, const QString &type); + static Result Load(Project *project, QXmlStreamReader *read_device, const QString &type); - static Result Save(const SaveData &data); - static Result Save(QXmlStreamWriter *write_device, const SaveData &data); + static Result Save(const SaveData &data, const QString &type); + static Result Save(QXmlStreamWriter *write_device, const SaveData &data, const QString &type); protected: virtual LoadData Load(Project *project, QXmlStreamReader *reader, void *reserved) const = 0; diff --git a/app/task/project/load/load.cpp b/app/task/project/load/load.cpp index 606f53e37..f1878dec9 100644 --- a/app/task/project/load/load.cpp +++ b/app/task/project/load/load.cpp @@ -37,7 +37,7 @@ bool ProjectLoadTask::Run() project_->set_filename(GetFilename()); - ProjectSerializer::Result result = ProjectSerializer::Load(project_, GetFilename()); + ProjectSerializer::Result result = ProjectSerializer::Load(project_, GetFilename(), QStringLiteral("project")); switch (result.code()) { case ProjectSerializer::kSuccess: diff --git a/app/task/project/save/save.cpp b/app/task/project/save/save.cpp index 1f5742bcf..3d4c5f6e5 100644 --- a/app/task/project/save/save.cpp +++ b/app/task/project/save/save.cpp @@ -42,7 +42,7 @@ bool ProjectSaveTask::Run() ProjectSerializer::SaveData data(project_, using_filename); - ProjectSerializer::Result result = ProjectSerializer::Save(data); + ProjectSerializer::Result result = ProjectSerializer::Save(data, QStringLiteral("project")); bool success = false; diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 9105d8cb1..2f4041123 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -45,6 +45,7 @@ const double NodeView::kMinimumScale = 0.1; NodeView::NodeView(QWidget *parent) : HandMovableView(parent), + NodeCopyPasteService(QStringLiteral("nodes")), drop_edge_(nullptr), create_edge_(nullptr), create_edge_output_item_(nullptr), diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 547f11cc9..42da36118 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -60,6 +60,7 @@ namespace olive { TimelineWidget::TimelineWidget(QWidget *parent) : super(true, true, parent), + NodeCopyPasteService(QStringLiteral("timeline")), rubberband_(QRubberBand::Rectangle, this), active_tool_(nullptr), use_audio_time_units_(false), From 4536ed87a58890773f9aa5667c3356159f272ac6 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 25 Apr 2022 14:00:21 -0700 Subject: [PATCH 57/81] nodeparamview: fixed crash caused by closing contexts --- app/widget/nodeparamview/nodeparamview.cpp | 35 ++++++++-------------- app/widget/nodeparamview/nodeparamview.h | 2 -- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 2aba90ed7..c299b882b 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -283,6 +283,16 @@ void NodeParamView::UpdateContexts() void NodeParamView::ItemAboutToBeRemoved(NodeParamViewItem *item) { + if (keyframe_view_) { + for (auto it=item->GetKeyframeConnections().begin(); it!=item->GetKeyframeConnections().end(); it++) { + for (auto jt=it->begin(); jt!=it->end(); jt++) { + for (auto kt=jt->begin(); kt!=jt->end(); kt++) { + keyframe_view_->RemoveKeyframesOfTrack(*kt); + } + } + } + } + if (focused_node_ == item) { focused_node_ = nullptr; emit FocusedNodeChanged(nullptr); @@ -455,27 +465,6 @@ void NodeParamView::AddNode(Node *n, Node *ctx, NodeParamViewContext *context) } } -void NodeParamView::RemoveNode(Node *n, Node *ctx) -{ - foreach (NodeParamViewContext *ctx_item, context_items_) { - NodeParamViewItem *item = ctx_item->GetItem(n, ctx); - - if (item) { - if (keyframe_view_) { - for (auto it=item->GetKeyframeConnections().begin(); it!=item->GetKeyframeConnections().end(); it++) { - for (auto jt=it->begin(); jt!=it->end(); jt++) { - for (auto kt=jt->begin(); kt!=jt->end(); kt++) { - keyframe_view_->RemoveKeyframesOfTrack(*kt); - } - } - } - } - } - - ctx_item->RemoveNode(n, ctx); - } -} - int GetDistanceBetweenNodes(Node *start, Node *end) { if (start == end) { @@ -675,7 +664,9 @@ void NodeParamView::NodeRemovedFromContext(Node *n) { Node *ctx = static_cast(sender()); - RemoveNode(n, ctx); + foreach (NodeParamViewContext *ctx_item, context_items_) { + ctx_item->RemoveNode(n, ctx); + } if (keyframe_view_) { QueueKeyframePositionUpdate(); diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 9a9207cb7..8061d5b1a 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -99,8 +99,6 @@ private: void AddNode(Node* n, Node *ctx, NodeParamViewContext *context); - void RemoveNode(Node *n, Node *ctx); - void SortItemsInContext(NodeParamViewContext *context); NodeParamViewContext *GetContextItemFromContext(Node *context); From abb84aa2ca061763bf15f822f918981f888b5c29 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 25 Apr 2022 14:26:07 -0700 Subject: [PATCH 58/81] rational: assert when limits are used for calculations These will necessarily result in overflows or underflows so I've attempted to weed this behavior out while adding asserts in case there are still usages I haven't found --- app/common/rational.cpp | 8 ++++++++ app/common/rational.h | 2 +- app/common/timerange.cpp | 7 ++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/common/rational.cpp b/app/common/rational.cpp index 34d627572..e7020f92b 100644 --- a/app/common/rational.cpp +++ b/app/common/rational.cpp @@ -186,6 +186,8 @@ const rational& rational::operator=(const rational &rhs) const rational& rational::operator+=(const rational &rhs) { + Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX); + if (!isNaN()) { if (rhs.isNaN()) { // Set to NaN @@ -209,6 +211,8 @@ const rational& rational::operator+=(const rational &rhs) const rational& rational::operator-=(const rational &rhs) { + Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX); + if (!isNaN()) { if (rhs.isNaN()) { // Set to NaN @@ -232,6 +236,8 @@ const rational& rational::operator-=(const rational &rhs) const rational& rational::operator/=(const rational &rhs) { + Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX); + if (!isNaN()) { if (rhs.isNaN()) { // Set to NaN @@ -250,6 +256,8 @@ const rational& rational::operator/=(const rational &rhs) const rational& rational::operator*=(const rational &rhs) { + Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX); + if (!isNaN()) { if (rhs.isNaN()) { denom_ = 0; diff --git a/app/common/rational.h b/app/common/rational.h index 074f1b864..26f625fc6 100644 --- a/app/common/rational.h +++ b/app/common/rational.h @@ -1,7 +1,7 @@ //Copyright 2015 Adam Quintero //This program is distributed under the terms of the GNU General Public License. -// Adapted by MattKC for the Olive Video Editor (2019) +// Adapted by MattKC for the Olive Video Editor (2019-2022) #ifndef RATIONAL_H #define RATIONAL_H diff --git a/app/common/timerange.cpp b/app/common/timerange.cpp index c352e476d..c4337a7aa 100644 --- a/app/common/timerange.cpp +++ b/app/common/timerange.cpp @@ -44,6 +44,7 @@ const rational &TimeRange::out() const const rational &TimeRange::length() const { + Q_ASSERT(!length_.isNaN()); return length_; } @@ -173,7 +174,11 @@ void TimeRange::normalize() } // Calculate length - length_ = out_ - in_; + if (out_ == RATIONAL_MIN || out_ == RATIONAL_MAX || in_ == RATIONAL_MIN || in_ == RATIONAL_MAX) { + length_ = rational::NaN; + } else { + length_ = out_ - in_; + } } void TimeRangeList::insert(const TimeRangeList &list_to_add) From 45b18f6df2335c20d7503c407587730147b6e0c6 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Apr 2022 09:04:34 -0700 Subject: [PATCH 59/81] fixed deserializer issue introduced by copy/paste fix --- app/node/project/serializer/serializer.cpp | 89 ++++--- app/node/project/serializer/serializer.h | 2 + .../project/serializer/serializer210528.cpp | 188 +++++++------- .../project/serializer/serializer210907.cpp | 188 +++++++------- .../project/serializer/serializer211228.cpp | 236 +++++++++--------- .../project/serializer/serializer220403.cpp | 236 +++++++++--------- 6 files changed, 464 insertions(+), 475 deletions(-) diff --git a/app/node/project/serializer/serializer.cpp b/app/node/project/serializer/serializer.cpp index ca2bee88e..5f3a3507e 100644 --- a/app/node/project/serializer/serializer.cpp +++ b/app/node/project/serializer/serializer.cpp @@ -86,53 +86,36 @@ ProjectSerializer::Result ProjectSerializer::Load(Project *project, QXmlStreamRe { // Determine project version uint version = 0; + Result res = kUnknownVersion; - while (!version && XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("olive") || reader->name() == type) { - while(!version && XMLReadNextStartElement(reader)) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("olive") + || reader->name() == QStringLiteral("project")) { // 0.1 projects only + + while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("version")) { version = reader->readElementText().toUInt(); + } else if (reader->name() == QStringLiteral("url")) { + project->SetSavedURL(reader->readElementText()); + + // HACK for 0.1 projects + if (version == 190219) { + res = LoadWithSerializerVersion(version, project, reader); + } + } else if (reader->name() == type) { + // Found our data + res = LoadWithSerializerVersion(version, project, reader); } else { reader->skipCurrentElement(); } } + } else { reader->skipCurrentElement(); } } - // Failed to find version in file - if (version == 0) { - return kUnknownVersion; - } - - // We should now have the version, if we have a serializer for it, use it to load the project - ProjectSerializer *serializer = nullptr; - - foreach (ProjectSerializer *s, instances_) { - if (version == s->Version()) { - serializer = s; - break; - } else if (version < s->Version()) { - // Assuming the instance list is in order, if the project version is less than any version - // we find, we must not support it anymore - return kProjectTooOld; - } - } - - if (serializer) { - LoadData ld = serializer->Load(project, reader, nullptr); - Result r(kSuccess); - if (reader->hasError()) { - r = Result(kXmlError); - r.SetDetails(reader->errorString()); - } - r.SetLoadData(ld); - return r; - } else { - // Reached the end of the list with no serializer, assume too new - return kProjectTooNew; - } + return res; } ProjectSerializer::Result ProjectSerializer::Save(const SaveData &data, const QString &type) @@ -209,4 +192,40 @@ bool ProjectSerializer::IsCancelled() const return false; } +ProjectSerializer::Result ProjectSerializer::LoadWithSerializerVersion(uint version, Project *project, QXmlStreamReader *reader) +{ + // Failed to find version in file + if (version == 0) { + return kUnknownVersion; + } + + // We should now have the version, if we have a serializer for it, use it to load the project + ProjectSerializer *serializer = nullptr; + + foreach (ProjectSerializer *s, instances_) { + if (version == s->Version()) { + serializer = s; + break; + } else if (version < s->Version()) { + // Assuming the instance list is in order, if the project version is less than any version + // we find, we must not support it anymore + return kProjectTooOld; + } + } + + if (serializer) { + LoadData ld = serializer->Load(project, reader, nullptr); + Result r(kSuccess); + if (reader->hasError()) { + r = Result(kXmlError); + r.SetDetails(reader->errorString()); + } + r.SetLoadData(ld); + return r; + } else { + // Reached the end of the list with no serializer, assume too new + return kProjectTooNew; + } +} + } diff --git a/app/node/project/serializer/serializer.h b/app/node/project/serializer/serializer.h index 58f9505cc..79968ea43 100644 --- a/app/node/project/serializer/serializer.h +++ b/app/node/project/serializer/serializer.h @@ -153,6 +153,8 @@ protected: bool IsCancelled() const; private: + static Result LoadWithSerializerVersion(uint version, Project *project, QXmlStreamReader *reader); + static QVector instances_; }; diff --git a/app/node/project/serializer/serializer210528.cpp b/app/node/project/serializer/serializer210528.cpp index 6c9ec058f..73ca85a4e 100644 --- a/app/node/project/serializer/serializer210528.cpp +++ b/app/node/project/serializer/serializer210528.cpp @@ -29,133 +29,125 @@ ProjectSerializer210528::LoadData ProjectSerializer210528::Load(Project *project XMLNodeData xml_node_data; while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("url")) { - project->SetSavedURL(reader->readElementText()); - } else if (reader->name() == QStringLiteral("project")) { + if (reader->name() == QStringLiteral("layout")) { + + // Since the main window's functions have to occur in the GUI thread (and we're likely + // loading in a secondary thread), we load all necessary data into a separate struct so we + // can continue loading and queue it with the main window so it can handle the data + // appropriately in its own thread. + + project->SetLayoutInfo(MainWindowLayoutInfo::fromXml(reader, xml_node_data.node_ptrs)); + + } else if (reader->name() == QStringLiteral("uuid")) { + + project->SetUuid(QUuid::fromString(reader->readElementText())); + + } else if (reader->name() == QStringLiteral("nodes")) { + while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("layout")) { + if (reader->name() == QStringLiteral("node")) { + bool is_root = false; + bool is_cm = false; + bool is_settings = false; + QString id; - // Since the main window's functions have to occur in the GUI thread (and we're likely - // loading in a secondary thread), we load all necessary data into a separate struct so we - // can continue loading and queue it with the main window so it can handle the data - // appropriately in its own thread. - - project->SetLayoutInfo(MainWindowLayoutInfo::fromXml(reader, xml_node_data.node_ptrs)); - - } else if (reader->name() == QStringLiteral("uuid")) { - - project->SetUuid(QUuid::fromString(reader->readElementText())); - - } else if (reader->name() == QStringLiteral("nodes")) { - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("node")) { - bool is_root = false; - bool is_cm = false; - bool is_settings = false; - QString id; - - { - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("id")) { - id = attr.value().toString(); - } else if (attr.name() == QStringLiteral("root") && attr.value() == QStringLiteral("1")) { - is_root = true; - } else if (attr.name() == QStringLiteral("cm") && attr.value() == QStringLiteral("1")) { - is_cm = true; - } else if (attr.name() == QStringLiteral("settings") && attr.value() == QStringLiteral("1")) { - is_settings = true; - } - } + { + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("id")) { + id = attr.value().toString(); + } else if (attr.name() == QStringLiteral("root") && attr.value() == QStringLiteral("1")) { + is_root = true; + } else if (attr.name() == QStringLiteral("cm") && attr.value() == QStringLiteral("1")) { + is_cm = true; + } else if (attr.name() == QStringLiteral("settings") && attr.value() == QStringLiteral("1")) { + is_settings = true; } - - if (id.isEmpty()) { - qWarning() << "Failed to load node with empty ID"; - reader->skipCurrentElement(); - } else { - Node* node; - - if (is_root) { - node = project->root(); - } else if (is_cm) { - node = project->color_manager(); - } else if (is_settings) { - node = project->settings(); - } else { - node = NodeFactory::CreateFromID(id); - } - - if (!node) { - qWarning() << "Failed to find node with ID" << id; - reader->skipCurrentElement(); - } else { - LoadNode(node, xml_node_data, reader); - node->setParent(project); - } - } - } else { - reader->skipCurrentElement(); } } - } else if (reader->name() == QStringLiteral("positions")) { + if (id.isEmpty()) { + qWarning() << "Failed to load node with empty ID"; + reader->skipCurrentElement(); + } else { + Node* node; - while (XMLReadNextStartElement(reader)) { + if (is_root) { + node = project->root(); + } else if (is_cm) { + node = project->color_manager(); + } else if (is_settings) { + node = project->settings(); + } else { + node = NodeFactory::CreateFromID(id); + } - if (reader->name() == QStringLiteral("context")) { + if (!node) { + qWarning() << "Failed to find node with ID" << id; + reader->skipCurrentElement(); + } else { + LoadNode(node, xml_node_data, reader); + node->setParent(project); + } + } + } else { + reader->skipCurrentElement(); + } + } - quintptr context_ptr = 0; - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("ptr")) { - context_ptr = attr.value().toULongLong(); - break; - } - } + } else if (reader->name() == QStringLiteral("positions")) { - Node *context = xml_node_data.node_ptrs.value(context_ptr); + while (XMLReadNextStartElement(reader)) { - if (!context) { - qWarning() << "Failed to find pointer for context"; - reader->skipCurrentElement(); - } else { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("node")) { - quintptr node_ptr; - Node::Position node_pos; + if (reader->name() == QStringLiteral("context")) { - if (LoadPosition(reader, &node_ptr, &node_pos)) { - Node *node = xml_node_data.node_ptrs.value(node_ptr); + quintptr context_ptr = 0; + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("ptr")) { + context_ptr = attr.value().toULongLong(); + break; + } + } - if (node) { - context->SetNodePositionInContext(node, node_pos); - } else { - qWarning() << "Failed to find pointer for node position"; - reader->skipCurrentElement(); - } - } + Node *context = xml_node_data.node_ptrs.value(context_ptr); + + if (!context) { + qWarning() << "Failed to find pointer for context"; + reader->skipCurrentElement(); + } else { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + quintptr node_ptr; + Node::Position node_pos; + + if (LoadPosition(reader, &node_ptr, &node_pos)) { + Node *node = xml_node_data.node_ptrs.value(node_ptr); + + if (node) { + context->SetNodePositionInContext(node, node_pos); } else { + qWarning() << "Failed to find pointer for node position"; reader->skipCurrentElement(); } } + } else { + reader->skipCurrentElement(); } - - } else { - - reader->skipCurrentElement(); - } - } } else { - // Skip this reader->skipCurrentElement(); } + } + } else { + + // Skip this reader->skipCurrentElement(); + } } diff --git a/app/node/project/serializer/serializer210907.cpp b/app/node/project/serializer/serializer210907.cpp index 870fa0b30..cd23aaea9 100644 --- a/app/node/project/serializer/serializer210907.cpp +++ b/app/node/project/serializer/serializer210907.cpp @@ -29,133 +29,125 @@ ProjectSerializer210907::LoadData ProjectSerializer210907::Load(Project *project XMLNodeData xml_node_data; while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("url")) { - project->SetSavedURL(reader->readElementText()); - } else if (reader->name() == QStringLiteral("project")) { + if (reader->name() == QStringLiteral("layout")) { + + // Since the main window's functions have to occur in the GUI thread (and we're likely + // loading in a secondary thread), we load all necessary data into a separate struct so we + // can continue loading and queue it with the main window so it can handle the data + // appropriately in its own thread. + + project->SetLayoutInfo(MainWindowLayoutInfo::fromXml(reader, xml_node_data.node_ptrs)); + + } else if (reader->name() == QStringLiteral("uuid")) { + + project->SetUuid(QUuid::fromString(reader->readElementText())); + + } else if (reader->name() == QStringLiteral("nodes")) { + while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("layout")) { + if (reader->name() == QStringLiteral("node")) { + bool is_root = false; + bool is_cm = false; + bool is_settings = false; + QString id; - // Since the main window's functions have to occur in the GUI thread (and we're likely - // loading in a secondary thread), we load all necessary data into a separate struct so we - // can continue loading and queue it with the main window so it can handle the data - // appropriately in its own thread. - - project->SetLayoutInfo(MainWindowLayoutInfo::fromXml(reader, xml_node_data.node_ptrs)); - - } else if (reader->name() == QStringLiteral("uuid")) { - - project->SetUuid(QUuid::fromString(reader->readElementText())); - - } else if (reader->name() == QStringLiteral("nodes")) { - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("node")) { - bool is_root = false; - bool is_cm = false; - bool is_settings = false; - QString id; - - { - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("id")) { - id = attr.value().toString(); - } else if (attr.name() == QStringLiteral("root") && attr.value() == QStringLiteral("1")) { - is_root = true; - } else if (attr.name() == QStringLiteral("cm") && attr.value() == QStringLiteral("1")) { - is_cm = true; - } else if (attr.name() == QStringLiteral("settings") && attr.value() == QStringLiteral("1")) { - is_settings = true; - } - } + { + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("id")) { + id = attr.value().toString(); + } else if (attr.name() == QStringLiteral("root") && attr.value() == QStringLiteral("1")) { + is_root = true; + } else if (attr.name() == QStringLiteral("cm") && attr.value() == QStringLiteral("1")) { + is_cm = true; + } else if (attr.name() == QStringLiteral("settings") && attr.value() == QStringLiteral("1")) { + is_settings = true; } - - if (id.isEmpty()) { - qWarning() << "Failed to load node with empty ID"; - reader->skipCurrentElement(); - } else { - Node* node; - - if (is_root) { - node = project->root(); - } else if (is_cm) { - node = project->color_manager(); - } else if (is_settings) { - node = project->settings(); - } else { - node = NodeFactory::CreateFromID(id); - } - - if (!node) { - qWarning() << "Failed to find node with ID" << id; - reader->skipCurrentElement(); - } else { - LoadNode(node, xml_node_data, reader); - node->setParent(project); - } - } - } else { - reader->skipCurrentElement(); } } - } else if (reader->name() == QStringLiteral("positions")) { + if (id.isEmpty()) { + qWarning() << "Failed to load node with empty ID"; + reader->skipCurrentElement(); + } else { + Node* node; - while (XMLReadNextStartElement(reader)) { + if (is_root) { + node = project->root(); + } else if (is_cm) { + node = project->color_manager(); + } else if (is_settings) { + node = project->settings(); + } else { + node = NodeFactory::CreateFromID(id); + } - if (reader->name() == QStringLiteral("context")) { + if (!node) { + qWarning() << "Failed to find node with ID" << id; + reader->skipCurrentElement(); + } else { + LoadNode(node, xml_node_data, reader); + node->setParent(project); + } + } + } else { + reader->skipCurrentElement(); + } + } - quintptr context_ptr = 0; - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("ptr")) { - context_ptr = attr.value().toULongLong(); - break; - } - } + } else if (reader->name() == QStringLiteral("positions")) { - Node *context = xml_node_data.node_ptrs.value(context_ptr); + while (XMLReadNextStartElement(reader)) { - if (!context) { - qWarning() << "Failed to find pointer for context"; - reader->skipCurrentElement(); - } else { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("node")) { - quintptr node_ptr; - Node::Position node_pos; + if (reader->name() == QStringLiteral("context")) { - if (LoadPosition(reader, &node_ptr, &node_pos)) { - Node *node = xml_node_data.node_ptrs.value(node_ptr); + quintptr context_ptr = 0; + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("ptr")) { + context_ptr = attr.value().toULongLong(); + break; + } + } - if (node) { - context->SetNodePositionInContext(node, node_pos); - } else { - qWarning() << "Failed to find pointer for node position"; - reader->skipCurrentElement(); - } - } + Node *context = xml_node_data.node_ptrs.value(context_ptr); + + if (!context) { + qWarning() << "Failed to find pointer for context"; + reader->skipCurrentElement(); + } else { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + quintptr node_ptr; + Node::Position node_pos; + + if (LoadPosition(reader, &node_ptr, &node_pos)) { + Node *node = xml_node_data.node_ptrs.value(node_ptr); + + if (node) { + context->SetNodePositionInContext(node, node_pos); } else { + qWarning() << "Failed to find pointer for node position"; reader->skipCurrentElement(); } } + } else { + reader->skipCurrentElement(); } - - } else { - - reader->skipCurrentElement(); - } - } } else { - // Skip this reader->skipCurrentElement(); } + } + } else { + + // Skip this reader->skipCurrentElement(); + } } diff --git a/app/node/project/serializer/serializer211228.cpp b/app/node/project/serializer/serializer211228.cpp index fb7c507d3..2faea0a0d 100644 --- a/app/node/project/serializer/serializer211228.cpp +++ b/app/node/project/serializer/serializer211228.cpp @@ -31,157 +31,149 @@ ProjectSerializer211228::LoadData ProjectSerializer211228::Load(Project *project XMLNodeData xml_node_data; while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("url")) { - project->SetSavedURL(reader->readElementText()); - } else if (reader->name() == QStringLiteral("project")) { + if (reader->name() == QStringLiteral("layout")) { + + // Since the main window's functions have to occur in the GUI thread (and we're likely + // loading in a secondary thread), we load all necessary data into a separate struct so we + // can continue loading and queue it with the main window so it can handle the data + // appropriately in its own thread. + + project->SetLayoutInfo(MainWindowLayoutInfo::fromXml(reader, xml_node_data.node_ptrs)); + + } else if (reader->name() == QStringLiteral("uuid")) { + + project->SetUuid(QUuid::fromString(reader->readElementText())); + + } else if (reader->name() == QStringLiteral("nodes")) { + while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("layout")) { + if (reader->name() == QStringLiteral("node")) { + bool is_root = false; + bool is_cm = false; + bool is_settings = false; + QString id; - // Since the main window's functions have to occur in the GUI thread (and we're likely - // loading in a secondary thread), we load all necessary data into a separate struct so we - // can continue loading and queue it with the main window so it can handle the data - // appropriately in its own thread. - - project->SetLayoutInfo(MainWindowLayoutInfo::fromXml(reader, xml_node_data.node_ptrs)); - - } else if (reader->name() == QStringLiteral("uuid")) { - - project->SetUuid(QUuid::fromString(reader->readElementText())); - - } else if (reader->name() == QStringLiteral("nodes")) { - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("node")) { - bool is_root = false; - bool is_cm = false; - bool is_settings = false; - QString id; - - { - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("id")) { - id = attr.value().toString(); - } else if (attr.name() == QStringLiteral("root") && attr.value() == QStringLiteral("1")) { - is_root = true; - } else if (attr.name() == QStringLiteral("cm") && attr.value() == QStringLiteral("1")) { - is_cm = true; - } else if (attr.name() == QStringLiteral("settings") && attr.value() == QStringLiteral("1")) { - is_settings = true; - } - } + { + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("id")) { + id = attr.value().toString(); + } else if (attr.name() == QStringLiteral("root") && attr.value() == QStringLiteral("1")) { + is_root = true; + } else if (attr.name() == QStringLiteral("cm") && attr.value() == QStringLiteral("1")) { + is_cm = true; + } else if (attr.name() == QStringLiteral("settings") && attr.value() == QStringLiteral("1")) { + is_settings = true; } - - if (id.isEmpty()) { - qWarning() << "Failed to load node with empty ID"; - reader->skipCurrentElement(); - } else { - Node* node; - - if (is_root) { - node = project->root(); - } else if (is_cm) { - node = project->color_manager(); - } else if (is_settings) { - node = project->settings(); - } else { - node = NodeFactory::CreateFromID(id); - } - - if (!node) { - qWarning() << "Failed to find node with ID" << id; - reader->skipCurrentElement(); - } else { - LoadNode(node, xml_node_data, reader); - node->setParent(project); - } - } - } else { - reader->skipCurrentElement(); } } - } else if (reader->name() == QStringLiteral("positions")) { + if (id.isEmpty()) { + qWarning() << "Failed to load node with empty ID"; + reader->skipCurrentElement(); + } else { + Node* node; - while (XMLReadNextStartElement(reader)) { + if (is_root) { + node = project->root(); + } else if (is_cm) { + node = project->color_manager(); + } else if (is_settings) { + node = project->settings(); + } else { + node = NodeFactory::CreateFromID(id); + } - if (reader->name() == QStringLiteral("context")) { + if (!node) { + qWarning() << "Failed to find node with ID" << id; + reader->skipCurrentElement(); + } else { + LoadNode(node, xml_node_data, reader); + node->setParent(project); + } + } + } else { + reader->skipCurrentElement(); + } + } - quintptr context_ptr = 0; - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("ptr")) { - context_ptr = attr.value().toULongLong(); - break; - } - } + } else if (reader->name() == QStringLiteral("positions")) { - if (context_ptr) { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("node")) { - quintptr node_ptr; - Node::Position node_pos; + while (XMLReadNextStartElement(reader)) { - if (LoadPosition(reader, &node_ptr, &node_pos)) { - if (node_ptr) { - positions[context_ptr].insert(node_ptr, node_pos); - } else { - qWarning() << "Failed to find pointer for node position"; - reader->skipCurrentElement(); - } - } + if (reader->name() == QStringLiteral("context")) { + + quintptr context_ptr = 0; + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("ptr")) { + context_ptr = attr.value().toULongLong(); + break; + } + } + + if (context_ptr) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + quintptr node_ptr; + Node::Position node_pos; + + if (LoadPosition(reader, &node_ptr, &node_pos)) { + if (node_ptr) { + positions[context_ptr].insert(node_ptr, node_pos); } else { + qWarning() << "Failed to find pointer for node position"; reader->skipCurrentElement(); } } } else { - qWarning() << "Attempted to load context with no pointer"; reader->skipCurrentElement(); } - - } else { - - reader->skipCurrentElement(); - - } - - } - - - } else if (reader->name() == QStringLiteral("properties")) { - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("node")) { - quintptr ptr = 0; - - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("ptr")) { - ptr = attr.value().toULongLong(); - - // Only attribute we're looking for right now - break; - } - } - - if (ptr) { - QMap properties_for_node; - while (XMLReadNextStartElement(reader)) { - properties_for_node.insert(reader->name().toString(), reader->readElementText()); - } - properties.insert(ptr, properties_for_node); - } - } else { - reader->skipCurrentElement(); } + } else { + qWarning() << "Attempted to load context with no pointer"; + reader->skipCurrentElement(); } } else { - // Skip this reader->skipCurrentElement(); } + } + + + } else if (reader->name() == QStringLiteral("properties")) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + quintptr ptr = 0; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("ptr")) { + ptr = attr.value().toULongLong(); + + // Only attribute we're looking for right now + break; + } + } + + if (ptr) { + QMap properties_for_node; + while (XMLReadNextStartElement(reader)) { + properties_for_node.insert(reader->name().toString(), reader->readElementText()); + } + properties.insert(ptr, properties_for_node); + } + } else { + reader->skipCurrentElement(); + } + } + } else { + + // Skip this reader->skipCurrentElement(); + } } diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index 3515e7706..b5d8d85b7 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -31,157 +31,149 @@ ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project XMLNodeData xml_node_data; while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("url")) { - project->SetSavedURL(reader->readElementText()); - } else if (reader->name() == QStringLiteral("project")) { + if (reader->name() == QStringLiteral("layout")) { + + // Since the main window's functions have to occur in the GUI thread (and we're likely + // loading in a secondary thread), we load all necessary data into a separate struct so we + // can continue loading and queue it with the main window so it can handle the data + // appropriately in its own thread. + + project->SetLayoutInfo(MainWindowLayoutInfo::fromXml(reader, xml_node_data.node_ptrs)); + + } else if (reader->name() == QStringLiteral("uuid")) { + + project->SetUuid(QUuid::fromString(reader->readElementText())); + + } else if (reader->name() == QStringLiteral("nodes")) { + while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("layout")) { + if (reader->name() == QStringLiteral("node")) { + bool is_root = false; + bool is_cm = false; + bool is_settings = false; + QString id; - // Since the main window's functions have to occur in the GUI thread (and we're likely - // loading in a secondary thread), we load all necessary data into a separate struct so we - // can continue loading and queue it with the main window so it can handle the data - // appropriately in its own thread. - - project->SetLayoutInfo(MainWindowLayoutInfo::fromXml(reader, xml_node_data.node_ptrs)); - - } else if (reader->name() == QStringLiteral("uuid")) { - - project->SetUuid(QUuid::fromString(reader->readElementText())); - - } else if (reader->name() == QStringLiteral("nodes")) { - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("node")) { - bool is_root = false; - bool is_cm = false; - bool is_settings = false; - QString id; - - { - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("id")) { - id = attr.value().toString(); - } else if (attr.name() == QStringLiteral("root") && attr.value() == QStringLiteral("1")) { - is_root = true; - } else if (attr.name() == QStringLiteral("cm") && attr.value() == QStringLiteral("1")) { - is_cm = true; - } else if (attr.name() == QStringLiteral("settings") && attr.value() == QStringLiteral("1")) { - is_settings = true; - } - } + { + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("id")) { + id = attr.value().toString(); + } else if (attr.name() == QStringLiteral("root") && attr.value() == QStringLiteral("1")) { + is_root = true; + } else if (attr.name() == QStringLiteral("cm") && attr.value() == QStringLiteral("1")) { + is_cm = true; + } else if (attr.name() == QStringLiteral("settings") && attr.value() == QStringLiteral("1")) { + is_settings = true; } - - if (id.isEmpty()) { - qWarning() << "Failed to load node with empty ID"; - reader->skipCurrentElement(); - } else { - Node* node; - - if (is_root) { - node = project->root(); - } else if (is_cm) { - node = project->color_manager(); - } else if (is_settings) { - node = project->settings(); - } else { - node = NodeFactory::CreateFromID(id); - } - - if (!node) { - qWarning() << "Failed to find node with ID" << id; - reader->skipCurrentElement(); - } else { - LoadNode(node, xml_node_data, reader); - node->setParent(project); - } - } - } else { - reader->skipCurrentElement(); } } - } else if (reader->name() == QStringLiteral("positions")) { + if (id.isEmpty()) { + qWarning() << "Failed to load node with empty ID"; + reader->skipCurrentElement(); + } else { + Node* node; - while (XMLReadNextStartElement(reader)) { + if (is_root) { + node = project->root(); + } else if (is_cm) { + node = project->color_manager(); + } else if (is_settings) { + node = project->settings(); + } else { + node = NodeFactory::CreateFromID(id); + } - if (reader->name() == QStringLiteral("context")) { + if (!node) { + qWarning() << "Failed to find node with ID" << id; + reader->skipCurrentElement(); + } else { + LoadNode(node, xml_node_data, reader); + node->setParent(project); + } + } + } else { + reader->skipCurrentElement(); + } + } - quintptr context_ptr = 0; - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("ptr")) { - context_ptr = attr.value().toULongLong(); - break; - } - } + } else if (reader->name() == QStringLiteral("positions")) { - if (context_ptr) { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("node")) { - quintptr node_ptr; - Node::Position node_pos; + while (XMLReadNextStartElement(reader)) { - if (LoadPosition(reader, &node_ptr, &node_pos)) { - if (node_ptr) { - positions[context_ptr].insert(node_ptr, node_pos); - } else { - qWarning() << "Failed to find pointer for node position"; - reader->skipCurrentElement(); - } - } + if (reader->name() == QStringLiteral("context")) { + + quintptr context_ptr = 0; + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("ptr")) { + context_ptr = attr.value().toULongLong(); + break; + } + } + + if (context_ptr) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + quintptr node_ptr; + Node::Position node_pos; + + if (LoadPosition(reader, &node_ptr, &node_pos)) { + if (node_ptr) { + positions[context_ptr].insert(node_ptr, node_pos); } else { + qWarning() << "Failed to find pointer for node position"; reader->skipCurrentElement(); } } } else { - qWarning() << "Attempted to load context with no pointer"; reader->skipCurrentElement(); } - - } else { - - reader->skipCurrentElement(); - - } - - } - - - } else if (reader->name() == QStringLiteral("properties")) { - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("node")) { - quintptr ptr = 0; - - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("ptr")) { - ptr = attr.value().toULongLong(); - - // Only attribute we're looking for right now - break; - } - } - - if (ptr) { - QMap properties_for_node; - while (XMLReadNextStartElement(reader)) { - properties_for_node.insert(reader->name().toString(), reader->readElementText()); - } - properties.insert(ptr, properties_for_node); - } - } else { - reader->skipCurrentElement(); } + } else { + qWarning() << "Attempted to load context with no pointer"; + reader->skipCurrentElement(); } } else { - // Skip this reader->skipCurrentElement(); } + } + + + } else if (reader->name() == QStringLiteral("properties")) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + quintptr ptr = 0; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("ptr")) { + ptr = attr.value().toULongLong(); + + // Only attribute we're looking for right now + break; + } + } + + if (ptr) { + QMap properties_for_node; + while (XMLReadNextStartElement(reader)) { + properties_for_node.insert(reader->name().toString(), reader->readElementText()); + } + properties.insert(ptr, properties_for_node); + } + } else { + reader->skipCurrentElement(); + } + } + } else { + + // Skip this reader->skipCurrentElement(); + } } From d1bb9318547366db89eae148a684687f47b03da2 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Apr 2022 14:23:19 -0700 Subject: [PATCH 60/81] nodeparamview: implemented add button --- app/node/audio/pan/pan.cpp | 3 ++ app/node/audio/volume/volume.cpp | 3 ++ app/node/block/clip/clip.cpp | 2 + .../cornerpin/cornerpindistortnode.cpp | 3 ++ app/node/distort/crop/cropdistortnode.cpp | 3 ++ app/node/distort/flip/flipdistortnode.cpp | 3 ++ .../transform/transformdistortnode.cpp | 3 ++ app/node/effect/opacity/opacityeffect.cpp | 3 ++ app/node/factory.cpp | 6 ++- app/node/factory.h | 2 +- app/node/filter/blur/blur.cpp | 3 ++ app/node/filter/mosaic/mosaicfilternode.cpp | 3 ++ app/node/filter/stroke/stroke.cpp | 3 ++ .../colordifferencekey/colordifferencekey.cpp | 6 ++- app/node/keying/despill/despill.cpp | 3 ++ app/node/node.cpp | 4 +- app/node/node.h | 23 +++++++++- .../nodeparamview/nodeparamviewcontext.cpp | 42 ++++++++++++++++++- .../nodeparamview/nodeparamviewcontext.h | 2 + 19 files changed, 114 insertions(+), 6 deletions(-) diff --git a/app/node/audio/pan/pan.cpp b/app/node/audio/pan/pan.cpp index 9eb8e0766..e6458bfaa 100644 --- a/app/node/audio/pan/pan.cpp +++ b/app/node/audio/pan/pan.cpp @@ -35,6 +35,9 @@ PanNode::PanNode() SetInputProperty(kPanningInput, QStringLiteral("min"), -1.0); SetInputProperty(kPanningInput, QStringLiteral("max"), 1.0); SetInputProperty(kPanningInput, QStringLiteral("view"), FloatSlider::kPercentage); + + SetFlags(kAudioEffect); + SetEffectInput(kSamplesInput); } Node *PanNode::copy() const diff --git a/app/node/audio/volume/volume.cpp b/app/node/audio/volume/volume.cpp index e6c26b50e..6617dcae3 100644 --- a/app/node/audio/volume/volume.cpp +++ b/app/node/audio/volume/volume.cpp @@ -34,6 +34,9 @@ VolumeNode::VolumeNode() AddInput(kVolumeInput, NodeValue::kFloat, 1.0); SetInputProperty(kVolumeInput, QStringLiteral("min"), 0.0); SetInputProperty(kVolumeInput, QStringLiteral("view"), FloatSlider::kDecibel); + + SetFlags(kAudioEffect); + SetEffectInput(kSamplesInput); } Node *VolumeNode::copy() const diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 4e5cfdb81..f5afb79fa 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -57,6 +57,8 @@ ClipBlock::ClipBlock() : PrependInput(kBufferIn, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable)); SetValueHintForInput(kBufferIn, ValueHint(NodeValue::kBuffer)); + + SetEffectInput(kBufferIn); } Node *ClipBlock::copy() const diff --git a/app/node/distort/cornerpin/cornerpindistortnode.cpp b/app/node/distort/cornerpin/cornerpindistortnode.cpp index e4429ede7..54c5870da 100644 --- a/app/node/distort/cornerpin/cornerpindistortnode.cpp +++ b/app/node/distort/cornerpin/cornerpindistortnode.cpp @@ -48,6 +48,9 @@ CornerPinDistortNode::CornerPinDistortNode() gizmo_resize_handle_[1] = AddDraggableGizmo({NodeKeyframeTrackReference(NodeInput(this, kTopRightInput), 0), NodeKeyframeTrackReference(NodeInput(this, kTopRightInput), 1)}); gizmo_resize_handle_[2] = AddDraggableGizmo({NodeKeyframeTrackReference(NodeInput(this, kBottomRightInput), 0), NodeKeyframeTrackReference(NodeInput(this, kBottomRightInput), 1)}); gizmo_resize_handle_[3] = AddDraggableGizmo({NodeKeyframeTrackReference(NodeInput(this, kBottomLeftInput), 0), NodeKeyframeTrackReference(NodeInput(this, kBottomLeftInput), 1)}); + + SetFlags(kVideoEffect); + SetEffectInput(kTextureInput); } void CornerPinDistortNode::Retranslate() diff --git a/app/node/distort/crop/cropdistortnode.cpp b/app/node/distort/crop/cropdistortnode.cpp index b204c7eda..e60b9afe3 100644 --- a/app/node/distort/crop/cropdistortnode.cpp +++ b/app/node/distort/crop/cropdistortnode.cpp @@ -56,6 +56,9 @@ CropDistortNode::CropDistortNode() point_gizmo_[kGizmoScaleBottomRight] = AddDraggableGizmo({kRightInput, kBottomInput}); point_gizmo_[kGizmoScaleCenterLeft] = AddDraggableGizmo({kLeftInput}); point_gizmo_[kGizmoScaleCenterRight] = AddDraggableGizmo({kRightInput}); + + SetFlags(kVideoEffect); + SetEffectInput(kTextureInput); } void CropDistortNode::Retranslate() diff --git a/app/node/distort/flip/flipdistortnode.cpp b/app/node/distort/flip/flipdistortnode.cpp index 577437fe5..3bc78536d 100644 --- a/app/node/distort/flip/flipdistortnode.cpp +++ b/app/node/distort/flip/flipdistortnode.cpp @@ -33,6 +33,9 @@ FlipDistortNode::FlipDistortNode() AddInput(kHorizontalInput, NodeValue::kBoolean, false); AddInput(kVerticalInput, NodeValue::kBoolean, false); + + SetFlags(kVideoEffect); + SetEffectInput(kTextureInput); } Node* FlipDistortNode::copy() const diff --git a/app/node/distort/transform/transformdistortnode.cpp b/app/node/distort/transform/transformdistortnode.cpp index 59ec09bc2..0f2bcfac2 100644 --- a/app/node/distort/transform/transformdistortnode.cpp +++ b/app/node/distort/transform/transformdistortnode.cpp @@ -64,6 +64,9 @@ TransformDistortNode::TransformDistortNode() point_gizmo_[i]->AddInput(NodeKeyframeTrackReference(NodeInput(this, kScaleInput), 1)); point_gizmo_[i]->SetDragValueBehavior(PointGizmo::kAbsolute); } + + SetFlags(kVideoEffect); + SetEffectInput(kTextureInput); } void TransformDistortNode::Retranslate() diff --git a/app/node/effect/opacity/opacityeffect.cpp b/app/node/effect/opacity/opacityeffect.cpp index b8ac2d017..b4dcba646 100644 --- a/app/node/effect/opacity/opacityeffect.cpp +++ b/app/node/effect/opacity/opacityeffect.cpp @@ -24,6 +24,9 @@ OpacityEffect::OpacityEffect() SetInputProperty(kValueInput, QStringLiteral("view"), FloatSlider::kPercentage); SetInputProperty(kValueInput, QStringLiteral("min"), 0.0); SetInputProperty(kValueInput, QStringLiteral("max"), 1.0); + + SetFlags(kVideoEffect); + SetEffectInput(kTextureInput); } void OpacityEffect::Retranslate() diff --git a/app/node/factory.cpp b/app/node/factory.cpp index 3f32fe17a..cf5e3b0c4 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -86,7 +86,7 @@ void NodeFactory::Destroy() library_.clear(); } -Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item, Node::CategoryID restrict_to) +Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item, Node::CategoryID restrict_to, uint64_t restrict_flags) { Menu* menu = new Menu(parent); menu->setToolTipsVisible(true); @@ -99,6 +99,10 @@ Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item, Node::Cate continue; } + if (restrict_flags && !(n->GetFlags() & restrict_flags)) { + continue; + } + if (hidden_.contains(i)) { // Skip this node continue; diff --git a/app/node/factory.h b/app/node/factory.h index 0a461892f..56192cc7b 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -81,7 +81,7 @@ public: static void Destroy(); - static Menu* CreateMenu(QWidget *parent, bool create_none_item = false, Node::CategoryID restrict_to = Node::kCategoryUnknown); + static Menu* CreateMenu(QWidget *parent, bool create_none_item = false, Node::CategoryID restrict_to = Node::kCategoryUnknown, uint64_t restrict_flags = 0); static Node* CreateFromMenuAction(QAction* action); diff --git a/app/node/filter/blur/blur.cpp b/app/node/filter/blur/blur.cpp index c488b8008..aa159dcce 100644 --- a/app/node/filter/blur/blur.cpp +++ b/app/node/filter/blur/blur.cpp @@ -43,6 +43,9 @@ BlurFilterNode::BlurFilterNode() AddInput(kVertInput, NodeValue::kBoolean, true); AddInput(kRepeatEdgePixelsInput, NodeValue::kBoolean, true); + + SetFlags(kVideoEffect); + SetEffectInput(kTextureInput); } Node *BlurFilterNode::copy() const diff --git a/app/node/filter/mosaic/mosaicfilternode.cpp b/app/node/filter/mosaic/mosaicfilternode.cpp index 242d5682f..0dc562218 100644 --- a/app/node/filter/mosaic/mosaicfilternode.cpp +++ b/app/node/filter/mosaic/mosaicfilternode.cpp @@ -35,6 +35,9 @@ MosaicFilterNode::MosaicFilterNode() AddInput(kVertInput, NodeValue::kFloat, 18.0); SetInputProperty(kVertInput, QStringLiteral("min"), 1.0); + + SetFlags(kVideoEffect); + SetEffectInput(kTextureInput); } void MosaicFilterNode::Retranslate() diff --git a/app/node/filter/stroke/stroke.cpp b/app/node/filter/stroke/stroke.cpp index 7818d7e46..d6e784146 100644 --- a/app/node/filter/stroke/stroke.cpp +++ b/app/node/filter/stroke/stroke.cpp @@ -46,6 +46,9 @@ StrokeFilterNode::StrokeFilterNode() SetInputProperty(kOpacityInput, QStringLiteral("max"), 1.0f); AddInput(kInnerInput, NodeValue::kBoolean, false); + + SetFlags(kVideoEffect); + SetEffectInput(kTextureInput); } Node *StrokeFilterNode::copy() const diff --git a/app/node/keying/colordifferencekey/colordifferencekey.cpp b/app/node/keying/colordifferencekey/colordifferencekey.cpp index 1f2ca4d1c..a805ab550 100644 --- a/app/node/keying/colordifferencekey/colordifferencekey.cpp +++ b/app/node/keying/colordifferencekey/colordifferencekey.cpp @@ -25,7 +25,8 @@ const QString ColorDifferenceKeyNode::kShadowsInput = QStringLiteral("shadows_in const QString ColorDifferenceKeyNode::kHighlightsInput = QStringLiteral("highlights_in"); const QString ColorDifferenceKeyNode::kMaskOnlyInput = QStringLiteral("mask_only_in"); -ColorDifferenceKeyNode::ColorDifferenceKeyNode() { +ColorDifferenceKeyNode::ColorDifferenceKeyNode() +{ AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); AddInput(kGarbageMatteInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); @@ -41,6 +42,9 @@ ColorDifferenceKeyNode::ColorDifferenceKeyNode() { SetInputProperty(kShadowsInput, QStringLiteral("min"), 0.0); AddInput(kMaskOnlyInput, NodeValue::kBoolean, false); + + SetFlags(kVideoEffect); + SetEffectInput(kTextureInput); } Node *ColorDifferenceKeyNode::copy() const diff --git a/app/node/keying/despill/despill.cpp b/app/node/keying/despill/despill.cpp index 147ee60df..09a8f2f45 100644 --- a/app/node/keying/despill/despill.cpp +++ b/app/node/keying/despill/despill.cpp @@ -33,6 +33,9 @@ DespillNode::DespillNode() AddInput(kMethodInput, NodeValue::kCombo, 0); AddInput(kPreserveLuminanceInput, NodeValue::kBoolean, false); + + SetFlags(kVideoEffect); + SetEffectInput(kTextureInput); } Node* DespillNode::copy() const diff --git a/app/node/node.cpp b/app/node/node.cpp index c8afb1f12..b0a7ec8aa 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -48,7 +48,9 @@ Node::Node() : folder_(nullptr), operation_stack_(0), cache_result_(false), - flags_(kNone) + flags_(kNone), + effect_element_(-1), + bypass_(false) { } diff --git a/app/node/node.h b/app/node/node.h index 6d2c27144..66b5497fe 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -95,7 +95,9 @@ public: enum Flag { kNone = 0, - kDontShowInParamView = 0x1 + kDontShowInParamView = 0x1, + kVideoEffect = 0x2, + kAudioEffect = 0x4 }; Node(); @@ -539,6 +541,11 @@ public: int InputArraySize(const QString& id) const; + NodeInput GetEffectInput() + { + return effect_input_.isEmpty() ? NodeInput() : NodeInput(this, effect_input_, effect_element_); + } + class ValueHint { public: explicit ValueHint(const QVector &types = QVector(), int index = -1, const QString &tag = QString()) : @@ -889,6 +896,9 @@ public: cache_result_ = e; } + bool IsBypassed() const { return bypass_; } + void SetBypassed(bool e) { bypass_ = e; } + class ArrayRemoveCommand : public UndoCommand { public: @@ -1027,6 +1037,12 @@ protected: virtual void childEvent(QChildEvent *event) override; + void SetEffectInput(const QString &input, int element = -1) + { + effect_input_ = input; + effect_element_ = element; + } + void SetToolTip(const QString& s) { tooltip_ = s; @@ -1342,6 +1358,11 @@ private: QVector gizmos_; + QString effect_input_; + int effect_element_; + + bool bypass_; + private slots: /** * @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time diff --git a/app/widget/nodeparamview/nodeparamviewcontext.cpp b/app/widget/nodeparamview/nodeparamviewcontext.cpp index a07c3975b..8fe70ca7a 100644 --- a/app/widget/nodeparamview/nodeparamviewcontext.cpp +++ b/app/widget/nodeparamview/nodeparamviewcontext.cpp @@ -23,6 +23,8 @@ #include #include "node/block/clip/clip.h" +#include "node/factory.h" +#include "widget/nodeview/nodeviewundo.h" namespace olive { @@ -130,7 +132,45 @@ void NodeParamViewContext::Retranslate() void NodeParamViewContext::AddEffectButtonClicked() { - QMessageBox::information(this, tr("STUB"), tr("This feature is coming soon. Thanks for testing development builds of Olive :)")); + Menu *m = NodeFactory::CreateMenu(this, false, Node::kCategoryUnknown, Node::kVideoEffect); + connect(m, &Menu::triggered, this, &NodeParamViewContext::AddEffectMenuItemTriggered); + m->exec(QCursor::pos()); + delete m; +} + +void NodeParamViewContext::AddEffectMenuItemTriggered(QAction *a) +{ + Node *n = NodeFactory::CreateFromMenuAction(a); + + if (n) { + NodeInput new_node_input = n->GetEffectInput(); + MultiUndoCommand *command = new MultiUndoCommand(); + + QVector graphs_added_to; + + foreach (Node *ctx, contexts_) { + NodeInput ctx_input = ctx->GetEffectInput(); + + if (!graphs_added_to.contains(ctx->parent())) { + command->add_child(new NodeAddCommand(ctx->parent(), n)); + graphs_added_to.append(ctx->parent()); + } + + command->add_child(new NodeSetPositionCommand(n, ctx, ctx->GetNodePositionInContext(ctx))); + command->add_child(new NodeSetPositionCommand(ctx, ctx, ctx->GetNodePositionInContext(ctx) + QPointF(1, 0))); + + if (ctx_input.IsConnected()) { + Node *prev_output = ctx_input.GetConnectedOutput(); + + command->add_child(new NodeEdgeRemoveCommand(prev_output, ctx_input)); + command->add_child(new NodeEdgeAddCommand(prev_output, new_node_input)); + } + + command->add_child(new NodeEdgeAddCommand(n, ctx_input)); + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); + } } } diff --git a/app/widget/nodeparamview/nodeparamviewcontext.h b/app/widget/nodeparamview/nodeparamviewcontext.h index 5652277e9..29be6dd58 100644 --- a/app/widget/nodeparamview/nodeparamviewcontext.h +++ b/app/widget/nodeparamview/nodeparamviewcontext.h @@ -91,6 +91,8 @@ private: private slots: void AddEffectButtonClicked(); + void AddEffectMenuItemTriggered(QAction *a); + }; } From 001834cbfa0015bf7126ac9e8f7139e616a47070 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Apr 2022 15:07:01 -0700 Subject: [PATCH 61/81] node: implemented bypassing --- app/node/audio/pan/pan.cpp | 4 +++ app/node/audio/volume/volume.cpp | 4 +++ app/node/block/block.cpp | 3 -- app/node/block/block.h | 1 - app/node/color/colormanager/colormanager.cpp | 4 +++ .../cornerpin/cornerpindistortnode.cpp | 4 +++ app/node/distort/crop/cropdistortnode.cpp | 4 +++ app/node/distort/flip/flipdistortnode.cpp | 4 +++ .../transform/transformdistortnode.cpp | 6 ++-- app/node/filter/blur/blur.cpp | 4 +++ app/node/filter/mosaic/mosaicfilternode.cpp | 4 +++ app/node/filter/stroke/stroke.cpp | 4 +++ app/node/generator/matrix/matrix.cpp | 4 +++ app/node/generator/noise/noise.cpp | 4 +++ app/node/generator/polygon/polygon.cpp | 4 +++ app/node/generator/solid/solid.cpp | 4 +++ app/node/generator/text/textv1.cpp | 4 +++ app/node/group/group.cpp | 2 ++ app/node/input/value/valuenode.cpp | 2 ++ .../colordifferencekey/colordifferencekey.cpp | 4 +++ app/node/keying/despill/despill.cpp | 5 ++- app/node/math/math/math.cpp | 4 ++- app/node/math/merge/merge.cpp | 4 +++ app/node/math/trigonometry/trigonometry.cpp | 4 +++ app/node/node.cpp | 7 +++-- app/node/node.h | 7 ++--- app/node/output/track/track.cpp | 2 +- .../projectsettings/projectsettings.cpp | 4 +++ app/node/time/timeoffset/timeoffsetnode.cpp | 2 ++ app/node/time/timeremap/timeremap.cpp | 2 ++ app/node/traverser.cpp | 31 +++++++++++++------ .../nodeparamview/nodeparamviewitem.cpp | 5 +++ .../nodeparamviewitemtitlebar.cpp | 5 +++ .../nodeparamview/nodeparamviewitemtitlebar.h | 15 +++++++++ 34 files changed, 145 insertions(+), 26 deletions(-) diff --git a/app/node/audio/pan/pan.cpp b/app/node/audio/pan/pan.cpp index e6458bfaa..5e6d87a39 100644 --- a/app/node/audio/pan/pan.cpp +++ b/app/node/audio/pan/pan.cpp @@ -27,6 +27,8 @@ namespace olive { const QString PanNode::kSamplesInput = QStringLiteral("samples_in"); const QString PanNode::kPanningInput = QStringLiteral("panning_in"); +#define super Node + PanNode::PanNode() { AddInput(kSamplesInput, NodeValue::kSamples, InputFlags(kInputFlagNotKeyframable)); @@ -118,6 +120,8 @@ void PanNode::ProcessSamples(const NodeValueRow &values, const SampleBufferPtr i void PanNode::Retranslate() { + super::Retranslate(); + SetInputName(kSamplesInput, tr("Samples")); SetInputName(kPanningInput, tr("Pan")); } diff --git a/app/node/audio/volume/volume.cpp b/app/node/audio/volume/volume.cpp index 6617dcae3..79feef607 100644 --- a/app/node/audio/volume/volume.cpp +++ b/app/node/audio/volume/volume.cpp @@ -27,6 +27,8 @@ namespace olive { const QString VolumeNode::kSamplesInput = QStringLiteral("samples_in"); const QString VolumeNode::kVolumeInput = QStringLiteral("volume_in"); +#define super MathNodeBase + VolumeNode::VolumeNode() { AddInput(kSamplesInput, NodeValue::kSamples, InputFlags(kInputFlagNotKeyframable)); @@ -83,6 +85,8 @@ void VolumeNode::ProcessSamples(const NodeValueRow &values, const SampleBufferPt void VolumeNode::Retranslate() { + super::Retranslate(); + SetInputName(kSamplesInput, tr("Samples")); SetInputName(kVolumeInput, tr("Volume")); } diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index a726d6b0f..68ee09863 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -32,7 +32,6 @@ namespace olive { #define super Node const QString Block::kLengthInput = QStringLiteral("length_in"); -const QString Block::kEnabledInput = QStringLiteral("enabled_in"); Block::Block() : previous_(nullptr), @@ -46,8 +45,6 @@ Block::Block() : SetInputProperty(kLengthInput, QStringLiteral("viewlock"), true); IgnoreHashingFrom(kLengthInput); - AddInput(kEnabledInput, NodeValue::kBoolean, true, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); - SetFlags(kDontShowInParamView); } diff --git a/app/node/block/block.h b/app/node/block/block.h index 7f03ec805..a555cb065 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -118,7 +118,6 @@ public: virtual void InvalidateCache(const TimeRange& range, const QString& from, int element = -1, InvalidateCacheOptions options = InvalidateCacheOptions()) override; static const QString kLengthInput; - static const QString kEnabledInput; public slots: diff --git a/app/node/color/colormanager/colormanager.cpp b/app/node/color/colormanager/colormanager.cpp index 4f946a513..895a522b4 100644 --- a/app/node/color/colormanager/colormanager.cpp +++ b/app/node/color/colormanager/colormanager.cpp @@ -34,6 +34,8 @@ const QString ColorManager::kConfigFilenameIn = QStringLiteral("config"); const QString ColorManager::kDefaultColorspaceIn = QStringLiteral("default_input"); const QString ColorManager::kReferenceSpaceIn = QStringLiteral("reference_space"); +#define super Node + OCIO::ConstConfigRcPtr ColorManager::default_config_ = nullptr; ColorManager::ColorManager() : @@ -253,6 +255,8 @@ void ColorManager::GetDefaultLumaCoefs(double *rgb) const void ColorManager::Retranslate() { + super::Retranslate(); + SetInputName(kConfigFilenameIn, tr("Configuration")); SetInputName(kDefaultColorspaceIn, tr("Default Input")); SetInputName(kReferenceSpaceIn, tr("Reference Space")); diff --git a/app/node/distort/cornerpin/cornerpindistortnode.cpp b/app/node/distort/cornerpin/cornerpindistortnode.cpp index 54c5870da..febb41061 100644 --- a/app/node/distort/cornerpin/cornerpindistortnode.cpp +++ b/app/node/distort/cornerpin/cornerpindistortnode.cpp @@ -33,6 +33,8 @@ const QString CornerPinDistortNode::kBottomRightInput = QStringLiteral("bottom_r const QString CornerPinDistortNode::kBottomLeftInput = QStringLiteral("bottom_left_in"); const QString CornerPinDistortNode::kPerspectiveInput = QStringLiteral("perspective_in"); +#define super Node + CornerPinDistortNode::CornerPinDistortNode() { AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); @@ -55,6 +57,8 @@ CornerPinDistortNode::CornerPinDistortNode() void CornerPinDistortNode::Retranslate() { + super::Retranslate(); + SetInputName(kTextureInput, tr("Texture")); SetInputName(kPerspectiveInput, tr("Perspective")); SetInputName(kTopLeftInput, tr("Top Left")); diff --git a/app/node/distort/crop/cropdistortnode.cpp b/app/node/distort/crop/cropdistortnode.cpp index e60b9afe3..18274109d 100644 --- a/app/node/distort/crop/cropdistortnode.cpp +++ b/app/node/distort/crop/cropdistortnode.cpp @@ -33,6 +33,8 @@ const QString CropDistortNode::kRightInput = QStringLiteral("right_in"); const QString CropDistortNode::kBottomInput = QStringLiteral("bottom_in"); const QString CropDistortNode::kFeatherInput = QStringLiteral("feather_in"); +#define super Node + CropDistortNode::CropDistortNode() { AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); @@ -63,6 +65,8 @@ CropDistortNode::CropDistortNode() void CropDistortNode::Retranslate() { + super::Retranslate(); + SetInputName(kTextureInput, tr("Texture")); SetInputName(kLeftInput, tr("Left")); SetInputName(kTopInput, tr("Top")); diff --git a/app/node/distort/flip/flipdistortnode.cpp b/app/node/distort/flip/flipdistortnode.cpp index 3bc78536d..21a835e0c 100644 --- a/app/node/distort/flip/flipdistortnode.cpp +++ b/app/node/distort/flip/flipdistortnode.cpp @@ -26,6 +26,8 @@ const QString FlipDistortNode::kTextureInput = QStringLiteral("tex_in"); const QString FlipDistortNode::kHorizontalInput = QStringLiteral("horiz_in"); const QString FlipDistortNode::kVerticalInput = QStringLiteral("vert_in"); +#define super Node + FlipDistortNode::FlipDistortNode() { AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); @@ -65,6 +67,8 @@ QString FlipDistortNode::Description() const void FlipDistortNode::Retranslate() { + super::Retranslate(); + SetInputName(kTextureInput, tr("Input")); SetInputName(kHorizontalInput, tr("Horizontal")); SetInputName(kVerticalInput, tr("Vertical")); diff --git a/app/node/distort/transform/transformdistortnode.cpp b/app/node/distort/transform/transformdistortnode.cpp index 0f2bcfac2..e60d901f2 100644 --- a/app/node/distort/transform/transformdistortnode.cpp +++ b/app/node/distort/transform/transformdistortnode.cpp @@ -32,7 +32,7 @@ const QString TransformDistortNode::kTextureInput = QStringLiteral("tex_in"); const QString TransformDistortNode::kAutoscaleInput = QStringLiteral("autoscale_in"); const QString TransformDistortNode::kInterpolationInput = QStringLiteral("interpolation_in"); -#define super Node +#define super MatrixGenerator TransformDistortNode::TransformDistortNode() { @@ -71,7 +71,7 @@ TransformDistortNode::TransformDistortNode() void TransformDistortNode::Retranslate() { - MatrixGenerator::Retranslate(); + super::Retranslate(); SetInputName(kAutoscaleInput, tr("Auto-Scale")); SetInputName(kTextureInput, tr("Texture")); @@ -153,7 +153,7 @@ void TransformDistortNode::Hash(QCryptographicHash &hash, const NodeGlobals &glo } } - Node::Hash(out, GetValueHintForInput(kTextureInput), hash, globals, video_params); + super::Hash(out, GetValueHintForInput(kTextureInput), hash, globals, video_params); } void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, double y, const rational &time) diff --git a/app/node/filter/blur/blur.cpp b/app/node/filter/blur/blur.cpp index aa159dcce..1865afbca 100644 --- a/app/node/filter/blur/blur.cpp +++ b/app/node/filter/blur/blur.cpp @@ -29,6 +29,8 @@ const QString BlurFilterNode::kHorizInput = QStringLiteral("horiz_in"); const QString BlurFilterNode::kVertInput = QStringLiteral("vert_in"); const QString BlurFilterNode::kRepeatEdgePixelsInput = QStringLiteral("repeat_edge_pixels_in"); +#define super Node + BlurFilterNode::BlurFilterNode() { AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); @@ -75,6 +77,8 @@ QString BlurFilterNode::Description() const void BlurFilterNode::Retranslate() { + super::Retranslate(); + SetInputName(kTextureInput, tr("Input")); SetInputName(kMethodInput, tr("Method")); SetComboBoxStrings(kMethodInput, { tr("Box"), tr("Gaussian") }); diff --git a/app/node/filter/mosaic/mosaicfilternode.cpp b/app/node/filter/mosaic/mosaicfilternode.cpp index 0dc562218..e6f5f0545 100644 --- a/app/node/filter/mosaic/mosaicfilternode.cpp +++ b/app/node/filter/mosaic/mosaicfilternode.cpp @@ -26,6 +26,8 @@ const QString MosaicFilterNode::kTextureInput = QStringLiteral("tex_in"); const QString MosaicFilterNode::kHorizInput = QStringLiteral("horiz_in"); const QString MosaicFilterNode::kVertInput = QStringLiteral("vert_in"); +#define super Node + MosaicFilterNode::MosaicFilterNode() { AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); @@ -42,6 +44,8 @@ MosaicFilterNode::MosaicFilterNode() void MosaicFilterNode::Retranslate() { + super::Retranslate(); + SetInputName(kTextureInput, tr("Texture")); SetInputName(kHorizInput, tr("Horizontal")); SetInputName(kVertInput, tr("Vertical")); diff --git a/app/node/filter/stroke/stroke.cpp b/app/node/filter/stroke/stroke.cpp index d6e784146..8883a9047 100644 --- a/app/node/filter/stroke/stroke.cpp +++ b/app/node/filter/stroke/stroke.cpp @@ -31,6 +31,8 @@ const QString StrokeFilterNode::kRadiusInput = QStringLiteral("radius_in"); const QString StrokeFilterNode::kOpacityInput = QStringLiteral("opacity_in"); const QString StrokeFilterNode::kInnerInput = QStringLiteral("inner_in"); +#define super Node + StrokeFilterNode::StrokeFilterNode() { AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); @@ -78,6 +80,8 @@ QString StrokeFilterNode::Description() const void StrokeFilterNode::Retranslate() { + super::Retranslate(); + SetInputName(kTextureInput, tr("Input")); SetInputName(kColorInput, tr("Color")); SetInputName(kRadiusInput, tr("Radius")); diff --git a/app/node/generator/matrix/matrix.cpp b/app/node/generator/matrix/matrix.cpp index 461d2ca3f..bbd5c9ba6 100644 --- a/app/node/generator/matrix/matrix.cpp +++ b/app/node/generator/matrix/matrix.cpp @@ -33,6 +33,8 @@ const QString MatrixGenerator::kScaleInput = QStringLiteral("scale_in"); const QString MatrixGenerator::kUniformScaleInput = QStringLiteral("uniform_scale_in"); const QString MatrixGenerator::kAnchorInput = QStringLiteral("anchor_in"); +#define super Node + MatrixGenerator::MatrixGenerator() { AddInput(kPositionInput, NodeValue::kVec2, QVector2D(0.0, 0.0)); @@ -81,6 +83,8 @@ QString MatrixGenerator::Description() const void MatrixGenerator::Retranslate() { + super::Retranslate(); + SetInputName(kPositionInput, tr("Position")); SetInputName(kRotationInput, tr("Rotation")); SetInputName(kScaleInput, tr("Scale")); diff --git a/app/node/generator/noise/noise.cpp b/app/node/generator/noise/noise.cpp index 21eaa215e..7ed7eb2b9 100644 --- a/app/node/generator/noise/noise.cpp +++ b/app/node/generator/noise/noise.cpp @@ -25,6 +25,8 @@ namespace olive { const QString NoiseGeneratorNode::kColorInput = QStringLiteral("color_in"); const QString NoiseGeneratorNode::kStrengthInput = QStringLiteral("strength_in"); +#define super Node + NoiseGeneratorNode::NoiseGeneratorNode() { AddInput(kStrengthInput, NodeValue::kFloat, 20); @@ -59,6 +61,8 @@ QString NoiseGeneratorNode::Description() const void NoiseGeneratorNode::Retranslate() { + super::Retranslate(); + SetInputName(kStrengthInput, tr("Strength")); SetInputName(kColorInput, tr("Color")); } diff --git a/app/node/generator/polygon/polygon.cpp b/app/node/generator/polygon/polygon.cpp index bd3ae07cf..c96d5b932 100644 --- a/app/node/generator/polygon/polygon.cpp +++ b/app/node/generator/polygon/polygon.cpp @@ -30,6 +30,8 @@ namespace olive { const QString PolygonGenerator::kPointsInput = QStringLiteral("points_in"); const QString PolygonGenerator::kColorInput = QStringLiteral("color_in"); +#define super Node + PolygonGenerator::PolygonGenerator() { AddInput(kPointsInput, NodeValue::kBezier, QVector2D(0, 0), InputFlags(kInputFlagArray)); @@ -86,6 +88,8 @@ QString PolygonGenerator::Description() const void PolygonGenerator::Retranslate() { + super::Retranslate(); + SetInputName(kPointsInput, tr("Points")); SetInputName(kColorInput, tr("Color")); } diff --git a/app/node/generator/solid/solid.cpp b/app/node/generator/solid/solid.cpp index 76b2340df..0327af098 100644 --- a/app/node/generator/solid/solid.cpp +++ b/app/node/generator/solid/solid.cpp @@ -26,6 +26,8 @@ namespace olive { const QString SolidGenerator::kColorInput = QStringLiteral("color_in"); +#define super Node + SolidGenerator::SolidGenerator() { // Default to a color that isn't black @@ -59,6 +61,8 @@ QString SolidGenerator::Description() const void SolidGenerator::Retranslate() { + super::Retranslate(); + SetInputName(kColorInput, tr("Color")); } diff --git a/app/node/generator/text/textv1.cpp b/app/node/generator/text/textv1.cpp index d6c4c8588..45dd1fad7 100644 --- a/app/node/generator/text/textv1.cpp +++ b/app/node/generator/text/textv1.cpp @@ -38,6 +38,8 @@ const QString TextGeneratorV1::kVAlignInput = QStringLiteral("valign_in"); const QString TextGeneratorV1::kFontInput = QStringLiteral("font_in"); const QString TextGeneratorV1::kFontSizeInput = QStringLiteral("font_size_in"); +#define super Node + TextGeneratorV1::TextGeneratorV1() { AddInput(kTextInput, NodeValue::kText, tr("Sample Text")); @@ -75,6 +77,8 @@ QString TextGeneratorV1::Description() const void TextGeneratorV1::Retranslate() { + super::Retranslate(); + SetInputName(kTextInput, tr("Text")); SetInputName(kHtmlInput, tr("Enable HTML")); SetInputName(kFontInput, tr("Font")); diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp index 793140c8a..a3089478d 100644 --- a/app/node/group/group.cpp +++ b/app/node/group/group.cpp @@ -53,6 +53,8 @@ QString NodeGroup::Description() const void NodeGroup::Retranslate() { + super::Retranslate(); + for (auto it=GetContextPositions().cbegin(); it!=GetContextPositions().cend(); it++) { it.key()->Retranslate(); } diff --git a/app/node/input/value/valuenode.cpp b/app/node/input/value/valuenode.cpp index 215e7a08c..00b3d0c5c 100644 --- a/app/node/input/value/valuenode.cpp +++ b/app/node/input/value/valuenode.cpp @@ -48,6 +48,8 @@ ValueNode::ValueNode() void ValueNode::Retranslate() { + super::Retranslate(); + SetInputName(kTypeInput, QStringLiteral("Type")); SetInputName(kValueInput, QStringLiteral("Value")); diff --git a/app/node/keying/colordifferencekey/colordifferencekey.cpp b/app/node/keying/colordifferencekey/colordifferencekey.cpp index a805ab550..5770d0f55 100644 --- a/app/node/keying/colordifferencekey/colordifferencekey.cpp +++ b/app/node/keying/colordifferencekey/colordifferencekey.cpp @@ -25,6 +25,8 @@ const QString ColorDifferenceKeyNode::kShadowsInput = QStringLiteral("shadows_in const QString ColorDifferenceKeyNode::kHighlightsInput = QStringLiteral("highlights_in"); const QString ColorDifferenceKeyNode::kMaskOnlyInput = QStringLiteral("mask_only_in"); +#define super Node + ColorDifferenceKeyNode::ColorDifferenceKeyNode() { AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); @@ -74,6 +76,8 @@ QString ColorDifferenceKeyNode::Description() const void ColorDifferenceKeyNode::Retranslate() { + super::Retranslate(); + SetInputName(kTextureInput, tr("Input")); SetInputName(kGarbageMatteInput, tr("Garbage Matte")); SetInputName(kCoreMatteInput, tr("Core Matte")); diff --git a/app/node/keying/despill/despill.cpp b/app/node/keying/despill/despill.cpp index 09a8f2f45..344b1f869 100644 --- a/app/node/keying/despill/despill.cpp +++ b/app/node/keying/despill/despill.cpp @@ -24,6 +24,8 @@ const QString DespillNode::kColorInput = QStringLiteral("color_in"); const QString DespillNode::kMethodInput = QStringLiteral("method_in"); const QString DespillNode::kPreserveLuminanceInput = QStringLiteral("preserve_luminance_input"); +#define super Node + DespillNode::DespillNode() { AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); @@ -65,12 +67,13 @@ QString DespillNode::Description() const void DespillNode::Retranslate() { + super::Retranslate(); + SetInputName(kTextureInput, tr("Input")); SetInputName(kColorInput, tr("Key Color")); SetComboBoxStrings(kColorInput, {tr("Green"), tr("Blue")}); - SetInputName(kMethodInput, tr("Method")); SetComboBoxStrings(kMethodInput, {tr("Average"), tr("Double Red Average"), tr("Double Average"), tr("Limit")}); diff --git a/app/node/math/math/math.cpp b/app/node/math/math/math.cpp index d601c2a8d..932a963d1 100644 --- a/app/node/math/math/math.cpp +++ b/app/node/math/math/math.cpp @@ -27,6 +27,8 @@ const QString MathNode::kParamAIn = QStringLiteral("param_a_in"); const QString MathNode::kParamBIn = QStringLiteral("param_b_in"); const QString MathNode::kParamCIn = QStringLiteral("param_c_in"); +#define super MathNodeBase + MathNode::MathNode() { AddInput(kMethodIn, NodeValue::kCombo, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); @@ -67,7 +69,7 @@ QString MathNode::Description() const void MathNode::Retranslate() { - Node::Retranslate(); + super::Retranslate(); SetInputName(kMethodIn, tr("Method")); SetInputName(kParamAIn, tr("Value")); diff --git a/app/node/math/merge/merge.cpp b/app/node/math/merge/merge.cpp index 5b1582c84..5940358aa 100644 --- a/app/node/math/merge/merge.cpp +++ b/app/node/math/merge/merge.cpp @@ -27,6 +27,8 @@ namespace olive { const QString MergeNode::kBaseIn = QStringLiteral("base_in"); const QString MergeNode::kBlendIn = QStringLiteral("blend_in"); +#define super Node + MergeNode::MergeNode() { AddInput(kBaseIn, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); @@ -63,6 +65,8 @@ QString MergeNode::Description() const void MergeNode::Retranslate() { + super::Retranslate(); + SetInputName(kBaseIn, tr("Base")); SetInputName(kBlendIn, tr("Blend")); diff --git a/app/node/math/trigonometry/trigonometry.cpp b/app/node/math/trigonometry/trigonometry.cpp index 3899cc6e4..5c6e16663 100644 --- a/app/node/math/trigonometry/trigonometry.cpp +++ b/app/node/math/trigonometry/trigonometry.cpp @@ -25,6 +25,8 @@ namespace olive { const QString TrigonometryNode::kMethodIn = QStringLiteral("method_in"); const QString TrigonometryNode::kXIn = QStringLiteral("x_in"); +#define super Node + TrigonometryNode::TrigonometryNode() { AddInput(kMethodIn, NodeValue::kCombo, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); @@ -59,6 +61,8 @@ QString TrigonometryNode::Description() const void TrigonometryNode::Retranslate() { + super::Retranslate(); + QStringList strings = {tr("Sine"), tr("Cosine"), tr("Tangent"), diff --git a/app/node/node.cpp b/app/node/node.cpp index b0a7ec8aa..12b92d665 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -42,6 +42,8 @@ namespace olive { #define super QObject +const QString Node::kEnabledInput = QStringLiteral("enabled_in"); + Node::Node() : can_be_deleted_(true), override_color_(-1), @@ -49,9 +51,9 @@ Node::Node() : operation_stack_(0), cache_result_(false), flags_(kNone), - effect_element_(-1), - bypass_(false) + effect_element_(-1) { + AddInput(kEnabledInput, NodeValue::kBoolean, true); } Node::~Node() @@ -105,6 +107,7 @@ QString Node::Description() const void Node::Retranslate() { + SetInputName(kEnabledInput, tr("Enabled")); } QIcon Node::icon() const diff --git a/app/node/node.h b/app/node/node.h index 66b5497fe..106448c1c 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -896,9 +896,6 @@ public: cache_result_ = e; } - bool IsBypassed() const { return bypass_; } - void SetBypassed(bool e) { bypass_ = e; } - class ArrayRemoveCommand : public UndoCommand { public: @@ -959,6 +956,8 @@ public: static void SetValueAtTime(const NodeInput &input, const rational &time, const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key); + static const QString kEnabledInput; + protected: virtual void Hash(QCryptographicHash& hash, const NodeGlobals &globals, const VideoParams& video_params) const; @@ -1361,8 +1360,6 @@ private: QString effect_input_; int effect_element_; - bool bypass_; - private slots: /** * @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 6325285d0..f4244804b 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -277,7 +277,7 @@ void Track::InputValueChangedEvent(const QString &input, int element) void Track::Retranslate() { - Node::Retranslate(); + super::Retranslate(); SetInputName(kBlockInput, tr("Blocks")); SetInputName(kMutedInput, tr("Muted")); diff --git a/app/node/project/projectsettings/projectsettings.cpp b/app/node/project/projectsettings/projectsettings.cpp index 2ed08b05b..59b3bedc0 100644 --- a/app/node/project/projectsettings/projectsettings.cpp +++ b/app/node/project/projectsettings/projectsettings.cpp @@ -25,6 +25,8 @@ namespace olive { const QString ProjectSettingsNode::kCacheSetting = QStringLiteral("cache_setting"); const QString ProjectSettingsNode::kCachePath = QStringLiteral("cache_path"); +#define super Node + ProjectSettingsNode::ProjectSettingsNode() { AddInput(kCacheSetting, NodeValue::kCombo, 0, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); @@ -36,6 +38,8 @@ ProjectSettingsNode::ProjectSettingsNode() void ProjectSettingsNode::Retranslate() { + super::Retranslate(); + SetInputName(kCacheSetting, tr("Disk Cache Location")); SetInputName(kCachePath, tr("Disk Cache Path")); SetComboBoxStrings(kCacheSetting, {tr("Use Default Location"), tr("Store Alongside Project"), tr("Use Custom Location")}); diff --git a/app/node/time/timeoffset/timeoffsetnode.cpp b/app/node/time/timeoffset/timeoffsetnode.cpp index d137847ee..0ef179393 100644 --- a/app/node/time/timeoffset/timeoffsetnode.cpp +++ b/app/node/time/timeoffset/timeoffsetnode.cpp @@ -41,6 +41,8 @@ TimeOffsetNode::TimeOffsetNode() void TimeOffsetNode::Retranslate() { + super::Retranslate(); + SetInputName(kTimeInput, QStringLiteral("Time")); SetInputName(kInputInput, QStringLiteral("Input")); } diff --git a/app/node/time/timeremap/timeremap.cpp b/app/node/time/timeremap/timeremap.cpp index 1f52a27c4..ff2b1ff77 100644 --- a/app/node/time/timeremap/timeremap.cpp +++ b/app/node/time/timeremap/timeremap.cpp @@ -87,6 +87,8 @@ TimeRange TimeRemapNode::OutputTimeAdjustment(const QString &input, int element, void TimeRemapNode::Retranslate() { + super::Retranslate(); + SetInputName(kTimeInput, QStringLiteral("Time")); SetInputName(kInputInput, QStringLiteral("Input")); } diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 754995a5d..62dca9c79 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -246,20 +246,33 @@ NodeValueTable NodeTraverser::GenerateTable(const Node *n, const Node::ValueHint // Generate row for node NodeValueDatabase database = GenerateDatabase(n, range); - NodeValueRow row = GenerateRow(&database, n, range); - //qDebug() << "FIXME: Implement pre-process of row"; + // Check for bypass + bool is_enabled; + if (!database[Node::kEnabledInput].Has(NodeValue::kBoolean)) { + // Fallback if we couldn't find a bool value + is_enabled = true; + } else { + is_enabled = database[Node::kEnabledInput].Get(NodeValue::kBoolean).toBool(); + } - // Generate output table - NodeValueTable table = database.Merge(); + if (is_enabled) { + NodeValueRow row = GenerateRow(&database, n, range); + //qDebug() << "FIXME: Implement pre-process of row"; - // By this point, the node should have all the inputs it needs to render correctly - n->Value(row, GenerateGlobals(video_params_, range), &table); + // Generate output table + NodeValueTable table = database.Merge(); - // Post-process table - PostProcessTable(n, hint, range, table); + // By this point, the node should have all the inputs it needs to render correctly + n->Value(row, GenerateGlobals(video_params_, range), &table); - return table; + // Post-process table + PostProcessTable(n, hint, range, table); + + return table; + } else { + return database.Merge(); + } } NodeValueTable NodeTraverser::GenerateBlockTable(const Track *track, const TimeRange &range) diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 7fedb2fea..9a61e0718 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -63,6 +63,11 @@ NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior c setBackgroundRole(QPalette::Window); + // Connect title bar enabled checkbox + //title_bar()->SetEnabledCheckBoxVisible(true); + //title_bar()->SetEnabledCheckBoxChecked(node_->IsEnabled()); + //connect(title_bar(), &NodeParamViewItemTitleBar::EnabledCheckBoxClicked, node_, &Node::SetEnabled); + Retranslate(); } diff --git a/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp b/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp index 6d61bd1ba..1d79ff4d9 100644 --- a/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp +++ b/app/widget/nodeparamview/nodeparamviewitemtitlebar.cpp @@ -56,6 +56,11 @@ NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) : pin_btn_->setVisible(false); layout->addWidget(pin_btn_); connect(pin_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::PinToggled); + + enabled_checkbox_ = new QCheckBox(); + enabled_checkbox_->setVisible(false); + layout->addWidget(enabled_checkbox_); + connect(enabled_checkbox_, &QCheckBox::clicked, this, &NodeParamViewItemTitleBar::EnabledCheckBoxClicked); } void NodeParamViewItemTitleBar::SetExpanded(bool e) diff --git a/app/widget/nodeparamview/nodeparamviewitemtitlebar.h b/app/widget/nodeparamview/nodeparamviewitemtitlebar.h index 7ddaac1aa..9bea7f06f 100644 --- a/app/widget/nodeparamview/nodeparamviewitemtitlebar.h +++ b/app/widget/nodeparamview/nodeparamviewitemtitlebar.h @@ -21,6 +21,7 @@ #ifndef NODEPARAMVIEWITEMTITLEBAR_H #define NODEPARAMVIEWITEMTITLEBAR_H +#include #include #include @@ -59,6 +60,16 @@ public slots: add_fx_btn_->setVisible(e); } + void SetEnabledCheckBoxVisible(bool e) + { + enabled_checkbox_->setVisible(e); + } + + void SetEnabledCheckBoxChecked(bool e) + { + enabled_checkbox_->setChecked(e); + } + signals: void ExpandedStateChanged(bool e); @@ -66,6 +77,8 @@ signals: void AddEffectButtonClicked(); + void EnabledCheckBoxClicked(bool e); + protected: virtual void paintEvent(QPaintEvent *event) override; @@ -82,6 +95,8 @@ private: QPushButton *add_fx_btn_; + QCheckBox *enabled_checkbox_; + }; } From 827dd98d6cf1941698693d60e15633bcdbda9008 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 26 Apr 2022 15:19:04 -0700 Subject: [PATCH 62/81] nodeparamview: implement deleting node --- app/widget/nodeparamview/nodeparamview.cpp | 23 +++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index c299b882b..8af158346 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -28,6 +28,7 @@ #include "common/functiontimer.h" #include "common/timecodefunctions.h" #include "node/output/viewer/viewer.h" +#include "widget/nodeview/nodeviewundo.h" namespace olive { @@ -369,8 +370,28 @@ Node *NodeParamView::GetTimeTarget() const void NodeParamView::DeleteSelected() { - if (keyframe_view_) { + if (keyframe_view_ && keyframe_view_->hasFocus()) { keyframe_view_->DeleteSelected(); + } else if (focused_node_) { + MultiUndoCommand *c = new MultiUndoCommand(); + Node *n = focused_node_->GetNode(); + + // Create command to delete node from context and/or graph + NodeViewDeleteCommand *dc = new NodeViewDeleteCommand(); + dc->AddNode(n, focused_node_->GetContext()); + c->add_child(dc); + + // Copy any outputs that were connected + if (n->GetEffectInput().IsValid()) { + if (Node *out = n->GetEffectInput().GetConnectedOutput()) { + for (auto it=n->output_connections().cbegin(); it!=n->output_connections().cend(); it++) { + c->add_child(new NodeEdgeAddCommand(out, it->second)); + } + } + } + + + Core::instance()->undo_stack()->push(c); } } From 7825d0e16d38c04188b021e160fea609da3cf7d0 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 28 Apr 2022 09:54:49 -0700 Subject: [PATCH 63/81] nodeview: improved drop functionality --- app/widget/nodeview/nodeview.cpp | 36 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 2f4041123..f11703822 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -500,26 +500,26 @@ void NodeView::mouseMoveEvent(QMouseEvent *event) if (new_drop_edge) { drop_input_.Reset(); - NodeValue::Type drop_edge_data_type = NodeValue::kNone; + NodeValue::Type drop_edge_data_type = new_drop_edge->input().GetDataType(); - // Run the Node and determine what type is being used - NodeTraverser traverser; - NodeValue drop_edge_value = traverser.GenerateRow(new_drop_edge->output(), TimeRange(0, 0))[new_drop_edge->input().input()]; - drop_edge_data_type = drop_edge_value.type(); + // Determine best input to connect to our new node + if (attached_node->GetEffectInput().IsValid()) { + // If node specifies an effect input, use that immediately + drop_input_ = attached_node->GetEffectInput(); + } else { + // Otherwise, we may have to iterate to find a valid one + for (const QString& input : attached_node->inputs()) { + NodeInput i(attached_node, input); - // Iterate through the inputs of our dragging node and see if our node has any acceptable - // inputs to connect to for this type - for (const QString& input : attached_node->inputs()) { - NodeInput i(attached_node, input); - - if (attached_node->IsInputConnectable(input)) { - if (attached_node->GetInputDataType(input) == drop_edge_data_type) { - // Found exactly the type we're looking for, set and break this loop - drop_input_ = i; - break; - } else if (!drop_input_.IsValid()) { - // Default to first connectable input - drop_input_ = i; + if (attached_node->IsInputConnectable(input)) { + if (attached_node->GetInputDataType(input) == drop_edge_data_type) { + // Found exactly the type we're looking for, set and break this loop + drop_input_ = i; + break; + } else if (!drop_input_.IsValid()) { + // Default to first connectable input + drop_input_ = i; + } } } } From 6e08b61ecc4e1f2b31e05c0f82ffd3efe628dcbe Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 28 Apr 2022 10:44:02 -0700 Subject: [PATCH 64/81] nodeview: prevent default behavior when items are attached Fixes buggy behavior when dropping attached nodes and moving the cursor --- app/widget/nodeview/nodeview.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index f11703822..c5bcb355a 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -451,8 +451,10 @@ void NodeView::mousePressEvent(QMouseEvent *event) } } - // Default QGraphicsView functionality (selecting, dragging, etc.) - super::mousePressEvent(event); + if (attached_items_.isEmpty()) { + // 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(); @@ -473,7 +475,9 @@ void NodeView::mouseMoveEvent(QMouseEvent *event) return; } - super::mouseMoveEvent(event); + if (attached_items_.isEmpty()) { + super::mouseMoveEvent(event); + } // See if there are any items attached if (!attached_items_.isEmpty()) { @@ -560,6 +564,8 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) Node *select_context = nullptr; QVector select_nodes; + bool had_attached_items = !attached_items_.isEmpty(); + if (!attached_items_.isEmpty()) { select_context = nullptr; @@ -643,7 +649,9 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) Core::instance()->undo_stack()->pushIfHasChildren(command); - super::mouseReleaseEvent(event); + if (!had_attached_items) { + super::mouseReleaseEvent(event); + } if (select_context) { scene_.context_map().value(select_context)->Select(select_nodes); From 67f1a1ad0cf00680020820a8dc862c9ef9aa751f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 1 May 2022 10:27:15 -0700 Subject: [PATCH 65/81] viewer: improve safe margins rendering --- app/widget/viewer/viewerdisplay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 7914763a9..4a41a4c43 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -514,7 +514,7 @@ void ViewerDisplayWidget::OnPaint() QPainter p(inner_widget()); p.setWorldTransform(GenerateWorldTransform()); - p.setPen(Qt::lightGray); + p.setPen(QPen(Qt::lightGray, 0)); p.setBrush(Qt::NoBrush); int x = 0, y = 0, w = width(), h = height(); From 004c66a073d93f897d50809914548ccbec0ebbb5 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 1 May 2022 10:47:58 -0700 Subject: [PATCH 66/81] node: implement find path utility function --- app/node/node.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++++ app/node/node.h | 2 ++ 2 files changed, 46 insertions(+) diff --git a/app/node/node.cpp b/app/node/node.cpp index 12b92d665..6e02dc68e 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -2002,6 +2002,50 @@ void Node::SetValueAtTime(const NodeInput &input, const rational &time, const QV } } +void FindPathInternal(std::list &vec, Node *to, int &path_index) +{ + Node *from = vec.back(); + + for (auto it=from->input_connections().cbegin(); it!=from->input_connections().cend(); it++) { + vec.push_back(it->second); + if (it->second == to) { + // Found a path, determine if it's the one we want + if (path_index == 0) { + // It is! + break; + } else { + path_index--; + } + } + + // Recurse to see if we can find it here + FindPathInternal(vec, to, path_index); + if (vec.back() == to) { + // Found through recursion + break; + } else { + // Must not be available through this path + vec.pop_back(); + } + } +} + +std::list Node::FindPath(Node *from, Node *to, int path_index) +{ + std::list v; + + v.push_back(from); + + FindPathInternal(v, to, path_index); + + if (v.size() == 1) { + // Failed to find path, return empty list + v.pop_back(); + } + + return v; +} + Project *Node::ArrayInsertCommand::GetRelevantProject() const { return node_->project(); diff --git a/app/node/node.h b/app/node/node.h index 106448c1c..98fcd8e27 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -956,6 +956,8 @@ public: static void SetValueAtTime(const NodeInput &input, const rational &time, const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key); + static std::list FindPath(Node *from, Node *to, int path_index = 0); + static const QString kEnabledInput; protected: From 838017a46879a1c6df117c5bdf9e57a3d9742bac Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 1 May 2022 11:16:13 -0700 Subject: [PATCH 67/81] config: set default marker color to lime This feels slightly closer to the existing color --- app/config/config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/config.cpp b/app/config/config.cpp index 66d764a87..501cc4f35 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -136,7 +136,7 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("OnlinePixelFormat"), NodeValue::kInt, VideoParams::kFormatFloat32); SetEntryInternal(QStringLiteral("OfflinePixelFormat"), NodeValue::kInt, VideoParams::kFormatFloat16); - SetEntryInternal(QStringLiteral("MarkerColor"), NodeValue::kInt, ColorCoding::kGreen); + SetEntryInternal(QStringLiteral("MarkerColor"), NodeValue::kInt, ColorCoding::kLime); } void Config::Load() From f04ff5f1ff9a4799d12840aa6e4f1287f2db34a8 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 1 May 2022 11:16:33 -0700 Subject: [PATCH 68/81] preferences: minor relabelling of marker settings --- app/dialog/preferences/tabs/preferencesappearancetab.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/dialog/preferences/tabs/preferencesappearancetab.cpp b/app/dialog/preferences/tabs/preferencesappearancetab.cpp index 157f0a37a..761ea18dd 100644 --- a/app/dialog/preferences/tabs/preferencesappearancetab.cpp +++ b/app/dialog/preferences/tabs/preferencesappearancetab.cpp @@ -85,11 +85,11 @@ PreferencesAppearanceTab::PreferencesAppearanceTab() row++; { QGroupBox* marker_group = new QGroupBox(); - marker_group->setTitle(tr("Default Marker Color")); + marker_group->setTitle(tr("Miscellaneous")); QGridLayout* marker_layout = new QGridLayout(marker_group); - marker_layout->addWidget(new QLabel("Marker"), 0, 0); + marker_layout->addWidget(new QLabel("Default Marker Color"), 0, 0); marker_btn_ = new ColorCodingComboBox(); marker_btn_->SetColor(Config::Current()[QStringLiteral("MarkerColor")].toInt()); From cdcd83c09a8429d81372b916a13628b3c1d5679b Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 1 May 2022 15:45:20 -0700 Subject: [PATCH 69/81] ported seekablewidget to timebasedview Cuts down on a lot of code duplication and opens up usage of the new TimelineViewSelectionManager framework --- app/node/node.cpp | 13 +- app/node/project/project.cpp | 14 + app/node/project/project.h | 8 + .../project/serializer/serializer210528.cpp | 3 +- .../project/serializer/serializer210907.cpp | 3 +- .../project/serializer/serializer211228.cpp | 3 +- .../project/serializer/serializer220403.cpp | 3 +- app/timeline/timelinemarker.cpp | 203 +++++++++----- app/timeline/timelinemarker.h | 132 +++++++-- app/timeline/timelinepoints.cpp | 15 +- app/timeline/timelinepoints.h | 9 +- app/widget/CMakeLists.txt | 1 - app/widget/marker/CMakeLists.txt | 26 -- app/widget/marker/marker.cpp | 263 ------------------ app/widget/marker/marker.h | 92 ------ app/widget/marker/markercopypaste.cpp | 136 --------- app/widget/marker/markercopypaste.h | 42 --- app/widget/marker/markerundo.cpp | 138 --------- app/widget/marker/markerundo.h | 119 -------- .../resizabletimelinescrollbar.cpp | 4 +- app/widget/timebased/timebasedview.h | 8 +- app/widget/timebased/timebasedwidget.cpp | 20 +- app/widget/timelinewidget/timelinewidget.cpp | 15 +- app/widget/timelinewidget/timelinewidget.h | 2 - .../timelinewidget/view/timelineview.cpp | 16 +- app/widget/timeruler/seekablewidget.cpp | 193 +++---------- app/widget/timeruler/seekablewidget.h | 61 ++-- app/widget/timeruler/timeruler.cpp | 65 +++-- app/widget/timeruler/timeruler.h | 2 +- app/widget/viewer/audiowaveformview.cpp | 25 +- app/widget/viewer/audiowaveformview.h | 2 +- app/widget/viewer/viewer.cpp | 2 +- 32 files changed, 425 insertions(+), 1213 deletions(-) delete mode 100644 app/widget/marker/CMakeLists.txt delete mode 100644 app/widget/marker/marker.cpp delete mode 100644 app/widget/marker/marker.h delete mode 100644 app/widget/marker/markercopypaste.cpp delete mode 100644 app/widget/marker/markercopypaste.h delete mode 100644 app/widget/marker/markerundo.cpp delete mode 100644 app/widget/marker/markerundo.h diff --git a/app/node/node.cpp b/app/node/node.cpp index 6e02dc68e..4a0c60b03 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -80,18 +80,9 @@ NodeGraph *Node::parent() const return static_cast(QObject::parent()); } -Project* Node::project() const +Project *Node::project() const { - QObject *t = this->parent(); - - while (t) { - if (Project *p = dynamic_cast(t)) { - return p; - } - t = t->parent(); - } - - return nullptr; + return Project::GetProjectFromObject(this); } QString Node::ShortName() const diff --git a/app/node/project/project.cpp b/app/node/project/project.cpp index d8923c9cd..c4340b177 100644 --- a/app/node/project/project.cpp +++ b/app/node/project/project.cpp @@ -169,6 +169,20 @@ void Project::RegenerateUuid() uuid_ = QUuid::createUuid(); } +Project *Project::GetProjectFromObject(const QObject *o) +{ + QObject *t = o->parent(); + + while (t) { + if (Project *p = dynamic_cast(t)) { + return p; + } + t = t->parent(); + } + + return nullptr; +} + void Project::ColorManagerValueChanged(const NodeInput &input, const TimeRange &range) { Q_UNUSED(input) diff --git a/app/node/project/project.h b/app/node/project/project.h index 2fccff6d3..1119e26fc 100644 --- a/app/node/project/project.h +++ b/app/node/project/project.h @@ -109,6 +109,14 @@ public: saved_url_ = url; } + /** + * @brief Find project parent from object + * + * If an object is expected to be a child of a project, this function will traverse its parent + * tree until it finds it. + */ + static Project *GetProjectFromObject(const QObject *o); + signals: void NameChanged(); diff --git a/app/node/project/serializer/serializer210528.cpp b/app/node/project/serializer/serializer210528.cpp index 73ca85a4e..baf53e442 100644 --- a/app/node/project/serializer/serializer210528.cpp +++ b/app/node/project/serializer/serializer210528.cpp @@ -20,6 +20,7 @@ #include "serializer210528.h" +#include "config/config.h" #include "node/factory.h" namespace olive { @@ -604,7 +605,7 @@ void ProjectSerializer210528::LoadMarkerList(QXmlStreamReader *reader, TimelineM } } - markers->AddMarker(TimeRange(in, out), name); + new TimelineMarker(Config::Current()[QStringLiteral("MarkerColor")].toInt(), TimeRange(in, out), name, markers); } reader->skipCurrentElement(); diff --git a/app/node/project/serializer/serializer210907.cpp b/app/node/project/serializer/serializer210907.cpp index cd23aaea9..96b69f214 100644 --- a/app/node/project/serializer/serializer210907.cpp +++ b/app/node/project/serializer/serializer210907.cpp @@ -20,6 +20,7 @@ #include "serializer210907.h" +#include "config/config.h" #include "node/factory.h" namespace olive { @@ -596,7 +597,7 @@ void ProjectSerializer210907::LoadMarkerList(QXmlStreamReader *reader, TimelineM } } - markers->AddMarker(TimeRange(in, out), name); + new TimelineMarker(Config::Current()[QStringLiteral("MarkerColor")].toInt(), TimeRange(in, out), name, markers); } reader->skipCurrentElement(); diff --git a/app/node/project/serializer/serializer211228.cpp b/app/node/project/serializer/serializer211228.cpp index 2faea0a0d..139fd2438 100644 --- a/app/node/project/serializer/serializer211228.cpp +++ b/app/node/project/serializer/serializer211228.cpp @@ -20,6 +20,7 @@ #include "serializer211228.h" +#include "config/config.h" #include "node/factory.h" namespace olive { @@ -646,7 +647,7 @@ void ProjectSerializer211228::LoadMarkerList(QXmlStreamReader *reader, TimelineM } } - markers->AddMarker(TimeRange(in, out), name); + new TimelineMarker(Config::Current()[QStringLiteral("MarkerColor")].toInt(), TimeRange(in, out), name, markers); } reader->skipCurrentElement(); diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index b5d8d85b7..31d3ea726 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -20,6 +20,7 @@ #include "serializer220403.h" +#include "config/config.h" #include "node/factory.h" namespace olive { @@ -1001,7 +1002,7 @@ void ProjectSerializer220403::LoadMarkerList(QXmlStreamReader *reader, TimelineM } } - markers->AddMarker(TimeRange(in, out), name); + new TimelineMarker(Config::Current()[QStringLiteral("MarkerColor")].toInt(), TimeRange(in, out), name, markers); } reader->skipCurrentElement(); diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index e75a625ba..2251ab3a3 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -23,21 +23,15 @@ #include "common/xmlutils.h" #include "config/config.h" #include "core.h" -#include "widget/marker/markerundo.h" namespace olive { -TimelineMarker::TimelineMarker(const TimeRange &time, const QString &name, QObject *parent) : +TimelineMarker::TimelineMarker(int color, const TimeRange &time, const QString &name, QObject *parent) : QObject(parent), time_(time), - name_(name) + name_(name), + color_(color) { - color_ = Config::Current()[QStringLiteral("MarkerColor")].toInt(); -} - -const TimeRange &TimelineMarker::time() const -{ - return time_; } void TimelineMarker::set_time(const TimeRange &time) @@ -46,35 +40,12 @@ void TimelineMarker::set_time(const TimeRange &time) emit TimeChanged(time_); } -void TimelineMarker::set_time_undo(TimeRange time) { - UndoCommand *command = new MarkerChangeTimeCommand(Core::instance()->GetActiveProject(), this, time); - - Core::instance()->undo_stack()->push(command); -} - -const QString &TimelineMarker::name() const -{ - return name_; -} - void TimelineMarker::set_name(const QString &name) { name_ = name; emit NameChanged(name_); } -void TimelineMarker::set_name_undo(QString name) -{ - UndoCommand *command = new MarkerChangeNameCommand(Core::instance()->GetActiveProject(), this, name); - - Core::instance()->undo_stack()->push(command); -} - -int TimelineMarker::color() -{ - return color_; -} - void TimelineMarker::set_color(int c) { color_ = c; @@ -82,51 +53,133 @@ void TimelineMarker::set_color(int c) emit ColorChanged(color_); } -bool TimelineMarker::active() -{ - return active_; -} - -void TimelineMarker::set_active(bool active) -{ - active_ = active; - - emit ActiveChanged(active_); -} - -TimelineMarkerList::~TimelineMarkerList() -{ - qDeleteAll(markers_); -} - -TimelineMarker* TimelineMarkerList::AddMarker(const TimeRange &time, const QString &name, int color) -{ - TimelineMarker* m = new TimelineMarker(time, name); - if (color >= 0) { - m->set_color(color); - } - markers_.append(m); - emit MarkerAdded(m); - return m; -} - -void TimelineMarkerList::RemoveMarker(TimelineMarker *marker) -{ - for (int i=0;i &TimelineMarkerList::list() const +const std::vector &TimelineMarkerList::list() const { return markers_; } +void TimelineMarkerList::childEvent(QChildEvent *e) +{ + QObject::childEvent(e); + + if (TimelineMarker *marker = dynamic_cast(e->child())) { + if (e->type() == QChildEvent::ChildAdded) { + markers_.push_back(marker); + } else if (e->type() == QChildEvent::ChildRemoved) { + auto it = std::find(markers_.begin(), markers_.end(), marker); + if (it != markers_.end()) { + markers_.erase(it); + } + } + } +} + +MarkerAddCommand::MarkerAddCommand(TimelineMarkerList *marker_list, const TimeRange &range, const QString &name, int color) : + marker_list_(marker_list) +{ + added_marker_ = new TimelineMarker(color, range, name, &memory_manager_); +} + +Project* MarkerAddCommand::GetRelevantProject() const +{ + return Project::GetProjectFromObject(marker_list_); +} + +void MarkerAddCommand::redo() +{ + added_marker_->setParent(marker_list_); +} + +void MarkerAddCommand::undo() +{ + added_marker_->setParent(&memory_manager_); +} + +MarkerRemoveCommand::MarkerRemoveCommand(TimelineMarker *marker) : + marker_(marker) +{ +} + +Project* MarkerRemoveCommand::GetRelevantProject() const +{ + return Project::GetProjectFromObject(marker_list_); +} + +void MarkerRemoveCommand::redo() +{ + marker_list_ = marker_->parent(); + marker_->setParent(&memory_manager_); +} + +void MarkerRemoveCommand::undo() +{ + marker_->setParent(marker_list_); +} + +MarkerChangeColorCommand::MarkerChangeColorCommand(TimelineMarker *marker, int new_color) : + marker_(marker), + new_color_(new_color) +{ +} + +Project* MarkerChangeColorCommand::GetRelevantProject() const +{ + return Project::GetProjectFromObject(marker_); +} + +void MarkerChangeColorCommand::redo() +{ + old_color_ = marker_->color(); + marker_->set_color(new_color_); +} + +void MarkerChangeColorCommand::undo() +{ + marker_->set_color(old_color_); +} + +MarkerChangeNameCommand::MarkerChangeNameCommand(TimelineMarker *marker, QString new_name) : + marker_(marker), + new_name_(new_name) +{ +} + +Project* MarkerChangeNameCommand::GetRelevantProject() const +{ + return Project::GetProjectFromObject(marker_); +} + +void MarkerChangeNameCommand::redo() +{ + old_name_ = marker_->name(); + marker_->set_name(new_name_); +} + +void MarkerChangeNameCommand::undo() +{ + marker_->set_name(old_name_); +} + +MarkerChangeTimeCommand::MarkerChangeTimeCommand(TimelineMarker* marker, TimeRange time) : + marker_(marker), + new_time_(time) +{ +} + +Project* MarkerChangeTimeCommand::GetRelevantProject() const +{ + return Project::GetProjectFromObject(marker_); +} + +void MarkerChangeTimeCommand::redo() +{ + old_time_ = marker_->time(); + marker_->set_time(new_time_); +} + +void MarkerChangeTimeCommand::undo() +{ + marker_->set_time(old_time_); +} + } diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index e33afce04..61822859c 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -26,6 +26,7 @@ #include #include "common/timerange.h" +#include "undo/undocommand.h" namespace olive { @@ -33,26 +34,17 @@ class TimelineMarker : public QObject { Q_OBJECT public: - TimelineMarker(const TimeRange& time = TimeRange(), const QString& name = QString(), QObject* parent = nullptr); + TimelineMarker(int color, const TimeRange& time, const QString& name = QString(), QObject* parent = nullptr); - const TimeRange &time() const; + const TimeRange &time() const { return time_; } void set_time(const TimeRange& time); - const QString& name() const; + const QString& name() const { return name_; } void set_name(const QString& name); - int color(); + int color() const { return color_; } void set_color(int c); - bool active(); - void set_active(bool active); - - void Load(QXmlStreamReader* reader); - -public slots: - void set_name_undo(QString name); - void set_time_undo(TimeRange time); - signals: void TimeChanged(const TimeRange& time); @@ -60,8 +52,6 @@ signals: void ColorChanged(int c); - void ActiveChanged(bool active); - private: TimeRange time_; @@ -69,31 +59,121 @@ private: int color_; - bool active_; - }; class TimelineMarkerList : public QObject { Q_OBJECT public: - TimelineMarkerList() = default; + TimelineMarkerList(QObject *parent = nullptr) : + QObject(parent) + { + } - virtual ~TimelineMarkerList() override; - - TimelineMarker* AddMarker(const TimeRange& time = TimeRange(), const QString& name = QString(), int color = -1); - - void RemoveMarker(TimelineMarker* marker); - - const QList &list() const; + const std::vector &list() const; signals: void MarkerAdded(TimelineMarker* marker); void MarkerRemoved(TimelineMarker* marker); +protected: + virtual void childEvent(QChildEvent *e) override; + private: - QList markers_; + std::vector markers_; + +}; + +class MarkerAddCommand : public UndoCommand { +public: + MarkerAddCommand(TimelineMarkerList* marker_list, const TimeRange& range, const QString& name, int color); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo() override; + virtual void undo() override; + +private: + TimelineMarkerList* marker_list_; + TimeRange range_; + QString name_; + int color_; + + TimelineMarker* added_marker_; + QObject memory_manager_; + +}; + +class MarkerRemoveCommand : public UndoCommand { +public: + MarkerRemoveCommand(TimelineMarker* marker); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo() override; + virtual void undo() override; + +private: + TimelineMarker* marker_; + QObject* marker_list_; + TimeRange range_; + QString name_; + int color_; + + QObject memory_manager_; + +}; + +class MarkerChangeColorCommand : public UndoCommand { +public: + MarkerChangeColorCommand(TimelineMarker* marker, int new_color); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo() override; + virtual void undo() override; + +private: + TimelineMarker* marker_; + int old_color_; + int new_color_; + +}; + +class MarkerChangeNameCommand : public UndoCommand { +public: + MarkerChangeNameCommand(TimelineMarker* marker, QString name); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo() override; + virtual void undo() override; + +private: + TimelineMarker* marker_; + QString old_name_; + QString new_name_; +}; + +class MarkerChangeTimeCommand : public UndoCommand { +public: + MarkerChangeTimeCommand(TimelineMarker* marker, TimeRange time); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo() override; + virtual void undo() override; + +private: + TimelineMarker* marker_; + TimeRange old_time_; + TimeRange new_time_; }; diff --git a/app/timeline/timelinepoints.cpp b/app/timeline/timelinepoints.cpp index 4329c42b4..61ed3633d 100644 --- a/app/timeline/timelinepoints.cpp +++ b/app/timeline/timelinepoints.cpp @@ -24,24 +24,31 @@ namespace olive { +TimelinePoints::TimelinePoints(QObject *parent) : + QObject(parent) +{ + markers_ = new TimelineMarkerList(this); + workarea_ = new TimelineWorkArea(this); +} + TimelineMarkerList *TimelinePoints::markers() { - return &markers_; + return markers_; } const TimelineMarkerList *TimelinePoints::markers() const { - return &markers_; + return markers_; } const TimelineWorkArea *TimelinePoints::workarea() const { - return &workarea_; + return workarea_; } TimelineWorkArea *TimelinePoints::workarea() { - return &workarea_; + return workarea_; } } diff --git a/app/timeline/timelinepoints.h b/app/timeline/timelinepoints.h index 14527608d..62290bef4 100644 --- a/app/timeline/timelinepoints.h +++ b/app/timeline/timelinepoints.h @@ -29,10 +29,11 @@ namespace olive { -class TimelinePoints +class TimelinePoints : public QObject { + Q_OBJECT public: - TimelinePoints() = default; + TimelinePoints(QObject *parent = nullptr); TimelineMarkerList* markers(); const TimelineMarkerList* markers() const; @@ -41,9 +42,9 @@ public: const TimelineWorkArea* workarea() const; private: - TimelineMarkerList markers_; + TimelineMarkerList *markers_; - TimelineWorkArea workarea_; + TimelineWorkArea *workarea_; }; diff --git a/app/widget/CMakeLists.txt b/app/widget/CMakeLists.txt index 10c8ce38f..9faf9c462 100644 --- a/app/widget/CMakeLists.txt +++ b/app/widget/CMakeLists.txt @@ -29,7 +29,6 @@ add_subdirectory(focusablelineedit) add_subdirectory(handmovableview) add_subdirectory(keyframeview) add_subdirectory(manageddisplay) -add_subdirectory(marker) add_subdirectory(menu) add_subdirectory(nodecombobox) add_subdirectory(nodeparamview) diff --git a/app/widget/marker/CMakeLists.txt b/app/widget/marker/CMakeLists.txt deleted file mode 100644 index 73922308e..000000000 --- a/app/widget/marker/CMakeLists.txt +++ /dev/null @@ -1,26 +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/marker/marker.h - widget/marker/marker.cpp - widget/marker/markercopypaste.h - widget/marker/markercopypaste.cpp - widget/marker/markerundo.h - widget/marker/markerundo.cpp - PARENT_SCOPE -) diff --git a/app/widget/marker/marker.cpp b/app/widget/marker/marker.cpp deleted file mode 100644 index eb058a3f2..000000000 --- a/app/widget/marker/marker.cpp +++ /dev/null @@ -1,263 +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 "marker.h" - -#include -#include - -#include "config/config.h" -#include "common/qtutils.h" -#include "panel/panelmanager.h" -#include "panel/timeline/timeline.h" -#include "ui/colorcoding.h" -#include "widget/menu/menu.h" -#include "widget/menu/menushared.h" -#include "widget/timeruler/seekablewidget.h" -#include "widget/timelinewidget/timelinewidget.h" - -namespace olive { - -Marker::Marker(QWidget *parent) : - QWidget(parent), - active_(false), - drag_allowed_(false), - dragging_(false) -{ - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - - QFontMetrics fm = fontMetrics(); - marker_height_ = fm.height(); - marker_width_ = QtUtils::QFontMetricsWidth(fm, "H"); - resize(marker_width_, marker_height_); - - - setContextMenuPolicy(Qt::CustomContextMenu); - - connect(this, &Marker::customContextMenuRequested, this, &Marker::ShowContextMenu); -} - -void Marker::SetActive(bool active) -{ - active_ = active; - - // Generaly the same functions delete both markers and timeline blocks. This helps ensure that - // markers and timeline blocks can never be selected at the same time - if (active) { - TimelinePanel *timeline = PanelManager::instance()->MostRecentlyFocused(); - if (timeline) { - timeline->timeline_widget()->DeselectAll(); - } - } - - update(); -} - -bool Marker::active() -{ - return active_; -} - -void Marker::paintEvent(QPaintEvent *event) -{ - QFontMetrics fm = fontMetrics(); - - int y = marker_height_; - - int half_width = marker_width_ / 2; - - int x = half_width; - - QPainter p(this); - if (active_) { - p.setPen(Qt::white); - } else { - p.setPen(Qt::black); - } - - QColor color = ColorCoding::GetColor(marker_color_).toQColor(); - p.setBrush(color); - - p.setRenderHint(QPainter::Antialiasing); - - int half_marker_height = marker_height_ / 3; - - QPoint points[] = { - QPoint(x, y), - QPoint(x - half_width, y - half_marker_height), - QPoint(x - half_width, y - marker_height_), - QPoint(x + 1 + half_width, y - marker_height_), - QPoint(x + 1 + half_width, y - half_marker_height), - QPoint(x + 1, y), - }; - - p.drawPolygon(points, 6); - - if (!name_.isEmpty()) { - resize(marker_width_ + fm.horizontalAdvance(name_) + fm.horizontalAdvance(" "), marker_height_); - - // Draw background rectangle - color.setAlphaF(0.5); - p.fillRect(x, y, fm.horizontalAdvance(name_) + fm.horizontalAdvance(" ") * 2 + half_width, -marker_height_, color); - - // Draw text - p.drawText(x + marker_width_, y - half_marker_height, name_); - } else { - resize(marker_width_, marker_height_); - } - - // In case this is a clip marker, redraw the timeline to update the clip marker - TimelinePanel* timeline = PanelManager::instance()->MostRecentlyFocused(); - if (timeline) { - timeline->timeline_widget()->Refresh(); - } -} - -void Marker::mousePressEvent(QMouseEvent* e) -{ - // Only select if clicking on the icon and not the label - if (e->pos().x() > marker_width_) { - return; - } - if (e->button() == Qt::LeftButton || e->button() == Qt::RightButton) { - SeekableParent()->SeekToScreenPoint(this->x() + 4); - if (!active_) { - if (e->modifiers() != Qt::ShiftModifier) { - SeekableParent()->DeselectAllMarkers(); - } - emit ActiveChanged(true); - } else { - if (e->modifiers() == Qt::ShiftModifier) { - emit ActiveChanged(false); - } - } - - update(); - } - - if (e->button() == Qt::LeftButton) { - click_position_ = e->globalPos(); - marker_start_x_ = x(); - drag_allowed_ = true; - } -} - -void Marker::mouseMoveEvent(QMouseEvent* e) -{ - if (drag_allowed_) { - dragging_ = true; - - int new_position = marker_start_x_ + e->globalPos().x() - click_position_.x(); - - rational marker_time = SeekableParent()->ScreenToTime(new_position); - - if (SeekableParent()->GetSnapService()) { - if (Core::instance()->snapping()) { - rational movement; - SeekableParent()->GetSnapService()->SnapPoint({marker_time}, &movement); - if (!movement.isNull()) { - marker_time += movement; - } - - new_position = SeekableParent()->TimeToScene(marker_time); - } - } - - if (new_position > -marker_width_ / 2 && new_position < SeekableParent()->width() - marker_width_ / 2) { - this->move(new_position-2, this->pos().y()); - repaint(); - } - } -} - -void Marker::mouseReleaseEvent(QMouseEvent* e) -{ - if (dragging_) { - rational time = SeekableParent()->SceneToTime(this->x() + 4); - time = time < 0.0 ? 0.0 : time; - emit TimeChanged(TimeRange(time, time)); - - dragging_ = false; - } - - drag_allowed_ = false; -} - -SeekableWidget* Marker::SeekableParent() -{ - return dynamic_cast(parent()); -} - -void Marker::ShowContextMenu() -{ - // Only show context menu if we clicked on the icon and not the label - QRect marker_icon(0, 0, marker_width_, marker_height_); - if (!marker_icon.contains(this->mapFromGlobal(QCursor::pos()))) { - return; - } - - Menu m(this); - - // Color menu - ColorLabelMenu color_coding_menu; - connect(&color_coding_menu, &ColorLabelMenu::ColorSelected, this, &Marker::ColorChanged); - m.addMenu(&color_coding_menu); - - m.addSeparator(); - MenuShared::instance()->AddItemsForEditMenu(&m, false); - - m.addSeparator(); - QAction rename; - rename.setText(tr("Rename")); - m.addAction(&rename); - connect(&rename, &QAction::triggered, this, &Marker::Rename); - - m.exec(QCursor::pos()); -} - -void Marker::Rename() -{ - bool ok; - QString marker_name = QInputDialog::getText(this, tr("Set Marker"), tr("Marker name:"), QLineEdit::Normal, name_, &ok); - if (ok) { - emit NameChanged(marker_name); - } -} - -void Marker::SetColor(int c) -{ - marker_color_ = c; - - update(); -} - -void Marker::SetName(QString s) -{ - name_ = s; - - update(); -} - -void Marker::SetTime(TimeRange time) -{ - move(SeekableParent()->TimeToScene(time.in())-2, y()); -} - -} // namespace olive diff --git a/app/widget/marker/marker.h b/app/widget/marker/marker.h deleted file mode 100644 index 972dbb05a..000000000 --- a/app/widget/marker/marker.h +++ /dev/null @@ -1,92 +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 MARKER_H -#define MARKER_H - -#include -#include -#include - -#include "common/define.h" -#include "common/timerange.h" -#include "widget/colorlabelmenu/colorlabelmenu.h" - -namespace olive { - -class SeekableWidget; - -class Marker : public QWidget { - Q_OBJECT - public: - Marker(QWidget* parent = nullptr); - - bool active(); - - void Rename(); - - SeekableWidget* SeekableParent(); - - public slots: - void SetColor(int c); - void SetActive(bool active); - void SetName(QString s); - void SetTime(TimeRange t); - - protected: - void paintEvent(QPaintEvent* event) override; - virtual void mousePressEvent(QMouseEvent* event) override; - virtual void mouseMoveEvent(QMouseEvent* event) override; - virtual void mouseReleaseEvent(QMouseEvent* event) override; - - signals: - void ColorChanged(int c); - void markerSelected(Marker* marker); - void ActiveChanged(bool active); - void NameChanged(QString name); - void TimeChanged(TimeRange time); - - private: - - int marker_color_; - - bool active_; - - QString name_; - - QPointF click_position_; - - int marker_start_x_; - - bool drag_allowed_; - - bool dragging_; - - int marker_height_; - - int marker_width_; - - private slots: - void ShowContextMenu(); -}; - -} // namespace olive - -#endif // MARKER_H diff --git a/app/widget/marker/markercopypaste.cpp b/app/widget/marker/markercopypaste.cpp deleted file mode 100644 index f5352907e..000000000 --- a/app/widget/marker/markercopypaste.cpp +++ /dev/null @@ -1,136 +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 "markercopypaste.h" - -#include - -#include "core.h" -#include "markerundo.h" -#include "widget/timebased/timebasedwidget.h" - -namespace olive { - -void MarkerCopyPasteService::CopyMarkersToClipboard(const QVector& markers, void* userdata) -{ - QString copy_str; - - QXmlStreamWriter writer(©_str); - writer.setAutoFormatting(true); - - writer.writeStartDocument(); - writer.writeStartElement(QStringLiteral("olive")); - - //writer.writeTextElement(QStringLiteral("version"), QString::number(Core::kProjectVersion)); - - writer.writeStartElement(QStringLiteral("markers")); - - // Get earliest marker for offset - rational min_start = RATIONAL_MAX; - foreach (TimelineMarker* marker, markers) { - if (marker->time().in() < min_start) { - min_start = marker->time().in(); - } - } - - foreach(TimelineMarker * marker, markers) { - // Cache marker in/out points - rational in = marker->time().in(); - rational out = marker->time().out(); - - // Temporarily set in/out to be relative to the earliest selected marker - marker->set_time(TimeRange(in - min_start, marker->time().out() - min_start)); - //marker->Save(&writer); - - // Reset in/out - marker->set_time(TimeRange(in, out)); - } - - - writer.writeEndElement(); // markers - writer.writeEndElement(); // olive - writer.writeEndDocument(); - - Core::CopyStringToClipboard(copy_str); -} - -void MarkerCopyPasteService::PasteMarkersFromClipboard(TimelineMarkerList* list, MultiUndoCommand* command, - rational offset) { - QString clipboard = Core::PasteStringFromClipboard(); - - if (clipboard.isEmpty()) { - return; - } - - QXmlStreamReader reader(clipboard); - uint data_version = 0; - - while (XMLReadNextStartElement(&reader)) { - if (reader.name() == QStringLiteral("olive")) { - while (XMLReadNextStartElement(&reader)) { - if (reader.name() == QStringLiteral("version")) { - data_version = reader.readElementText().toUInt(); - } else if (reader.name() == QStringLiteral("markers")) { - while (XMLReadNextStartElement(&reader)) { - if (data_version == 0) { - return; - } - QString name; - int color = -1; - rational in, out; - - XMLAttributeLoop((&reader), attr) { - if (attr.name() == QStringLiteral("name")) { - name = attr.value().toString(); - } - - if (attr.name() == QStringLiteral("color")) { - color = attr.value().toInt(); - } - - if (attr.name() == QStringLiteral("in")) { - in = rational::fromString(attr.value().toString()); - } - - if (attr.name() == QStringLiteral("out")) { - out = rational::fromString(attr.value().toString()); - } - } - - command->add_child(new MarkerAddCommand(Core::instance()->GetActiveProject(), - list, - TimeRange(in + offset, out + offset), - name, - color)); - } - } - } - } - } - - if (reader.hasError()) { - // If this was NOT an internal error, we assume it's an XML error that the user needs to know about - QMessageBox::critical((QWidget*)Core::instance()->main_window(), - QCoreApplication::translate("MarkerCopyPasteWidget", "Error pasting markers"), - QCoreApplication::translate("MarkerCopyPasteWidget", "Failed to paste markers: %1").arg(reader.errorString()), - QMessageBox::Ok); - - return; - } - - Core::instance()->undo_stack()->pushIfHasChildren(command); - -} -} //namespace olive diff --git a/app/widget/marker/markercopypaste.h b/app/widget/marker/markercopypaste.h deleted file mode 100644 index e0715fb40..000000000 --- a/app/widget/marker/markercopypaste.h +++ /dev/null @@ -1,42 +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 MARKERCOPYPASTEWIDGET_H -#define MARKERCOPYPASTEWIDGET_H - -#include -#include - -#include "widget/marker/marker.h" -#include "node/project/serializer/serializer.h" -#include "timeline/timelinemarker.h" -#include "undo/undocommand.h" - -namespace olive { - -class MarkerCopyPasteService -{ -public: - MarkerCopyPasteService() = default; - -protected: - void CopyMarkersToClipboard(const QVector &markers, void* userdata = nullptr); - - void PasteMarkersFromClipboard(TimelineMarkerList* list, MultiUndoCommand *command, rational offset); -}; - -} - -#endif // MARKERCOPYPASTEWIDGET_H diff --git a/app/widget/marker/markerundo.cpp b/app/widget/marker/markerundo.cpp deleted file mode 100644 index e75a0a68e..000000000 --- a/app/widget/marker/markerundo.cpp +++ /dev/null @@ -1,138 +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 "markerundo.h" - -namespace olive { - -MarkerAddCommand::MarkerAddCommand(Project *project, TimelineMarkerList *marker_list, const TimeRange &range, const QString &name, int color) : - project_(project), - marker_list_(marker_list), - range_(range), - name_(name), - color_(color) -{ -} - -Project* MarkerAddCommand::GetRelevantProject() const -{ - return project_; -} - -void MarkerAddCommand::redo() -{ - added_marker_ = marker_list_->AddMarker(range_, name_, color_); -} - -void MarkerAddCommand::undo() -{ - marker_list_->RemoveMarker(added_marker_); -} - -MarkerRemoveCommand::MarkerRemoveCommand(Project *project, TimelineMarker *marker, TimelineMarkerList *marker_list) : - project_(project), - marker_(marker), - marker_list_(marker_list), - range_(marker->time()), - name_(marker->name()), - color_(marker->color()) -{ -} - -Project* MarkerRemoveCommand::GetRelevantProject() const -{ - return project_; -} - -void MarkerRemoveCommand::redo() -{ - marker_list_->RemoveMarker(marker_); -} - -void MarkerRemoveCommand::undo() -{ - marker_list_->AddMarker(range_, name_, color_); -} - -MarkerChangeColorCommand::MarkerChangeColorCommand(Project *project, TimelineMarker *marker, int new_color) : - project_(project), - marker_(marker), - old_color_(marker->color()), - new_color_(new_color) -{ -} - -Project* MarkerChangeColorCommand::GetRelevantProject() const -{ - return project_; -} - -void MarkerChangeColorCommand::redo() -{ - marker_->set_color(new_color_); -} - -void MarkerChangeColorCommand::undo() -{ - marker_->set_color(old_color_); -} - -MarkerChangeNameCommand::MarkerChangeNameCommand(Project *project, TimelineMarker *marker, QString new_name) : - project_(project), - marker_(marker), - old_name_(marker->name()), - new_name_(new_name) -{ -} - -Project* MarkerChangeNameCommand::GetRelevantProject() const -{ - return project_; -} - -void MarkerChangeNameCommand::redo() -{ - marker_->set_name(new_name_); -} - -void MarkerChangeNameCommand::undo() -{ - marker_->set_name(old_name_); -} - -MarkerChangeTimeCommand::MarkerChangeTimeCommand(Project* project, TimelineMarker* marker, TimeRange time) : - project_(project), - marker_(marker), - old_time_(marker->time()), - new_time_(time) -{ -} - -Project* MarkerChangeTimeCommand::GetRelevantProject() const -{ - return project_; -} - -void MarkerChangeTimeCommand::redo() -{ - marker_->set_time(new_time_); -} - -void MarkerChangeTimeCommand::undo() -{ - marker_->set_time(old_time_); -} - -} diff --git a/app/widget/marker/markerundo.h b/app/widget/marker/markerundo.h deleted file mode 100644 index d5ef865bc..000000000 --- a/app/widget/marker/markerundo.h +++ /dev/null @@ -1,119 +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 MARKERUNDO_H -#define MARKERUNDO_H - -#include "undo/undocommand.h" -#include "timeline/timelinemarker.h" - -namespace olive { - -class MarkerAddCommand : public UndoCommand { - public: - MarkerAddCommand(Project* project, TimelineMarkerList* marker_list, const TimeRange& range, const QString& name, - int color = -1); - - virtual Project* GetRelevantProject() const override; - - protected: - virtual void redo() override; - virtual void undo() override; - - private: - Project* project_; - TimelineMarkerList* marker_list_; - TimeRange range_; - QString name_; - int color_; - - TimelineMarker* added_marker_; -}; - -class MarkerRemoveCommand : public UndoCommand { - public: - MarkerRemoveCommand(Project* project, TimelineMarker* marker, TimelineMarkerList* marker_list); - - virtual Project* GetRelevantProject() const override; - - protected: - virtual void redo() override; - virtual void undo() override; - - private: - Project* project_; - TimelineMarker* marker_; - TimelineMarkerList* marker_list_; - TimeRange range_; - QString name_; - int color_; -}; - -class MarkerChangeColorCommand : public UndoCommand { - public: - MarkerChangeColorCommand(Project* project, TimelineMarker* marker, int new_color); - - virtual Project* GetRelevantProject() const override; - - protected: - virtual void redo() override; - virtual void undo() override; - - private: - Project* project_; - TimelineMarker* marker_; - int old_color_; - int new_color_; -}; - -class MarkerChangeNameCommand : public UndoCommand { - public: - MarkerChangeNameCommand(Project* project, TimelineMarker* marker, QString name); - - virtual Project* GetRelevantProject() const override; - - protected: - virtual void redo() override; - virtual void undo() override; - - private: - Project* project_; - TimelineMarker* marker_; - QString old_name_; - QString new_name_; -}; - -class MarkerChangeTimeCommand : public UndoCommand { - public: - MarkerChangeTimeCommand(Project* project, TimelineMarker* marker, TimeRange time); - - virtual Project* GetRelevantProject() const override; - - protected: - virtual void redo() override; - virtual void undo() override; - - private: - Project* project_; - TimelineMarker* marker_; - TimeRange old_time_; - TimeRange new_time_; - - -}; - -} - -#endif // TIMELINEUNDOTRACK_H diff --git a/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp b/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp index e45b3d1fc..59d50d0f1 100644 --- a/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp +++ b/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp @@ -77,7 +77,7 @@ void ResizableTimelineScrollBar::paintEvent(QPaintEvent *event) if (points_ && !timebase().isNull() - && (points_->workarea()->enabled() || !points_->markers()->list().isEmpty())) { + && (points_->workarea()->enabled() || !points_->markers()->list().empty())) { QStyleOptionSlider opt; initStyleOption(&opt); @@ -110,7 +110,7 @@ void ResizableTimelineScrollBar::paintEvent(QPaintEvent *event) workarea_color); } - if (!points_->markers()->list().isEmpty()) { + if (!points_->markers()->list().empty()) { foreach (TimelineMarker* marker, points_->markers()->list()) { QColor marker_color = ColorCoding::GetColor(marker->color()).toQColor(); int64_t in = qRound64(ratio * TimeToScene(marker->time().in())); diff --git a/app/widget/timebased/timebasedview.h b/app/widget/timebased/timebasedview.h index a51728afb..4bca7f9da 100644 --- a/app/widget/timebased/timebasedview.h +++ b/app/widget/timebased/timebasedview.h @@ -43,6 +43,9 @@ public: return snapped_; } + const rational &GetTime() const { return playhead_; } + + SnapService *GetSnapService() const { return snap_service_; } void SetSnapService(SnapService* service); const double& GetYScale() const; @@ -85,11 +88,6 @@ protected: virtual void ZoomIntoCursorPosition(QWheelEvent *event, double multiplier, const QPointF &cursor_pos) override; - const rational &GetPlayheadTime() const - { - return playhead_; - } - bool PlayheadPress(QMouseEvent* event); bool PlayheadMove(QMouseEvent* event); bool PlayheadRelease(QMouseEvent* event); diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index 23da400ce..b28398b45 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -27,7 +27,6 @@ #include "config/config.h" #include "core.h" #include "node/project/sequence/sequence.h" -#include "widget/marker/markerundo.h" #include "widget/timelinewidget/undo/timelineundoworkarea.h" namespace olive { @@ -40,7 +39,7 @@ TimeBasedWidget::TimeBasedWidget(bool ruler_text_visible, bool ruler_cache_statu auto_set_timebase_(true) { ruler_ = new TimeRuler(ruler_text_visible, ruler_cache_status_visible, this); - connect(ruler_, &TimeRuler::TimeChanged, this, &TimeBasedWidget::SetTimeAndSignal); + ConnectTimelineView(ruler_, true); scrollbar_ = new ResizableTimelineScrollBar(Qt::Horizontal, this); connect(scrollbar_, &ResizableScrollBar::ResizeBegan, this, &TimeBasedWidget::ScrollBarResizeBegan); @@ -593,14 +592,25 @@ void TimeBasedWidget::SetMarker() QString marker_name; if (Config::Current()[QStringLiteral("SetNameWithMarker")].toBool()) { - marker_name = QInputDialog::getText(this, tr("Set Marker"), tr("Marker name:"), QLineEdit::Normal, QString(), &ok); + marker_name = QInputDialog::getText(this, tr("Add Marker"), tr("Name:"), QLineEdit::Normal, QString(), &ok); } else { ok = true; } if (ok) { - Core::instance()->undo_stack()->push(new MarkerAddCommand(GetConnectedNode()->project(), - GetConnectedNode()->GetTimelinePoints()->markers(), TimeRange(GetTime(), GetTime()), marker_name)); + int color; + + TimelineMarkerList *markers = GetConnectedNode()->GetTimelinePoints()->markers(); + + if (!markers->list().empty()) { + // Use last invoked color if applicable + color = markers->list().back()->color(); + } else { + // Fallback to default color in preferences + color = Config::Current()[QStringLiteral("MarkerColor")].toInt(); + } + + Core::instance()->undo_stack()->push(new MarkerAddCommand(markers, TimeRange(GetTime(), GetTime()), marker_name, color)); } } diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index c5a89bc2c..0cb52f29a 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -398,15 +398,6 @@ void TimelineWidget::DeselectAll() SignalDeselectedAllBlocks(); } -bool TimelineWidget::MarkersActive() -{ - if (ruler()->GetActiveTimelineMarkers().size() > 0) { - return true; - } else { - return false; - } -} - void TimelineWidget::RippleToIn() { RippleTo(Timeline::kTrimIn); @@ -505,10 +496,11 @@ void TimelineWidget::ReplaceBlocksWithGaps(const QVector &blocks, void TimelineWidget::DeleteSelected(bool ripple) { - if (MarkersActive()) { + if (ruler()->hasFocus()) { ruler()->DeleteSelected(); return; } + QVector selected_list = GetSelectedBlocks(); QVector blocks_to_delete; @@ -641,11 +633,10 @@ void TimelineWidget::CopySelected(bool cut) return; } - if (MarkersActive()) { + if (ruler()->hasFocus()) { ruler()->CopySelected(cut); return; } - QVector selected = GetSelectedBlocks(); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 18ac70365..e96b657a4 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -59,8 +59,6 @@ public: void DeselectAll(); - bool MarkersActive(); - void RippleToIn(); void RippleToOut(); diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 2bd1b5a31..d8b8a28c2 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -530,19 +530,19 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q } // Draw markers - if (clip->connected_viewer() && !clip->connected_viewer()->GetLength().isNull()) { + qDebug() << "MARKERS ON CLIP STUB"; + /*if (clip->connected_viewer() && !clip->connected_viewer()->GetLength().isNull()) { if (clip->connected_viewer()->GetTimelinePoints()->markers()->list().size() > 0) { - QList marker_list = clip->connected_viewer()->GetTimelinePoints()->markers()->list(); - + std::vector marker_list = clip->connected_viewer()->GetTimelinePoints()->markers()->list(); + int marker_width = QtUtils::QFontMetricsWidth(fm, "H"); clip_marker_positions_.clear(); // Only draw markers if the block UI is large enough to draw all the markers - if (marker_list.length() * marker_width < block_right - block_left) { + if (marker_list.size() * marker_width < block_right - block_left) { - QListIterator iterator(marker_list); - while(iterator.hasNext()) { - TimelineMarker *marker = iterator.next(); + for (auto it=marker_list.cbegin(); it!=marker_list.cend(); it++) { + TimelineMarker *marker = *it; // Make sure marker is within In/Out points of the clip if (marker->time().in() >= clip->media_in() && marker->time().out() <= clip->media_in() + clip->length()) { // Only draw names that we have room for @@ -561,7 +561,7 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q } } } - } + }*/ } // For transitions, show lines representing a transition diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 1e46da75e..467a9f649 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -26,17 +26,17 @@ #include "common/qtutils.h" #include "core.h" -#include "widget/marker/markerundo.h" #include "widget/timebased/timebasedwidget.h" namespace olive { +#define super TimeBasedView + SeekableWidget::SeekableWidget(QWidget* parent) : - TimelineScaledWidget(parent), + super(parent), timeline_points_(nullptr), - scroll_(0), - snap_service_(nullptr), - dragging_(false) + dragging_(false), + selection_manager_(this) { QFontMetrics fm = fontMetrics(); @@ -46,19 +46,18 @@ SeekableWidget::SeekableWidget(QWidget* parent) : playhead_width_ = QtUtils::QFontMetricsWidth(fm, "H"); setContextMenuPolicy(Qt::CustomContextMenu); + setFocusPolicy(Qt::ClickFocus); } void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) { if (timeline_points_) { + selection_manager_.ClearSelection(); + disconnect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast(&SeekableWidget::update)); disconnect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&SeekableWidget::update)); disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&SeekableWidget::update)); disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&SeekableWidget::update)); - - foreach(Marker* marker_widget, marker_map_.values()) { - marker_widget->deleteLater(); - } } timeline_points_ = points; @@ -68,47 +67,26 @@ void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) connect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&SeekableWidget::update)); connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&SeekableWidget::update)); connect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&SeekableWidget::update)); - - connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, &SeekableWidget::addMarker); - connect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, &SeekableWidget::removeMarker); } - if (timeline_points() && !timeline_points()->markers()->list().isEmpty()) { - foreach (TimelineMarker *marker, timeline_points()->markers()->list()) { - if (!marker_map_.keys().contains(marker)) { - addMarker(marker); - } - } - } - - updateMarkerPositions(); - - update(); + viewport()->update(); } -void SeekableWidget::SetSnapService(SnapService *service) +void SeekableWidget::DeleteSelected() { - snap_service_ = service; -} - -void SeekableWidget::DeleteSelected() { MultiUndoCommand* command = new MultiUndoCommand(); - foreach (TimelineMarker *marker, GetActiveTimelineMarkers()) { - command->add_child(new MarkerRemoveCommand(Core::instance()->GetActiveProject(), marker, timeline_points_->markers())); + foreach (TimelineMarker *marker, selection_manager_.GetSelectedObjects()) { + command->add_child(new MarkerRemoveCommand(marker)); } Core::instance()->undo_stack()->pushIfHasChildren(command); } -const int &SeekableWidget::GetScroll() const -{ - return scroll_; -} - void SeekableWidget::CopySelected(bool cut) { - CopyMarkersToClipboard(GetActiveTimelineMarkers()); + qDebug() << "COPY IS STUB"; + //CopyMarkersToClipboard(selection_manager_.GetSelectedObjects()); if (cut) { DeleteSelected(); @@ -117,14 +95,15 @@ void SeekableWidget::CopySelected(bool cut) void SeekableWidget::PasteMarkers(bool insert, rational insert_time) { - MultiUndoCommand *command = new MultiUndoCommand(); - PasteMarkersFromClipboard(timeline_points()->markers(), command, insert_time); + //MultiUndoCommand *command = new MultiUndoCommand(); + //PasteMarkersFromClipboard(timeline_points()->markers(), command, insert_time); + qDebug() << "PASTE IS STUB"; } void SeekableWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { - SeekToScreenPoint(event->pos().x()); + SeekToScenePoint(mapToScene(event->pos()).x()); dragging_ = true; DeselectAllMarkers(); @@ -134,7 +113,7 @@ void SeekableWidget::mousePressEvent(QMouseEvent *event) void SeekableWidget::mouseMoveEvent(QMouseEvent *event) { if (event->buttons() & Qt::LeftButton) { - SeekToScreenPoint(event->pos().x()); + SeekToScenePoint(mapToScene(event->pos()).x()); } } @@ -142,18 +121,19 @@ void SeekableWidget::mouseReleaseEvent(QMouseEvent *event) { Q_UNUSED(event) - if (snap_service_) { - snap_service_->HideSnaps(); + if (GetSnapService()) { + GetSnapService()->HideSnaps(); } dragging_ = false; } -void SeekableWidget::ScaleChangedEvent(const double &) +void SeekableWidget::focusOutEvent(QFocusEvent *event) { - updateMarkerPositions(); + super::focusOutEvent(event); - update(); + // Deselect everything when we lose focus + DeselectAllMarkers(); } TimelinePoints *SeekableWidget::timeline_points() const @@ -161,131 +141,38 @@ TimelinePoints *SeekableWidget::timeline_points() const return timeline_points_; } -void SeekableWidget::SetTime(const rational &r) -{ - time_ = r; - - updateMarkerPositions(); - - update(); -} - -void SeekableWidget::SetScroll(int s) -{ - scroll_ = s; - - updateMarkerPositions(); - - update(); -} - -QVector SeekableWidget::GetActiveTimelineMarkers() { - QVector active_timelineMarkers; - foreach (TimelineMarker *marker, timeline_points()->markers()->list()) { - if (marker->active()) { - active_timelineMarkers.append(marker); - } - } - - return active_timelineMarkers; -} - - void SeekableWidget::DeselectAllMarkers() { - if (timebase().isNull()) { - return; - } - if (!timeline_points()) { - return; - } - foreach(TimelineMarker* marker, timeline_points()->markers()->list()) { - marker->set_active(false); - } -} + selection_manager_.ClearSelection(); -void SeekableWidget::addMarker(TimelineMarker* marker) -{ - if (!marker_map_.contains(marker)) { - Marker *marker_widget = new Marker(this); - marker_map_.insert(marker, marker_widget); - - /* - Markers are stored as TimelineMarkers and represented in the UI as Markers. As a single - TimelineMarker can be represented in various views it is necessary to make sure all instances - of the TimelineMarker's (UI) Markers are kept in sync. To do this, whenever a Marker is updated - in some way, it signals that change to the relevant TimelineMarker which in turn broadcasts - that update out to all the relevant Markers. - */ - - connect(marker_widget, &Marker::ColorChanged, this, &SeekableWidget::SetMarkerColor); - connect(marker, &TimelineMarker::ColorChanged, marker_widget, &Marker::SetColor); - - connect(marker_widget, &Marker::ActiveChanged, marker, &TimelineMarker::set_active); - connect(marker, &TimelineMarker::ActiveChanged, marker_widget, &Marker::SetActive); - - connect(marker_widget, &Marker::NameChanged, marker, &TimelineMarker::set_name_undo); - connect(marker, &TimelineMarker::NameChanged, marker_widget, &Marker::SetName); - - connect(marker_widget, &Marker::TimeChanged, marker, &TimelineMarker::set_time_undo); - connect(marker, &TimelineMarker::TimeChanged, marker_widget, &Marker::SetTime); - - marker_widget->move(TimeToScreen(marker->time().in())-2, text_height_); - marker_widget->SetColor(marker->color()); - marker_widget->SetName(marker->name()); - marker_widget->show(); - } -} - -void SeekableWidget::removeMarker(TimelineMarker *marker) -{ - marker_map_.value(marker)->deleteLater(); - marker_map_.take(marker); + viewport()->update(); } void SeekableWidget::SetMarkerColor(int c) { MultiUndoCommand *command = new MultiUndoCommand(); - foreach(TimelineMarker* marker, GetActiveTimelineMarkers()) { - command->add_child(new MarkerChangeColorCommand(Core::instance()->GetActiveProject(), marker, c)); + foreach(TimelineMarker* marker, selection_manager_.GetSelectedObjects()) { + command->add_child(new MarkerChangeColorCommand(marker, c)); } Core::instance()->undo_stack()->pushIfHasChildren(command); } -void SeekableWidget::updateMarkerPositions() -{ - foreach (TimelineMarker* marker, marker_map_.keys()) { - Marker *m = marker_map_.value(marker); - m->move(TimeToScreen(marker->time().in())-2, text_height_); - } -} - -int SeekableWidget::TimeToScreen(const rational &time) const -{ - return qFloor(TimeToScene(time)) - scroll_; -} - -rational SeekableWidget::ScreenToTime(int x) const -{ - return qMax(rational(0), SceneToTime(x + scroll_)); -} - -void SeekableWidget::SeekToScreenPoint(int screen) +void SeekableWidget::SeekToScenePoint(qreal scene) { if (timebase().isNull()) { return; } - rational playhead_time = ScreenToTime(screen); + rational playhead_time = SceneToTime(scene); - if (Core::instance()->snapping() && snap_service_) { + if (Core::instance()->snapping() && GetSnapService()) { rational movement; - snap_service_->SnapPoint({playhead_time}, - &movement, - SnapService::kSnapAll & ~SnapService::kSnapToPlayhead); + GetSnapService()->SnapPoint({playhead_time}, + &movement, + SnapService::kSnapAll & ~SnapService::kSnapToPlayhead); playhead_time += movement; } @@ -305,20 +192,20 @@ void SeekableWidget::DrawTimelinePoints(QPainter* p, int marker_bottom) // Draw in/out workarea if (timeline_points()->workarea()->enabled()) { - int workarea_left = qMax(0, TimeToScreen(timeline_points()->workarea()->in())); + int workarea_left = qMax(0.0, TimeToScene(timeline_points()->workarea()->in())); int workarea_right; if (timeline_points()->workarea()->out() == TimelineWorkArea::kResetOut) { workarea_right = width(); } else { - workarea_right = qMin(width(), TimeToScreen(timeline_points()->workarea()->out())); + workarea_right = qMin(qreal(width()), TimeToScene(timeline_points()->workarea()->out())); } p->fillRect(workarea_left, 0, workarea_right - workarea_left, height(), palette().highlight()); } // Draw markers - if (marker_bottom > 0 && !timeline_points()->markers()->list().isEmpty()) { + if (marker_bottom > 0 && !timeline_points()->markers()->list().empty()) { int marker_top = marker_bottom - text_height_; @@ -327,8 +214,8 @@ void SeekableWidget::DrawTimelinePoints(QPainter* p, int marker_bottom) p->setBrush(Qt::green); foreach (TimelineMarker* marker, timeline_points()->markers()->list()) { - int marker_left = TimeToScreen(marker->time().in()); - int marker_right = TimeToScreen(marker->time().out()); + int marker_left = TimeToScene(marker->time().in()); + int marker_right = TimeToScene(marker->time().out()); if (marker_left >= width() || marker_right < 0) { continue; diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index cbbcf939f..56d6f2660 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -22,35 +22,29 @@ #define SEEKABLEWIDGET_H #include +#include #include "common/rational.h" #include "timeline/timelinepoints.h" #include "widget/snapservice/snapservice.h" -#include "widget/timebased/timescaledobject.h" -#include "widget/marker/marker.h" -#include "widget/marker/markercopypaste.h" +#include "widget/timebased/timebasedviewselectionmanager.h" +//#include "widget/marker/markercopypaste.h" namespace olive { -class SeekableWidget : public TimelineScaledWidget, public MarkerCopyPasteService +class SeekableWidget : public TimeBasedView//, public MarkerCopyPasteService { Q_OBJECT public: SeekableWidget(QWidget *parent = nullptr); - const rational& GetTime() const + int GetScroll() const { - return time_; + return horizontalScrollBar()->value(); } - const int& GetScroll() const; - void ConnectTimelinePoints(TimelinePoints* points); - void SetSnapService(SnapService* service); - - SnapService* GetSnapService() { return snap_service_; }; - bool IsDraggingPlayhead() const { return dragging_; @@ -62,35 +56,28 @@ public: void PasteMarkers(bool insert, rational insert_time); - QVector GetActiveTimelineMarkers(); - void DeselectAllMarkers(); - void SeekToScreenPoint(int screen); - - int TimeToScreen(const rational& time) const; - rational ScreenToTime(int x) const; + void SeekToScenePoint(qreal scene); public slots: - void SetTime(const rational &r); - - void SetScroll(int s); - - void addMarker(TimelineMarker* marker); - - void removeMarker(TimelineMarker* marker); - - void updateMarkerPositions(); + void SetScroll(int i) + { + horizontalScrollBar()->setValue(i); + } void SetMarkerColor(int c); -protected: + /*void SetMarkerTime(); + void SetMarkerName();*/ + +protected: virtual void mousePressEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override; virtual void mouseReleaseEvent(QMouseEvent *event) override; - virtual void ScaleChangedEvent(const double&) override; + virtual void focusOutEvent(QFocusEvent *event) override; void DrawTimelinePoints(QPainter *p, int marker_bottom = 0); @@ -106,30 +93,16 @@ protected: return playhead_width_; } -signals: - /** - * @brief Signal emitted whenever the time changes on this ruler, either by user or programmatically - */ - void TimeChanged(const rational &time); - private: - rational time_; - TimelinePoints* timeline_points_; - int scroll_; - int text_height_; int playhead_width_; - SnapService* snap_service_; - bool dragging_; - QMap marker_map_; - - QMap active_markers_map_; + TimeBasedViewSelectionManager selection_manager_; }; diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp index db887fe7e..ff2b3281d 100644 --- a/app/widget/timeruler/timeruler.cpp +++ b/app/widget/timeruler/timeruler.cpp @@ -58,6 +58,17 @@ TimeRuler::TimeRuler(bool text_visible, bool cache_status_visible, QWidget* pare // Connect context menu connect(this, &TimeRuler::customContextMenuRequested, this, &TimeRuler::ShowContextMenu); + + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + //horizontalScrollBar()->setVisible(false); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setBackgroundRole(QPalette::Window); + setFrameShape(QFrame::NoFrame); + + // NOTE: One day it might be preferable to use AlignBottom because the lines are anchored to + // the bottom of the widget. However, for now this makes sense since we just ported this + // from a QWidget's paintEvent. + setAlignment(Qt::AlignLeft | Qt::AlignTop); } void TimeRuler::SetPlaybackCache(PlaybackCache *cache) @@ -83,15 +94,13 @@ void TimeRuler::SetPlaybackCache(PlaybackCache *cache) update(); } -void TimeRuler::paintEvent(QPaintEvent *) +void TimeRuler::drawForeground(QPainter *p, const QRectF &rect) { // Nothing to paint if the timebase is invalid if (timebase().isNull()) { return; } - QPainter p(this); - // Draw timeline points if connected if (timeline_points()) { int marker_bottom = height() - text_height(); @@ -104,7 +113,7 @@ void TimeRuler::paintEvent(QPaintEvent *) marker_bottom -= cache_status_height_; } - DrawTimelinePoints(&p, marker_bottom); + DrawTimelinePoints(p, marker_bottom); } double width_of_frame = timebase_dbl() * GetScale(); @@ -173,11 +182,11 @@ void TimeRuler::paintEvent(QPaintEvent *) } // Set line color to main text color - p.setBrush(Qt::NoBrush); - p.setPen(palette().text().color()); + p->setBrush(Qt::NoBrush); + p->setPen(palette().text().color()); // Calculate line dimensions - QFontMetrics fm = p.fontMetrics(); + QFontMetrics fm = p->fontMetrics(); int line_bottom = height(); if (show_cache_status_) { @@ -197,8 +206,8 @@ void TimeRuler::paintEvent(QPaintEvent *) // FIXME: Hardcoded number const int kAverageTextWidth = 200; - for (int i=-kAverageTextWidth;i(i + GetScroll()); + for (int i=GetScroll()-kAverageTextWidth;i(i); if (long_interval > -1) { int this_long_unit = qFloor(screen_pt/long_interval); @@ -208,7 +217,7 @@ void TimeRuler::paintEvent(QPaintEvent *) if (text_visible_) { QRect text_rect; Qt::Alignment text_align; - QString timecode_str = Timecode::time_to_timecode(ScreenToTime(i), timebase(), Core::instance()->GetTimecodeDisplay()); + QString timecode_str = Timecode::time_to_timecode(SceneToTime(i), timebase(), Core::instance()->GetTimecodeDisplay()); int timecode_width = QtUtils::QFontMetricsWidth(fm, timecode_str); int timecode_left; @@ -226,9 +235,9 @@ void TimeRuler::paintEvent(QPaintEvent *) } if (timecode_left > last_text_draw) { - p.drawText(text_rect, - static_cast(text_align), - timecode_str); + p->drawText(text_rect, + static_cast(text_align), + timecode_str); last_text_draw = timecode_left + timecode_width; @@ -238,7 +247,7 @@ void TimeRuler::paintEvent(QPaintEvent *) } } - p.drawLine(i, line_y, i, line_bottom); + p->drawLine(i, line_y, i, line_bottom); last_long_unit = this_long_unit; } } @@ -246,7 +255,7 @@ void TimeRuler::paintEvent(QPaintEvent *) if (short_interval > -1) { int this_short_unit = qFloor(screen_pt/short_interval); if (this_short_unit != last_short_unit) { - p.drawLine(i, short_y, i, line_bottom); + p->drawLine(i, short_y, i, line_bottom); last_short_unit = this_short_unit; } } @@ -257,40 +266,40 @@ void TimeRuler::paintEvent(QPaintEvent *) // FIXME: Hardcoded to get video length, if we ever need audio length, this will have to change rational len = playback_cache_->viewer_parent()->GetVideoLength(); - int cache_screen_length = qMin(TimeToScreen(len), width()); + int cache_screen_length = qMin(TimeToScene(len), qreal(width())); if (cache_screen_length > 0) { int cache_y = height() - cache_status_height_; - p.fillRect(0, cache_y, cache_screen_length , cache_status_height_, Qt::green); + p->fillRect(0, cache_y, cache_screen_length , cache_status_height_, Qt::green); foreach (const TimeRange& range, playback_cache_->GetInvalidatedRanges(len)) { - int range_left = TimeToScreen(range.in()); + int range_left = TimeToScene(range.in()); if (range_left >= width()) { continue; } - int range_right = TimeToScreen(range.out()); + int range_right = TimeToScene(range.out()); if (range_right < 0) { continue; } int adjusted_left = qMax(0, range_left); - p.fillRect(adjusted_left, - cache_y, - qMin(width(), range_right) - adjusted_left, - cache_status_height_, - Qt::red); + p->fillRect(adjusted_left, + cache_y, + qMin(width(), range_right) - adjusted_left, + cache_status_height_, + Qt::red); } } } // Draw the playhead if it's on screen at the moment - int playhead_pos = TimeToScreen(GetTime()); - p.setPen(Qt::NoPen); - p.setBrush(PLAYHEAD_COLOR); - DrawPlayhead(&p, playhead_pos, line_bottom); + int playhead_pos = TimeToScene(GetTime()); + p->setPen(Qt::NoPen); + p->setBrush(PLAYHEAD_COLOR); + DrawPlayhead(p, playhead_pos, line_bottom); } void TimeRuler::TimebaseChangedEvent(const rational &tb) diff --git a/app/widget/timeruler/timeruler.h b/app/widget/timeruler/timeruler.h index 496117238..3a475d398 100644 --- a/app/widget/timeruler/timeruler.h +++ b/app/widget/timeruler/timeruler.h @@ -41,7 +41,7 @@ public: void SetPlaybackCache(PlaybackCache* cache); protected: - virtual void paintEvent(QPaintEvent* e) override; + virtual void drawForeground(QPainter *painter, const QRectF &rect) override; virtual void TimebaseChangedEvent(const rational& tb) override; diff --git a/app/widget/viewer/audiowaveformview.cpp b/app/widget/viewer/audiowaveformview.cpp index 198cff332..59f1dc4f6 100644 --- a/app/widget/viewer/audiowaveformview.cpp +++ b/app/widget/viewer/audiowaveformview.cpp @@ -38,6 +38,13 @@ AudioWaveformView::AudioWaveformView(QWidget *parent) : { setAutoFillBackground(true); setBackgroundRole(QPalette::Base); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + // NOTE: At some point it might make sense for this to be AlignCenter since the waveform + // originates from the center. But we're leaving it top/left for now since it was just + // ported from a QWidget's paintEvent. + setAlignment(Qt::AlignLeft | Qt::AlignTop); } void AudioWaveformView::SetViewer(AudioPlaybackCache *playback) @@ -60,9 +67,9 @@ void AudioWaveformView::SetViewer(AudioPlaybackCache *playback) } } -void AudioWaveformView::paintEvent(QPaintEvent *event) +void AudioWaveformView::drawForeground(QPainter *p, const QRectF &rect) { - super::paintEvent(event); + super::drawForeground(p, rect); if (!playback_) { return; @@ -74,20 +81,18 @@ void AudioWaveformView::paintEvent(QPaintEvent *event) return; } - QPainter p(this); - // Draw in/out points - DrawTimelinePoints(&p); + DrawTimelinePoints(p); // Draw waveform - p.setPen(QColor(64, 255, 160)); // FIXME: Hardcoded color - AudioVisualWaveform::DrawWaveform(&p, rect(), GetScale(), playback_->visual(), SceneToTime(GetScroll())); + p->setPen(QColor(64, 255, 160)); // FIXME: Hardcoded color + AudioVisualWaveform::DrawWaveform(p, rect.toRect(), GetScale(), playback_->visual(), SceneToTime(GetScroll())); // Draw playhead - p.setPen(PLAYHEAD_COLOR); + p->setPen(PLAYHEAD_COLOR); - int playhead_x = TimeToScreen(GetTime()); - p.drawLine(playhead_x, 0, playhead_x, height()); + int playhead_x = TimeToScene(GetTime()); + p->drawLine(playhead_x, 0, playhead_x, height()); } } diff --git a/app/widget/viewer/audiowaveformview.h b/app/widget/viewer/audiowaveformview.h index b89e56224..92f34d80e 100644 --- a/app/widget/viewer/audiowaveformview.h +++ b/app/widget/viewer/audiowaveformview.h @@ -39,7 +39,7 @@ public: void SetViewer(AudioPlaybackCache *playback); protected: - virtual void paintEvent(QPaintEvent* event) override; + virtual void drawForeground(QPainter *painter, const QRectF &rect) override; private: QThreadPool pool_; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index d589b409f..e5eae2440 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -97,6 +97,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) : // Create waveform view when audio is connected and video isn't waveform_view_ = new AudioWaveformView(); + ConnectTimelineView(waveform_view_, true); PassWheelEventsToScrollBar(waveform_view_); stack_->addWidget(waveform_view_); @@ -125,7 +126,6 @@ ViewerWidget::ViewerWidget(QWidget *parent) : SetScale(48.0); // Ensures that seeking on the waveform view updates the time as expected - connect(waveform_view_, &AudioWaveformView::TimeChanged, this, &ViewerWidget::SetTimeAndSignal); connect(waveform_view_, &AudioWaveformView::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu); connect(&playback_backup_timer_, &QTimer::timeout, this, &ViewerWidget::PlaybackTimerUpdate); From 6a0b5856c90f113cafd2a2fd2f0541fa728b1fd0 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 1 May 2022 21:17:22 -0700 Subject: [PATCH 70/81] various: extended marker and timeline functionality --- app/common/rational.cpp | 2 +- app/dialog/CMakeLists.txt | 1 + app/dialog/markerproperties/CMakeLists.txt | 22 +++ .../markerpropertiesdialog.cpp | 152 ++++++++++++++++ .../markerproperties/markerpropertiesdialog.h | 78 ++++++++ app/node/block/clip/clip.cpp | 20 ++- app/node/keyframe.cpp | 6 + app/node/keyframe.h | 2 + app/node/output/viewer/viewer.cpp | 2 + app/node/output/viewer/viewer.h | 4 +- .../project/serializer/serializer220403.cpp | 14 +- app/timeline/timelinemarker.cpp | 166 ++++++++++++++++-- app/timeline/timelinemarker.h | 67 ++++++- .../resizabletimelinescrollbar.cpp | 14 +- .../timebased/timebasedviewselectionmanager.h | 54 +++--- app/widget/timebased/timebasedwidget.cpp | 38 ++-- app/widget/timelinewidget/timelinewidget.cpp | 14 +- .../timelinewidget/view/timelineview.cpp | 121 ++++--------- app/widget/timelinewidget/view/timelineview.h | 2 - app/widget/timeruler/seekablewidget.cpp | 160 ++++++++++++----- app/widget/timeruler/seekablewidget.h | 25 ++- app/widget/timeruler/timeruler.cpp | 51 +++--- app/widget/timeruler/timeruler.h | 6 +- 23 files changed, 781 insertions(+), 240 deletions(-) create mode 100644 app/dialog/markerproperties/CMakeLists.txt create mode 100644 app/dialog/markerproperties/markerpropertiesdialog.cpp create mode 100644 app/dialog/markerproperties/markerpropertiesdialog.h diff --git a/app/common/rational.cpp b/app/common/rational.cpp index e7020f92b..d7d374230 100644 --- a/app/common/rational.cpp +++ b/app/common/rational.cpp @@ -16,7 +16,7 @@ rational rational::fromDouble(const double &flt, bool* ok) } // Use FFmpeg function for the time being - AVRational r = av_d2q(flt, INT_MAX); + AVRational r = av_d2q(flt, 65535); if (r.den == 0) { // If den == 0, we were unable to convert to a rational diff --git a/app/dialog/CMakeLists.txt b/app/dialog/CMakeLists.txt index a7ecf0f78..1f9df6e3d 100644 --- a/app/dialog/CMakeLists.txt +++ b/app/dialog/CMakeLists.txt @@ -24,6 +24,7 @@ add_subdirectory(export) add_subdirectory(footageproperties) add_subdirectory(footagerelink) add_subdirectory(keyframeproperties) +add_subdirectory(markerproperties) if(OpenTimelineIO_FOUND) add_subdirectory(otioproperties) endif() diff --git a/app/dialog/markerproperties/CMakeLists.txt b/app/dialog/markerproperties/CMakeLists.txt new file mode 100644 index 000000000..222c9246e --- /dev/null +++ b/app/dialog/markerproperties/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/markerproperties/markerpropertiesdialog.h + dialog/markerproperties/markerpropertiesdialog.cpp + PARENT_SCOPE +) diff --git a/app/dialog/markerproperties/markerpropertiesdialog.cpp b/app/dialog/markerproperties/markerpropertiesdialog.cpp new file mode 100644 index 000000000..5473af3ab --- /dev/null +++ b/app/dialog/markerproperties/markerpropertiesdialog.cpp @@ -0,0 +1,152 @@ +/*** + + 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 "markerpropertiesdialog.h" + +#include +#include +#include +#include +#include + +#include "core.h" + +namespace olive { + +#define super QDialog + +MarkerPropertiesDialog::MarkerPropertiesDialog(const QVector &markers, const rational &timebase, QWidget *parent) : + super(parent), + markers_(markers) +{ + QGridLayout *layout = new QGridLayout(this); + + int row = 0; + + QGroupBox *time_group = new QGroupBox(tr("Time")); + QGridLayout *time_layout = new QGridLayout(time_group); + + { + int time_row = 0; + + time_layout->addWidget(new QLabel(tr("In:")), time_row, 0); + + in_slider_ = new RationalSlider(); + time_layout->addWidget(in_slider_, time_row, 1); + + time_row++; + + time_layout->addWidget(new QLabel(tr("Out:")), time_row, 0); + + out_slider_ = new RationalSlider(); + time_layout->addWidget(out_slider_, time_row, 1); + } + + if (markers.size() == 1) { + in_slider_->SetValue(markers.first()->time_range().in()); + in_slider_->SetDisplayType(RationalSlider::kTime); + in_slider_->SetTimebase(timebase); + out_slider_->SetValue(markers.first()->time_range().out()); + out_slider_->SetDisplayType(RationalSlider::kTime); + out_slider_->SetTimebase(timebase); + } else { + // Markers cannot be on the same time, so we disable setting time if multiple markers are selected + in_slider_->setEnabled(false); + in_slider_->SetTristate(); + out_slider_->setEnabled(false); + out_slider_->SetTristate(); + } + + layout->addWidget(time_group, row, 0, 1, 2); + + row++; + + layout->addWidget(new QLabel(tr("Color:")), row, 0); + + color_menu_ = new ColorCodingComboBox(); + layout->addWidget(color_menu_, row, 1); + + color_menu_->SetColor(markers.first()->color()); + for (int i=1; icolor() != color_menu_->GetSelectedColor()) { + color_menu_->SetColor(-1); + break; + } + } + + row++; + + layout->addWidget(new QLabel(tr("Name:")), row, 0); + + label_edit_ = new LineEditWithFocusSignal(); + connect(label_edit_, &LineEditWithFocusSignal::Focused, this, [this]{ + label_edit_->setPlaceholderText(QString()); + }); + layout->addWidget(label_edit_, row, 1); + + // Determine what the startup label text should be + label_edit_->setText(markers.first()->name()); + for (int i=1; iname() != label_edit_->text()) { + label_edit_->clear(); + label_edit_->setPlaceholderText(tr("(multiple)")); + break; + } + } + + row++; + + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttons, &QDialogButtonBox::accepted, this, &MarkerPropertiesDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &MarkerPropertiesDialog::reject); + layout->addWidget(buttons, row, 0, 1, 2); +} + +void MarkerPropertiesDialog::accept() +{ + if (in_slider_->isEnabled() && in_slider_->GetValue() > out_slider_->GetValue()) { + QMessageBox::critical(this, tr("Invalid Values"), tr("In point must be less than or equal to out point.")); + return; + } + + MultiUndoCommand *command = new MultiUndoCommand(); + + int color = color_menu_->GetSelectedColor(); + + foreach (TimelineMarker *m, markers_) { + if (color != -1) { + command->add_child(new MarkerChangeColorCommand(m, color)); + } + + if (label_edit_->placeholderText().isEmpty()) { + command->add_child(new MarkerChangeNameCommand(m, label_edit_->text())); + } + } + + if (markers_.size() == 1) { + command->add_child(new MarkerChangeTimeCommand(markers_.first(), TimeRange(in_slider_->GetValue(), out_slider_->GetValue()))); + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); + + super::accept(); +} + +} diff --git a/app/dialog/markerproperties/markerpropertiesdialog.h b/app/dialog/markerproperties/markerpropertiesdialog.h new file mode 100644 index 000000000..218339af1 --- /dev/null +++ b/app/dialog/markerproperties/markerpropertiesdialog.h @@ -0,0 +1,78 @@ +/*** + + 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 MARKERPROPERTIESDIALOG_H +#define MARKERPROPERTIESDIALOG_H + +#include +#include + +#include "timeline/timelinemarker.h" +#include "widget/colorlabelmenu/colorcodingcombobox.h" +#include "widget/slider/rationalslider.h" + +namespace olive { + +class LineEditWithFocusSignal : public QLineEdit +{ + Q_OBJECT +public: + LineEditWithFocusSignal(QWidget *parent = nullptr) : + QLineEdit(parent) + { + } + +protected: + virtual void focusInEvent(QFocusEvent *e) override + { + QLineEdit::focusInEvent(e); + emit Focused(); + } + +signals: + void Focused(); + +}; + +class MarkerPropertiesDialog : public QDialog +{ + Q_OBJECT +public: + MarkerPropertiesDialog(const QVector &markers, const rational &timebase, QWidget *parent = nullptr); + +public slots: + virtual void accept() override; + +private: + QVector markers_; + + LineEditWithFocusSignal *label_edit_; + + ColorCodingComboBox *color_menu_; + + RationalSlider *in_slider_; + + RationalSlider *out_slider_; + +}; + +} + +#endif // MARKERPROPERTIESDIALOG_H diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index f5afb79fa..bb5d7188d 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -208,10 +208,22 @@ void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int // Find connected viewer node auto viewers = FindInputNodesConnectedToInput(NodeInput(this, kBufferIn)); - if (viewers.isEmpty()) { - connected_viewer_ = nullptr; - } else { - connected_viewer_ = viewers.first(); + ViewerOutput *new_connected_viewer = viewers.isEmpty() ? nullptr : viewers.first(); + + if (new_connected_viewer != connected_viewer_) { + if (connected_viewer_) { + disconnect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerAdded, this, &ClipBlock::PreviewChanged); + disconnect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerRemoved, this, &ClipBlock::PreviewChanged); + disconnect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerModified, this, &ClipBlock::PreviewChanged); + } + + connected_viewer_ = new_connected_viewer; + + if (connected_viewer_) { + connect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerAdded, this, &ClipBlock::PreviewChanged); + connect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerRemoved, this, &ClipBlock::PreviewChanged); + connect(connected_viewer_->GetTimelinePoints()->markers(), &TimelineMarkerList::MarkerModified, this, &ClipBlock::PreviewChanged); + } } super::InvalidateCache(adj, from, element, options); diff --git a/app/node/keyframe.cpp b/app/node/keyframe.cpp index 30b6e2508..277a1ed61 100644 --- a/app/node/keyframe.cpp +++ b/app/node/keyframe.cpp @@ -195,4 +195,10 @@ NodeKeyframe::BezierType NodeKeyframe::get_opposing_bezier_type(NodeKeyframe::Be } } +bool NodeKeyframe::has_sibling_at_time(const rational &t) const +{ + NodeKeyframe *k = parent()->GetKeyframeAtTimeOnTrack(input(), t, track(), element()); + return k && k != this; +} + } diff --git a/app/node/keyframe.h b/app/node/keyframe.h index a142c5015..b4c7f08e6 100644 --- a/app/node/keyframe.h +++ b/app/node/keyframe.h @@ -167,6 +167,8 @@ public: next_ = keyframe; } + bool has_sibling_at_time(const rational &t) const; + signals: /** * @brief Signal emitted when this keyframe's time is changed diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 9944a0e49..ee458d7d5 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -68,6 +68,8 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream } SetFlags(kDontShowInParamView); + + timeline_points_ = new TimelinePoints(this); } Node *ViewerOutput::copy() const diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index c16b86160..ee207c6ec 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -137,7 +137,7 @@ public: TimelinePoints* GetTimelinePoints() { - return &timeline_points_; + return timeline_points_; } QVector GetEnabledStreamsAsReferences() const; @@ -252,7 +252,7 @@ private: AudioParams cached_audio_params_; - TimelinePoints timeline_points_; + TimelinePoints *timeline_points_; bool video_cache_enabled_; bool audio_cache_enabled_; diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index 31d3ea726..aa80921dd 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -991,6 +991,7 @@ void ProjectSerializer220403::LoadMarkerList(QXmlStreamReader *reader, TimelineM if (reader->name() == QStringLiteral("marker")) { QString name; rational in, out; + int color = Config::Current()[QStringLiteral("MarkerColor")].toInt(); XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("name")) { @@ -999,10 +1000,12 @@ void ProjectSerializer220403::LoadMarkerList(QXmlStreamReader *reader, TimelineM in = rational::fromString(attr.value().toString()); } else if (attr.name() == QStringLiteral("out")) { out = rational::fromString(attr.value().toString()); + } else if (attr.name() == QStringLiteral("color")) { + color = attr.value().toInt(); } } - new TimelineMarker(Config::Current()[QStringLiteral("MarkerColor")].toInt(), TimeRange(in, out), name, markers); + new TimelineMarker(color, TimeRange(in, out), name, markers); } reader->skipCurrentElement(); @@ -1011,13 +1014,16 @@ void ProjectSerializer220403::LoadMarkerList(QXmlStreamReader *reader, TimelineM void ProjectSerializer220403::SaveMarkerList(QXmlStreamWriter *writer, TimelineMarkerList *markers) const { - foreach (TimelineMarker* marker, markers->list()) { + for (auto it=markers->cbegin(); it!=markers->cend(); it++) { + TimelineMarker* marker = *it; + writer->writeStartElement(QStringLiteral("marker")); writer->writeAttribute(QStringLiteral("name"), marker->name()); - writer->writeAttribute(QStringLiteral("in"), marker->time().in().toString()); - writer->writeAttribute(QStringLiteral("out"), marker->time().out().toString()); + writer->writeAttribute(QStringLiteral("in"), marker->time_range().in().toString()); + writer->writeAttribute(QStringLiteral("out"), marker->time_range().out().toString()); + writer->writeAttribute(QStringLiteral("color"), QString::number(marker->color())); writer->writeEndElement(); // marker } diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index 2251ab3a3..bb40b7790 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -20,18 +20,20 @@ #include "timelinemarker.h" +#include "common/qtutils.h" #include "common/xmlutils.h" #include "config/config.h" #include "core.h" +#include "ui/colorcoding.h" namespace olive { TimelineMarker::TimelineMarker(int color, const TimeRange &time, const QString &name, QObject *parent) : - QObject(parent), time_(time), name_(name), color_(color) { + setParent(parent); } void TimelineMarker::set_time(const TimeRange &time) @@ -40,6 +42,17 @@ void TimelineMarker::set_time(const TimeRange &time) emit TimeChanged(time_); } +void TimelineMarker::set_time(const rational &time) +{ + set_time(TimeRange(time, time + time_.length())); +} + +bool TimelineMarker::has_sibling_at_time(const rational &t) const +{ + TimelineMarker *m = static_cast(parent())->GetMarkerAtTime(t); + return m && m != this; +} + void TimelineMarker::set_name(const QString &name) { name_ = name; @@ -49,13 +62,65 @@ void TimelineMarker::set_name(const QString &name) void TimelineMarker::set_color(int c) { color_ = c; - emit ColorChanged(color_); } -const std::vector &TimelineMarkerList::list() const +int TimelineMarker::GetMarkerHeight(const QFontMetrics &fm) { - return markers_; + return fm.height(); +} + +QRect TimelineMarker::Draw(QPainter *p, const QPoint &pt, double scale, bool selected) +{ + QFontMetrics fm = p->fontMetrics(); + + int marker_height = GetMarkerHeight(fm); + int marker_width = QtUtils::QFontMetricsWidth(fm, QStringLiteral("H")); + + int half_width = marker_width / 2; + + QColor c = ColorCoding::GetColor(color()).toQColor(); + if (selected) { + p->setPen(Qt::white); + p->setBrush(c.lighter()); + } else { + p->setPen(Qt::black); + p->setBrush(c); + } + + int top = pt.y() - marker_height; + + if (time_.out() != time_.in()) { + QRect marker_rect(pt.x(), top, time_.length().toDouble() * scale, marker_height); + + p->drawRect(marker_rect); + + if (!name_.isEmpty()) { + p->setPen(ColorCoding::GetUISelectorColor(ColorCoding::GetColor(color_))); + p->drawText(marker_rect.adjusted(marker_width/4, 0, 0, 0), name_, Qt::AlignLeft | Qt::AlignVCenter); + } + + return marker_rect; + } else { + int half_marker_height = marker_height / 3; + int left = pt.x() - half_width; + int right = pt.x() + half_width; + int center_y = pt.y() - half_marker_height; + + QPoint points[] = { + pt, + QPoint(left, center_y), + QPoint(left, top), + QPoint(right, top), + QPoint(right, center_y), + pt, + }; + + p->setRenderHint(QPainter::Antialiasing); + p->drawPolygon(points, 6); + + return QRect(left, top, marker_width, marker_height); + } } void TimelineMarkerList::childEvent(QChildEvent *e) @@ -64,20 +129,93 @@ void TimelineMarkerList::childEvent(QChildEvent *e) if (TimelineMarker *marker = dynamic_cast(e->child())) { if (e->type() == QChildEvent::ChildAdded) { - markers_.push_back(marker); + + connect(marker, &TimelineMarker::TimeChanged, this, &TimelineMarkerList::HandleMarkerTimeChange); + connect(marker, &TimelineMarker::TimeChanged, this, &TimelineMarkerList::HandleMarkerModification); + connect(marker, &TimelineMarker::NameChanged, this, &TimelineMarkerList::HandleMarkerModification); + connect(marker, &TimelineMarker::ColorChanged, this, &TimelineMarkerList::HandleMarkerModification); + + InsertIntoList(marker); + + emit MarkerAdded(marker); + } else if (e->type() == QChildEvent::ChildRemoved) { - auto it = std::find(markers_.begin(), markers_.end(), marker); - if (it != markers_.end()) { - markers_.erase(it); - } + + RemoveFromList(marker); + + disconnect(marker, &TimelineMarker::TimeChanged, this, &TimelineMarkerList::HandleMarkerTimeChange); + disconnect(marker, &TimelineMarker::TimeChanged, this, &TimelineMarkerList::HandleMarkerModification); + disconnect(marker, &TimelineMarker::NameChanged, this, &TimelineMarkerList::HandleMarkerModification); + disconnect(marker, &TimelineMarker::ColorChanged, this, &TimelineMarkerList::HandleMarkerModification); + + emit MarkerRemoved(marker); + } } } -MarkerAddCommand::MarkerAddCommand(TimelineMarkerList *marker_list, const TimeRange &range, const QString &name, int color) : - marker_list_(marker_list) +void TimelineMarkerList::InsertIntoList(TimelineMarker *marker) { - added_marker_ = new TimelineMarker(color, range, name, &memory_manager_); + // Insertion sort by time to allow some loop optimizations + bool found = false; + for (auto it=markers_.begin(); it!=markers_.end(); it++) { + TimelineMarker *m = *it; + + Q_ASSERT(m->time() != marker->time()); + + if (m->time() > marker->time()) { + markers_.insert(it, marker); + found = true; + break; + } + } + + if (!found) { + markers_.push_back(marker); + } +} + +bool TimelineMarkerList::RemoveFromList(TimelineMarker *marker) +{ + auto it = std::find(markers_.begin(), markers_.end(), marker); + + if (it != markers_.end()) { + markers_.erase(it); + return true; + } + + return false; +} + +void TimelineMarkerList::HandleMarkerModification() +{ + emit MarkerModified(static_cast(sender())); +} + +void TimelineMarkerList::HandleMarkerTimeChange() +{ + TimelineMarker *m = static_cast(sender()); + + auto it = std::find(markers_.begin(), markers_.end(), m); + + if ((it+1 != markers_.end() && (*(it+1))->time() < m->time()) + || (it != markers_.begin() && (*(it-1))->time() > m->time())) { + // Re-sort into list + markers_.erase(it); + InsertIntoList(m); + } +} + +MarkerAddCommand::MarkerAddCommand(TimelineMarkerList *marker_list, const TimeRange &range, const QString &name, int color) : + MarkerAddCommand(marker_list, new TimelineMarker(color, range, name, &memory_manager_)) +{ +} + +MarkerAddCommand::MarkerAddCommand(TimelineMarkerList *marker_list, TimelineMarker *marker) : + marker_list_(marker_list), + added_marker_(marker) +{ + added_marker_->setParent(&memory_manager_); } Project* MarkerAddCommand::GetRelevantProject() const @@ -102,7 +240,7 @@ MarkerRemoveCommand::MarkerRemoveCommand(TimelineMarker *marker) : Project* MarkerRemoveCommand::GetRelevantProject() const { - return Project::GetProjectFromObject(marker_list_); + return Project::GetProjectFromObject(marker_); } void MarkerRemoveCommand::redo() @@ -173,7 +311,7 @@ Project* MarkerChangeTimeCommand::GetRelevantProject() const void MarkerChangeTimeCommand::redo() { - old_time_ = marker_->time(); + old_time_ = marker_->time_range(); marker_->set_time(new_time_); } diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index 61822859c..d55af84bf 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -21,6 +21,7 @@ #ifndef TIMELINEMARKER_H #define TIMELINEMARKER_H +#include #include #include #include @@ -36,8 +37,12 @@ class TimelineMarker : public QObject public: TimelineMarker(int color, const TimeRange& time, const QString& name = QString(), QObject* parent = nullptr); - const TimeRange &time() const { return time_; } + const rational &time() const { return time_.in(); } + const TimeRange &time_range() const { return time_; } void set_time(const TimeRange& time); + void set_time(const rational& time); + + bool has_sibling_at_time(const rational &t) const; const QString& name() const { return name_; } void set_name(const QString& name); @@ -45,6 +50,9 @@ public: int color() const { return color_; } void set_color(int c); + static int GetMarkerHeight(const QFontMetrics &fm); + QRect Draw(QPainter *p, const QPoint &pt, double scale, bool selected); + signals: void TimeChanged(const TimeRange& time); @@ -70,24 +78,79 @@ public: { } - const std::vector &list() const; + inline bool empty() const { return markers_.empty(); } + inline std::vector::iterator begin() { return markers_.begin(); } + inline std::vector::iterator end() { return markers_.end(); } + inline std::vector::const_iterator cbegin() const { return markers_.cbegin(); } + inline std::vector::const_iterator cend() const { return markers_.cend(); } + inline TimelineMarker *back() const { return markers_.back(); } + inline TimelineMarker *front() const { return markers_.front(); } + inline size_t size() const { return markers_.size(); } + + TimelineMarker *GetMarkerAtTime(const rational &t) const + { + for (auto it=markers_.cbegin(); it!=markers_.cend(); it++) { + TimelineMarker *m = *it; + if (m->time() == t) { + return m; + } + } + + return nullptr; + } + + TimelineMarker *GetClosestMarkerToTime(const rational &t) const + { + TimelineMarker *closest = nullptr; + + for (auto it=markers_.cbegin(); it!=markers_.cend(); it++) { + TimelineMarker *m = *it; + + rational this_diff = qAbs(m->time() - t); + + if (closest) { + rational stored_diff = qAbs(closest->time() - t); + + if (this_diff > stored_diff) { + // Since the list is organized by time, if the diff increases, assume we are only going + // to move further away from here and there's no need to check + break; + } + } + + closest = m; + } + + return closest; + } signals: void MarkerAdded(TimelineMarker* marker); void MarkerRemoved(TimelineMarker* marker); + void MarkerModified(TimelineMarker* marker); + protected: virtual void childEvent(QChildEvent *e) override; private: + void InsertIntoList(TimelineMarker *m); + bool RemoveFromList(TimelineMarker *m); + std::vector markers_; +private slots: + void HandleMarkerModification(); + + void HandleMarkerTimeChange(); + }; class MarkerAddCommand : public UndoCommand { public: MarkerAddCommand(TimelineMarkerList* marker_list, const TimeRange& range, const QString& name, int color); + MarkerAddCommand(TimelineMarkerList* marker_list, TimelineMarker *marker); virtual Project* GetRelevantProject() const override; diff --git a/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp b/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp index 59d50d0f1..0b06ac1bc 100644 --- a/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp +++ b/app/widget/resizablescrollbar/resizabletimelinescrollbar.cpp @@ -50,6 +50,7 @@ void ResizableTimelineScrollBar::ConnectTimelinePoints(TimelinePoints *points) disconnect(points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&ResizableTimelineScrollBar::update)); disconnect(points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&ResizableTimelineScrollBar::update)); disconnect(points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&ResizableTimelineScrollBar::update)); + disconnect(points_->markers(), &TimelineMarkerList::MarkerModified, this, static_cast(&ResizableTimelineScrollBar::update)); } points_ = points; @@ -59,6 +60,7 @@ void ResizableTimelineScrollBar::ConnectTimelinePoints(TimelinePoints *points) connect(points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&ResizableTimelineScrollBar::update)); connect(points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&ResizableTimelineScrollBar::update)); connect(points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&ResizableTimelineScrollBar::update)); + connect(points_->markers(), &TimelineMarkerList::MarkerModified, this, static_cast(&ResizableTimelineScrollBar::update)); } update(); @@ -77,7 +79,7 @@ void ResizableTimelineScrollBar::paintEvent(QPaintEvent *event) if (points_ && !timebase().isNull() - && (points_->workarea()->enabled() || !points_->markers()->list().empty())) { + && (points_->workarea()->enabled() || !points_->markers()->empty())) { QStyleOptionSlider opt; initStyleOption(&opt); @@ -110,11 +112,13 @@ void ResizableTimelineScrollBar::paintEvent(QPaintEvent *event) workarea_color); } - if (!points_->markers()->list().empty()) { - foreach (TimelineMarker* marker, points_->markers()->list()) { + if (!points_->markers()->empty()) { + for (auto it=points_->markers()->cbegin(); it!=points_->markers()->cend(); it++) { + TimelineMarker* marker = *it; + QColor marker_color = ColorCoding::GetColor(marker->color()).toQColor(); - int64_t in = qRound64(ratio * TimeToScene(marker->time().in())); - int64_t out = qRound64(ratio * TimeToScene(marker->time().out())); + int64_t in = qRound64(ratio * TimeToScene(marker->time_range().in())); + int64_t out = qRound64(ratio * TimeToScene(marker->time_range().out())); int64_t length = qMax(int64_t(1), out-in); p.fillRect(gr.x() + in, diff --git a/app/widget/timebased/timebasedviewselectionmanager.h b/app/widget/timebased/timebasedviewselectionmanager.h index ae5e32230..cc214e8dd 100644 --- a/app/widget/timebased/timebasedviewselectionmanager.h +++ b/app/widget/timebased/timebasedviewselectionmanager.h @@ -153,36 +153,54 @@ public: for (int i=0; itime(), view_->TimeToScene(obj->time())}; + dragging_[i] = obj->time(); } drag_mouse_start_ = view_->mapToScene(event->pos()); } - void DragMove(QMouseEvent *event, const QString &tip_format) + void DragMove(QMouseEvent *event, const QString &tip_format = QString()) { - QPointF diff = view_->mapToScene(event->pos()) - drag_mouse_start_; + rational time_diff = view_->SceneToTimeNoGrid(view_->mapToScene(event->pos()).x() - drag_mouse_start_.x()); + // Validate movement for (int i=0; iSceneToTimeNoGrid(dragging_.at(i).x + diff.x()); + rational proposed_time = dragging_.at(i) + time_diff; T *sel = selected_.at(i); // Magic number: use interval of 1ms to avoid collisions rational adj(1, 1000); - if (dragging_.at(i).time < proposed_time) { + if (dragging_.at(i) < proposed_time) { + // Negate adjustment value if origin is less than 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; + bool loop; + do { + loop = false; + while (sel->has_sibling_at_time(proposed_time)) { + proposed_time += adj; } - proposed_time += adj; - } + if (proposed_time < 0) { + // Prevent any object from going below zero + proposed_time = 0; - sel->set_time(proposed_time); + // Setting our proposed time to zero may (re)introduce a conflict that we just avoided + // with the sibling check above, so we request it to happen again. To avoid a negative + // adj bringing us back below zero, we force adj to positive so it'll only nudge higher + adj = qAbs(adj); + + loop = true; + } + } while (loop); + + time_diff = proposed_time - dragging_.at(i); + } + + // Apply movement + for (int i=0; iset_time(dragging_.at(i) + time_diff); } // Show information about this keyframe @@ -202,7 +220,7 @@ public: QToolTip::hideText(); for (int i=0; iadd_child(new SetTimeCommand(selected_.at(i), selected_.at(i)->time(), dragging_.at(i).time)); + command->add_child(new SetTimeCommand(selected_.at(i), selected_.at(i)->time(), dragging_.at(i))); } dragging_.clear(); @@ -271,7 +289,7 @@ private: virtual Project* GetRelevantProject() const override { - return key_->parent()->project(); + return Project::GetProjectFromObject(key_); } protected: @@ -300,13 +318,7 @@ private: QVector selected_; - struct DragObject - { - rational time; - double x; - }; - - QVector dragging_; + QVector dragging_; T *initial_drag_item_; diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index b28398b45..81197e616 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -26,6 +26,7 @@ #include "common/timecodefunctions.h" #include "config/config.h" #include "core.h" +#include "dialog/markerproperties/markerpropertiesdialog.h" #include "node/project/sequence/sequence.h" #include "widget/timelinewidget/undo/timelineundoworkarea.h" @@ -588,29 +589,36 @@ void TimeBasedWidget::SetMarker() return; } - bool ok; - QString marker_name; + TimelineMarkerList *markers = GetConnectedNode()->GetTimelinePoints()->markers(); - if (Config::Current()[QStringLiteral("SetNameWithMarker")].toBool()) { - marker_name = QInputDialog::getText(this, tr("Add Marker"), tr("Name:"), QLineEdit::Normal, QString(), &ok); + if (TimelineMarker *existing = markers->GetMarkerAtTime(GetTime())) { + // We already have a marker here, so pop open the edit dialog + MarkerPropertiesDialog mpd({existing}, timebase(), this); + mpd.exec(); } else { - ok = true; - } - - if (ok) { + // Create a new marker and place it here int color; - - TimelineMarkerList *markers = GetConnectedNode()->GetTimelinePoints()->markers(); - - if (!markers->list().empty()) { - // Use last invoked color if applicable - color = markers->list().back()->color(); + if (TimelineMarker *closest = markers->GetClosestMarkerToTime(GetTime())) { + // Copy color of closest marker to this time + color = closest->color(); } else { // Fallback to default color in preferences color = Config::Current()[QStringLiteral("MarkerColor")].toInt(); } - Core::instance()->undo_stack()->push(new MarkerAddCommand(markers, TimeRange(GetTime(), GetTime()), marker_name, color)); + TimelineMarker *marker = new TimelineMarker(color, TimeRange(GetTime(), GetTime())); + + if (Config::Current()[QStringLiteral("SetNameWithMarker")].toBool()) { + MarkerPropertiesDialog mpd({marker}, timebase(), this); + if (mpd.exec() != QDialog::Accepted) { + delete marker; + marker = nullptr; + } + } + + if (marker) { + Core::instance()->undo_stack()->push(new MarkerAddCommand(markers, marker)); + } } } diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 0cb52f29a..273aa7446 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1798,13 +1798,15 @@ bool TimelineWidget::SnapPoint(QVector start_times, rational* movement } if ((snap_points & kSnapToMarkers)) { - foreach (TimelineMarker* m, GetConnectedNode()->GetTimelinePoints()->markers()->list()) { - qreal marker_pos = TimeToScene(m->time().in()); - potential_snaps.append(AttemptSnap(screen_pt, marker_pos, start_times, m->time().in())); + for (auto it=GetConnectedNode()->GetTimelinePoints()->markers()->cbegin(); it!=GetConnectedNode()->GetTimelinePoints()->markers()->cend(); it++) { + TimelineMarker* m = *it; - if (m->time().in() != m->time().out()) { - marker_pos = TimeToScene(m->time().out()); - potential_snaps.append(AttemptSnap(screen_pt, marker_pos, start_times, m->time().out())); + qreal marker_pos = TimeToScene(m->time_range().in()); + potential_snaps.append(AttemptSnap(screen_pt, marker_pos, start_times, m->time_range().in())); + + if (m->time_range().in() != m->time_range().out()) { + marker_pos = TimeToScene(m->time_range().out()); + potential_snaps.append(AttemptSnap(screen_pt, marker_pos, start_times, m->time_range().out())); } } } diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index d8b8a28c2..c00070b21 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -67,7 +67,7 @@ void TimelineView::mousePressEvent(QMouseEvent *event) if (rect.contains(mapToScene(event->pos()))) { TimelinePanel *timeline = PanelManager::instance()->MostRecentlyFocused(); if (timeline) { - timeline->timeline_widget()->SetTime(clip_marker_positions_.key(rect)->time().in()); + timeline->timeline_widget()->SetTime(clip_marker_positions_.key(rect)->time_range().in()); } } } @@ -510,58 +510,43 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q SceneToTime(block_left - block_in, GetScale(), connected_track_list_->parent()->GetAudioParams().sample_rate_as_time_base())); } - // Draw zebra stripes - if (clip->connected_viewer() && !clip->connected_viewer()->GetLength().isNull()) { - if (clip->media_in() < 0) { - // Draw stripes for sections of clip < 0 - qreal zebra_right = TimeToScene(-clip->media_in()); - if (zebra_right > GetTimelineLeftBound()) { - DrawZebraStripes(painter, QRectF(block_left, block_top, zebra_right, block_height)); + // Draw zebra stripes and markers + if (clip->connected_viewer()) { + if (!clip->connected_viewer()->GetLength().isNull()) { + if (clip->media_in() < 0) { + // Draw stripes for sections of clip < 0 + qreal zebra_right = TimeToScene(-clip->media_in()); + if (zebra_right > GetTimelineLeftBound()) { + DrawZebraStripes(painter, QRectF(block_left, block_top, zebra_right, block_height)); + } } - } - if (clip->length() + clip->media_in() > clip->connected_viewer()->GetLength()) { - // Draw stripes for sections for clip > clip length - qreal zebra_left = TimeToScene(clip->out() - (clip->media_in() + clip->length() - clip->connected_viewer()->GetLength())); - if (zebra_left < GetTimelineRightBound()) { - DrawZebraStripes(painter, QRectF(zebra_left, block_top, block_right - zebra_left, block_height)); - } - } - } - - // Draw markers - qDebug() << "MARKERS ON CLIP STUB"; - /*if (clip->connected_viewer() && !clip->connected_viewer()->GetLength().isNull()) { - if (clip->connected_viewer()->GetTimelinePoints()->markers()->list().size() > 0) { - std::vector marker_list = clip->connected_viewer()->GetTimelinePoints()->markers()->list(); - - int marker_width = QtUtils::QFontMetricsWidth(fm, "H"); - clip_marker_positions_.clear(); - - // Only draw markers if the block UI is large enough to draw all the markers - if (marker_list.size() * marker_width < block_right - block_left) { - - for (auto it=marker_list.cbegin(); it!=marker_list.cend(); it++) { - TimelineMarker *marker = *it; - // Make sure marker is within In/Out points of the clip - if (marker->time().in() >= clip->media_in() && marker->time().out() <= clip->media_in() + clip->length()) { - // Only draw names that we have room for - bool draw_name = true; - if (!marker->name().isEmpty()) { - int length = fm.horizontalAdvance(marker->name()); - if (iterator.hasNext()) { - if (TimeToScene(iterator.peekNext()->time().in()) - TimeToScene(marker->time().out()) < (double)length) { - draw_name = false; - } - } - } - DrawClipMarker(painter, TimeToScene(clip->in() - clip->media_in() + marker->time().in()), - block_top + block_height, marker, draw_name); - } + if (clip->length() + clip->media_in() > clip->connected_viewer()->GetLength()) { + // Draw stripes for sections for clip > clip length + qreal zebra_left = TimeToScene(clip->out() - (clip->media_in() + clip->length() - clip->connected_viewer()->GetLength())); + if (zebra_left < GetTimelineRightBound()) { + DrawZebraStripes(painter, QRectF(zebra_left, block_top, block_right - zebra_left, block_height)); } } } - }*/ + + TimelineMarkerList *marker_list = clip->connected_viewer()->GetTimelinePoints()->markers(); + if (!marker_list->empty()) { + + clip_marker_positions_.clear(); + + for (auto it=marker_list->cbegin(); it!=marker_list->cend(); it++) { + TimelineMarker *marker = *it; + // Make sure marker is within In/Out points of the clip + if (marker->time_range().in() >= clip->media_in() && marker->time_range().out() <= clip->media_in() + clip->length()) { + QPoint marker_pt(TimeToScene(clip->in() - clip->media_in() + marker->time_range().in()), block_top + block_height); + painter->setClipRect(r); + marker->Draw(painter, marker_pt, GetScale(), false); + painter->setClipping(false); + } + } + } + } } // For transitions, show lines representing a transition @@ -631,46 +616,6 @@ void TimelineView::DrawZebraStripes(QPainter *painter, const QRectF &r) painter->setClipping(false); } -void TimelineView::DrawClipMarker(QPainter* painter, double marker_x, qreal marker_y, TimelineMarker* marker, bool draw_name) -{ - QFontMetrics fm = fontMetrics(); - - int marker_height = fm.height(); - int marker_width = QtUtils::QFontMetricsWidth(fm, "H"); - - int y = marker_y - 1; - - int half_width = marker_width / 2; - - int x = marker_x + half_width; - - painter->setPen(Qt::black); - - painter->setBrush(ColorCoding::GetColor(marker->color()).toQColor()); - - painter->setRenderHint(QPainter::Antialiasing); - - int half_marker_height = marker_height / 3; - - QPoint points[] = { - QPoint(x, y), - QPoint(x - half_width, y - half_marker_height), - QPoint(x - half_width, y - marker_height), - QPoint(x + 1 + half_width, y - marker_height), - QPoint(x + 1 + half_width, y - half_marker_height), - QPoint(x + 1, y), - }; - - painter->drawPolygon(points, 6); - - if (!marker->name().isEmpty() && draw_name) { - painter->drawText(x + marker_width, y - half_marker_height, marker->name()); - } - - QPointF scenePos = QPoint(x - half_width, y); - clip_marker_positions_.insert(marker, (QRectF(scenePos, QSize(marker_width, -marker_height)))); -} - int TimelineView::GetHeightOfAllTracks() const { if (connected_track_list_) { diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index c22c5caba..ff8368add 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -132,8 +132,6 @@ private: void DrawZebraStripes(QPainter *painter, const QRectF &r); - void DrawClipMarker(QPainter* painter, double marker_x, qreal marker_y, TimelineMarker* marker, bool draw_name); - int GetHeightOfAllTracks() const; void UpdatePlayheadRect(); diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 467a9f649..fbbe714b9 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -20,12 +20,16 @@ #include "seekablewidget.h" +#include #include #include #include #include "common/qtutils.h" #include "core.h" +#include "dialog/markerproperties/markerpropertiesdialog.h" +#include "widget/colorlabelmenu/colorlabelmenu.h" +#include "widget/menu/menushared.h" #include "widget/timebased/timebasedwidget.h" namespace olive { @@ -34,8 +38,10 @@ namespace olive { SeekableWidget::SeekableWidget(QWidget* parent) : super(parent), + NodeCopyPasteService(QStringLiteral("markers")), timeline_points_(nullptr), dragging_(false), + ignore_next_focus_out_(false), selection_manager_(this) { QFontMetrics fm = fontMetrics(); @@ -54,19 +60,21 @@ void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) if (timeline_points_) { selection_manager_.ClearSelection(); - disconnect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast(&SeekableWidget::update)); - disconnect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&SeekableWidget::update)); - disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&SeekableWidget::update)); - disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&SeekableWidget::update)); + disconnect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, viewport(), static_cast(&QWidget::update)); + disconnect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, viewport(), static_cast(&QWidget::update)); + disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, viewport(), static_cast(&QWidget::update)); + disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, viewport(), static_cast(&QWidget::update)); + disconnect(timeline_points_->markers(), &TimelineMarkerList::MarkerModified, viewport(), static_cast(&QWidget::update)); } timeline_points_ = points; if (timeline_points_) { - connect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast(&SeekableWidget::update)); - connect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&SeekableWidget::update)); - connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, this, static_cast(&SeekableWidget::update)); - connect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, this, static_cast(&SeekableWidget::update)); + connect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, viewport(), static_cast(&QWidget::update)); + connect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, viewport(), static_cast(&QWidget::update)); + connect(timeline_points_->markers(), &TimelineMarkerList::MarkerAdded, viewport(), static_cast(&QWidget::update)); + connect(timeline_points_->markers(), &TimelineMarkerList::MarkerRemoved, viewport(), static_cast(&QWidget::update)); + connect(timeline_points_->markers(), &TimelineMarkerList::MarkerModified, viewport(), static_cast(&QWidget::update)); } viewport()->update(); @@ -102,7 +110,9 @@ void SeekableWidget::PasteMarkers(bool insert, rational insert_time) void SeekableWidget::mousePressEvent(QMouseEvent *event) { - if (event->button() == Qt::LeftButton) { + if (TimelineMarker *initial = selection_manager_.MousePress(event)) { + selection_manager_.DragStart(initial, event); + } else if (!selection_manager_.GetObjectAtPoint(event->pos()) && event->button() == Qt::LeftButton) { SeekToScenePoint(mapToScene(event->pos()).x()); dragging_ = true; @@ -112,14 +122,20 @@ void SeekableWidget::mousePressEvent(QMouseEvent *event) void SeekableWidget::mouseMoveEvent(QMouseEvent *event) { - if (event->buttons() & Qt::LeftButton) { + if (selection_manager_.IsDragging()) { + selection_manager_.DragMove(event); + } else if (dragging_) { SeekToScenePoint(mapToScene(event->pos()).x()); } } void SeekableWidget::mouseReleaseEvent(QMouseEvent *event) { - Q_UNUSED(event) + if (selection_manager_.IsDragging()) { + MultiUndoCommand *command = new MultiUndoCommand(); + selection_manager_.DragStop(command); + Core::instance()->undo_stack()->pushIfHasChildren(command); + } if (GetSnapService()) { GetSnapService()->HideSnaps(); @@ -128,12 +144,25 @@ void SeekableWidget::mouseReleaseEvent(QMouseEvent *event) dragging_ = false; } +void SeekableWidget::mouseDoubleClickEvent(QMouseEvent *event) +{ + super::mouseDoubleClickEvent(event); + + if (selection_manager_.GetObjectAtPoint(event->pos()) && !selection_manager_.GetSelectedObjects().isEmpty()) { + ShowMarkerProperties(); + } +} + void SeekableWidget::focusOutEvent(QFocusEvent *event) { super::focusOutEvent(event); - // Deselect everything when we lose focus - DeselectAllMarkers(); + if (ignore_next_focus_out_) { + ignore_next_focus_out_ = false; + } else { + // Deselect everything when we lose focus + DeselectAllMarkers(); + } } TimelinePoints *SeekableWidget::timeline_points() const @@ -159,6 +188,20 @@ void SeekableWidget::SetMarkerColor(int c) Core::instance()->undo_stack()->pushIfHasChildren(command); } +void SeekableWidget::ShowMarkerProperties() +{ + MarkerPropertiesDialog mpd(selection_manager_.GetSelectedObjects(), timebase(), this); + ignore_next_focus_out_ = true; + mpd.exec(); +} + +void SeekableWidget::TimebaseChangedEvent(const rational &t) +{ + super::TimebaseChangedEvent(t); + + selection_manager_.SetTimebase(t); +} + void SeekableWidget::SeekToScenePoint(qreal scene) { if (timebase().isNull()) { @@ -171,8 +214,8 @@ void SeekableWidget::SeekToScenePoint(qreal scene) rational movement; GetSnapService()->SnapPoint({playhead_time}, - &movement, - SnapService::kSnapAll & ~SnapService::kSnapToPlayhead); + &movement, + SnapService::kSnapAll & ~SnapService::kSnapToPlayhead); playhead_time += movement; } @@ -184,56 +227,62 @@ void SeekableWidget::SeekToScenePoint(qreal scene) } } +void SeekableWidget::SelectionManagerSelectEvent(void *obj) +{ + super::SelectionManagerSelectEvent(obj); + + viewport()->update(); +} + +void SeekableWidget::SelectionManagerDeselectEvent(void *obj) +{ + super::SelectionManagerDeselectEvent(obj); + + viewport()->update(); +} + void SeekableWidget::DrawTimelinePoints(QPainter* p, int marker_bottom) { if (!timeline_points()) { return; } + int lim_left = GetScroll(); + int lim_right = lim_left + width(); + + selection_manager_.ClearDrawnObjects(); + // Draw in/out workarea if (timeline_points()->workarea()->enabled()) { - int workarea_left = qMax(0.0, TimeToScene(timeline_points()->workarea()->in())); + int workarea_left = qMax(qreal(lim_left), TimeToScene(timeline_points()->workarea()->in())); int workarea_right; if (timeline_points()->workarea()->out() == TimelineWorkArea::kResetOut) { - workarea_right = width(); + workarea_right = lim_right; } else { - workarea_right = qMin(qreal(width()), TimeToScene(timeline_points()->workarea()->out())); + workarea_right = qMin(qreal(lim_right), TimeToScene(timeline_points()->workarea()->out())); } p->fillRect(workarea_left, 0, workarea_right - workarea_left, height(), palette().highlight()); } // Draw markers - if (marker_bottom > 0 && !timeline_points()->markers()->list().empty()) { + if (marker_bottom > 0 && !timeline_points()->markers()->empty()) { + for (auto it=timeline_points()->markers()->cbegin(); it!=timeline_points()->markers()->cend(); it++) { + TimelineMarker* marker = *it; - int marker_top = marker_bottom - text_height_; - - // FIXME: Hardcoded marker colors - p->setPen(Qt::black); - p->setBrush(Qt::green); - - foreach (TimelineMarker* marker, timeline_points()->markers()->list()) { - int marker_left = TimeToScene(marker->time().in()); - int marker_right = TimeToScene(marker->time().out()); - - if (marker_left >= width() || marker_right < 0) { + int marker_right = TimeToScene(marker->time_range().out()); + if (marker_right < lim_left) { continue; } - if (marker->time().length() != 0) { - // Marker range - int rect_left = qMax(0, marker_left); - int rect_right = qMin(width(), marker_right); - - QRect marker_rect(rect_left, marker_top, rect_right - rect_left, marker_bottom - marker_top); - - p->drawRect(marker_rect); - - if (!marker->name().isEmpty()) { - p->drawText(marker_rect, marker->name()); - } + int marker_left = TimeToScene(marker->time_range().in()); + if (marker_left >= lim_right) { + break; } + + QRect marker_rect = marker->Draw(p, QPoint(marker_left, marker_bottom), GetScale(), selection_manager_.IsSelected(marker)); + selection_manager_.DeclareDrawnObject(marker, marker_rect); } } } @@ -264,4 +313,31 @@ void SeekableWidget::DrawPlayhead(QPainter *p, int x, int y) p->setRenderHint(QPainter::Antialiasing, false); } +bool SeekableWidget::ShowContextMenu(const QPoint &p) +{ + if (selection_manager_.GetObjectAtPoint(p) && !selection_manager_.GetSelectedObjects().isEmpty()) { + // Show marker-specific menu + Menu m; + + ColorLabelMenu color_coding_menu; + connect(&color_coding_menu, &ColorLabelMenu::ColorSelected, this, &SeekableWidget::SetMarkerColor); + m.addMenu(&color_coding_menu); + + m.addSeparator(); + + MenuShared::instance()->AddItemsForEditMenu(&m, false); + + m.addSeparator(); + + QAction *properties_action = m.addAction(tr("Properties")); + connect(properties_action, &QAction::triggered, this, &SeekableWidget::ShowMarkerProperties); + + ignore_next_focus_out_ = true; + m.exec(mapToGlobal(p)); + return true; + } else { + return false; + } +} + } diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index 56d6f2660..d06401989 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -25,14 +25,15 @@ #include #include "common/rational.h" +#include "node/nodecopypaste.h" #include "timeline/timelinepoints.h" +#include "widget/menu/menu.h" #include "widget/snapservice/snapservice.h" #include "widget/timebased/timebasedviewselectionmanager.h" -//#include "widget/marker/markercopypaste.h" namespace olive { -class SeekableWidget : public TimeBasedView//, public MarkerCopyPasteService +class SeekableWidget : public TimeBasedView, public NodeCopyPasteService { Q_OBJECT public: @@ -60,22 +61,22 @@ public: void SeekToScenePoint(qreal scene); + virtual void SelectionManagerSelectEvent(void *obj) override; + virtual void SelectionManagerDeselectEvent(void *obj) override; + public slots: void SetScroll(int i) { horizontalScrollBar()->setValue(i); } - void SetMarkerColor(int c); - - /*void SetMarkerTime(); - - void SetMarkerName();*/ + virtual void TimebaseChangedEvent(const rational &) override; 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 focusOutEvent(QFocusEvent *event) override; @@ -93,6 +94,9 @@ protected: return playhead_width_; } +protected slots: + virtual bool ShowContextMenu(const QPoint &p); + private: TimelinePoints* timeline_points_; @@ -102,8 +106,15 @@ private: bool dragging_; + bool ignore_next_focus_out_; + TimeBasedViewSelectionManager selection_manager_; +private slots: + void SetMarkerColor(int c); + + void ShowMarkerProperties(); + }; } diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp index ff2b3281d..f50e67d36 100644 --- a/app/widget/timeruler/timeruler.cpp +++ b/app/widget/timeruler/timeruler.cpp @@ -32,8 +32,10 @@ namespace olive { +#define super SeekableWidget + TimeRuler::TimeRuler(bool text_visible, bool cache_status_visible, QWidget* parent) : - SeekableWidget(parent), + super(parent), text_visible_(text_visible), centered_text_(true), show_cache_status_(cache_status_visible), @@ -102,18 +104,9 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect) } // Draw timeline points if connected + int marker_height = TimelineMarker::GetMarkerHeight(p->fontMetrics()); if (timeline_points()) { - int marker_bottom = height() - text_height(); - - if (show_cache_status_) { - marker_bottom -= cache_status_height_; - } - - if (text_visible_) { - marker_bottom -= cache_status_height_; - } - - DrawTimelinePoints(p, marker_bottom); + DrawTimelinePoints(p, marker_height); } double width_of_frame = timebase_dbl() * GetScale(); @@ -222,11 +215,11 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect) int timecode_left; if (centered_text_) { - text_rect = QRect(i - kAverageTextWidth/2, 0, kAverageTextWidth, fm.height()); + text_rect = QRect(i - kAverageTextWidth/2, marker_height, kAverageTextWidth, fm.height()); text_align = Qt::AlignCenter; timecode_left = i - timecode_width/2; } else { - text_rect = QRect(i, 0, kAverageTextWidth, fm.height()); + text_rect = QRect(i, marker_height, kAverageTextWidth, fm.height()); text_align = Qt::AlignLeft | Qt::AlignVCenter; timecode_left = i; @@ -265,13 +258,15 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect) if (show_cache_status_ && playback_cache_) { // FIXME: Hardcoded to get video length, if we ever need audio length, this will have to change rational len = playback_cache_->viewer_parent()->GetVideoLength(); + int lim_left = GetScroll(); + int lim_right = lim_left + width(); - int cache_screen_length = qMin(TimeToScene(len), qreal(width())); + int cache_screen_length = TimeToScene(len); if (cache_screen_length > 0) { int cache_y = height() - cache_status_height_; - p->fillRect(0, cache_y, cache_screen_length , cache_status_height_, Qt::green); + p->fillRect(0, cache_y, cache_screen_length, cache_status_height_, Qt::green); foreach (const TimeRange& range, playback_cache_->GetInvalidatedRanges(len)) { int range_left = TimeToScene(range.in()); @@ -284,11 +279,11 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect) continue; } - int adjusted_left = qMax(0, range_left); + int adjusted_left = qMax(lim_left, range_left); p->fillRect(adjusted_left, cache_y, - qMin(width(), range_right) - adjusted_left, + qMin(lim_right, range_right) - adjusted_left, cache_status_height_, Qt::red); } @@ -304,6 +299,8 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect) void TimeRuler::TimebaseChangedEvent(const rational &tb) { + super::TimebaseChangedEvent(tb); + timebase_flipped_dbl_ = tb.flipped().toDouble(); update(); @@ -314,14 +311,20 @@ int TimeRuler::CacheStatusHeight() const return fontMetrics().height() / 4; } -void TimeRuler::ShowContextMenu() +bool TimeRuler::ShowContextMenu(const QPoint &p) { - Menu m(this); + if (super::ShowContextMenu(p)) { + return true; + } else { + Menu m(this); - MenuShared::instance()->AddItemsForTimeRulerMenu(&m); - MenuShared::instance()->AboutToShowTimeRulerActions(timebase()); + MenuShared::instance()->AddItemsForTimeRulerMenu(&m); + MenuShared::instance()->AboutToShowTimeRulerActions(timebase()); - m.exec(QCursor::pos()); + m.exec(mapToGlobal(p)); + + return true; + } } void TimeRuler::UpdateHeight() @@ -339,7 +342,7 @@ void TimeRuler::UpdateHeight() } // Add marker height - height += text_height(); + height += TimelineMarker::GetMarkerHeight(fontMetrics()); setFixedHeight(height); } diff --git a/app/widget/timeruler/timeruler.h b/app/widget/timeruler/timeruler.h index 3a475d398..b6bfc8858 100644 --- a/app/widget/timeruler/timeruler.h +++ b/app/widget/timeruler/timeruler.h @@ -45,6 +45,9 @@ protected: virtual void TimebaseChangedEvent(const rational& tb) override; +protected slots: + virtual bool ShowContextMenu(const QPoint &p) override; + private: void UpdateHeight(); @@ -64,9 +67,6 @@ private: PlaybackCache* playback_cache_; -private slots: - void ShowContextMenu(); - }; } From f7396de1218b5ab634d920941db5331bfb021509 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 1 May 2022 22:33:58 -0700 Subject: [PATCH 71/81] markers: reimplemented copy/paste --- .../keyframeproperties/keyframeproperties.cpp | 18 +- .../keyframeproperties/keyframeproperties.h | 4 +- .../markerpropertiesdialog.cpp | 16 +- .../markerproperties/markerpropertiesdialog.h | 4 +- app/node/CMakeLists.txt | 2 - app/node/nodecopypaste.cpp | 91 --------- app/node/nodecopypaste.h | 57 ------ app/node/project/serializer/serializer.cpp | 61 ++++++ app/node/project/serializer/serializer.h | 25 ++- .../project/serializer/serializer220403.cpp | 193 +++++++++++------- .../project/serializer/serializer220403.h | 4 + app/task/project/load/load.cpp | 3 + app/task/project/save/save.cpp | 1 + app/timeline/timelinemarker.cpp | 6 + app/timeline/timelinemarker.h | 1 + app/widget/curvewidget/curveview.cpp | 12 +- app/widget/curvewidget/curvewidget.cpp | 14 +- app/widget/keyframeview/keyframeview.cpp | 10 +- app/widget/keyframeview/keyframeview.h | 2 +- app/widget/nodeview/nodeview.cpp | 93 +++++---- app/widget/nodeview/nodeview.h | 6 +- .../timebased/timebasedviewselectionmanager.h | 34 +-- app/widget/timelinewidget/timelinewidget.cpp | 135 ++++++------ app/widget/timelinewidget/timelinewidget.h | 6 +- app/widget/timeruler/seekablewidget.cpp | 71 +++++-- app/widget/timeruler/seekablewidget.h | 7 +- 26 files changed, 452 insertions(+), 424 deletions(-) delete mode 100644 app/node/nodecopypaste.cpp delete mode 100644 app/node/nodecopypaste.h diff --git a/app/dialog/keyframeproperties/keyframeproperties.cpp b/app/dialog/keyframeproperties/keyframeproperties.cpp index 9637c486f..65fbcd791 100644 --- a/app/dialog/keyframeproperties/keyframeproperties.cpp +++ b/app/dialog/keyframeproperties/keyframeproperties.cpp @@ -30,7 +30,7 @@ namespace olive { -KeyframePropertiesDialog::KeyframePropertiesDialog(const QVector &keys, const rational &timebase, QWidget *parent) : +KeyframePropertiesDialog::KeyframePropertiesDialog(const std::vector &keys, const rational &timebase, QWidget *parent) : QDialog(parent), keys_(keys), timebase_(timebase) @@ -91,7 +91,7 @@ KeyframePropertiesDialog::KeyframePropertiesDialog(const QVector bool all_same_bezier_out_x = true; bool all_same_bezier_out_y = true; - for (int i=0;i 0) { NodeKeyframe* prev_key = keys_.at(i-1); NodeKeyframe* this_key = keys_.at(i); @@ -126,7 +126,7 @@ KeyframePropertiesDialog::KeyframePropertiesDialog(const QVector // Determine if any keyframes are on the same track (in which case we can't set the time) if (can_set_time) { - for (int j=0;jtrack() == keys_.at(i)->track()) { can_set_time = false; @@ -147,7 +147,7 @@ KeyframePropertiesDialog::KeyframePropertiesDialog(const QVector } if (all_same_time) { - time_slider_->SetValue(keys_.first()->time()); + time_slider_->SetValue(keys_.front()->time()); } else { time_slider_->SetTristate(); } @@ -169,7 +169,7 @@ KeyframePropertiesDialog::KeyframePropertiesDialog(const QVector if (all_same_type) { // If all keyframes are the same type, set it here for (int i=0;icount();i++) { - if (type_select_->itemData(i).toInt() == keys_.first()->type()) { + if (type_select_->itemData(i).toInt() == keys_.front()->type()) { type_select_->setCurrentIndex(i); // Ensure UI updates for this index @@ -179,10 +179,10 @@ KeyframePropertiesDialog::KeyframePropertiesDialog(const QVector } } - SetUpBezierSlider(bezier_in_x_slider_, all_same_bezier_in_x, keys_.first()->bezier_control_in().x()); - SetUpBezierSlider(bezier_in_y_slider_, all_same_bezier_in_y, keys_.first()->bezier_control_in().y()); - SetUpBezierSlider(bezier_out_x_slider_, all_same_bezier_out_x, keys_.first()->bezier_control_out().x()); - SetUpBezierSlider(bezier_out_y_slider_, all_same_bezier_out_y, keys_.first()->bezier_control_out().y()); + SetUpBezierSlider(bezier_in_x_slider_, all_same_bezier_in_x, keys_.front()->bezier_control_in().x()); + SetUpBezierSlider(bezier_in_y_slider_, all_same_bezier_in_y, keys_.front()->bezier_control_in().y()); + SetUpBezierSlider(bezier_out_x_slider_, all_same_bezier_out_x, keys_.front()->bezier_control_out().x()); + SetUpBezierSlider(bezier_out_y_slider_, all_same_bezier_out_y, keys_.front()->bezier_control_out().y()); row++; diff --git a/app/dialog/keyframeproperties/keyframeproperties.h b/app/dialog/keyframeproperties/keyframeproperties.h index 835845943..418c09321 100644 --- a/app/dialog/keyframeproperties/keyframeproperties.h +++ b/app/dialog/keyframeproperties/keyframeproperties.h @@ -35,7 +35,7 @@ class KeyframePropertiesDialog : public QDialog { Q_OBJECT public: - KeyframePropertiesDialog(const QVector& keys, const rational& timebase, QWidget* parent = nullptr); + KeyframePropertiesDialog(const std::vector& keys, const rational& timebase, QWidget* parent = nullptr); public slots: virtual void accept() override; @@ -43,7 +43,7 @@ public slots: private: void SetUpBezierSlider(FloatSlider *slider, bool all_same, double value); - const QVector& keys_; + const std::vector& keys_; rational timebase_; diff --git a/app/dialog/markerproperties/markerpropertiesdialog.cpp b/app/dialog/markerproperties/markerpropertiesdialog.cpp index 5473af3ab..007625f8f 100644 --- a/app/dialog/markerproperties/markerpropertiesdialog.cpp +++ b/app/dialog/markerproperties/markerpropertiesdialog.cpp @@ -32,7 +32,7 @@ namespace olive { #define super QDialog -MarkerPropertiesDialog::MarkerPropertiesDialog(const QVector &markers, const rational &timebase, QWidget *parent) : +MarkerPropertiesDialog::MarkerPropertiesDialog(const std::vector &markers, const rational &timebase, QWidget *parent) : super(parent), markers_(markers) { @@ -60,10 +60,10 @@ MarkerPropertiesDialog::MarkerPropertiesDialog(const QVector & } if (markers.size() == 1) { - in_slider_->SetValue(markers.first()->time_range().in()); + in_slider_->SetValue(markers.front()->time_range().in()); in_slider_->SetDisplayType(RationalSlider::kTime); in_slider_->SetTimebase(timebase); - out_slider_->SetValue(markers.first()->time_range().out()); + out_slider_->SetValue(markers.front()->time_range().out()); out_slider_->SetDisplayType(RationalSlider::kTime); out_slider_->SetTimebase(timebase); } else { @@ -83,8 +83,8 @@ MarkerPropertiesDialog::MarkerPropertiesDialog(const QVector & color_menu_ = new ColorCodingComboBox(); layout->addWidget(color_menu_, row, 1); - color_menu_->SetColor(markers.first()->color()); - for (int i=1; iSetColor(markers.front()->color()); + for (size_t i=1; icolor() != color_menu_->GetSelectedColor()) { color_menu_->SetColor(-1); break; @@ -102,8 +102,8 @@ MarkerPropertiesDialog::MarkerPropertiesDialog(const QVector & layout->addWidget(label_edit_, row, 1); // Determine what the startup label text should be - label_edit_->setText(markers.first()->name()); - for (int i=1; isetText(markers.front()->name()); + for (size_t i=1; iname() != label_edit_->text()) { label_edit_->clear(); label_edit_->setPlaceholderText(tr("(multiple)")); @@ -141,7 +141,7 @@ void MarkerPropertiesDialog::accept() } if (markers_.size() == 1) { - command->add_child(new MarkerChangeTimeCommand(markers_.first(), TimeRange(in_slider_->GetValue(), out_slider_->GetValue()))); + command->add_child(new MarkerChangeTimeCommand(markers_.front(), TimeRange(in_slider_->GetValue(), out_slider_->GetValue()))); } Core::instance()->undo_stack()->pushIfHasChildren(command); diff --git a/app/dialog/markerproperties/markerpropertiesdialog.h b/app/dialog/markerproperties/markerpropertiesdialog.h index 218339af1..a358dc9a7 100644 --- a/app/dialog/markerproperties/markerpropertiesdialog.h +++ b/app/dialog/markerproperties/markerpropertiesdialog.h @@ -55,13 +55,13 @@ class MarkerPropertiesDialog : public QDialog { Q_OBJECT public: - MarkerPropertiesDialog(const QVector &markers, const rational &timebase, QWidget *parent = nullptr); + MarkerPropertiesDialog(const std::vector &markers, const rational &timebase, QWidget *parent = nullptr); public slots: virtual void accept() override; private: - QVector markers_; + std::vector markers_; LineEditWithFocusSignal *label_edit_; diff --git a/app/node/CMakeLists.txt b/app/node/CMakeLists.txt index cd510b021..3b66b8129 100644 --- a/app/node/CMakeLists.txt +++ b/app/node/CMakeLists.txt @@ -48,8 +48,6 @@ set(OLIVE_SOURCES node/keyframe.h node/node.cpp node/node.h - node/nodecopypaste.cpp - node/nodecopypaste.h node/param.cpp node/param.h node/splitvalue.h diff --git a/app/node/nodecopypaste.cpp b/app/node/nodecopypaste.cpp deleted file mode 100644 index 7741f9940..000000000 --- a/app/node/nodecopypaste.cpp +++ /dev/null @@ -1,91 +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 "nodecopypaste.h" - -#include - -#include "core.h" -#include "node/factory.h" -#include "widget/nodeview/nodeviewundo.h" -#include "window/mainwindow/mainwindow.h" - -namespace olive { - -void NodeCopyPasteService::CopyNodesToClipboard(QVector nodes, void *userdata) -{ - QString copy_str; - - QXmlStreamWriter writer(©_str); - - // For any groups, add children - for (int i=0; i(nodes.at(i))) { - for (auto it=g->GetContextPositions().cbegin(); it!=g->GetContextPositions().cend(); it++) { - if (!nodes.contains(it.key())) { - nodes.append(it.key()); - } - } - } - } - - ProjectSerializer::SaveData data(nodes.first()->project(), QString(), nodes); - - CopyNodesToClipboardCallback(nodes, &data, userdata); - - ProjectSerializer::Save(&writer, data, type_); - - Core::CopyStringToClipboard(copy_str); -} - -void NodeCopyPasteService::PasteNodesFromClipboard(void *userdata) -{ - QString clipboard = Core::PasteStringFromClipboard(); - if (clipboard.isEmpty()) { - return; - } - - QXmlStreamReader reader(clipboard); - - Project temp; - ProjectSerializer::Result res = ProjectSerializer::Load(&temp, &reader, type_); - - if (res.code() != ProjectSerializer::kSuccess) { - return; - } - - QVector pasted_nodes; - foreach (Node *n, temp.nodes()) { - if (!temp.default_nodes().contains(n)) { - // Move nodes out of Project - n->setParent(nullptr); - pasted_nodes.append(n); - } - } - - if (pasted_nodes.isEmpty()) { - return; - } - - PasteNodesToClipboardCallback(pasted_nodes, res.GetLoadData(), userdata); -} - -} diff --git a/app/node/nodecopypaste.h b/app/node/nodecopypaste.h deleted file mode 100644 index eaf3c2b86..000000000 --- a/app/node/nodecopypaste.h +++ /dev/null @@ -1,57 +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 NODECOPYPASTEWIDGET_H -#define NODECOPYPASTEWIDGET_H - -#include -#include - -#include "node/node.h" -#include "node/project/project.h" -#include "node/project/sequence/sequence.h" -#include "node/project/serializer/serializer.h" - -namespace olive { - -class NodeCopyPasteService -{ -public: - NodeCopyPasteService(const QString &type) : - type_(type) - {} - -protected: - void CopyNodesToClipboard(QVector nodes, void* userdata = nullptr); - - void PasteNodesFromClipboard(void* userdata = nullptr); - - virtual void CopyNodesToClipboardCallback(const QVector &nodes, ProjectSerializer::SaveData *data, void *userdata){} - - virtual void PasteNodesToClipboardCallback(const QVector &nodes, const ProjectSerializer::LoadData &load_data, void *userdata){} - -private: - QString type_; - -}; - -} - -#endif // NODECOPYPASTEWIDGET_H diff --git a/app/node/project/serializer/serializer.cpp b/app/node/project/serializer/serializer.cpp index 5f3a3507e..67bd879f7 100644 --- a/app/node/project/serializer/serializer.cpp +++ b/app/node/project/serializer/serializer.cpp @@ -118,6 +118,36 @@ ProjectSerializer::Result ProjectSerializer::Load(Project *project, QXmlStreamRe return res; } +ProjectSerializer::Result ProjectSerializer::Paste(const QString &type) +{ + QString clipboard = Core::PasteStringFromClipboard(); + if (clipboard.isEmpty()) { + return kNoData; + } + + QXmlStreamReader reader(clipboard); + + Project temp; + ProjectSerializer::Result res = ProjectSerializer::Load(&temp, &reader, type); + + if (res.code() != ProjectSerializer::kSuccess) { + return res; + } + + QVector pasted_nodes; + foreach (Node *n, temp.nodes()) { + if (!temp.default_nodes().contains(n)) { + // Move nodes out of Project + n->setParent(nullptr); + pasted_nodes.append(n); + } + } + + res.SetLoadedNodes(pasted_nodes); + + return res; +} + ProjectSerializer::Result ProjectSerializer::Save(const SaveData &data, const QString &type) { QString temp_save = FileFunctions::GetSafeTemporaryFilename(data.GetFilename()); @@ -187,6 +217,20 @@ ProjectSerializer::Result ProjectSerializer::Save(QXmlStreamWriter *writer, cons return kSuccess; } +ProjectSerializer::Result ProjectSerializer::Copy(const SaveData &data, const QString &type) +{ + QString copy_str; + QXmlStreamWriter writer(©_str); + + ProjectSerializer::Result res = ProjectSerializer::Save(&writer, data, type); + + if (res == kSuccess) { + Core::CopyStringToClipboard(copy_str); + } + + return res; +} + bool ProjectSerializer::IsCancelled() const { return false; @@ -228,4 +272,21 @@ ProjectSerializer::Result ProjectSerializer::LoadWithSerializerVersion(uint vers } } +void ProjectSerializer::SaveData::SetOnlySerializeNodesAndResolveGroups(QVector nodes) +{ + // For any groups, add children + for (int i=0; i(nodes.at(i))) { + for (auto it=g->GetContextPositions().cbegin(); it!=g->GetContextPositions().cend(); it++) { + if (!nodes.contains(it.key())) { + nodes.append(it.key()); + } + } + } + } + + SetOnlySerializeNodes(nodes); +} + } diff --git a/app/node/project/serializer/serializer.h b/app/node/project/serializer/serializer.h index 79968ea43..cd6b99082 100644 --- a/app/node/project/serializer/serializer.h +++ b/app/node/project/serializer/serializer.h @@ -50,7 +50,8 @@ public: kUnknownVersion, kFileError, kXmlError, - kOverwriteError + kOverwriteError, + kNoData }; using SerializedProperties = QHash >; @@ -62,6 +63,8 @@ public: SerializedProperties properties; + std::vector markers; + }; class Result @@ -84,6 +87,10 @@ public: void SetLoadData(const LoadData &p) { load_data_ = p; } + const QVector &GetLoadedNodes() const { return loaded_nodes_; } + + void SetLoadedNodes(const QVector &n) { loaded_nodes_ = n; } + private: ResultCode code_; @@ -91,17 +98,17 @@ public: LoadData load_data_; + QVector loaded_nodes_; + }; class SaveData { public: - SaveData(Project *project, const QString &filename, const QVector &only = QVector(), const SerializedProperties &p = SerializedProperties()) + SaveData(Project *project, const QString &filename = QString()) { project_ = project; filename_ = filename; - only_serialize_nodes_ = only; - properties_ = p; } Project *GetProject() const @@ -115,11 +122,13 @@ public: } const QVector &GetOnlySerializeNodes() const { return only_serialize_nodes_; } - void SetOnlySerializeNodes(const QVector &only) { only_serialize_nodes_ = only; } + void SetOnlySerializeNodesAndResolveGroups(QVector only); + + const std::vector &GetOnlySerializeMarkers() const { return only_serialize_markers_; } + void SetOnlySerializeMarkers(const std::vector &only) { only_serialize_markers_ = only; } const SerializedProperties &GetProperties() const { return properties_; } - void SetProperties(const SerializedProperties &p) { properties_ = p; } private: @@ -131,6 +140,8 @@ public: SerializedProperties properties_; + std::vector only_serialize_markers_; + }; static void Initialize(); @@ -139,9 +150,11 @@ public: static Result Load(Project *project, const QString &filename, const QString &type); static Result Load(Project *project, QXmlStreamReader *read_device, const QString &type); + static Result Paste(const QString &type); static Result Save(const SaveData &data, const QString &type); static Result Save(QXmlStreamWriter *write_device, const SaveData &data, const QString &type); + static Result Copy(const SaveData &data, const QString &type); protected: virtual LoadData Load(Project *project, QXmlStreamReader *reader, void *reserved) const = 0; diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index aa80921dd..c64e420bf 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -31,6 +31,8 @@ ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project QMap > positions; XMLNodeData xml_node_data; + LoadData load_data; + while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("layout")) { @@ -97,6 +99,18 @@ ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project } } + } else if (reader->name() == QStringLiteral("markers")) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("marker")) { + TimelineMarker *marker = new TimelineMarker(); + LoadMarker(reader, marker); + load_data.markers.push_back(marker); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("positions")) { while (XMLReadNextStartElement(reader)) { @@ -194,8 +208,6 @@ ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project // Make connections PostConnect(xml_node_data); - LoadData load_data; - // Resolve serialized properties (if any) for (auto it=properties.cbegin(); it!=properties.cend(); it++) { Node *node = xml_node_data.node_ptrs.value(it.key()); @@ -211,74 +223,94 @@ void ProjectSerializer220403::Save(QXmlStreamWriter *writer, const SaveData &dat { Project *project = data.GetProject(); - writer->writeTextElement(QStringLiteral("uuid"), data.GetProject()->GetUuid().toString()); + if (!data.GetOnlySerializeMarkers().empty()) { - writer->writeStartElement(QStringLiteral("nodes")); + writer->writeStartElement(QStringLiteral("markers")); - const QVector &using_node_list = (data.GetOnlySerializeNodes().isEmpty()) ? project->nodes() : data.GetOnlySerializeNodes(); + for (auto it=data.GetOnlySerializeMarkers().cbegin(); it!=data.GetOnlySerializeMarkers().cend(); it++) { + TimelineMarker *marker = *it; - foreach (Node* node, using_node_list) { - writer->writeStartElement(QStringLiteral("node")); + writer->writeStartElement(QStringLiteral("marker")); - if (node == project->root()) { - writer->writeAttribute(QStringLiteral("root"), QStringLiteral("1")); - } else if (node == project->color_manager()) { - writer->writeAttribute(QStringLiteral("cm"), QStringLiteral("1")); - } else if (node == project->settings()) { - writer->writeAttribute(QStringLiteral("settings"), QStringLiteral("1")); + SaveMarker(writer, marker); + + writer->writeEndElement(); // marker } - writer->writeAttribute(QStringLiteral("id"), node->id()); + writer->writeEndElement(); // markers - SaveNode(node, writer); + } else { - writer->writeEndElement(); // node - } + writer->writeTextElement(QStringLiteral("uuid"), data.GetProject()->GetUuid().toString()); - writer->writeEndElement(); // nodes + writer->writeStartElement(QStringLiteral("nodes")); - writer->writeStartElement(QStringLiteral("positions")); + const QVector &using_node_list = (data.GetOnlySerializeNodes().isEmpty()) ? project->nodes() : data.GetOnlySerializeNodes(); - foreach (Node* context, using_node_list) { - const Node::PositionMap &map = context->GetContextPositions(); + foreach (Node* node, using_node_list) { + writer->writeStartElement(QStringLiteral("node")); - if (!map.isEmpty()) { - writer->writeStartElement(QStringLiteral("context")); - - writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(context))); - - for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { - if (data.GetOnlySerializeNodes().isEmpty() || data.GetOnlySerializeNodes().contains(jt.key())) { - writer->writeStartElement(QStringLiteral("node")); - SavePosition(writer, jt.key(), jt.value()); - writer->writeEndElement(); // node - } + if (node == project->root()) { + writer->writeAttribute(QStringLiteral("root"), QStringLiteral("1")); + } else if (node == project->color_manager()) { + writer->writeAttribute(QStringLiteral("cm"), QStringLiteral("1")); + } else if (node == project->settings()) { + writer->writeAttribute(QStringLiteral("settings"), QStringLiteral("1")); } - writer->writeEndElement(); // context - } - } + writer->writeAttribute(QStringLiteral("id"), node->id()); - writer->writeEndElement(); // positions + SaveNode(node, writer); - writer->writeStartElement(QStringLiteral("properties")); - - for (auto it=data.GetProperties().cbegin(); it!=data.GetProperties().cend(); it++) { - writer->writeStartElement(QStringLiteral("node")); - - writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(it.key()))); - - for (auto jt=it.value().cbegin(); jt!=it.value().cend(); jt++) { - writer->writeTextElement(jt.key(), jt.value()); + writer->writeEndElement(); // node } - writer->writeEndElement(); // node + writer->writeEndElement(); // nodes + + writer->writeStartElement(QStringLiteral("positions")); + + foreach (Node* context, using_node_list) { + const Node::PositionMap &map = context->GetContextPositions(); + + if (!map.isEmpty()) { + writer->writeStartElement(QStringLiteral("context")); + + writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(context))); + + for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { + if (data.GetOnlySerializeNodes().isEmpty() || data.GetOnlySerializeNodes().contains(jt.key())) { + writer->writeStartElement(QStringLiteral("node")); + SavePosition(writer, jt.key(), jt.value()); + writer->writeEndElement(); // node + } + } + + writer->writeEndElement(); // context + } + } + + writer->writeEndElement(); // positions + + writer->writeStartElement(QStringLiteral("properties")); + + for (auto it=data.GetProperties().cbegin(); it!=data.GetProperties().cend(); it++) { + writer->writeStartElement(QStringLiteral("node")); + + writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(it.key()))); + + for (auto jt=it.value().cbegin(); jt!=it.value().cend(); jt++) { + writer->writeTextElement(jt.key(), jt.value()); + } + + writer->writeEndElement(); // node + } + + writer->writeEndElement(); // properties + + // Save main window project layout + project->GetLayoutInfo().toXml(writer); + } - - writer->writeEndElement(); // properties - - // Save main window project layout - project->GetLayoutInfo().toXml(writer); } void ProjectSerializer220403::LoadNode(Node *node, XMLNodeData &xml_node_data, QXmlStreamReader *reader) const @@ -954,6 +986,36 @@ void ProjectSerializer220403::SaveTimelinePoints(QXmlStreamWriter *writer, Timel writer->writeEndElement(); // markers } +void ProjectSerializer220403::LoadMarker(QXmlStreamReader *reader, TimelineMarker *marker) const +{ + rational in, out; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("name")) { + marker->set_name(attr.value().toString()); + } else if (attr.name() == QStringLiteral("in")) { + in = rational::fromString(attr.value().toString()); + } else if (attr.name() == QStringLiteral("out")) { + out = rational::fromString(attr.value().toString()); + } else if (attr.name() == QStringLiteral("color")) { + marker->set_color(attr.value().toInt()); + } + } + + marker->set_time(TimeRange(in, out)); + + // This element has no inner text, so just skip it + reader->skipCurrentElement(); +} + +void ProjectSerializer220403::SaveMarker(QXmlStreamWriter *writer, TimelineMarker *marker) const +{ + writer->writeAttribute(QStringLiteral("name"), marker->name()); + writer->writeAttribute(QStringLiteral("in"), marker->time_range().in().toString()); + writer->writeAttribute(QStringLiteral("out"), marker->time_range().out().toString()); + writer->writeAttribute(QStringLiteral("color"), QString::number(marker->color())); +} + void ProjectSerializer220403::LoadWorkArea(QXmlStreamReader *reader, TimelineWorkArea *workarea) const { rational range_in = workarea->in(); @@ -989,26 +1051,11 @@ void ProjectSerializer220403::LoadMarkerList(QXmlStreamReader *reader, TimelineM { while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("marker")) { - QString name; - rational in, out; - int color = Config::Current()[QStringLiteral("MarkerColor")].toInt(); - - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("name")) { - name = attr.value().toString(); - } else if (attr.name() == QStringLiteral("in")) { - in = rational::fromString(attr.value().toString()); - } else if (attr.name() == QStringLiteral("out")) { - out = rational::fromString(attr.value().toString()); - } else if (attr.name() == QStringLiteral("color")) { - color = attr.value().toInt(); - } - } - - new TimelineMarker(color, TimeRange(in, out), name, markers); + TimelineMarker *marker = new TimelineMarker(markers); + LoadMarker(reader, marker); + } else { + reader->skipCurrentElement(); } - - reader->skipCurrentElement(); } } @@ -1019,11 +1066,7 @@ void ProjectSerializer220403::SaveMarkerList(QXmlStreamWriter *writer, TimelineM writer->writeStartElement(QStringLiteral("marker")); - writer->writeAttribute(QStringLiteral("name"), marker->name()); - - writer->writeAttribute(QStringLiteral("in"), marker->time_range().in().toString()); - writer->writeAttribute(QStringLiteral("out"), marker->time_range().out().toString()); - writer->writeAttribute(QStringLiteral("color"), QString::number(marker->color())); + SaveMarker(writer, marker); writer->writeEndElement(); // marker } diff --git a/app/node/project/serializer/serializer220403.h b/app/node/project/serializer/serializer220403.h index f6a1b939f..16de30679 100644 --- a/app/node/project/serializer/serializer220403.h +++ b/app/node/project/serializer/serializer220403.h @@ -100,6 +100,10 @@ private: void SaveTimelinePoints(QXmlStreamWriter *writer, TimelinePoints *points) const; + void LoadMarker(QXmlStreamReader *reader, TimelineMarker *marker) const; + + void SaveMarker(QXmlStreamWriter *writer, TimelineMarker *marker) const; + void LoadWorkArea(QXmlStreamReader *reader, TimelineWorkArea *workarea) const; void SaveWorkArea(QXmlStreamWriter *writer, TimelineWorkArea *workarea) const; diff --git a/app/task/project/load/load.cpp b/app/task/project/load/load.cpp index f1878dec9..17497723c 100644 --- a/app/task/project/load/load.cpp +++ b/app/task/project/load/load.cpp @@ -57,6 +57,9 @@ bool ProjectLoadTask::Run() case ProjectSerializer::kXmlError: SetError(tr("Failed to read XML document. File may be corrupt. Error was: %1").arg(result.GetDetails())); break; + case ProjectSerializer::kNoData: + SetError(tr("Failed to find any data to parse.")); + break; // Errors that should never be thrown by a load case ProjectSerializer::kOverwriteError: diff --git a/app/task/project/save/save.cpp b/app/task/project/save/save.cpp index 3d4c5f6e5..24c95b665 100644 --- a/app/task/project/save/save.cpp +++ b/app/task/project/save/save.cpp @@ -66,6 +66,7 @@ bool ProjectSaveTask::Run() case ProjectSerializer::kProjectTooNew: case ProjectSerializer::kProjectTooOld: case ProjectSerializer::kUnknownVersion: + case ProjectSerializer::kNoData: SetError(tr("Unknown error.")); break; } diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index bb40b7790..32eb98976 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -28,6 +28,12 @@ namespace olive { +TimelineMarker::TimelineMarker(QObject *parent) : + color_(Config::Current()[QStringLiteral("MarkerColor")].toInt()) +{ + setParent(parent); +} + TimelineMarker::TimelineMarker(int color, const TimeRange &time, const QString &name, QObject *parent) : time_(time), name_(name), diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index d55af84bf..1389da26e 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -35,6 +35,7 @@ class TimelineMarker : public QObject { Q_OBJECT public: + TimelineMarker(QObject* parent = nullptr); TimelineMarker(int color, const TimeRange& time, const QString& name = QString(), QObject* parent = nullptr); const rational &time() const { return time_.in(); } diff --git a/app/widget/curvewidget/curveview.cpp b/app/widget/curvewidget/curveview.cpp index e756d7376..49748e5d2 100644 --- a/app/widget/curvewidget/curveview.cpp +++ b/app/widget/curvewidget/curveview.cpp @@ -406,7 +406,7 @@ void CurveView::FirstChanceMouseRelease(QMouseEvent *event) void CurveView::KeyframeDragStart(QMouseEvent *event) { drag_keyframe_values_.resize(GetSelectedKeyframes().size()); - for (int i=0; ivalue(); } @@ -418,7 +418,7 @@ void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip) { if (event->modifiers() & Qt::ShiftModifier) { // Lock to X axis only and set original values on all keys - for (int i=0; iset_value(drag_keyframe_values_.at(i)); } @@ -429,7 +429,7 @@ void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip) 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; iset_value(FloatSlider::TransformDisplayToValue(FloatSlider::TransformValueToDisplay(drag_keyframe_values_.at(i).toDouble(), display) - scaled_diff, display)); } - NodeKeyframe *tip_item = GetSelectedKeyframes().first(); + NodeKeyframe *tip_item = GetSelectedKeyframes().front(); bool ok; double num_value = tip_item->value().toDouble(&ok); @@ -472,7 +472,7 @@ void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip) void CurveView::KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *command) { - for (int i=0; ivalue().toDouble(), drag_keyframe_values_.at(i).toDouble())) { command->add_child(new NodeParamSetKeyframeValueCommand(k, k->value(), drag_keyframe_values_.at(i))); diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index 4d56896ae..e75d6746b 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -251,16 +251,16 @@ void CurveWidget::ConnectInputInternal(Node *node, const QString &input, int ele void CurveWidget::SelectionChanged() { - const QVector &selected = view_->GetSelectedKeyframes(); + const std::vector &selected = view_->GetSelectedKeyframes(); SetKeyframeButtonChecked(false); - SetKeyframeButtonEnabled(!selected.isEmpty()); + SetKeyframeButtonEnabled(!selected.empty()); - if (!selected.isEmpty()) { + if (!selected.empty()) { bool all_same_type = true; - NodeKeyframe::Type type = selected.first()->type(); + NodeKeyframe::Type type = selected.front()->type(); - for (int i=1;i &selected = view_->GetSelectedKeyframes(); - if (selected.isEmpty()) { + const std::vector &selected = view_->GetSelectedKeyframes(); + if (selected.empty()) { return; } diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index 0d5241e3c..16d845352 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -462,11 +462,11 @@ void KeyframeView::ShowContextMenu() QAction* bezier_key_action = nullptr; QAction* hold_key_action = nullptr; - if (!GetSelectedKeyframes().isEmpty()) { + if (!GetSelectedKeyframes().empty()) { bool all_keys_are_same_type = true; - NodeKeyframe::Type type = GetSelectedKeyframes().first()->type(); + NodeKeyframe::Type type = GetSelectedKeyframes().front()->type(); - for (int i=1;i &GetSelectedKeyframes() const + const std::vector &GetSelectedKeyframes() const { return selection_manager_.GetSelectedObjects(); } diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index c5bcb355a..07251995d 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -32,6 +32,7 @@ #include "node/distort/transform/transformdistortnode.h" #include "node/factory.h" #include "node/group/group.h" +#include "node/project/serializer/serializer.h" #include "node/traverser.h" #include "ui/icons/icons.h" #include "widget/menu/menushared.h" @@ -45,7 +46,6 @@ const double NodeView::kMinimumScale = 0.1; NodeView::NodeView(QWidget *parent) : HandMovableView(parent), - NodeCopyPasteService(QStringLiteral("nodes")), drop_edge_(nullptr), create_edge_(nullptr), create_edge_output_item_(nullptr), @@ -204,7 +204,31 @@ void NodeView::CopySelected(bool cut) return; } - CopyNodesToClipboard(selected_nodes_); + QString copy_str; + QXmlStreamWriter writer(©_str); + + ProjectSerializer::SaveData sdata(selected_nodes_.first()->project()); + sdata.SetOnlySerializeNodesAndResolveGroups(selected_nodes_); + + ProjectSerializer::SerializedProperties properties; + + for (Node *n : selected_nodes_) { + NodeViewItem *item = GetAssumedItemForSelectedNode(n); + + if (item) { + Node::Position pos = item->GetNodePositionData(); + + properties[n][QStringLiteral("x")] = QString::number(pos.position.x()); + properties[n][QStringLiteral("y")] = QString::number(pos.position.y()); + properties[n][QStringLiteral("expanded")] = QString::number(pos.expanded); + } + } + + sdata.SetProperties(properties); + + ProjectSerializer::Save(&writer, sdata, QStringLiteral("nodes")); + + Core::CopyStringToClipboard(copy_str); if (cut) { DeleteSelected(); @@ -213,9 +237,31 @@ void NodeView::CopySelected(bool cut) void NodeView::Paste() { - if (!contexts_.isEmpty()) { - PasteNodesFromClipboard(); + if (contexts_.isEmpty()) { + return; } + + ProjectSerializer::Result res = ProjectSerializer::Paste(QStringLiteral("nodes")); + if (res.GetLoadedNodes().isEmpty()) { + return; + } + + Node::PositionMap map; + + for (auto it=res.GetLoadData().properties.cbegin(); it!=res.GetLoadData().properties.cend(); it++) { + Node::Position pos; + + const QMap &node_props = it.value(); + pos.position.setX(node_props.value(QStringLiteral("x")).toDouble()); + pos.position.setY(node_props.value(QStringLiteral("y")).toDouble()); + pos.expanded = node_props.value(QStringLiteral("expanded")).toDouble(); + + qDebug() << it.key() << pos.position; + + map.insert(it.key(), pos); + } + + PostPaste(res.GetLoadedNodes(), map); } void NodeView::Duplicate() @@ -1007,43 +1053,6 @@ bool NodeView::eventFilter(QObject *object, QEvent *event) return super::eventFilter(object, event); } -void NodeView::CopyNodesToClipboardCallback(const QVector &nodes, ProjectSerializer::SaveData *sdata, void *userdata) -{ - ProjectSerializer::SerializedProperties properties; - - for (Node *n : nodes) { - NodeViewItem *item = GetAssumedItemForSelectedNode(n); - - if (item) { - Node::Position pos = item->GetNodePositionData(); - - properties[n][QStringLiteral("x")] = QString::number(pos.position.x()); - properties[n][QStringLiteral("y")] = QString::number(pos.position.y()); - properties[n][QStringLiteral("expanded")] = QString::number(pos.expanded); - } - } - - sdata->SetProperties(properties); -} - -void NodeView::PasteNodesToClipboardCallback(const QVector &nodes, const ProjectSerializer::LoadData &ldata, void *userdata) -{ - Node::PositionMap map; - - for (auto it=ldata.properties.cbegin(); it!=ldata.properties.cend(); it++) { - Node::Position pos; - - const QMap &node_props = it.value(); - pos.position.setX(node_props.value(QStringLiteral("x")).toDouble()); - pos.position.setY(node_props.value(QStringLiteral("y")).toDouble()); - pos.expanded = node_props.value(QStringLiteral("expanded")).toDouble(); - - map.insert(it.key(), pos); - } - - PostPaste(nodes, map); -} - void NodeView::changeEvent(QEvent *e) { // Add translation code @@ -1548,7 +1557,7 @@ void NodeView::PostPaste(const QVector &new_nodes, const Node::PositionM AttachedItem &ai = new_attached[i]; if (ai.item) { - ai.original_pos = first_item->pos() - ai.item->pos(); + ai.original_pos = ai.item->pos() - first_item->pos(); } } } diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 3c62818db..26894511e 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -26,7 +26,6 @@ #include "core.h" #include "node/graph.h" -#include "node/nodecopypaste.h" #include "nodeviewedge.h" #include "nodeviewcontext.h" #include "nodeviewminimap.h" @@ -42,7 +41,7 @@ namespace olive { * This widget takes a NodeGraph object and constructs a QGraphicsScene representing its data, viewing and allowing * the user to make modifications to it. */ -class NodeView : public HandMovableView, public NodeCopyPasteService +class NodeView : public HandMovableView { Q_OBJECT public: @@ -139,9 +138,6 @@ protected: virtual bool eventFilter(QObject *object, QEvent *event) override; - virtual void CopyNodesToClipboardCallback(const QVector &nodes, ProjectSerializer::SaveData *data, void* userdata) override; - virtual void PasteNodesToClipboardCallback(const QVector &nodes, const ProjectSerializer::LoadData &ldata, void *userdata) override; - virtual void changeEvent(QEvent *e) override; private: diff --git a/app/widget/timebased/timebasedviewselectionmanager.h b/app/widget/timebased/timebasedviewselectionmanager.h index cc214e8dd..d13d4754e 100644 --- a/app/widget/timebased/timebasedviewselectionmanager.h +++ b/app/widget/timebased/timebasedviewselectionmanager.h @@ -48,7 +48,7 @@ public: void DeclareDrawnObject(T *object, const QRectF &pos) { - drawn_objects_.append({object, pos}); + drawn_objects_.push_back({object, pos}); } bool Select(T *key) @@ -56,7 +56,7 @@ public: Q_ASSERT(key); if (!IsSelected(key)) { - selected_.append(key); + selected_.push_back(key); return true; } @@ -67,7 +67,13 @@ public: { Q_ASSERT(key); - return selected_.removeOne(key); + auto it = std::find(selected_.cbegin(), selected_.cend(), key); + if (it == selected_.cend()) { + return false; + } else { + selected_.erase(it); + return true; + } } void ClearSelection() @@ -77,10 +83,10 @@ public: bool IsSelected(T *key) const { - return selected_.contains(key); + return std::find(selected_.cbegin(), selected_.cend(), key) != selected_.cend(); } - const QVector &GetSelectedObjects() const + const std::vector &GetSelectedObjects() const { return selected_; } @@ -142,7 +148,7 @@ public: bool IsDragging() const { - return !dragging_.isEmpty(); + return !dragging_.empty(); } void DragStart(T *initial_item, QMouseEvent *event) @@ -150,7 +156,7 @@ public: initial_drag_item_ = initial_item; dragging_.resize(selected_.size()); - for (int i=0; itime(); @@ -164,7 +170,7 @@ public: rational time_diff = view_->SceneToTimeNoGrid(view_->mapToScene(event->pos()).x() - drag_mouse_start_.x()); // Validate movement - for (int i=0; iset_time(dragging_.at(i) + time_diff); } @@ -219,7 +225,7 @@ public: { QToolTip::hideText(); - for (int i=0; iadd_child(new SetTimeCommand(selected_.at(i), selected_.at(i)->time(), dragging_.at(i))); } @@ -314,11 +320,11 @@ private: TimeBasedView *view_; using DrawnObject = QPair; - QVector drawn_objects_; + std::vector drawn_objects_; - QVector selected_; + std::vector selected_; - QVector dragging_; + std::vector dragging_; T *initial_drag_item_; @@ -328,7 +334,7 @@ private: QRubberBand *rubberband_; QPoint rubberband_start_; - QVector rubberband_preselected_; + std::vector rubberband_preselected_; }; diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 273aa7446..f62938346 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -31,6 +31,7 @@ #include "dialog/sequence/sequence.h" #include "dialog/speedduration/speeddurationdialog.h" #include "node/block/transition/transition.h" +#include "node/project/serializer/serializer.h" #include "tool/add.h" #include "tool/beam.h" #include "tool/edit.h" @@ -60,7 +61,6 @@ namespace olive { TimelineWidget::TimelineWidget(QWidget *parent) : super(true, true, parent), - NodeCopyPasteService(QStringLiteral("timeline")), rubberband_(QRubberBand::Rectangle, this), active_tool_(nullptr), use_audio_time_units_(false), @@ -312,66 +312,6 @@ void TimelineWidget::DisconnectNodeEvent(ViewerOutput *n) } } -void TimelineWidget::CopyNodesToClipboardCallback(const QVector &nodes, ProjectSerializer::SaveData *sdata, void *userdata) -{ - // Cache the earliest in point so all copied clips have a "relative" in point that can be pasted anywhere - QVector& selected = *static_cast*>(userdata); - rational earliest_in = RATIONAL_MAX; - ProjectSerializer::SerializedProperties properties; - - foreach (Block* block, selected) { - earliest_in = qMin(earliest_in, block->in()); - } - - foreach (Block* block, selected) { - properties[block][QStringLiteral("in")] = (block->in() - earliest_in).toString(); - properties[block][QStringLiteral("track")] = block->track()->ToReference().ToString(); - } - - sdata->SetProperties(properties); -} - -void TimelineWidget::PasteNodesToClipboardCallback(const QVector &nodes, const ProjectSerializer::LoadData &load_data, void *userdata) -{ - bool insert = *(bool*)userdata; - - MultiUndoCommand *command = new MultiUndoCommand(); - - foreach (Node *n, nodes) { - command->add_child(new NodeAddCommand(GetConnectedNode()->project(), n)); - } - - rational paste_start = GetTime(); - - if (insert) { - rational paste_end = GetTime(); - - for (auto it=load_data.properties.cbegin(); it!=load_data.properties.cend(); it++) { - rational length = static_cast(it.key())->length(); - rational in = rational::fromString(it.value()[QStringLiteral("in")]); - - paste_end = qMax(paste_end, paste_start + in + length); - } - - if (paste_end != paste_start) { - InsertGapsAt(paste_start, paste_end - paste_start, command); - } - } - - for (auto it=load_data.properties.cbegin(); it!=load_data.properties.cend(); it++) { - Block *block = static_cast(it.key()); - rational in = rational::fromString(it.value()[QStringLiteral("in")]); - Track::Reference track = Track::Reference::FromString(it.value()[QStringLiteral("track")]); - - command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()), - track.index(), - block, - paste_start + in)); - } - - Core::instance()->undo_stack()->pushIfHasChildren(command); -} - void TimelineWidget::SelectAll() { QVector newly_selected_blocks; @@ -633,20 +573,17 @@ void TimelineWidget::CopySelected(bool cut) return; } - if (ruler()->hasFocus()) { - ruler()->CopySelected(cut); + if (ruler()->hasFocus() && ruler()->CopySelected(cut)) { return; } - QVector selected = GetSelectedBlocks(); - - if (selected.isEmpty()) { + if (selected_blocks_.isEmpty()) { return; } QVector selected_nodes; - foreach (Block* block, selected) { + foreach (Block* block, selected_blocks_) { selected_nodes.append(block); QVector deps = block->GetDependencies(); @@ -658,7 +595,25 @@ void TimelineWidget::CopySelected(bool cut) } } - CopyNodesToClipboard(selected_nodes, &selected); + ProjectSerializer::SaveData sdata(selected_nodes.first()->project()); + sdata.SetOnlySerializeNodesAndResolveGroups(selected_nodes); + + // Cache the earliest in point so all copied clips have a "relative" in point that can be pasted anywhere + rational earliest_in = RATIONAL_MAX; + ProjectSerializer::SerializedProperties properties; + + foreach (Block* block, selected_blocks_) { + earliest_in = qMin(earliest_in, block->in()); + } + + foreach (Block* block, selected_blocks_) { + properties[block][QStringLiteral("in")] = (block->in() - earliest_in).toString(); + properties[block][QStringLiteral("track")] = block->track()->ToReference().ToString(); + } + + sdata.SetProperties(properties); + + ProjectSerializer::Copy(sdata, QStringLiteral("timeline")); if (cut) { DeleteSelected(); @@ -671,12 +626,50 @@ void TimelineWidget::Paste(bool insert) return; } - if (ruler()->underMouse()) { - ruler()->PasteMarkers(insert, GetTime()); + if (ruler()->hasFocus() && ruler()->PasteMarkers(insert, GetTime())) { return; } - PasteNodesFromClipboard(&insert); + ProjectSerializer::Result res = ProjectSerializer::Paste(QStringLiteral("timeline")); + if (res.GetLoadedNodes().isEmpty()) { + return; + } + + MultiUndoCommand *command = new MultiUndoCommand(); + + foreach (Node *n, res.GetLoadedNodes()) { + command->add_child(new NodeAddCommand(GetConnectedNode()->project(), n)); + } + + rational paste_start = GetTime(); + + if (insert) { + rational paste_end = GetTime(); + + for (auto it=res.GetLoadData().properties.cbegin(); it!=res.GetLoadData().properties.cend(); it++) { + rational length = static_cast(it.key())->length(); + rational in = rational::fromString(it.value()[QStringLiteral("in")]); + + paste_end = qMax(paste_end, paste_start + in + length); + } + + if (paste_end != paste_start) { + InsertGapsAt(paste_start, paste_end - paste_start, command); + } + } + + for (auto it=res.GetLoadData().properties.cbegin(); it!=res.GetLoadData().properties.cend(); it++) { + Block *block = static_cast(it.key()); + rational in = rational::fromString(it.value()[QStringLiteral("in")]); + Track::Reference track = Track::Reference::FromString(it.value()[QStringLiteral("track")]); + + command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()), + track.index(), + block, + paste_start + in)); + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); } void TimelineWidget::DeleteInToOut(bool ripple) diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index e96b657a4..b19159403 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -27,7 +27,6 @@ #include "core.h" #include "node/block/transition/transition.h" -#include "node/nodecopypaste.h" #include "node/output/viewer/viewer.h" #include "timeline/timelinecommon.h" #include "timelineandtrackview.h" @@ -45,7 +44,7 @@ namespace olive { * * Encapsulates TimelineViews, TimeRulers, and scrollbars for a complete widget to manipulate Timelines */ -class TimelineWidget : public TimeBasedWidget, public NodeCopyPasteService, public SnapService +class TimelineWidget : public TimeBasedWidget, public SnapService { Q_OBJECT public: @@ -277,9 +276,6 @@ protected: virtual void ConnectNodeEvent(ViewerOutput* n) override; virtual void DisconnectNodeEvent(ViewerOutput* n) override; - virtual void CopyNodesToClipboardCallback(const QVector &nodes, ProjectSerializer::SaveData *data, void *userdata) override; - virtual void PasteNodesToClipboardCallback(const QVector &nodes, const ProjectSerializer::LoadData &load_data, void *userdata) override; - private: QVector GetEditToInfo(const rational &playhead_time, Timeline::MovementMode mode); diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index fbbe714b9..c23a010c5 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -28,6 +28,7 @@ #include "common/qtutils.h" #include "core.h" #include "dialog/markerproperties/markerpropertiesdialog.h" +#include "node/project/serializer/serializer.h" #include "widget/colorlabelmenu/colorlabelmenu.h" #include "widget/menu/menushared.h" #include "widget/timebased/timebasedwidget.h" @@ -38,7 +39,6 @@ namespace olive { SeekableWidget::SeekableWidget(QWidget* parent) : super(parent), - NodeCopyPasteService(QStringLiteral("markers")), timeline_points_(nullptr), dragging_(false), ignore_next_focus_out_(false), @@ -91,21 +91,68 @@ void SeekableWidget::DeleteSelected() Core::instance()->undo_stack()->pushIfHasChildren(command); } -void SeekableWidget::CopySelected(bool cut) +bool SeekableWidget::CopySelected(bool cut) { - qDebug() << "COPY IS STUB"; - //CopyMarkersToClipboard(selection_manager_.GetSelectedObjects()); + if (!selection_manager_.GetSelectedObjects().empty()) { + ProjectSerializer::SaveData sdata(Project::GetProjectFromObject(timeline_points_)); + sdata.SetOnlySerializeMarkers(selection_manager_.GetSelectedObjects()); - if (cut) { - DeleteSelected(); + ProjectSerializer::Copy(sdata, QStringLiteral("markers")); + + if (cut) { + DeleteSelected(); + } + + return true; + } else { + return false; } } -void SeekableWidget::PasteMarkers(bool insert, rational insert_time) +bool SeekableWidget::PasteMarkers(bool insert, rational insert_time) { - //MultiUndoCommand *command = new MultiUndoCommand(); - //PasteMarkersFromClipboard(timeline_points()->markers(), command, insert_time); - qDebug() << "PASTE IS STUB"; + ProjectSerializer::Result res = ProjectSerializer::Paste(QStringLiteral("markers")); + if (res == ProjectSerializer::kSuccess) { + const std::vector &markers = res.GetLoadData().markers; + if (!markers.empty()) { + MultiUndoCommand *command = new MultiUndoCommand(); + + // Normalize markers to start at playhead + rational min = RATIONAL_MAX; + for (auto it=markers.cbegin(); it!=markers.cend(); it++) { + min = qMin(min, (*it)->time()); + } + min -= GetTime(); + + // Avoid duplicates + bool loop; + do { + loop = false; + for (auto it=markers.cbegin(); it!=markers.cend(); it++) { + rational proposed_time = (*it)->time() - min; + + if (timeline_points_->markers()->GetMarkerAtTime(proposed_time)) { + min -= timebase(); + loop = true; + break; + } + } + } while (loop); + + for (auto it=markers.cbegin(); it!=markers.cend(); it++) { + TimelineMarker *m = *it; + + m->set_time(m->time() - min); + + command->add_child(new MarkerAddCommand(timeline_points_->markers(), m)); + } + + Core::instance()->undo_stack()->push(command); + return true; + } + } + + return false; } void SeekableWidget::mousePressEvent(QMouseEvent *event) @@ -148,7 +195,7 @@ void SeekableWidget::mouseDoubleClickEvent(QMouseEvent *event) { super::mouseDoubleClickEvent(event); - if (selection_manager_.GetObjectAtPoint(event->pos()) && !selection_manager_.GetSelectedObjects().isEmpty()) { + if (selection_manager_.GetObjectAtPoint(event->pos()) && !selection_manager_.GetSelectedObjects().empty()) { ShowMarkerProperties(); } } @@ -315,7 +362,7 @@ void SeekableWidget::DrawPlayhead(QPainter *p, int x, int y) bool SeekableWidget::ShowContextMenu(const QPoint &p) { - if (selection_manager_.GetObjectAtPoint(p) && !selection_manager_.GetSelectedObjects().isEmpty()) { + if (selection_manager_.GetObjectAtPoint(p) && !selection_manager_.GetSelectedObjects().empty()) { // Show marker-specific menu Menu m; diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index d06401989..37d87903c 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -25,7 +25,6 @@ #include #include "common/rational.h" -#include "node/nodecopypaste.h" #include "timeline/timelinepoints.h" #include "widget/menu/menu.h" #include "widget/snapservice/snapservice.h" @@ -33,7 +32,7 @@ namespace olive { -class SeekableWidget : public TimeBasedView, public NodeCopyPasteService +class SeekableWidget : public TimeBasedView { Q_OBJECT public: @@ -53,9 +52,9 @@ public: void DeleteSelected(); - void CopySelected(bool cut); + bool CopySelected(bool cut); - void PasteMarkers(bool insert, rational insert_time); + bool PasteMarkers(bool insert, rational insert_time); void DeselectAllMarkers(); From 756ff77ae8bdcea963d08c2c25654440e354cd06 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 1 May 2022 22:41:07 -0700 Subject: [PATCH 72/81] cleaned up clip marker click code --- .../timelinewidget/view/timelineview.cpp | 21 ++++++++++++------- app/widget/timelinewidget/view/timelineview.h | 2 +- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index c00070b21..b1462efda 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -63,11 +63,17 @@ TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent) : void TimelineView::mousePressEvent(QMouseEvent *event) { // If we click on marker, jump to that point in the timeline - foreach (QRectF rect, clip_marker_positions_.values()) { - if (rect.contains(mapToScene(event->pos()))) { - TimelinePanel *timeline = PanelManager::instance()->MostRecentlyFocused(); - if (timeline) { - timeline->timeline_widget()->SetTime(clip_marker_positions_.key(rect)->time_range().in()); + QPointF scene_pos = mapToScene(event->pos()); + for (auto it=clip_marker_rects_.cbegin(); it!=clip_marker_rects_.cend(); it++) { + if (it.value().contains(scene_pos)) { + QObject *p = this->parent(); + while (p) { + if (TimelineWidget *timeline = dynamic_cast(p)) { + timeline->SetTime(it.key()->time()); + break; + } + + p = p->parent(); } } } @@ -533,7 +539,7 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q TimelineMarkerList *marker_list = clip->connected_viewer()->GetTimelinePoints()->markers(); if (!marker_list->empty()) { - clip_marker_positions_.clear(); + clip_marker_rects_.clear(); for (auto it=marker_list->cbegin(); it!=marker_list->cend(); it++) { TimelineMarker *marker = *it; @@ -541,7 +547,8 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q if (marker->time_range().in() >= clip->media_in() && marker->time_range().out() <= clip->media_in() + clip->length()) { QPoint marker_pt(TimeToScene(clip->in() - clip->media_in() + marker->time_range().in()), block_top + block_height); painter->setClipRect(r); - marker->Draw(painter, marker_pt, GetScale(), false); + QRect marker_rect = marker->Draw(painter, marker_pt, GetScale(), false); + clip_marker_rects_.insert(marker, marker_rect); painter->setClipping(false); } } diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index ff8368add..05568a03a 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -155,7 +155,7 @@ private: ClipBlock *transition_overlay_out_; ClipBlock *transition_overlay_in_; - QMap clip_marker_positions_; + QMap clip_marker_rects_; private slots: void TrackListChanged(); From e6d4fe43de67dc46ec6d491b675438471c19103f Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 2 May 2022 00:01:49 -0700 Subject: [PATCH 73/81] reworked and vastly improved snapping subsystem --- app/dialog/export/export.cpp | 1 + app/node/keyframe.h | 9 + app/timeline/timelinemarker.h | 9 +- app/widget/curvewidget/curvewidget.cpp | 1 + app/widget/keyframeview/keyframeview.h | 5 + .../keyframeviewinputconnection.h | 2 +- app/widget/nodeparamview/nodeparamview.cpp | 2 + app/widget/nodeparamview/nodeparamview.h | 10 ++ app/widget/snapservice/CMakeLists.txt | 22 --- app/widget/snapservice/snapservice.cpp | 1 - app/widget/snapservice/snapservice.h | 31 ---- app/widget/timebased/timebasedview.cpp | 10 +- app/widget/timebased/timebasedview.h | 14 +- .../timebasedviewselectionmanager.cpp | 1 - .../timebased/timebasedviewselectionmanager.h | 36 +++- app/widget/timebased/timebasedwidget.cpp | 154 ++++++++++++++++++ app/widget/timebased/timebasedwidget.h | 26 ++- app/widget/timebased/timescaledobject.h | 1 + app/widget/timelinewidget/timelinewidget.cpp | 119 +------------- app/widget/timelinewidget/timelinewidget.h | 11 +- app/widget/timelinewidget/tool/add.cpp | 2 +- app/widget/timelinewidget/tool/import.cpp | 4 +- app/widget/timelinewidget/tool/pointer.cpp | 9 +- app/widget/timelinewidget/tool/tool.h | 2 +- app/widget/timelinewidget/tool/transition.cpp | 2 +- app/widget/timeruler/seekablewidget.cpp | 23 ++- app/widget/timeruler/seekablewidget.h | 4 +- app/widget/timeruler/timeruler.cpp | 2 +- app/widget/viewer/viewer.cpp | 1 + 29 files changed, 289 insertions(+), 225 deletions(-) delete mode 100644 app/widget/snapservice/CMakeLists.txt delete mode 100644 app/widget/snapservice/snapservice.cpp delete mode 100644 app/widget/snapservice/snapservice.h diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 83f8a0d8d..c9aa7249c 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -38,6 +38,7 @@ #include "node/project/sequence/sequence.h" #include "task/taskmanager.h" #include "ui/icons/icons.h" +#include "widget/timeruler/timeruler.h" namespace olive { diff --git a/app/node/keyframe.h b/app/node/keyframe.h index b4c7f08e6..a6163039a 100644 --- a/app/node/keyframe.h +++ b/app/node/keyframe.h @@ -26,6 +26,7 @@ #include #include "common/rational.h" +#include "common/timerange.h" #include "node/param.h" namespace olive { @@ -86,6 +87,14 @@ public: const rational& time() const; void set_time(const rational& time); + /** + * @brief Dummy function for TimeBasedViewSelectionManager compatibility + * + * FIXME: Once we upgrade to C++17, we won't need this because we'll be able to check types in + * TimeBasedViewSelectionManager's template functions + */ + TimeRange time_range() const { return TimeRange(time_, time_); } + /** * @brief The value of this keyframe (i.e. the value to use at this keyframe's time) */ diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index 1389da26e..cc1fd110c 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -38,10 +38,17 @@ public: TimelineMarker(QObject* parent = nullptr); TimelineMarker(int color, const TimeRange& time, const QString& name = QString(), QObject* parent = nullptr); + /** + * @brief Dummy function for TimeBasedViewSelectionManager compatibility + * + * FIXME: Once we upgrade to C++17, we won't need this because we'll be able to check types in + * TimeBasedViewSelectionManager's template functions + */ const rational &time() const { return time_.in(); } + void set_time(const rational& time); + const TimeRange &time_range() const { return time_; } void set_time(const TimeRange& time); - void set_time(const rational& time); bool has_sibling_at_time(const rational &t) const; diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index e75d6746b..202e90a8c 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -31,6 +31,7 @@ #include "common/timecodefunctions.h" #include "node/node.h" #include "widget/keyframeview/keyframeviewundo.h" +#include "widget/timeruler/timeruler.h" namespace olive { diff --git a/app/widget/keyframeview/keyframeview.h b/app/widget/keyframeview/keyframeview.h index e7a2102f5..096be2f9f 100644 --- a/app/widget/keyframeview/keyframeview.h +++ b/app/widget/keyframeview/keyframeview.h @@ -63,6 +63,11 @@ public: return selection_manager_.GetSelectedObjects(); } + const QVector &GetKeyframeTracks() const + { + return tracks_; + } + virtual void SelectionManagerSelectEvent(void *obj) override; virtual void SelectionManagerDeselectEvent(void *obj) override; diff --git a/app/widget/keyframeview/keyframeviewinputconnection.h b/app/widget/keyframeview/keyframeviewinputconnection.h index b6b0c1da5..5b3275fc0 100644 --- a/app/widget/keyframeview/keyframeviewinputconnection.h +++ b/app/widget/keyframeview/keyframeviewinputconnection.h @@ -50,7 +50,7 @@ public: void SetYBehavior(YBehavior e); - const QVector GetKeyframes() const + const QVector &GetKeyframes() const { return input_.input().node()->GetKeyframeTracks(input_.input()).at(input_.track()); } diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 8af158346..8e3beb82e 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -29,6 +29,7 @@ #include "common/timecodefunctions.h" #include "node/output/viewer/viewer.h" #include "widget/nodeview/nodeviewundo.h" +#include "widget/timeruler/timeruler.h" namespace olive { @@ -117,6 +118,7 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) : // Create keyframe view keyframe_view_ = new KeyframeView(); keyframe_view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + keyframe_view_->SetSnapService(this); ConnectTimelineView(keyframe_view_); keyframe_area_layout->addWidget(keyframe_view_); diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 8061d5b1a..c0078b83e 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -88,6 +88,16 @@ protected: virtual void ConnectedNodeChangeEvent(ViewerOutput* n) override; + virtual const QVector *GetSnapKeyframes() const override + { + return keyframe_view_ ? &keyframe_view_->GetKeyframeTracks() : nullptr; + } + + virtual const std::vector *GetSnapIgnoreKeyframes() const override + { + return keyframe_view_ ? &keyframe_view_->GetSelectedKeyframes() : nullptr; + } + private: void UpdateItemTime(const rational &time); diff --git a/app/widget/snapservice/CMakeLists.txt b/app/widget/snapservice/CMakeLists.txt deleted file mode 100644 index 6224fa753..000000000 --- a/app/widget/snapservice/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/snapservice/snapservice.cpp - widget/snapservice/snapservice.h - PARENT_SCOPE -) diff --git a/app/widget/snapservice/snapservice.cpp b/app/widget/snapservice/snapservice.cpp deleted file mode 100644 index b3a853f1e..000000000 --- a/app/widget/snapservice/snapservice.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "snapservice.h" diff --git a/app/widget/snapservice/snapservice.h b/app/widget/snapservice/snapservice.h deleted file mode 100644 index ad83fb3c9..000000000 --- a/app/widget/snapservice/snapservice.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef SNAPSERVICE_H -#define SNAPSERVICE_H - -#include "common/rational.h" - -namespace olive { - -class SnapService -{ -public: - SnapService() = default; - - enum SnapPoints { - kSnapToClips = 0x1, - kSnapToPlayhead = 0x2, - kSnapToMarkers = 0x4, - kSnapAll = 0xFF - }; - - /** - * @brief Snaps point `start_point` that is moving by `movement` to currently existing clips - */ - virtual bool SnapPoint(QVector start_times, rational *movement, int snap_points = kSnapAll) = 0; - - virtual void HideSnaps() = 0; - -}; - -} - -#endif // SNAPSERVICE_H diff --git a/app/widget/timebased/timebasedview.cpp b/app/widget/timebased/timebasedview.cpp index 05a749976..dc5e68cd8 100644 --- a/app/widget/timebased/timebasedview.cpp +++ b/app/widget/timebased/timebasedview.cpp @@ -26,6 +26,7 @@ #include #include "common/timecodefunctions.h" +#include "widget/timebased/timebasedwidget.h" namespace olive { @@ -63,7 +64,7 @@ void TimeBasedView::TimebaseChangedEvent(const rational &) viewport()->update(); } -void TimeBasedView::EnableSnap(const QVector &points) +void TimeBasedView::EnableSnap(const std::vector &points) { snapped_ = true; snap_time_ = points; @@ -78,11 +79,6 @@ void TimeBasedView::DisableSnap() viewport()->update(); } -void TimeBasedView::SetSnapService(SnapService *service) -{ - snap_service_ = service; -} - const double &TimeBasedView::GetYScale() const { return y_scale_; @@ -211,7 +207,7 @@ bool TimeBasedView::PlayheadMove(QMouseEvent *event) if (Core::instance()->snapping() && snap_service_) { rational movement; - snap_service_->SnapPoint({mouse_time}, &movement, SnapService::kSnapAll & ~SnapService::kSnapToPlayhead); + snap_service_->SnapPoint({mouse_time}, &movement, TimeBasedWidget::kSnapAll & ~TimeBasedWidget::kSnapToPlayhead); mouse_time += movement; } diff --git a/app/widget/timebased/timebasedview.h b/app/widget/timebased/timebasedview.h index 4bca7f9da..8771f6c01 100644 --- a/app/widget/timebased/timebasedview.h +++ b/app/widget/timebased/timebasedview.h @@ -22,21 +22,23 @@ #define TIMELINEVIEWBASE_H #include +#include #include "core.h" #include "timescaledobject.h" #include "widget/handmovableview/handmovableview.h" -#include "widget/snapservice/snapservice.h" namespace olive { +class TimeBasedWidget; + class TimeBasedView : public HandMovableView, public TimeScaledObject { Q_OBJECT public: TimeBasedView(QWidget* parent = nullptr); - void EnableSnap(const QVector &points); + void EnableSnap(const std::vector &points); void DisableSnap(); bool IsSnapped() const { @@ -45,8 +47,8 @@ public: const rational &GetTime() const { return playhead_; } - SnapService *GetSnapService() const { return snap_service_; } - void SetSnapService(SnapService* service); + TimeBasedWidget *GetSnapService() const { return snap_service_; } + void SetSnapService(TimeBasedWidget* service) { snap_service_ = service; } const double& GetYScale() const; void SetYScale(const double& y_scale); @@ -117,11 +119,11 @@ private: QGraphicsScene scene_; bool snapped_; - QVector snap_time_; + std::vector snap_time_; rational end_time_; - SnapService* snap_service_; + TimeBasedWidget* snap_service_; bool y_axis_enabled_; diff --git a/app/widget/timebased/timebasedviewselectionmanager.cpp b/app/widget/timebased/timebasedviewselectionmanager.cpp index dd44a172e..577046aff 100644 --- a/app/widget/timebased/timebasedviewselectionmanager.cpp +++ b/app/widget/timebased/timebasedviewselectionmanager.cpp @@ -22,5 +22,4 @@ namespace olive { - } diff --git a/app/widget/timebased/timebasedviewselectionmanager.h b/app/widget/timebased/timebasedviewselectionmanager.h index d13d4754e..106eef182 100644 --- a/app/widget/timebased/timebasedviewselectionmanager.h +++ b/app/widget/timebased/timebasedviewselectionmanager.h @@ -29,6 +29,7 @@ #include "common/rational.h" #include "common/timecodefunctions.h" #include "timebasedview.h" +#include "timebasedwidget.h" namespace olive { @@ -38,9 +39,15 @@ class TimeBasedViewSelectionManager public: TimeBasedViewSelectionManager(TimeBasedView *view) : view_(view), - rubberband_(nullptr) + rubberband_(nullptr), + snap_mask_(TimeBasedWidget::kSnapAll) {} + void SetSnapMask(TimeBasedWidget::SnapMask e) + { + snap_mask_ = e; + } + void ClearDrawnObjects() { drawn_objects_.clear(); @@ -156,19 +163,40 @@ public: initial_drag_item_ = initial_item; dragging_.resize(selected_.size()); + snap_points_.resize(selected_.size()*2); for (size_t i=0; itime(); + + snap_points_[i] = obj->time(); + snap_points_[i+selected_.size()] = obj->time_range().out(); } drag_mouse_start_ = view_->mapToScene(event->pos()); } + void SnapPoints(rational *movement) + { + if (Core::instance()->snapping() && view_->GetSnapService()) { + view_->GetSnapService()->SnapPoint(snap_points_, movement, snap_mask_); + } + } + + void Unsnap() + { + if (view_->GetSnapService()) { + view_->GetSnapService()->HideSnaps(); + } + } + void DragMove(QMouseEvent *event, const QString &tip_format = QString()) { rational time_diff = view_->SceneToTimeNoGrid(view_->mapToScene(event->pos()).x() - drag_mouse_start_.x()); + // Snap points + SnapPoints(&time_diff); + // Validate movement for (size_t i=0; ihas_sibling_at_time(proposed_time)) { proposed_time += adj; + Unsnap(); } if (proposed_time < 0) { // Prevent any object from going below zero proposed_time = 0; + Unsnap(); // Setting our proposed time to zero may (re)introduce a conflict that we just avoided // with the sibling check above, so we request it to happen again. To avoid a negative @@ -230,6 +260,7 @@ public: } dragging_.clear(); + Unsnap(); } void RubberBandStart(QMouseEvent *event) @@ -325,6 +356,7 @@ private: std::vector selected_; std::vector dragging_; + std::vector snap_points_; T *initial_drag_item_; @@ -336,6 +368,8 @@ private: QPoint rubberband_start_; std::vector rubberband_preselected_; + TimeBasedWidget::SnapMask snap_mask_; + }; } diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index 81197e616..379dd8102 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -23,11 +23,13 @@ #include #include "common/autoscroll.h" +#include "common/range.h" #include "common/timecodefunctions.h" #include "config/config.h" #include "core.h" #include "dialog/markerproperties/markerpropertiesdialog.h" #include "node/project/sequence/sequence.h" +#include "widget/timeruler/timeruler.h" #include "widget/timelinewidget/undo/timelineundoworkarea.h" namespace olive { @@ -41,6 +43,7 @@ TimeBasedWidget::TimeBasedWidget(bool ruler_text_visible, bool ruler_cache_statu { ruler_ = new TimeRuler(ruler_text_visible, ruler_cache_status_visible, this); ConnectTimelineView(ruler_, true); + ruler()->SetSnapService(this); scrollbar_ = new ResizableTimelineScrollBar(Qt::Horizontal, this); connect(scrollbar_, &ResizableScrollBar::ResizeBegan, this, &TimeBasedWidget::ScrollBarResizeBegan); @@ -693,4 +696,155 @@ bool TimeBasedWidget::eventFilter(QObject *object, QEvent *event) return false; } +struct SnapData { + rational time; + rational movement; +}; + +void AttemptSnap(std::vector &snap_data, + const std::vector& screen_pt, + double compare_pt, + const std::vector& start_times, + const rational& compare_time) +{ + const qreal kSnapRange = 10; // FIXME: Hardcoded number + + for (size_t i=0;i &start_times, rational *movement, SnapMask snap_points) +{ + std::vector screen_pt(start_times.size()); + + for (size_t i=0; i potential_snaps; + + if (snap_points & kSnapToPlayhead) { + rational playhead_abs_time = GetTime(); + qreal playhead_pos = TimeToScene(playhead_abs_time); + AttemptSnap(potential_snaps, screen_pt, playhead_pos, start_times, playhead_abs_time); + } + + if ((snap_points & kSnapToClips) && GetSnapBlocks()) { + for (auto it=GetSnapBlocks()->cbegin(); it!=GetSnapBlocks()->cend(); it++) { + Block *b = *it; + + qreal rect_left = TimeToScene(b->in()); + qreal rect_right = TimeToScene(b->out()); + + // Attempt snapping to clip in point + AttemptSnap(potential_snaps, screen_pt, rect_left, start_times, b->in()); + + // Attempt snapping to clip out point + AttemptSnap(potential_snaps, screen_pt, rect_right, start_times, b->out()); + + if (snap_points & kSnapToMarkers) { + // Snap to clip markers too + if (ClipBlock *clip = dynamic_cast(b)) { + if (clip->connected_viewer()) { + TimelineMarkerList *markers = clip->connected_viewer()->GetTimelinePoints()->markers(); + for (auto jt=markers->cbegin(); jt!=markers->cend(); jt++) { + TimelineMarker *marker = *jt; + + TimeRange marker_range = marker->time_range() + clip->in() - clip->media_in(); + + qreal marker_in_screen = TimeToScene(marker_range.in()); + qreal marker_out_screen = TimeToScene(marker_range.out()); + + AttemptSnap(potential_snaps, screen_pt, marker_in_screen, start_times, marker_range.in()); + AttemptSnap(potential_snaps, screen_pt, marker_out_screen, start_times, marker_range.out()); + } + } + } + } + } + } + + if ((snap_points & kSnapToMarkers) && ruler()->GetTimelinePoints()) { + for (auto it=ruler()->GetTimelinePoints()->markers()->cbegin(); it!=ruler()->GetTimelinePoints()->markers()->cend(); it++) { + TimelineMarker* m = *it; + + qreal marker_pos = TimeToScene(m->time_range().in()); + AttemptSnap(potential_snaps, screen_pt, marker_pos, start_times, m->time_range().in()); + + if (m->time_range().in() != m->time_range().out()) { + marker_pos = TimeToScene(m->time_range().out()); + AttemptSnap(potential_snaps, screen_pt, marker_pos, start_times, m->time_range().out()); + } + } + } + + if ((snap_points & kSnapToKeyframes) && GetSnapKeyframes()) { + for (auto it=GetSnapKeyframes()->cbegin(); it!=GetSnapKeyframes()->cend(); it++) { + const QVector &keys = (*it)->GetKeyframes(); + for (auto jt=keys.cbegin(); jt!=keys.cend(); jt++) { + NodeKeyframe *key = *jt; + + auto ignore = GetSnapIgnoreKeyframes(); + if (ignore && std::find(ignore->cbegin(), ignore->cend(), key) != ignore->cend()) { + continue; + } + + qreal key_scene_pt = TimeToScene(key->time()); + + AttemptSnap(potential_snaps, screen_pt, key_scene_pt, start_times, key->time()); + } + } + } + + if (potential_snaps.empty()) { + HideSnaps(); + return false; + } + + int closest_snap = 0; + rational closest_diff = qAbs(potential_snaps.at(0).movement - *movement); + + // Determine which snap point was the closest + for (size_t i=1; i snap_times; + foreach (const SnapData& d, potential_snaps) { + if (d.movement == *movement) { + snap_times.push_back(d.time); + } + } + + ShowSnaps(snap_times); + + return true; +} + +void TimeBasedWidget::ShowSnaps(const std::vector ×) +{ + foreach (TimeBasedView* view, timeline_views_) { + view->EnableSnap(times); + } +} + +void TimeBasedWidget::HideSnaps() +{ + foreach (TimeBasedView* view, timeline_views_) { + view->DisableSnap(); + } +} + } diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index e8b6bee76..3dce38e5a 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -25,13 +25,15 @@ #include "node/output/viewer/viewer.h" #include "timeline/timelinecommon.h" +#include "widget/keyframeview/keyframeviewinputconnection.h" #include "widget/resizablescrollbar/resizabletimelinescrollbar.h" #include "widget/timebased/timescaledobject.h" #include "widget/timelinewidget/view/timelineview.h" -#include "widget/timeruler/timeruler.h" namespace olive { +class TimeRuler; + class TimeBasedWidget : public TimelineScaledWidget { Q_OBJECT @@ -54,6 +56,22 @@ public: virtual bool eventFilter(QObject* object, QEvent* event) override; + using SnapMask = uint32_t; + enum SnapPoints { + kSnapToClips = 0x1, + kSnapToPlayhead = 0x2, + kSnapToMarkers = 0x4, + kSnapToKeyframes = 0x8, + kSnapAll = UINT32_MAX + }; + + /** + * @brief Snaps point `start_point` that is moving by `movement` to currently existing clips + */ + bool SnapPoint(const std::vector &start_times, rational *movement, SnapMask snap_points = kSnapAll); + void ShowSnaps(const std::vector ×); + void HideSnaps(); + public slots: void SetTime(const rational &time); @@ -119,6 +137,10 @@ protected: void PassWheelEventsToScrollBar(QObject* object); + virtual const QVector *GetSnapBlocks() const { return nullptr; } + virtual const QVector *GetSnapKeyframes() const { return nullptr; } + virtual const std::vector *GetSnapIgnoreKeyframes() const { return nullptr; } + protected slots: /** * @brief Slot to center the horizontal scroll bar on the playhead's current position @@ -141,7 +163,7 @@ signals: void ConnectedNodeChanged(ViewerOutput* old, ViewerOutput* now); private: - + /** * @brief Set either in or out point to the current playhead diff --git a/app/widget/timebased/timescaledobject.h b/app/widget/timebased/timescaledobject.h index acdc44acb..d5cd102e2 100644 --- a/app/widget/timebased/timescaledobject.h +++ b/app/widget/timebased/timescaledobject.h @@ -24,6 +24,7 @@ #include #include "common/rational.h" +#include "node/block/block.h" namespace olive { diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index f62938346..8766cd2fb 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -54,6 +54,7 @@ #include "widget/menu/menu.h" #include "widget/menu/menushared.h" #include "widget/nodeview/nodeviewundo.h" +#include "widget/timeruler/timeruler.h" namespace olive { @@ -82,7 +83,6 @@ TimelineWidget::TimelineWidget(QWidget *parent) : ruler_and_time_layout->addWidget(timecode_label_); ruler_and_time_layout->addWidget(ruler()); - ruler()->SetSnapService(this); // Create list of TimelineViews - these MUST correspond to the ViewType enum @@ -1515,13 +1515,6 @@ void TimelineWidget::EditTo(Timeline::MovementMode mode) Core::instance()->undo_stack()->pushIfHasChildren(command); } -void TimelineWidget::ShowSnap(const QVector ×) -{ - foreach (TimelineAndTrackView* tview, views_) { - tview->view()->EnableSnap(times); - } -} - void TimelineWidget::UpdateViewports(const Track::Type &type) { if (type == Track::kNone) { @@ -1573,13 +1566,6 @@ QVector TimelineWidget::GetBlocksInGlobalRect(const QPoint &p1, const Q return blocks_in_rect; } -void TimelineWidget::HideSnaps() -{ - foreach (TimelineAndTrackView* tview, views_) { - tview->view()->DisableSnap(); - } -} - QByteArray TimelineWidget::SaveSplitterState() const { return view_splitter_->saveState(); @@ -1734,109 +1720,6 @@ Block *TimelineWidget::GetItemAtScenePos(const TimelineCoordinate& coord) return views_.at(coord.GetTrack().type())->view()->GetItemAtScenePos(coord.GetFrame(), coord.GetTrack().index()); } -struct SnapData { - rational time; - rational movement; -}; - -QVector AttemptSnap(const QVector& screen_pt, - double compare_pt, - const QVector& start_times, - const rational& compare_time) { - const qreal kSnapRange = 10; // FIXME: Hardcoded number - - QVector snap_data; - - for (int i=0;i start_times, rational* movement, int snap_points) -{ - if (!GetConnectedNode()) { - return false; - } - - QVector screen_pt; - - foreach (const rational& s, start_times) { - screen_pt.append(TimeToScene(s + *movement)); - } - - QVector potential_snaps; - - if (snap_points & kSnapToPlayhead) { - rational playhead_abs_time = GetTime(); - qreal playhead_pos = TimeToScene(playhead_abs_time); - potential_snaps.append(AttemptSnap(screen_pt, playhead_pos, start_times, playhead_abs_time)); - } - - if (snap_points & kSnapToClips) { - foreach (Block* b, added_blocks_) { - qreal rect_left = TimeToScene(b->in()); - qreal rect_right = TimeToScene(b->out()); - - // Attempt snapping to clip in point - potential_snaps.append(AttemptSnap(screen_pt, rect_left, start_times, b->in())); - - // Attempt snapping to clip out point - potential_snaps.append(AttemptSnap(screen_pt, rect_right, start_times, b->out())); - } - } - - if ((snap_points & kSnapToMarkers)) { - for (auto it=GetConnectedNode()->GetTimelinePoints()->markers()->cbegin(); it!=GetConnectedNode()->GetTimelinePoints()->markers()->cend(); it++) { - TimelineMarker* m = *it; - - qreal marker_pos = TimeToScene(m->time_range().in()); - potential_snaps.append(AttemptSnap(screen_pt, marker_pos, start_times, m->time_range().in())); - - if (m->time_range().in() != m->time_range().out()) { - marker_pos = TimeToScene(m->time_range().out()); - potential_snaps.append(AttemptSnap(screen_pt, marker_pos, start_times, m->time_range().out())); - } - } - } - - if (potential_snaps.isEmpty()) { - HideSnaps(); - return false; - } - - int closest_snap = 0; - rational closest_diff = qAbs(potential_snaps.at(0).movement - *movement); - - // Determine which snap point was the closest - for (int i=1; i snap_times; - foreach (const SnapData& d, potential_snaps) { - if (d.movement == *movement) { - snap_times.append(d.time); - } - } - - ShowSnap(snap_times); - - return true; -} - void TimelineWidget::SetSplitterSizesCommand::redo() { old_sizes_ = splitter_->sizes(); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index b19159403..841b793ee 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -31,7 +31,6 @@ #include "timeline/timelinecommon.h" #include "timelineandtrackview.h" #include "widget/slider/rationalslider.h" -#include "widget/snapservice/snapservice.h" #include "widget/timebased/timebasedwidget.h" #include "widget/timelinewidget/timelinewidgetselections.h" #include "widget/timelinewidget/tool/import.h" @@ -44,7 +43,7 @@ namespace olive { * * Encapsulates TimelineViews, TimeRulers, and scrollbars for a complete widget to manipulate Timelines */ -class TimelineWidget : public TimeBasedWidget, public SnapService +class TimelineWidget : public TimeBasedWidget { Q_OBJECT public: @@ -113,10 +112,6 @@ public: return selected_blocks_; } - virtual bool SnapPoint(QVector start_times, rational *movement, int snap_points = kSnapAll) override; - - virtual void HideSnaps() override; - QByteArray SaveSplitterState() const; void RestoreSplitterState(const QByteArray& state); @@ -276,6 +271,8 @@ protected: virtual void ConnectNodeEvent(ViewerOutput* n) override; virtual void DisconnectNodeEvent(ViewerOutput* n) override; + virtual const QVector *GetSnapBlocks() const override { return &added_blocks_; } + private: QVector GetEditToInfo(const rational &playhead_time, Timeline::MovementMode mode); @@ -283,8 +280,6 @@ private: void EditTo(Timeline::MovementMode mode); - void ShowSnap(const QVector& times); - void UpdateViewports(const Track::Type& type = Track::kNone); QVector GetBlocksInGlobalRect(const QPoint &p1, const QPoint &p2); diff --git a/app/widget/timelinewidget/tool/add.cpp b/app/widget/timelinewidget/tool/add.cpp index a8f778b5f..2dd7455c5 100644 --- a/app/widget/timelinewidget/tool/add.cpp +++ b/app/widget/timelinewidget/tool/add.cpp @@ -79,7 +79,7 @@ void AddTool::MousePress(TimelineViewMouseEvent *event) ghost_->SetTrack(track); parent()->AddGhost(ghost_); - snap_points_.append(drag_start_point_); + snap_points_.push_back(drag_start_point_); } } diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index f9ab4b6f8..de84a94b5 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -243,8 +243,8 @@ void ImportTool::FootageToGhosts(rational ghost_start, const DraggedFootageData ghost->SetMediaIn(ghost_in); ghost->SetTrack(Track::Reference(track_type, track_offsets.at(track_type))); - snap_points_.append(ghost->GetIn()); - snap_points_.append(ghost->GetOut()); + snap_points_.push_back(ghost->GetIn()); + snap_points_.push_back(ghost->GetOut()); // Increment track count for this track type track_offsets[track_type]++; diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 4816d4bc6..ad76ff6a6 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -35,6 +35,7 @@ #include "pointer.h" #include "widget/nodeview/nodeviewundo.h" #include "widget/timelinewidget/undo/timelineundopointer.h" +#include "widget/timeruler/timeruler.h" namespace olive { @@ -793,14 +794,14 @@ void PointerTool::AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::Movem // Prepare snap points (optimizes snapping for later) switch (mode) { case Timeline::kMove: - snap_points_.append(ghost->GetIn()); - snap_points_.append(ghost->GetOut()); + snap_points_.push_back(ghost->GetIn()); + snap_points_.push_back(ghost->GetOut()); break; case Timeline::kTrimIn: - snap_points_.append(ghost->GetIn()); + snap_points_.push_back(ghost->GetIn()); break; case Timeline::kTrimOut: - snap_points_.append(ghost->GetOut()); + snap_points_.push_back(ghost->GetOut()); break; default: break; diff --git a/app/widget/timelinewidget/tool/tool.h b/app/widget/timelinewidget/tool/tool.h index c627f542e..47fafbd90 100644 --- a/app/widget/timelinewidget/tool/tool.h +++ b/app/widget/timelinewidget/tool/tool.h @@ -78,7 +78,7 @@ protected: void InsertGapsAtGhostDestination(MultiUndoCommand* command); - QVector snap_points_; + std::vector snap_points_; bool dragging_; diff --git a/app/widget/timelinewidget/tool/transition.cpp b/app/widget/timelinewidget/tool/transition.cpp index 594659ccb..bd95a429a 100644 --- a/app/widget/timelinewidget/tool/transition.cpp +++ b/app/widget/timelinewidget/tool/transition.cpp @@ -73,7 +73,7 @@ void TransitionTool::MousePress(TimelineViewMouseEvent *event) parent()->AddGhost(ghost_); - snap_points_.append(transition_start_point); + snap_points_.push_back(transition_start_point); // Set the drag start point drag_start_point_ = event->GetFrame(); diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index c23a010c5..b5334fe83 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -53,6 +53,8 @@ SeekableWidget::SeekableWidget(QWidget* parent) : setContextMenuPolicy(Qt::CustomContextMenu); setFocusPolicy(Qt::ClickFocus); + + selection_manager_.SetSnapMask(TimeBasedWidget::kSnapAll & ~TimeBasedWidget::kSnapToMarkers); } void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points) @@ -212,11 +214,6 @@ void SeekableWidget::focusOutEvent(QFocusEvent *event) } } -TimelinePoints *SeekableWidget::timeline_points() const -{ - return timeline_points_; -} - void SeekableWidget::DeselectAllMarkers() { selection_manager_.ClearSelection(); @@ -262,7 +259,7 @@ void SeekableWidget::SeekToScenePoint(qreal scene) GetSnapService()->SnapPoint({playhead_time}, &movement, - SnapService::kSnapAll & ~SnapService::kSnapToPlayhead); + TimeBasedWidget::kSnapAll & ~TimeBasedWidget::kSnapToPlayhead); playhead_time += movement; } @@ -290,7 +287,7 @@ void SeekableWidget::SelectionManagerDeselectEvent(void *obj) void SeekableWidget::DrawTimelinePoints(QPainter* p, int marker_bottom) { - if (!timeline_points()) { + if (!GetTimelinePoints()) { return; } @@ -300,22 +297,22 @@ void SeekableWidget::DrawTimelinePoints(QPainter* p, int marker_bottom) selection_manager_.ClearDrawnObjects(); // Draw in/out workarea - if (timeline_points()->workarea()->enabled()) { - int workarea_left = qMax(qreal(lim_left), TimeToScene(timeline_points()->workarea()->in())); + if (GetTimelinePoints()->workarea()->enabled()) { + int workarea_left = qMax(qreal(lim_left), TimeToScene(GetTimelinePoints()->workarea()->in())); int workarea_right; - if (timeline_points()->workarea()->out() == TimelineWorkArea::kResetOut) { + if (GetTimelinePoints()->workarea()->out() == TimelineWorkArea::kResetOut) { workarea_right = lim_right; } else { - workarea_right = qMin(qreal(lim_right), TimeToScene(timeline_points()->workarea()->out())); + workarea_right = qMin(qreal(lim_right), TimeToScene(GetTimelinePoints()->workarea()->out())); } p->fillRect(workarea_left, 0, workarea_right - workarea_left, height(), palette().highlight()); } // Draw markers - if (marker_bottom > 0 && !timeline_points()->markers()->empty()) { - for (auto it=timeline_points()->markers()->cbegin(); it!=timeline_points()->markers()->cend(); it++) { + if (marker_bottom > 0 && !GetTimelinePoints()->markers()->empty()) { + for (auto it=GetTimelinePoints()->markers()->cbegin(); it!=GetTimelinePoints()->markers()->cend(); it++) { TimelineMarker* marker = *it; int marker_right = TimeToScene(marker->time_range().out()); diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index 37d87903c..1bc8e61de 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -27,7 +27,6 @@ #include "common/rational.h" #include "timeline/timelinepoints.h" #include "widget/menu/menu.h" -#include "widget/snapservice/snapservice.h" #include "widget/timebased/timebasedviewselectionmanager.h" namespace olive { @@ -43,6 +42,7 @@ public: return horizontalScrollBar()->value(); } + TimelinePoints* GetTimelinePoints() const { return timeline_points_; } void ConnectTimelinePoints(TimelinePoints* points); bool IsDraggingPlayhead() const @@ -81,8 +81,6 @@ protected: void DrawTimelinePoints(QPainter *p, int marker_bottom = 0); - TimelinePoints* timeline_points() const; - void DrawPlayhead(QPainter* p, int x, int y); inline const int& text_height() const { diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp index f50e67d36..61f95b020 100644 --- a/app/widget/timeruler/timeruler.cpp +++ b/app/widget/timeruler/timeruler.cpp @@ -105,7 +105,7 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect) // Draw timeline points if connected int marker_height = TimelineMarker::GetMarkerHeight(p->fontMetrics()); - if (timeline_points()) { + if (GetTimelinePoints()) { DrawTimelinePoints(p, marker_height); } diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index e5eae2440..996e5e5bc 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -43,6 +43,7 @@ #include "viewerpreventsleep.h" #include "widget/menu/menu.h" #include "window/mainwindow/mainwindow.h" +#include "widget/timeruler/timeruler.h" namespace olive { From 059331c29595df61639465e7058c98f407bb26d2 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 2 May 2022 00:26:11 -0700 Subject: [PATCH 74/81] markerremovecommand: removed unused members --- app/timeline/timelinemarker.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index cc1fd110c..36c61f6b5 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -190,9 +190,6 @@ protected: private: TimelineMarker* marker_; QObject* marker_list_; - TimeRange range_; - QString name_; - int color_; QObject memory_manager_; From 5441f2f9a45a549ed02f5946ab13a25eab80d16d Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 2 May 2022 00:35:43 -0700 Subject: [PATCH 75/81] markeraddcommand: removed unused members --- app/timeline/timelinemarker.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index 36c61f6b5..492c61b9b 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -168,9 +168,6 @@ protected: private: TimelineMarkerList* marker_list_; - TimeRange range_; - QString name_; - int color_; TimelineMarker* added_marker_; QObject memory_manager_; From b9e1498ea09f8527af6b66f0d4bcaa836b0e7a56 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 2 May 2022 09:45:59 -0700 Subject: [PATCH 76/81] curvewidget: connect snapping subsystem --- app/widget/curvewidget/curvewidget.cpp | 1 + app/widget/curvewidget/curvewidget.h | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index 202e90a8c..516e4b378 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -93,6 +93,7 @@ CurveWidget::CurveWidget(QWidget *parent) : view_ = new CurveView(); ConnectTimelineView(view_); + view_->SetSnapService(this); ruler_view_layout->addWidget(view_); layout->addLayout(ruler_view_layout); diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index ba6d1304b..3fd8ca4fe 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -67,6 +67,16 @@ protected: virtual void ConnectedNodeChangeEvent(ViewerOutput* n) override; + virtual const QVector *GetSnapKeyframes() const override + { + return &view_->GetKeyframeTracks(); + } + + virtual const std::vector *GetSnapIgnoreKeyframes() const override + { + return &view_->GetSelectedKeyframes(); + } + private: void SetKeyframeButtonEnabled(bool enable); From 38a5c36a35534340c68abeda647d931189d86b1a Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 2 May 2022 10:31:59 -0700 Subject: [PATCH 77/81] rational: rewrite Our rational is now a much thinner wrapper around AVRational. Their code is better than ours and using it greatly reduces our own maintenance cost --- app/common/rational.cpp | 338 ++++++++++------------------------------ app/common/rational.h | 109 +++++++------ 2 files changed, 135 insertions(+), 312 deletions(-) diff --git a/app/common/rational.cpp b/app/common/rational.cpp index d7d374230..2efad3f1f 100644 --- a/app/common/rational.cpp +++ b/app/common/rational.cpp @@ -1,5 +1,22 @@ -//Copyright 2015 Adam Quintero -//This program is distributed under the terms of the GNU General Public License. +/*** + + 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 "rational.h" @@ -16,7 +33,7 @@ rational rational::fromDouble(const double &flt, bool* ok) } // Use FFmpeg function for the time being - AVRational r = av_d2q(flt, 65535); + AVRational r = av_d2q(flt, INT_MAX); if (r.den == 0) { // If den == 0, we were unable to convert to a rational @@ -39,9 +56,9 @@ rational rational::fromString(const QString &str, bool* ok) switch (elements.size()) { case 1: - return rational(elements.first().toLongLong(ok)); + return rational(elements.first().toInt(ok)); case 2: - return rational(elements.at(0).toLongLong(ok), elements.at(1).toLongLong(ok)); + return rational(elements.at(0).toInt(ok), elements.at(1).toInt(ok)); default: // Returns NaN with ok set to false if (ok) { @@ -51,63 +68,12 @@ rational rational::fromString(const QString &str, bool* ok) } } -//Function: ensures denom >= 0 - -void rational::fix_signs() -{ - // Normalize so that denominator is always positive and only numerator is positive - if (denom_ < 0) { - denom_ = -denom_; - numer_ = -numer_; - } else if (denom_ == intType(0)) { - // Normalize to 0/0 (aka NaN) if denominator is zero - numer_ = intType(0); - } else if (numer_ == intType(0)) { - // Normalize to 0/1 if numerator is zero - denom_ = intType(1); - } -} - -//Function: ensures lowest form - -void rational::reduce() -{ - if (!isNull() && denom_ != 1) { - // Euclidean often fails if numbers are negative, we abs it and re-neg it later if necessary - bool neg = numer_ < 0; - - numer_ = qAbs(numer_); - - intType d = gcd(numer_, denom_); - - if (d > 1) { - numer_ /= d; - denom_ /= d; - } - - if (neg) { - numer_ = -numer_; - } - } -} - -//Function: finds greatest common denominator - -intType rational::gcd(const intType &x, const intType &y) -{ - if (y == 0) { - return x; - } else { - return gcd(y, x % y); - } -} - //Function: convert to double double rational::toDouble() const { - if (denom_ != 0) { - return static_cast(numer_) / static_cast(denom_); + if (r_.den != 0) { + return av_q2d(r_); } else { return qSNaN(); } @@ -115,12 +81,7 @@ double rational::toDouble() const AVRational rational::toAVRational() const { - AVRational r; - - r.num = static_cast(numer_); - r.den = static_cast(denom_); - - return r; + return r_; } #ifdef USE_OTIO @@ -143,44 +104,41 @@ rational rational::flipped() const void rational::flip() { if (!isNull()) { - std::swap(denom_, numer_); + std::swap(r_.den, r_.num); + FixSigns(); } } -bool rational::isNull() const -{ - return numerator() == 0; -} - -bool rational::isNaN() const -{ - return denominator() == 0; -} - -const intType &rational::numerator() const -{ - return numer_; -} - -const intType &rational::denominator() const -{ - return denom_; -} - QString rational::toString() const { - return QStringLiteral("%1/%2").arg(QString::number(numer_), QString::number(denom_)); + return QStringLiteral("%1/%2").arg(QString::number(r_.num), QString::number(r_.den)); +} + +void rational::FixSigns() +{ + if (r_.den < 0) { + // Normalize so that denominator is always positive + r_.den = -r_.den; + r_.num = -r_.num; + } else if (r_.den == 0) { + // Normalize to 0/0 (aka NaN) if denominator is zero + r_.num = 0; + } else if (r_.num == 0) { + // Normalize to 0/1 if numerator is zero + r_.den = 1; + } +} + +void rational::Reduce() +{ + av_reduce(&r_.num, &r_.den, r_.num, r_.den, INT_MAX); } //Assignment Operators const rational& rational::operator=(const rational &rhs) { - if (this != &rhs) { - numer_ = rhs.numer_; - denom_ = rhs.denom_; - } - + r_ = rhs.r_; return *this; } @@ -190,19 +148,10 @@ const rational& rational::operator+=(const rational &rhs) if (!isNaN()) { if (rhs.isNaN()) { - // Set to NaN - denom_ = 0; - fix_signs(); - } else if (!rhs.isNull()) { - if (isNull()) { - numer_ = rhs.numer_; - denom_ = rhs.denom_; - } else { - numer_ = (numer_ * rhs.denom_) + (rhs.numer_ * denom_); - denom_ = denom_ * rhs.denom_; - fix_signs(); - reduce(); - } + *this = NaN; + } else { + r_ = av_add_q(r_, rhs.r_); + FixSigns(); } } @@ -215,39 +164,10 @@ const rational& rational::operator-=(const rational &rhs) if (!isNaN()) { if (rhs.isNaN()) { - // Set to NaN - denom_ = 0; - fix_signs(); - } else if (!rhs.isNull()) { - if (isNull()) { - numer_ = -rhs.numer_; - denom_ = rhs.denom_; - } else { - numer_ = (numer_ * rhs.denom_) - (rhs.numer_ * denom_); - denom_ = denom_ * rhs.denom_; - fix_signs(); - reduce(); - } - } - } - - return *this; -} - -const rational& rational::operator/=(const rational &rhs) -{ - Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX); - - if (!isNaN()) { - if (rhs.isNaN()) { - // Set to NaN - denom_ = 0; - fix_signs(); + *this = NaN; } else { - numer_ = numer_ * rhs.denom_; - denom_ = denom_ * rhs.numer_; - fix_signs(); - reduce(); + r_ = av_sub_q(r_, rhs.r_); + FixSigns(); } } @@ -260,13 +180,26 @@ const rational& rational::operator*=(const rational &rhs) if (!isNaN()) { if (rhs.isNaN()) { - denom_ = 0; - fix_signs(); + *this = NaN; } else { - numer_ = numer_ * rhs.numer_; - denom_ = denom_ * rhs.denom_; - fix_signs(); - reduce(); + r_ = av_mul_q(r_, rhs.r_); + FixSigns(); + } + } + + return *this; +} + +const rational& rational::operator/=(const rational &rhs) +{ + Q_ASSERT(*this != RATIONAL_MIN && *this != RATIONAL_MAX && rhs != RATIONAL_MIN && rhs != RATIONAL_MAX); + + if (!isNaN()) { + if (rhs.isNaN()) { + *this = NaN; + } else { + r_ = av_div_q(r_, rhs.r_); + FixSigns(); } } @@ -307,87 +240,29 @@ rational rational::operator*(const rational &rhs) const bool rational::operator<(const rational &rhs) const { - if (isNaN() || rhs.isNaN()) { - return false; - } - - if (isNull() && rhs.isNull()) { - return false; - } - - if (rhs == RATIONAL_MAX - || *this == RATIONAL_MIN) { - // We will always either be LESS THAN (true) or EQUAL (false) - return (*this != rhs); - } - - if (*this == RATIONAL_MAX - || rhs == RATIONAL_MIN) { - // We will always be GREATER THAN (false) or EQUAL (false) - return false; - } - - if (!isNull() && rhs.isNull()) { - return (numer_ * denom_ < intType(0)); - } - - if (isNull() && !rhs.isNull()) { - return !(rhs.numer_ * rhs.denom_ < intType(0)); - } - - return ((numer_ * rhs.denom_) < (denom_ * rhs.numer_)); + return av_cmp_q(r_, rhs.r_) == -1; } bool rational::operator<=(const rational &rhs) const { - if (isNaN() || rhs.isNaN()) { - return false; - } - - if (isNull() && rhs.isNull()) { - return true; - } - - if (rhs == RATIONAL_MAX - || *this == RATIONAL_MIN) { - // We will always either be LESS THAN (true) or EQUAL (true) - return true; - } - - if (*this == RATIONAL_MAX - || rhs == RATIONAL_MIN) { - // We will always be GREATER THAN (false) or EQUAL (true) - return rhs == *this; - } - - if (!isNull() && rhs.isNull()) { - return (numer_ * denom_ < intType(0)); - } - - if (isNull() && !rhs.isNull()) { - return !(rhs.numer_ * rhs.denom_ < intType(0)); - } - - return ((numer_ * rhs.denom_) <= (denom_ * rhs.numer_)); + int cmp = av_cmp_q(r_, rhs.r_); + return cmp == 0 || cmp == -1; } bool rational::operator>(const rational &rhs) const { - return rhs < *this; + return av_cmp_q(r_, rhs.r_) == 1; } bool rational::operator>=(const rational &rhs) const { - return rhs <= *this; + int cmp = av_cmp_q(r_, rhs.r_); + return cmp == 0 || cmp == 1; } bool rational::operator==(const rational &rhs) const { - if (isNaN() || rhs.isNaN()) { - return false; - } - - return (numer_ == rhs.numer_ && denom_ == rhs.denom_); + return av_cmp_q(r_, rhs.r_) == 0; } bool rational::operator!=(const rational &rhs) const @@ -395,55 +270,6 @@ bool rational::operator!=(const rational &rhs) const return !(*this == rhs); } -const rational& rational::operator+() const -{ - return *this; -} - -rational rational::operator-() const -{ - return rational(numer_, -denom_); -} - -bool rational::operator!() const -{ - return !numer_; -} - -//IO - -std::ostream& operator<<(std::ostream &out, const rational &value) -{ - out << value.numer_; - - if (value.denom_ != 1) { - out << '/' << value.denom_; - return out; - } - - return out; -} - -std::istream& operator>>(std::istream &in, rational &value) -{ - in >> value.numer_; - value.denom_ = 1; - - char ch; - in.get(ch); - - if(!in.eof()) { - if(ch == '/') { - in >> value.denom_; - value.fix_signs(); - value.reduce(); - } else { - in.putback(ch); - } - } - return in; -} - uint qHash(const rational &r, uint seed) { return ::qHash(r.toDouble(), seed); diff --git a/app/common/rational.h b/app/common/rational.h index 26f625fc6..73c839c81 100644 --- a/app/common/rational.h +++ b/app/common/rational.h @@ -1,60 +1,66 @@ -//Copyright 2015 Adam Quintero -//This program is distributed under the terms of the GNU General Public License. +/*** -// Adapted by MattKC for the Olive Video Editor (2019-2022) + 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 RATIONAL_H #define RATIONAL_H -#include + +extern "C" { +#include +} + +#include +#include #ifdef USE_OTIO #include #endif -#include -#include - -extern "C" { -#include -} - #include "common/define.h" namespace olive { -typedef int64_t intType; -/* - * Zero Handling - * 0/0 = 0 - * 0/non-zero = 0 - * non-zero/0 = 0 -*/ class rational { public: - //constructors - rational(const intType &numerator = 0) : - numer_(numerator), - denom_(1) + rational(const int &numerator = 0) { + r_.num = numerator; + r_.den = 1; } - rational(const intType &numerator, const intType &denominator) : - numer_(numerator), - denom_(denominator) + rational(const int &numerator, const int &denominator) { - fix_signs(); - reduce(); + r_.num = numerator; + r_.den = denominator; + + FixSigns(); + Reduce(); } rational(const rational &rhs) = default; - rational(const AVRational& r) : - numer_(r.num), - denom_(r.den) + rational(const AVRational& r) { - fix_signs(); - reduce(); + r_ = r; + + FixSigns(); } static rational fromDouble(const double& flt, bool *ok = nullptr); @@ -84,9 +90,9 @@ public: bool operator!=(const rational &rhs) const; //Unary operators - const rational& operator+() const; - rational operator-() const; - bool operator!() const; + const rational& operator+() const { return *this; } + rational operator-() const { return rational(r_.num, -r_.den); } + bool operator!() const { return !r_.num; } //Function: convert to double double toDouble() const; @@ -100,7 +106,7 @@ public: return fromDouble(t.to_seconds()); } - // Convert Olive ratioanls to opentime rationals with the given framerate (defaults to 24) + // Convert Olive rationals to opentime rationals with the given framerate (defaults to 24) opentime::RationalTime toRationalTime(double framerate = 24) const; #endif @@ -111,35 +117,26 @@ public: // Returns whether the rational is valid but equal to zero or not // // A NaN is always a null, but a null is not always a NaN - bool isNull() const; + bool isNull() const { return r_.num == 0; } - // Returns whether this rational is not a valid number - bool isNaN() const; + // Returns whether this rational is not a valid number (denominator == 0) + bool isNaN() const { return r_.den == 0; } - //IO - friend std::ostream& operator<<(std::ostream &out, const rational &value); - friend std::istream& operator>>(std::istream &in, rational &value); - - const intType& numerator() const; - const intType& denominator() const; + const int& numerator() const { return r_.num; } + const int& denominator() const { return r_.den; } QString toString() const; private: - //numerator and denominator - intType numer_; - intType denom_; + void FixSigns(); + void Reduce(); + + AVRational r_; - //Function: ensures denom >= 0 - void fix_signs(); - //Function: ensures lowest form - void reduce(); - //Function: finds greatest common denominator - static intType gcd(const intType &x, const intType &y); }; -#define RATIONAL_MIN rational(INT64_MIN, 1) -#define RATIONAL_MAX rational(INT64_MAX, 1) +#define RATIONAL_MIN rational(INT_MIN) +#define RATIONAL_MAX rational(INT_MAX) uint qHash(const rational& r, uint seed = 0); From ac1c4733ac9a8d79cacd0331f72371fc0f87fd59 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 2 May 2022 10:32:17 -0700 Subject: [PATCH 78/81] various: added now-missing includes --- app/render/opengl/openglrenderer.cpp | 1 + app/render/videoparams.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index 8d9786973..f270bb568 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -20,6 +20,7 @@ #include "openglrenderer.h" +#include #include #include #include diff --git a/app/render/videoparams.cpp b/app/render/videoparams.cpp index dbdc47e13..032357aea 100644 --- a/app/render/videoparams.cpp +++ b/app/render/videoparams.cpp @@ -20,6 +20,10 @@ #include "videoparams.h" +extern "C" { +#include +} + #include #include "core.h" From a34fb2145d2d9d2ce552f86d6a97bb749de8a515 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 2 May 2022 12:37:29 -0700 Subject: [PATCH 79/81] rational: fixed otio code --- app/common/rational.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/common/rational.cpp b/app/common/rational.cpp index 2efad3f1f..62b93c6cd 100644 --- a/app/common/rational.cpp +++ b/app/common/rational.cpp @@ -89,7 +89,7 @@ opentime::RationalTime rational::toRationalTime(double framerate) const { // Is this the best way of doing this? // Olive can store rationals as 0/0 which causes errors in OTIO - opentime::RationalTime time = opentime::RationalTime(numer_, denom_ == 0 ? 1 : denom_); + opentime::RationalTime time = opentime::RationalTime(r_.num, r_.den == 0 ? 1 : r_.den); return time.rescaled_to(framerate); } #endif From 9101f5dc4960d30b6107e59379268068ee2ec60c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 2 May 2022 14:00:03 -0700 Subject: [PATCH 80/81] implemented core audio recording support --- app/audio/audiomanager.cpp | 85 +++++++++++++++++-- app/audio/audiomanager.h | 9 ++ app/codec/ffmpeg/ffmpegencoder.cpp | 37 +++++--- app/codec/ffmpeg/ffmpegencoder.h | 4 +- app/core.cpp | 6 +- app/core.h | 2 +- app/dialog/export/CMakeLists.txt | 2 + app/dialog/export/export.cpp | 54 ++---------- app/dialog/export/export.h | 9 +- app/dialog/export/exportaudiotab.h | 5 +- app/dialog/export/exportformatcombobox.cpp | 83 ++++++++++++++++++ app/dialog/export/exportformatcombobox.h | 63 ++++++++++++++ .../preferences/tabs/preferencesaudiotab.cpp | 24 ++++-- app/node/project/projectviewmodel.cpp | 2 +- app/panel/sequenceviewer/sequenceviewer.cpp | 7 ++ app/panel/sequenceviewer/sequenceviewer.h | 3 + app/panel/timeline/timeline.cpp | 1 + app/panel/timeline/timeline.h | 2 + app/task/project/import/import.cpp | 8 +- app/task/project/import/import.h | 8 +- .../playbackcontrols/playbackcontrols.cpp | 14 +++ .../playbackcontrols/playbackcontrols.h | 23 +++++ app/widget/timelinewidget/timelinewidget.cpp | 30 +++++++ app/widget/timelinewidget/timelinewidget.h | 8 ++ app/widget/timelinewidget/tool/import.cpp | 8 +- app/widget/timelinewidget/tool/import.h | 4 +- app/widget/timelinewidget/tool/record.cpp | 79 ++++++++++++++++- app/widget/timelinewidget/tool/record.h | 12 +++ .../timelinewidget/view/timelineview.cpp | 24 ++++++ app/widget/timelinewidget/view/timelineview.h | 5 ++ app/widget/viewer/viewer.cpp | 85 ++++++++++++++++--- app/widget/viewer/viewer.h | 14 +++ app/window/mainwindow/mainwindow.cpp | 1 + 33 files changed, 610 insertions(+), 111 deletions(-) create mode 100644 app/dialog/export/exportformatcombobox.cpp create mode 100644 app/dialog/export/exportformatcombobox.h diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp index 201b78ce6..9794b77c5 100644 --- a/app/audio/audiomanager.cpp +++ b/app/audio/audiomanager.cpp @@ -26,6 +26,7 @@ #include +#include "audio/packedprocessor.h" #include "config/config.h" namespace olive { @@ -68,6 +69,19 @@ int OutputCallback(const void *input, void *output, unsigned long frameCount, co return paContinue; } +int InputCallback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData) +{ + FFmpegEncoder *f = static_cast(userData); + + SampleBufferPtr s = SampleBuffer::Create(); + s->set_sample_count(frameCount); + s->set_audio_params(f->params().audio_params()); + + f->WriteAudioData(f->params().audio_params(), false, reinterpret_cast(&input), frameCount); + + return paContinue; +} + void AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &samples) { if (output_device_ == paNoDevice) { @@ -79,13 +93,7 @@ void AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &sam CloseOutputStream(); - PaStreamParameters p; - - p.channelCount = output_params_.channel_count(); - p.device = output_device_; - p.hostApiSpecificStreamInfo = nullptr; - p.sampleFormat = GetPortAudioSampleFormat(output_params_.format()); - p.suggestedLatency = Pa_GetDeviceInfo(output_device_)->defaultLowOutputLatency; + PaStreamParameters p = GetPortAudioParams(params, output_device_); Pa_OpenStream(&output_stream_, nullptr, &p, output_params_.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, OutputCallback, output_buffer_); @@ -176,6 +184,52 @@ void AudioManager::HardReset() Pa_Initialize(); } +bool AudioManager::StartRecording(const QString &filename, const AudioParams ¶ms) +{ + if (input_device_ == paNoDevice) { + return false; + } + + EncodingParams encode_param; + encode_param.EnableAudio(params, ExportCodec::kCodecMP3); + encode_param.SetFilename(filename); + + input_encoder_ = new FFmpegEncoder(encode_param); + if (!input_encoder_->Open()) { + qCritical() << "Failed to open encoder for recording"; + return false; + } + + PaStreamParameters p = GetPortAudioParams(params, input_device_); + + if (Pa_OpenStream(&input_stream_, &p, nullptr, params.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, InputCallback, input_encoder_) == paNoError) { + if (Pa_StartStream(input_stream_) == paNoError) { + return true; + } + } + + StopRecording(); + return false; +} + +void AudioManager::StopRecording() +{ + if (input_stream_) { + if (Pa_IsStreamActive(input_stream_)) { + Pa_StopStream(input_stream_); + } + Pa_CloseStream(input_stream_); + + input_stream_ = nullptr; + } + + if (input_encoder_) { + input_encoder_->Close(); + delete input_encoder_; + input_encoder_ = nullptr; + } +} + PaDeviceIndex AudioManager::FindConfigDeviceByName(bool is_output_device) { QString entry = is_output_device ? QStringLiteral("AudioOutput") : QStringLiteral("AudioInput"); @@ -199,8 +253,23 @@ PaDeviceIndex AudioManager::FindDeviceByName(const QString &s, bool is_output_de return is_output_device ? Pa_GetDefaultOutputDevice() : Pa_GetDefaultInputDevice(); } +PaStreamParameters AudioManager::GetPortAudioParams(const AudioParams ¶ms, PaDeviceIndex device) +{ + PaStreamParameters p; + + p.channelCount = params.channel_count(); + p.device = device; + p.hostApiSpecificStreamInfo = nullptr; + p.sampleFormat = GetPortAudioSampleFormat(params.format()); + p.suggestedLatency = Pa_GetDeviceInfo(device)->defaultLowOutputLatency; + + return p; +} + AudioManager::AudioManager() : - output_stream_(nullptr) + output_stream_(nullptr), + input_stream_(nullptr), + input_encoder_(nullptr) { #ifdef PA_HAS_JACK // PortAudio doesn't do a strcpy, so we need a const char that's readily accessible (i.e. not diff --git a/app/audio/audiomanager.h b/app/audio/audiomanager.h index 0d0329bd6..62fd0e920 100644 --- a/app/audio/audiomanager.h +++ b/app/audio/audiomanager.h @@ -28,6 +28,7 @@ #include "audiovisualwaveform.h" #include "common/define.h" +#include "codec/ffmpeg/ffmpegencoder.h" #include "render/audioparams.h" #include "render/audioplaybackcache.h" #include "render/previewaudiodevice.h" @@ -73,9 +74,15 @@ public: void HardReset(); + bool StartRecording(const QString &filename, const AudioParams ¶ms); + + void StopRecording(); + static PaDeviceIndex FindConfigDeviceByName(bool is_output_device); static PaDeviceIndex FindDeviceByName(const QString &s, bool is_output_device); + static PaStreamParameters GetPortAudioParams(const AudioParams &p, PaDeviceIndex device); + signals: void OutputNotify(); @@ -96,6 +103,8 @@ private: PreviewAudioDevice *output_buffer_; PaDeviceIndex input_device_; + PaStream *input_stream_; + FFmpegEncoder *input_encoder_; }; diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index c1ea0927a..9b38505b6 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -236,11 +236,6 @@ fail: bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio) { - if (!InitializeResampleContext(audio)) { - qCritical() << "Failed to initialize resample context"; - return false; - } - bool result = true; // Create input buffer @@ -258,6 +253,25 @@ bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio) } } + result = WriteAudioData(audio->audio_params(), true, const_cast(input_data), input_sample_count); + + if (input_data) { + av_freep(&input_data[0]); + av_freep(&input_data); + } + + return result; +} + +bool FFmpegEncoder::WriteAudioData(const AudioParams &audio_params, bool planar, const uint8_t **input_data, int input_sample_count) +{ + if (!InitializeResampleContext(audio_params, planar)) { + qCritical() << "Failed to initialize resample context"; + return false; + } + + bool result = true; + // Create output buffer int output_sample_count = input_sample_count ? swr_get_out_samples(audio_resample_ctx_, input_sample_count) : 102400; uint8_t** output_data = nullptr; @@ -308,11 +322,6 @@ bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio) av_freep(&output_data); } - if (input_data) { - av_freep(&input_data[0]); - av_freep(&input_data); - } - return result; } @@ -774,7 +783,7 @@ void FFmpegEncoder::FlushCodecCtx(AVCodecContext *codec_ctx, AVStream* stream) av_packet_free(&pkt); } -bool FFmpegEncoder::InitializeResampleContext(SampleBufferPtr audio) +bool FFmpegEncoder::InitializeResampleContext(const AudioParams &audio, bool planar) { if (audio_resample_ctx_) { return true; @@ -785,9 +794,9 @@ bool FFmpegEncoder::InitializeResampleContext(SampleBufferPtr audio) static_cast(audio_codec_ctx_->channel_layout), audio_codec_ctx_->sample_fmt, audio_codec_ctx_->sample_rate, - static_cast(audio->audio_params().channel_layout()), - FFmpegUtils::GetFFmpegSampleFormat(audio->audio_params().format(), true), - audio->audio_params().sample_rate(), + static_cast(audio.channel_layout()), + FFmpegUtils::GetFFmpegSampleFormat(audio.format(), planar), + audio.sample_rate(), 0, nullptr); if (!audio_resample_ctx_) { diff --git a/app/codec/ffmpeg/ffmpegencoder.h b/app/codec/ffmpeg/ffmpegencoder.h index d75d3f4ea..82e7040da 100644 --- a/app/codec/ffmpeg/ffmpegencoder.h +++ b/app/codec/ffmpeg/ffmpegencoder.h @@ -47,6 +47,8 @@ public: virtual bool WriteAudio(olive::SampleBufferPtr audio) override; + bool WriteAudioData(const AudioParams &audio_params, bool planar, const uint8_t **data, int input_sample_count); + virtual bool WriteSubtitle(const SubtitleBlock *sub_block) override; virtual void Close() override; @@ -76,7 +78,7 @@ private: void FlushEncoders(); void FlushCodecCtx(AVCodecContext* codec_ctx, AVStream *stream); - bool InitializeResampleContext(SampleBufferPtr audio); + bool InitializeResampleContext(const AudioParams &audio, bool planar); static const AVCodec *GetEncoder(ExportCodec::Codec c); diff --git a/app/core.cpp b/app/core.cpp index d4e091959..c86f1a9e5 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -245,14 +245,14 @@ UndoStack *Core::undo_stack() return &undo_stack_; } -void Core::ImportFiles(const QStringList &urls, ProjectViewModel* model, Folder* parent) +void Core::ImportFiles(const QStringList &urls, Folder* parent) { if (urls.isEmpty()) { QMessageBox::critical(main_window_, tr("Import error"), tr("Nothing to import")); return; } - ProjectImportTask* pim = new ProjectImportTask(model, parent, urls); + ProjectImportTask* pim = new ProjectImportTask(parent, urls); if (!pim->GetFileCount()) { // No files to import @@ -364,7 +364,7 @@ void Core::DialogImportShow() // Get the selected folder in this panel Folder* folder = active_project_panel->GetSelectedFolder(); - ImportFiles(files, active_project_panel->model(), folder); + ImportFiles(files, folder); } } diff --git a/app/core.h b/app/core.h index 653769af6..759cdd522 100644 --- a/app/core.h +++ b/app/core.h @@ -182,7 +182,7 @@ public: * * @param urls */ - void ImportFiles(const QStringList& urls, ProjectViewModel *model, Folder *parent); + void ImportFiles(const QStringList& urls, Folder *parent); /** * @brief Get the currently active tool diff --git a/app/dialog/export/CMakeLists.txt b/app/dialog/export/CMakeLists.txt index 845d4ab91..481feeed5 100644 --- a/app/dialog/export/CMakeLists.txt +++ b/app/dialog/export/CMakeLists.txt @@ -24,6 +24,8 @@ set(OLIVE_SOURCES dialog/export/exportadvancedvideodialog.h dialog/export/exportaudiotab.cpp dialog/export/exportaudiotab.h + dialog/export/exportformatcombobox.cpp + dialog/export/exportformatcombobox.h dialog/export/exportsubtitlestab.cpp dialog/export/exportsubtitlestab.h dialog/export/exportvideotab.cpp diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index c9aa7249c..ba5280897 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -118,7 +118,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : row++; preferences_layout->addWidget(new QLabel(tr("Format:")), row, 0); - format_combobox_ = new QComboBox(); + format_combobox_ = new ExportFormatComboBox(); preferences_layout->addWidget(format_combobox_, row, 1, 1, 3); row++; @@ -193,33 +193,11 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : // Set default filename SetDefaultFilename(); - // Populate combobox formats - for (int i=0; i(i)); - - bool inserted = false; - - for (int j=0; jcount(); j++) { - if (format_combobox_->itemText(j) > format_name) { - format_combobox_->insertItem(j, format_name, i); - inserted = true; - break; - } - } - - if (!inserted) { - format_combobox_->addItem(format_name, i); - } - } - // Set defaults previously_selected_format_ = ExportFormat::kFormatMPEG4; - SetCurrentFormat(ExportFormat::kFormatMPEG4); - connect(format_combobox_, - static_cast(&QComboBox::currentIndexChanged), - this, - &ExportDialog::FormatChanged); - FormatChanged(format_combobox_->currentIndex()); + format_combobox_->SetFormat(ExportFormat::kFormatMPEG4); + connect(format_combobox_, &ExportFormatComboBox::FormatChanged, this, &ExportDialog::FormatChanged); + FormatChanged(format_combobox_->GetFormat()); VideoParams vp = viewer_node_->GetVideoParams(); AudioParams ap = viewer_node_->GetAudioParams(); @@ -273,11 +251,6 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : preview_viewer_->SetColorTransform(video_tab_->CurrentOCIOColorSpace()); } -ExportFormat::Format ExportDialog::GetSelectedFormat() const -{ - return static_cast(format_combobox_->currentData().toInt()); -} - rational ExportDialog::GetSelectedTimebase() const { return video_tab_->GetSelectedFrameRate().flipped(); @@ -293,7 +266,7 @@ void ExportDialog::StartExport() // Validate if the entered filename contains the correct extension (the extension is necessary // for both FFmpeg and OIIO to determine the output format) - QString necessary_ext = QStringLiteral(".%1").arg(ExportFormat::GetExtension(GetSelectedFormat())); + QString necessary_ext = QStringLiteral(".%1").arg(ExportFormat::GetExtension(format_combobox_->GetFormat())); QString proposed_filename = filename_edit_->text().trimmed(); // If it doesn't, see if the user wants to append it automatically. If not, we don't abort the export. @@ -428,7 +401,7 @@ void ExportDialog::AddPreferencesTab(QWidget *inner_widget, const QString &title void ExportDialog::BrowseFilename() { - ExportFormat::Format f = GetSelectedFormat(); + ExportFormat::Format f = format_combobox_->GetFormat(); QString browsed_fn = QFileDialog::getSaveFileName(this, "", @@ -444,11 +417,10 @@ void ExportDialog::BrowseFilename() } } -void ExportDialog::FormatChanged(int index) +void ExportDialog::FormatChanged(ExportFormat::Format current_format) { QString current_filename = filename_edit_->text().trimmed(); QString previously_selected_ext = ExportFormat::GetExtension(previously_selected_format_); - ExportFormat::Format current_format = static_cast(format_combobox_->itemData(index).toInt()); QString currently_selected_ext = ExportFormat::GetExtension(current_format); // If the previous extension was added, remove it @@ -546,7 +518,7 @@ ExportParams ExportDialog::GenerateParams() const AudioParams::kInternalFormat); ExportParams params; - params.set_encoder(Encoder::GetTypeFromFormat(GetSelectedFormat())); + params.set_encoder(Encoder::GetTypeFromFormat(format_combobox_->GetFormat())); params.SetFilename(filename_edit_->text().trimmed()); params.SetExportLength(viewer_node_->GetLength()); @@ -594,16 +566,6 @@ ExportParams ExportDialog::GenerateParams() const return params; } -void ExportDialog::SetCurrentFormat(ExportFormat::Format format) -{ - for (int i=0; icount(); i++) { - if (format_combobox_->itemData(i).toInt() == format) { - format_combobox_->setCurrentIndex(i); - break; - } - } -} - rational ExportDialog::GetExportLength() const { if (range_combobox_->currentIndex() == kRangeInToOut) { diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index ad8056fcc..d0938a4b1 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -29,6 +29,7 @@ #include "codec/exportcodec.h" #include "codec/exportformat.h" +#include "dialog/export/exportformatcombobox.h" #include "exportaudiotab.h" #include "exportsubtitlestab.h" #include "exportvideotab.h" @@ -43,8 +44,6 @@ class ExportDialog : public QDialog public: ExportDialog(ViewerOutput* viewer_node, QWidget* parent = nullptr); - ExportFormat::Format GetSelectedFormat() const; - rational GetSelectedTimebase() const; protected: @@ -58,8 +57,6 @@ private: ExportParams GenerateParams() const; - void SetCurrentFormat(ExportFormat::Format format); - ViewerOutput* viewer_node_; ExportFormat::Format previously_selected_format_; @@ -82,7 +79,7 @@ private: ViewerWidget* preview_viewer_; QLineEdit* filename_edit_; - QComboBox* format_combobox_; + ExportFormatComboBox* format_combobox_; ExportVideoTab* video_tab_; ExportAudioTab* audio_tab_; @@ -98,7 +95,7 @@ private: private slots: void BrowseFilename(); - void FormatChanged(int index); + void FormatChanged(ExportFormat::Format current_format); void ResolutionChanged(); diff --git a/app/dialog/export/exportaudiotab.h b/app/dialog/export/exportaudiotab.h index ef5155018..4e29e3d48 100644 --- a/app/dialog/export/exportaudiotab.h +++ b/app/dialog/export/exportaudiotab.h @@ -37,8 +37,6 @@ class ExportAudioTab : public QWidget public: ExportAudioTab(QWidget* parent = nullptr); - int SetFormat(ExportFormat::Format format); - QComboBox* codec_combobox() const { return codec_combobox_; @@ -59,6 +57,9 @@ public: return bit_rate_slider_; } +public slots: + int SetFormat(ExportFormat::Format format); + private: QComboBox* codec_combobox_; SampleRateComboBox* sample_rate_combobox_; diff --git a/app/dialog/export/exportformatcombobox.cpp b/app/dialog/export/exportformatcombobox.cpp new file mode 100644 index 000000000..9f1b1927f --- /dev/null +++ b/app/dialog/export/exportformatcombobox.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 "exportformatcombobox.h" + +namespace olive { + +ExportFormatComboBox::ExportFormatComboBox(Mode mode, QWidget *parent) : + QComboBox(parent) +{ + // Populate combobox formats + for (int i=0; i(i); + + switch (mode) { + case kShowAllFormats: + break; + case kShowAudioOnly: + if (!ExportFormat::GetVideoCodecs(f).isEmpty()) { + continue; + } + break; + case kShowVideoOnly: + if (!ExportFormat::GetAudioCodecs(f).isEmpty()) { + continue; + } + break; + } + + QString format_name = ExportFormat::GetName(f); + + bool inserted = false; + + // Sort formats alphabetically + for (int j=0; j format_name) { + insertItem(j, format_name, i); + inserted = true; + break; + } + } + + if (!inserted) { + addItem(format_name, i); + } + } + + connect(this, static_cast(&QComboBox::currentIndexChanged), this, &ExportFormatComboBox::HandleIndexChange); +} + +void ExportFormatComboBox::SetFormat(ExportFormat::Format fmt) +{ + for (int i=0; i(itemData(index).toInt())); +} + +} diff --git a/app/dialog/export/exportformatcombobox.h b/app/dialog/export/exportformatcombobox.h new file mode 100644 index 000000000..3dd33e848 --- /dev/null +++ b/app/dialog/export/exportformatcombobox.h @@ -0,0 +1,63 @@ +/*** + + 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 EXPORTFORMATCOMBOBOX_H +#define EXPORTFORMATCOMBOBOX_H + +#include + +#include "codec/exportformat.h" + +namespace olive { + +class ExportFormatComboBox : public QComboBox +{ + Q_OBJECT +public: + enum Mode { + kShowAllFormats, + kShowAudioOnly, + kShowVideoOnly + }; + + ExportFormatComboBox(Mode mode, QWidget *parent = nullptr); + ExportFormatComboBox(QWidget *parent = nullptr) : + ExportFormatComboBox(kShowAllFormats, parent) + {} + + ExportFormat::Format GetFormat() const + { + return static_cast(currentData().toInt()); + } + +signals: + void FormatChanged(ExportFormat::Format fmt); + +public slots: + void SetFormat(ExportFormat::Format fmt); + +private slots: + void HandleIndexChange(int index); + +}; + +} + +#endif // EXPORTFORMATCOMBOBOX_H diff --git a/app/dialog/preferences/tabs/preferencesaudiotab.cpp b/app/dialog/preferences/tabs/preferencesaudiotab.cpp index 8b16c350b..e8371cf9f 100644 --- a/app/dialog/preferences/tabs/preferencesaudiotab.cpp +++ b/app/dialog/preferences/tabs/preferencesaudiotab.cpp @@ -26,6 +26,8 @@ #include "audio/audiomanager.h" #include "config/config.h" +#include "dialog/export/exportaudiotab.h" +#include "dialog/export/exportformatcombobox.h" namespace olive { @@ -87,12 +89,24 @@ PreferencesAudioTab::PreferencesAudioTab() row++; - input_layout->addWidget(new QLabel(tr("Recording Mode:"), this), row, 0); + QGroupBox *recording_group = new QGroupBox(tr("Recording")); + input_layout->addWidget(recording_group, row, 0, 1, 2); - recording_combobox_ = new QComboBox(); - recording_combobox_->addItem(tr("Mono")); - recording_combobox_->addItem(tr("Stereo")); - input_layout->addWidget(recording_combobox_, row, 1); + QVBoxLayout *recording_layout = new QVBoxLayout(recording_group); + + QHBoxLayout *fmt_layout = new QHBoxLayout(); + recording_layout->addLayout(fmt_layout); + + fmt_layout->addWidget(new QLabel(tr("Format:"))); + + ExportFormatComboBox *fmt_combo = new ExportFormatComboBox(ExportFormatComboBox::kShowAudioOnly); + fmt_combo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + fmt_layout->addWidget(fmt_combo); + + ExportAudioTab *audio_recording_options = new ExportAudioTab(); + recording_layout->addWidget(audio_recording_options); + + connect(fmt_combo, &ExportFormatComboBox::FormatChanged, audio_recording_options, &ExportAudioTab::SetFormat); } QHBoxLayout* refresh_layout = new QHBoxLayout(); diff --git a/app/node/project/projectviewmodel.cpp b/app/node/project/projectviewmodel.cpp index 1e0c2c30b..e4af49620 100644 --- a/app/node/project/projectviewmodel.cpp +++ b/app/node/project/projectviewmodel.cpp @@ -405,7 +405,7 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action } // Trigger an import - Core::instance()->ImportFiles(urls, this, static_cast(drop_item)); + Core::instance()->ImportFiles(urls, static_cast(drop_item)); return true; } diff --git a/app/panel/sequenceviewer/sequenceviewer.cpp b/app/panel/sequenceviewer/sequenceviewer.cpp index 5cda8dda3..9a76f1a18 100644 --- a/app/panel/sequenceviewer/sequenceviewer.cpp +++ b/app/panel/sequenceviewer/sequenceviewer.cpp @@ -19,6 +19,7 @@ ***/ #include "sequenceviewer.h" +#include "panel/timeline/timeline.h" namespace olive { @@ -29,6 +30,12 @@ SequenceViewerPanel::SequenceViewerPanel(QWidget *parent) : Retranslate(); } +void SequenceViewerPanel::StartCapture(const TimeRange &time, const Track::Reference &track) +{ + TimelinePanel *tp = static_cast(sender()); + static_cast(GetTimeBasedWidget())->StartCapture(tp->timeline_widget(), time, track); +} + void SequenceViewerPanel::Retranslate() { ViewerPanel::Retranslate(); diff --git a/app/panel/sequenceviewer/sequenceviewer.h b/app/panel/sequenceviewer/sequenceviewer.h index 6f5f11e1e..486e762a8 100644 --- a/app/panel/sequenceviewer/sequenceviewer.h +++ b/app/panel/sequenceviewer/sequenceviewer.h @@ -31,6 +31,9 @@ class SequenceViewerPanel : public ViewerPanel public: SequenceViewerPanel(QWidget* parent); +public slots: + void StartCapture(const TimeRange &time, const Track::Reference &track); + protected: virtual void Retranslate() override; diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index 461d94f16..e4fd4e274 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -34,6 +34,7 @@ TimelinePanel::TimelinePanel(QWidget *parent) : Retranslate(); connect(tw, &TimelineWidget::BlockSelectionChanged, this, &TimelinePanel::BlockSelectionChanged); + connect(tw, &TimelineWidget::RequestCaptureStart, this, &TimelinePanel::RequestCaptureStart ); } void TimelinePanel::SplitAtPlayhead() diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index 360187b0a..9e40b232d 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -112,6 +112,8 @@ protected: signals: void BlockSelectionChanged(const QVector& selected_blocks); + void RequestCaptureStart(const TimeRange &time, const Track::Reference &track); + }; } diff --git a/app/task/project/import/import.cpp b/app/task/project/import/import.cpp index 2e2823b15..352a8f175 100644 --- a/app/task/project/import/import.cpp +++ b/app/task/project/import/import.cpp @@ -30,9 +30,8 @@ namespace olive { -ProjectImportTask::ProjectImportTask(ProjectViewModel *model, Folder *folder, const QStringList &filenames) : +ProjectImportTask::ProjectImportTask(Folder *folder, const QStringList &filenames) : command_(nullptr), - model_(model), folder_(folder) { foreach (const QString& f, filenames) { @@ -117,6 +116,9 @@ void ProjectImportTask::Import(Folder *folder, QFileInfoList import, int &counte // Create undoable command that adds the items to the model AddItemToFolder(folder, footage, parent_command); + + // Add to vector + imported_footage_.push_back(footage); } else { // Add to list so we can tell the user about it later invalid_files_.append(file_info.absoluteFilePath()); @@ -220,7 +222,7 @@ void ProjectImportTask::ValidateImageSequence(Footage *footage, QFileInfoList& i void ProjectImportTask::AddItemToFolder(Folder *folder, Node *item, MultiUndoCommand *command) { // Create undoable command that adds the items to the model - Project* project = model_->project(); + Project* project = folder->project(); NodeAddCommand* nac = new NodeAddCommand(project, item); nac->PushToThread(project->thread()); diff --git a/app/task/project/import/import.h b/app/task/project/import/import.h index 79559b598..3f884478a 100644 --- a/app/task/project/import/import.h +++ b/app/task/project/import/import.h @@ -34,7 +34,7 @@ class ProjectImportTask : public Task { Q_OBJECT public: - ProjectImportTask(ProjectViewModel* model, Folder* folder, const QStringList& filenames); + ProjectImportTask(Folder* folder, const QStringList& filenames); const int& GetFileCount() const; @@ -53,6 +53,8 @@ public: return !invalid_files_.isEmpty(); } + const QVector &GetImportedFootage() const { return imported_footage_; } + protected: virtual bool Run() override; @@ -71,8 +73,6 @@ private: MultiUndoCommand* command_; - ProjectViewModel* model_; - Folder* folder_; QFileInfoList filenames_; @@ -83,6 +83,8 @@ private: QList image_sequence_ignore_files_; + QVector imported_footage_; + }; } diff --git a/app/widget/playbackcontrols/playbackcontrols.cpp b/app/widget/playbackcontrols/playbackcontrols.cpp index 41f771a17..6a0c7ab6d 100644 --- a/app/widget/playbackcontrols/playbackcontrols.cpp +++ b/app/widget/playbackcontrols/playbackcontrols.cpp @@ -156,6 +156,10 @@ PlaybackControls::PlaybackControls(QWidget *parent) : SetAudioVideoDragButtonsVisible(false); connect(Core::instance(), &Core::TimecodeDisplayChanged, this, &PlaybackControls::TimecodeChanged); + + play_blink_timer_ = new QTimer(this); + play_blink_timer_->setInterval(500); + connect(play_blink_timer_, &QTimer::timeout, this, &PlaybackControls::PlayBlink); } void PlaybackControls::SetTimecodeEnabled(bool enabled) @@ -231,10 +235,20 @@ void PlaybackControls::UpdateIcons() audio_drag_btn_->setIcon(icon::Audio); } +void PlaybackControls::SetButtonRecordingState(QPushButton *btn, bool on) +{ + btn->setStyleSheet(on ? QStringLiteral("background: red;") : QString()); +} + void PlaybackControls::TimecodeChanged() { // Update end time SetEndTime(end_time_); } +void PlaybackControls::PlayBlink() +{ + SetButtonRecordingState(play_btn_, play_btn_->styleSheet().isEmpty()); +} + } diff --git a/app/widget/playbackcontrols/playbackcontrols.h b/app/widget/playbackcontrols/playbackcontrols.h index b1d283f56..f467a1924 100644 --- a/app/widget/playbackcontrols/playbackcontrols.h +++ b/app/widget/playbackcontrols/playbackcontrols.h @@ -61,6 +61,23 @@ public slots: void ShowPlayButton(); + void StartPlayBlink() + { + play_blink_timer_->start(); + SetButtonRecordingState(play_btn_, true); + } + + void StopPlayBlink() + { + play_blink_timer_->stop(); + SetButtonRecordingState(play_btn_, false); + } + + void SetPauseButtonRecordingState(bool on) + { + SetButtonRecordingState(pause_btn_, on); + } + signals: /** * @brief Signal emitted when "Go to Start" is clicked @@ -108,6 +125,8 @@ protected: private: void UpdateIcons(); + static void SetButtonRecordingState(QPushButton *btn, bool on); + QWidget* lower_left_container_; QWidget* lower_right_container_; @@ -129,9 +148,13 @@ private: QStackedWidget* playpause_stack_; + QTimer *play_blink_timer_; + private slots: void TimecodeChanged(); + void PlayBlink(); + }; } diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 8766cd2fb..58629b2c2 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -32,6 +32,7 @@ #include "dialog/speedduration/speeddurationdialog.h" #include "node/block/transition/transition.h" #include "node/project/serializer/serializer.h" +#include "task/project/import/import.h" #include "tool/add.h" #include "tool/beam.h" #include "tool/edit.h" @@ -788,6 +789,35 @@ void TimelineWidget::ShowSpeedDurationDialogForSelectedClips() } } +void TimelineWidget::RecordingCallback(const QString &filename, const TimeRange &time, const Track::Reference &track) +{ + ProjectImportTask task(GetConnectedNode()->project()->root(), {filename}); + task.Start(); + + MultiUndoCommand *import_command = task.GetCommand(); + Core::instance()->undo_stack()->pushIfHasChildren(import_command); + + if (task.GetImportedFootage().empty()) { + qCritical() << "Failed to import recorded audio file" << filename; + } else { + import_tool_->PlaceAt({task.GetImportedFootage().front()}, time.in(), false, track.index()); + } +} + +void TimelineWidget::EnableRecordingOverlay(const TimelineCoordinate &coord) +{ + foreach (TimelineAndTrackView* tview, views_) { + tview->view()->EnableRecordingOverlay(coord); + } +} + +void TimelineWidget::DisableRecordingOverlay() +{ + foreach (TimelineAndTrackView* tview, views_) { + tview->view()->DisableRecordingOverlay(); + } +} + void TimelineWidget::InsertGapsAt(const rational &earliest_point, const rational &insert_length, MultiUndoCommand *command) { for (int i=0;i& selected_blocks); + void RequestCaptureStart(const TimeRange &time, const Track::Reference &track); + protected: virtual void resizeEvent(QResizeEvent *event) override; diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index de84a94b5..fdcd49afc 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -173,7 +173,7 @@ void ImportTool::DragDrop(TimelineViewMouseEvent *event) } } -void ImportTool::PlaceAt(const QVector &footage, const rational &start, bool insert) +void ImportTool::PlaceAt(const QVector &footage, const rational &start, bool insert, int track_offset) { DraggedFootageData refs; @@ -181,10 +181,10 @@ void ImportTool::PlaceAt(const QVector &footage, const rational refs.append({f, f->GetEnabledStreamsAsReferences()}); } - PlaceAt(refs, start, insert); + PlaceAt(refs, start, insert, track_offset); } -void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert) +void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert, int track_offset) { dragged_footage_ = footage; @@ -192,7 +192,7 @@ void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &star return; } - PrepGhosts(start, 0); + PrepGhosts(start, track_offset); DropGhosts(insert); } diff --git a/app/widget/timelinewidget/tool/import.h b/app/widget/timelinewidget/tool/import.h index 70ad33676..965f5960d 100644 --- a/app/widget/timelinewidget/tool/import.h +++ b/app/widget/timelinewidget/tool/import.h @@ -37,8 +37,8 @@ public: using DraggedFootageData = QVector > >; - void PlaceAt(const QVector &footage, const rational& start, bool insert); - void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert); + void PlaceAt(const QVector &footage, const rational& start, bool insert, int track_offset = 0); + void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert, int track_offset = 0); enum DropWithoutSequenceBehavior { kDWSAsk, diff --git a/app/widget/timelinewidget/tool/record.cpp b/app/widget/timelinewidget/tool/record.cpp index 4a904fb14..c8a7fc001 100644 --- a/app/widget/timelinewidget/tool/record.cpp +++ b/app/widget/timelinewidget/tool/record.cpp @@ -1,11 +1,88 @@ #include "record.h" +#include "widget/timelinewidget/timelinewidget.h" + namespace olive { RecordTool::RecordTool(TimelineWidget *parent) : - BeamTool(parent) + BeamTool(parent), + ghost_(nullptr) { } +void RecordTool::MousePress(TimelineViewMouseEvent *event) +{ + const Track::Reference& track = event->GetTrack(); + + // Check if track is locked + Track* t = parent()->GetTrackFromReference(track); + if (t && t->IsLocked()) { + return; + } + + if (t->type() != Track::kAudio) { + // We only support audio tracks here + return; + } + + drag_start_point_ = ValidatedCoordinate(event->GetCoordinates(true)).GetFrame(); + + ghost_ = new TimelineViewGhostItem(); + ghost_->SetIn(drag_start_point_); + ghost_->SetOut(drag_start_point_); + ghost_->SetTrack(track); + parent()->AddGhost(ghost_); + + snap_points_.push_back(drag_start_point_); +} + +void RecordTool::MouseMove(TimelineViewMouseEvent *event) +{ + if (!ghost_) { + return; + } + + // Calculate movement + rational movement = event->GetFrame() - drag_start_point_; + + // Validation: Ensure in point never goes below 0 + if (movement < -ghost_->GetIn()) { + movement = -ghost_->GetIn(); + } + + // Snap movement + bool snapped; + + if (Core::instance()->snapping()) { + snapped = parent()->SnapPoint(snap_points_, &movement); + } else { + snapped = false; + } + + // Make adjustment + if (!movement) { + ghost_->SetInAdjustment(0); + ghost_->SetOutAdjustment(0); + } else if (movement > 0) { + ghost_->SetInAdjustment(0); + ghost_->SetOutAdjustment(movement); + } else if (movement < 0) { + ghost_->SetInAdjustment(movement); + ghost_->SetOutAdjustment(0); + } + + Q_UNUSED(snapped) +} + +void RecordTool::MouseRelease(TimelineViewMouseEvent *event) +{ + if (ghost_) { + emit parent()->RequestCaptureStart(TimeRange(ghost_->GetAdjustedIn(), ghost_->GetAdjustedOut()), ghost_->GetTrack()); + parent()->ClearGhosts(); + snap_points_.clear(); + ghost_ = nullptr; + } +} + } diff --git a/app/widget/timelinewidget/tool/record.h b/app/widget/timelinewidget/tool/record.h index 89c1a1738..4fbe7f85b 100644 --- a/app/widget/timelinewidget/tool/record.h +++ b/app/widget/timelinewidget/tool/record.h @@ -29,6 +29,18 @@ class RecordTool : public BeamTool { public: RecordTool(TimelineWidget* parent); + + virtual void MousePress(TimelineViewMouseEvent *event) override; + virtual void MouseMove(TimelineViewMouseEvent *event) override; + virtual void MouseRelease(TimelineViewMouseEvent *event) override; + +protected: + void MouseMoveInternal(const rational& cursor_frame, bool outwards); + + TimelineViewGhostItem* ghost_; + + rational drag_start_point_; + }; } diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index b1462efda..32f268c03 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -348,6 +348,16 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect) track_y + GetTrackHeight(track_index)); } + // Draw recording overlay + if (recording_overlay_ && recording_coord_.GetTrack().type() == connected_track_list_->type()) { + painter->setPen(QPen(Qt::red, 2)); + painter->setBrush(QColor(255, 128, 128)); + + int x = TimeToScene(recording_coord_.GetFrame()); + painter->drawRect(x, GetTrackY(recording_coord_.GetTrack().index()), + TimeToScene(GetTime()) - x, GetTrackHeight(recording_coord_.GetTrack().index())); + } + // Draw standard TimelineViewBase things (such as playhead) super::drawForeground(painter, rect); } @@ -364,6 +374,7 @@ void TimelineView::ToolChangedEvent(Tool::Item tool) case Tool::kAdd: case Tool::kTransition: case Tool::kZoom: + case Tool::kRecord : setCursor(Qt::CrossCursor); break; case Tool::kTrackSelect: @@ -745,6 +756,19 @@ void TimelineView::SetTransitionOverlay(ClipBlock *out, ClipBlock *in) } } +void TimelineView::EnableRecordingOverlay(const TimelineCoordinate &coord) +{ + recording_overlay_ = true; + recording_coord_ = coord; + viewport()->update(); +} + +void TimelineView::DisableRecordingOverlay() +{ + recording_overlay_ = false; + viewport()->update(); +} + int TimelineView::SceneToTrack(double y) { int track = -1; diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index 05568a03a..3d5513637 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -56,6 +56,8 @@ public: void SetBeamCursor(const TimelineCoordinate& coord); void SetTransitionOverlay(ClipBlock *out, ClipBlock *in); + void EnableRecordingOverlay(const TimelineCoordinate &coord); + void DisableRecordingOverlay(); void SetSelectionList(QHash* s) { @@ -157,6 +159,9 @@ private: QMap clip_marker_rects_; + bool recording_overlay_; + TimelineCoordinate recording_coord_; + private slots: void TrackListChanged(); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 996e5e5bc..acc7b45c4 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -65,7 +65,9 @@ ViewerWidget::ViewerWidget(QWidget *parent) : color_menu_enabled_(true), time_changed_from_timer_(false), prequeuing_video_(false), - prequeuing_audio_(0) + prequeuing_audio_(0), + record_armed_(false), + recording_(false) { // Set up main layout QVBoxLayout* layout = new QVBoxLayout(this); @@ -158,6 +160,10 @@ void ViewerWidget::TimeChangedEvent(const rational &time) PauseInternal(); } + if (record_armed_) { + DisarmRecording(); + } + controls_->SetTime(time); waveform_view_->SetTime(time); @@ -377,6 +383,17 @@ void ViewerWidget::SetGizmos(Node *node) display_widget_->SetGizmos(node); } +void ViewerWidget::StartCapture(TimelineWidget *source, const TimeRange &time, const Track::Reference &track) +{ + SetTimeAndSignal(time.in()); + ArmForRecording(); + + recording_filename_ = QStringLiteral("/home/matt/Desktop/ass.mp3"); + recording_callback_ = source; + recording_range_ = time; + recording_track_ = track; +} + FramePtr ViewerWidget::DecodeCachedImage(const QString &cache_path, const QByteArray& hash, const rational& time) { FramePtr frame = FrameHashCache::LoadCacheFrame(cache_path, hash); @@ -428,6 +445,18 @@ void ViewerWidget::DecrementPrequeuedAudio() } } +void ViewerWidget::ArmForRecording() +{ + controls_->StartPlayBlink(); + record_armed_ = true; +} + +void ViewerWidget::DisarmRecording() +{ + controls_->StopPlayBlink(); + record_armed_ = false; +} + void ViewerWidget::QueueNextAudioBuffer() { rational queue_end = audio_playback_queue_time_ + (kAudioPlaybackInterval * playback_speed_); @@ -595,13 +624,20 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) viewer->auto_cacher_.SetAudioPaused(true); } + // Disarm recording if armed + if (record_armed_) { + DisarmRecording(); + } + // If the playhead is beyond the end, restart at 0 - rational last_frame = GetConnectedNode()->GetLength() - timebase(); - if (!in_to_out_only && GetTime() >= last_frame) { - if (speed > 0) { - SetTimeAndSignal(0); - } else { - SetTimeAndSignal(last_frame); + if (!recording_) { + rational last_frame = GetConnectedNode()->GetLength() - timebase(); + if (!in_to_out_only && GetTime() >= last_frame) { + if (speed > 0) { + SetTimeAndSignal(0); + } else { + SetTimeAndSignal(last_frame); + } } } @@ -657,6 +693,15 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) void ViewerWidget::PauseInternal() { + if (recording_) { + AudioManager::instance()->StopRecording(); + recording_ = false; + controls_->SetPauseButtonRecordingState(false); + + recording_callback_->DisableRecordingOverlay(); + recording_callback_->RecordingCallback(recording_filename_, recording_range_, recording_track_); + } + if (IsPlaying()) { playback_speed_ = 0; controls_->ShowPlayButton(); @@ -1121,6 +1166,17 @@ void ViewerWidget::Play(bool in_to_out_only) } else { in_to_out_only = false; } + } else if (record_armed_) { + if (AudioManager::instance()->StartRecording(recording_filename_, GetConnectedNode()->GetAudioParams())) { + recording_ = true; + controls_->SetPauseButtonRecordingState(true); + recording_callback_->EnableRecordingOverlay(TimelineCoordinate(recording_range_.in(), recording_track_)); + } else { + QMessageBox::critical(this, tr("Audio Recording"), tr("Failed to start audio recording")); + return; + } + + DisarmRecording(); } PlayInternal(1, in_to_out_only); @@ -1205,7 +1261,13 @@ void ViewerWidget::PlaybackTimerUpdate() rational min_time, max_time; - if (play_in_to_out_only_ && GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) { + if (recording_ && recording_range_.out() != recording_range_.in()) { + + // Limit recording range if applicable + min_time = recording_range_.in(); + max_time = recording_range_.out(); + + } else if (play_in_to_out_only_ && GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) { // If "play in to out" is enabled or we're looping AND we have a workarea, only play the workarea min_time = GetConnectedNode()->GetTimelinePoints()->workarea()->in(); @@ -1229,8 +1291,9 @@ void ViewerWidget::PlaybackTimerUpdate() bool end_of_line = false; bool play_after_pause = false; - if ((playback_speed_ < 0 && current_time <= min_time) - || (playback_speed_ > 0 && current_time >= max_time)) { + if ((!recording_ || recording_range_.out() != recording_range_.in()) + && ((playback_speed_ < 0 && current_time <= min_time) + || (playback_speed_ > 0 && current_time >= max_time))) { // Determine which timestamp we tripped rational tripped_time; @@ -1245,7 +1308,7 @@ void ViewerWidget::PlaybackTimerUpdate() // or restart playback end_of_line = true; - if (Config::Current()[QStringLiteral("Loop")].toBool()) { + if (Config::Current()[QStringLiteral("Loop")].toBool() && !recording_) { // If we're looping, jump to the other side of the workarea and continue time_to_set = (tripped_time == min_time) ? max_time : min_time; diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index e8ef472c9..4bf043af1 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -41,6 +41,7 @@ #include "viewerwindow.h" #include "widget/playbackcontrols/playbackcontrols.h" #include "widget/timebased/timebasedwidget.h" +#include "widget/timelinewidget/timelinewidget.h" namespace olive { @@ -87,6 +88,8 @@ public: void SetGizmos(Node* node); + void StartCapture(TimelineWidget *source, const TimeRange &time, const Track::Reference &track); + public slots: void Play(bool in_to_out_only); @@ -205,6 +208,10 @@ private: void DecrementPrequeuedAudio(); + void ArmForRecording(); + + void DisarmRecording(); + QStackedWidget* stack_; ViewerSizer* sizer_; @@ -255,6 +262,13 @@ private: static QVector instances_; + bool record_armed_; + bool recording_; + TimelineWidget *recording_callback_; + TimeRange recording_range_; + Track::Reference recording_track_; + QString recording_filename_; + private slots: void PlaybackTimerUpdate(); diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index f956e9051..5a5fc6693 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -555,6 +555,7 @@ TimelinePanel* MainWindow::AppendTimelinePanel() connect(panel, &TimelinePanel::TimeChanged, curve_panel_, &ParamPanel::SetTime); connect(panel, &TimelinePanel::TimeChanged, param_panel_, &ParamPanel::SetTime); connect(panel, &TimelinePanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime); + connect(panel, &TimelinePanel::RequestCaptureStart, sequence_viewer_panel_, &SequenceViewerPanel::StartCapture); connect(panel, &TimelinePanel::BlockSelectionChanged, this, &MainWindow::TimelinePanelSelectionChanged); connect(param_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime); connect(curve_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime); From d2010760ed2a5fb566b83e4d59df01d5a6e2f899 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 2 May 2022 14:38:29 -0700 Subject: [PATCH 81/81] rational: restored ostream operator --- app/common/rational.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/common/rational.h b/app/common/rational.h index 73c839c81..4701e1e0a 100644 --- a/app/common/rational.h +++ b/app/common/rational.h @@ -25,6 +25,7 @@ extern "C" { #include } +#include #include #include @@ -127,6 +128,13 @@ public: QString toString() const; + friend std::ostream& operator<<(std::ostream &out, const rational &value) + { + out << value.r_.num << '/' << value.r_.den; + + return out; + } + private: void FixSigns(); void Reduce();