From 8b4a073f3513c836d43a15d9a84bd19478f48c31 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 3 Oct 2020 22:33:10 +1000 Subject: [PATCH] timeline: implemented system of moving selections and making them undoable --- app/widget/timelinewidget/CMakeLists.txt | 2 + app/widget/timelinewidget/timelinewidget.cpp | 141 +++++++++--------- app/widget/timelinewidget/timelinewidget.h | 24 ++- .../timelinewidgetselections.cpp | 59 ++++++++ .../timelinewidget/timelinewidgetselections.h | 44 ++++++ app/widget/timelinewidget/undo/undo.cpp | 19 +++ app/widget/timelinewidget/undo/undo.h | 18 +++ 7 files changed, 231 insertions(+), 76 deletions(-) create mode 100644 app/widget/timelinewidget/timelinewidgetselections.cpp create mode 100644 app/widget/timelinewidget/timelinewidgetselections.h diff --git a/app/widget/timelinewidget/CMakeLists.txt b/app/widget/timelinewidget/CMakeLists.txt index 293c4c972..9672f1a58 100644 --- a/app/widget/timelinewidget/CMakeLists.txt +++ b/app/widget/timelinewidget/CMakeLists.txt @@ -29,5 +29,7 @@ set(OLIVE_SOURCES widget/timelinewidget/timelinescaledobject.cpp widget/timelinewidget/timelinewidget.h widget/timelinewidget/timelinewidget.cpp + widget/timelinewidget/timelinewidgetselections.h + widget/timelinewidget/timelinewidgetselections.cpp PARENT_SCOPE ) diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index a732e4c0d..2bd52f40f 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -119,6 +119,7 @@ TimelineWidget::TimelineWidget(QWidget *parent) : view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); view->SetSnapService(this); view->SetSelectionList(&selections_); + view->SetGhostList(&ghost_items_); view_splitter_->addWidget(tview); @@ -173,20 +174,17 @@ TimelineWidget::~TimelineWidget() void TimelineWidget::Clear() { - QList deselected_blocks; - - QMap::const_iterator iterator; - for (iterator=block_items_.begin(); iterator!=block_items_.end(); iterator++) { - if (IsItemSelected(iterator.value())) { - deselected_blocks.append(iterator.key()); - } - + // Delete all items + for (auto iterator=block_items_.begin(); iterator!=block_items_.end(); iterator++) { delete iterator.value(); } block_items_.clear(); - emit BlocksDeselected(deselected_blocks); + // Emit that we've deselected any selected blocks + emit BlocksDeselected(selected_blocks_); + selected_blocks_.clear(); + // Set null timebase SetTimebase(0); } @@ -236,10 +234,6 @@ void TimelineWidget::ScaleChangedEvent(const double &scale) } } - foreach (TimelineViewGhostItem* ghost, ghost_items_) { - ghost->SetScale(scale); - } - foreach (TimelineAndTrackView* view, views_) { view->view()->SetScale(scale); } @@ -375,34 +369,31 @@ rational TimelineWidget::GetToolTipTimebase() const void TimelineWidget::SelectAll() { - QList blocks_selected; + QList newly_selected_blocks; - QMap::const_iterator i; - - for (i=block_items_.constBegin(); i!=block_items_.end(); i++) { - if (!IsItemSelected(i.value())) { - AddSelection(i.value()); - blocks_selected.append(i.key()); + for (auto it=block_items_.cbegin(); it!=block_items_.cend(); it++) { + if (!selected_blocks_.contains(it.key())) { + newly_selected_blocks.append(it.key()); + AddSelection(it.key()->range(), it.value()->Track()); } } - emit BlocksSelected(blocks_selected); + SignalSelectedBlocks(newly_selected_blocks); } void TimelineWidget::DeselectAll() { - QList blocks_deselected; + // Clear selections + selections_.clear(); - QMap::const_iterator i; + // Update all viewports + UpdateViewports(); - for (i=block_items_.constBegin(); i!=block_items_.end(); i++) { - if (IsItemSelected(i.value())) { - RemoveSelection(i.value()); - blocks_deselected.append(i.key()); - } - } + // Emit signal that any previously selected block is no longer selected + emit BlocksDeselected(selected_blocks_); - emit BlocksDeselected(blocks_deselected); + // Empty selected blocks list + selected_blocks_.clear(); } void TimelineWidget::RippleToIn() @@ -544,6 +535,9 @@ void TimelineWidget::DeleteSelected(bool ripple) // Replace clips with gaps (effectively deleting them) ReplaceBlocksWithGaps(clips_to_delete, true, command); + // Remove all selections + new TimelineSetSelectionsCommand(this, TimelineWidgetSelections(), GetSelections(), command); + // Insert ripple command now that it's all cleaned up gaps if (ripple) { TimeRangeList range_list; @@ -780,16 +774,8 @@ QList TimelineWidget::GetSelectedBlocks() { QList list; - QMapIterator iterator(block_items_); - - while (iterator.hasNext()) { - iterator.next(); - - TimelineViewBlockItem* item = iterator.value(); - - if (item && IsItemSelected(item)) { - list.append(item); - } + foreach (Block* b, selected_blocks_) { + list.append(block_items_.value(b)); } return list; @@ -950,20 +936,30 @@ void TimelineWidget::RemoveBlock(const QList &blocks) QList deselect_blocks; foreach (Block* b, blocks) { + // Disconnect all signals disconnect(b, &Block::Refreshed, this, &TimelineWidget::BlockRefreshed); disconnect(b, &Block::LinksChanged, this, &TimelineWidget::BlockUpdated); disconnect(b, &Block::LabelChanged, this, &TimelineWidget::BlockUpdated); disconnect(b, &Block::EnabledChanged, this, &TimelineWidget::BlockUpdated); + // Take item from map TimelineViewBlockItem* item = block_items_.take(b); - delete_items.append(item); - if (IsItemSelected(item)) { + // If selected, deselect it + int select_index = selected_blocks_.indexOf(b); + if (select_index > -1) { + selected_blocks_.removeAt(select_index); deselect_blocks.append(b); + RemoveSelection(item); } + + // Finally, delete item + delete item; } if (!deselect_blocks.isEmpty()) { + // We already removed the blocks from selected_blocks_, so we can signal directly rather than + // through emit BlocksDeselected(deselect_blocks); } @@ -1171,9 +1167,9 @@ void TimelineWidget::ViewTimestampChanged(int64_t ts) void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost) { - ghost->SetScale(GetScale()); ghost_items_.append(ghost); - views_.at(ghost->Track().type())->view()->scene()->addItem(ghost); + + UpdateViewports(ghost->GetTrack().type()); } void TimelineWidget::UpdateViewTimebases() @@ -1231,6 +1227,22 @@ const QRect& TimelineWidget::GetRubberBandGeometry() const return rubberband_.geometry(); } +void TimelineWidget::SignalSelectedBlocks(const QList &selected_blocks) +{ + selected_blocks_.append(selected_blocks); + + emit BlocksSelected(selected_blocks); +} + +void TimelineWidget::SignalDeselectedBlocks(const QList &deselected_blocks) +{ + foreach (Block* b, deselected_blocks) { + selected_blocks_.removeOne(b); + } + + emit BlocksDeselected(deselected_blocks); +} + QVector TimelineWidget::GetEditToInfo(const rational& playhead_time, Timeline::MovementMode mode) { @@ -1372,6 +1384,17 @@ void TimelineWidget::ShowSnap(const QList ×) } } +void TimelineWidget::UpdateViewports(const Timeline::TrackType &type) +{ + if (type == Timeline::kTrackTypeNone) { + foreach (TimelineAndTrackView* tview, views_) { + tview->view()->viewport()->update(); + } + } else { + views_.at(type)->view()->viewport()->update(); + } +} + void TimelineWidget::HideSnaps() { foreach (TimelineAndTrackView* tview, views_) { @@ -1396,10 +1419,8 @@ void TimelineWidget::StartRubberBandSelect(bool enable_selecting, bool select_li // We don't touch any blocks that are already selected. If you want these to be deselected by // default, call DeselectAll() befoer calling StartRubberBandSelect() - foreach (TimelineViewBlockItem* block, block_items_) { - if (IsItemSelected(block)) { - rubberband_already_selected_.append(block); - } + foreach (Block* b, selected_blocks_) { + rubberband_already_selected_.append(block_items_.value(b)); } MoveRubberBandSelect(enable_selecting, select_links); @@ -1507,7 +1528,7 @@ void TimelineWidget::AddSelection(const TimeRange &time, const TrackReference &t { selections_[track].InsertTimeRange(time); - views_.at(track.type())->view()->viewport()->update(); + UpdateViewports(track.type()); } void TimelineWidget::AddSelection(TimelineViewBlockItem *item) @@ -1519,7 +1540,7 @@ void TimelineWidget::RemoveSelection(const TimeRange &time, const TrackReference { selections_[track].RemoveTimeRange(time); - views_.at(track.type())->view()->viewport()->update(); + UpdateViewports(track.type()); } void TimelineWidget::RemoveSelection(TimelineViewBlockItem *item) @@ -1527,22 +1548,11 @@ void TimelineWidget::RemoveSelection(TimelineViewBlockItem *item) RemoveSelection(item->block()->range(), item->Track()); } -void TimelineWidget::ShiftSelections(const rational &diff) -{ - for (auto it=selections_.begin(); it!=selections_.end(); it++) { - for (auto it2=it.value().begin(); it2!=it.value().end(); it2++) { - (*it2) += diff; - } - } -} - -void TimelineWidget::SetSelections(const TimelineWidget::Selections &s) +void TimelineWidget::SetSelections(const TimelineWidgetSelections &s) { selections_ = s; - foreach (TimelineAndTrackView* tview, views_) { - tview->view()->viewport(),update(); - } + UpdateViewports(); } TimelineViewBlockItem *TimelineWidget::GetItemAtScenePos(const TimelineCoordinate& coord) @@ -1561,11 +1571,6 @@ TimelineViewBlockItem *TimelineWidget::GetItemAtScenePos(const TimelineCoordinat return nullptr; } -bool TimelineWidget::IsItemSelected(TimelineViewBlockItem *item) const -{ - return selections_[item->Track()].ContainsTimeRange(item->block()->range()); -} - struct SnapData { rational time; rational movement; diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 89d91633e..1147e0f02 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -34,6 +34,7 @@ #include "widget/nodecopypaste/nodecopypaste.h" #include "widget/slider/timeslider.h" #include "widget/timebased/timebased.h" +#include "widget/timelinewidget/timelinewidgetselections.h" #include "widget/timelinewidget/tool/import.h" #include "widget/timelinewidget/tool/tool.h" @@ -113,22 +114,18 @@ public: return block_items_; } - using Selections = QHash; - void AddSelection(const TimeRange& time, const TrackReference& track); void AddSelection(TimelineViewBlockItem* item); void RemoveSelection(const TimeRange& time, const TrackReference& track); void RemoveSelection(TimelineViewBlockItem* item); - void ShiftSelections(const rational& diff); - - const Selections& GetSelections() const + const TimelineWidgetSelections& GetSelections() const { return selections_; } - void SetSelections(const Selections& s); + void SetSelections(const TimelineWidgetSelections &s); TrackOutput* GetTrackFromReference(const TrackReference& ref); @@ -159,7 +156,10 @@ public: rational GetToolTipTimebase() const; - bool IsItemSelected(TimelineViewBlockItem* item) const; + bool IsBlockSelected(Block* b) const + { + return selected_blocks_.contains(b); + } void SetBlockLinksSelected(Block *block, bool selected); @@ -169,6 +169,10 @@ public: const QRect &GetRubberBandGeometry() const; + void SignalSelectedBlocks(const QList& selected_blocks); + + void SignalDeselectedBlocks(const QList& deselected_blocks); + signals: void BlocksSelected(const QList& selected_blocks); @@ -203,13 +207,15 @@ private: void ShowSnap(const QList& times); + void UpdateViewports(const Timeline::TrackType& type = Timeline::kTrackTypeNone); + QPoint drag_origin_; QRubberBand rubberband_; QList rubberband_already_selected_; QList rubberband_now_selected_; - Selections selections_; + TimelineWidgetSelections selections_; TimelineTool* GetActiveTool(); @@ -227,6 +233,8 @@ private: TimeSlider* timecode_label_; + QList selected_blocks_; + int deferred_scroll_value_; bool use_audio_time_units_; diff --git a/app/widget/timelinewidget/timelinewidgetselections.cpp b/app/widget/timelinewidget/timelinewidgetselections.cpp new file mode 100644 index 000000000..036911895 --- /dev/null +++ b/app/widget/timelinewidget/timelinewidgetselections.cpp @@ -0,0 +1,59 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 "timelinewidgetselections.h" + +OLIVE_NAMESPACE_ENTER + +void TimelineWidgetSelections::ShiftTime(const rational &diff) +{ + for (auto it=this->begin(); it!=this->end(); it++) { + for (auto it2=it.value().begin(); it2!=it.value().end(); it2++) { + (*it2) += diff; + } + } +} + +void TimelineWidgetSelections::ShiftTracks(Timeline::TrackType type, int diff) +{ + TimelineWidgetSelections cached_selections; + + { + // Take all selections of this track type + auto it = this->begin(); + while (it != this->end()) { + if (it.key().type() == type) { + cached_selections.insert(it.key(), it.value()); + it = this->erase(it); + } else { + it++; + } + } + } + + // Then re-insert them with the diff applied + for (auto it=cached_selections.cbegin(); it!=cached_selections.cend(); it++) { + TrackReference ref(it.key().type(), it.key().index() + diff); + + this->insert(ref, it.value()); + } +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/widget/timelinewidget/timelinewidgetselections.h b/app/widget/timelinewidget/timelinewidgetselections.h new file mode 100644 index 000000000..f5bea68ed --- /dev/null +++ b/app/widget/timelinewidget/timelinewidgetselections.h @@ -0,0 +1,44 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 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 TIMELINEWIDGETSELECTIONS_H +#define TIMELINEWIDGETSELECTIONS_H + +#include + +#include "common/timerange.h" +#include "timeline/trackreference.h" + +OLIVE_NAMESPACE_ENTER + +class TimelineWidgetSelections : public QHash +{ +public: + TimelineWidgetSelections() = default; + + void ShiftTime(const rational& diff); + + void ShiftTracks(Timeline::TrackType type, int diff); + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // TIMELINEWIDGETSELECTIONS_H diff --git a/app/widget/timelinewidget/undo/undo.cpp b/app/widget/timelinewidget/undo/undo.cpp index dd6a48b09..824228eb9 100644 --- a/app/widget/timelinewidget/undo/undo.cpp +++ b/app/widget/timelinewidget/undo/undo.cpp @@ -25,6 +25,7 @@ #include "node/block/transition/transition.h" #include "node/graph.h" #include "widget/nodeview/nodeviewundo.h" +#include "widget/timelinewidget/timelinewidget.h" OLIVE_NAMESPACE_ENTER @@ -1696,4 +1697,22 @@ void TransitionRemoveCommand::undo_internal() track_->InvalidateCache(TimeRange(block_->in(), block_->out()), track_->block_input(), track_->block_input()); } +TimelineSetSelectionsCommand::TimelineSetSelectionsCommand(TimelineWidget *timeline, const TimelineWidgetSelections &now, const TimelineWidgetSelections &old, QUndoCommand *parent) : + QUndoCommand(parent), + timeline_(timeline), + old_(old), + now_(now) +{ +} + +void TimelineSetSelectionsCommand::redo() +{ + timeline_->SetSelections(now_); +} + +void TimelineSetSelectionsCommand::undo() +{ + timeline_->SetSelections(old_); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/timelinewidget/undo/undo.h b/app/widget/timelinewidget/undo/undo.h index c8f53c00a..220f4133d 100644 --- a/app/widget/timelinewidget/undo/undo.h +++ b/app/widget/timelinewidget/undo/undo.h @@ -30,6 +30,7 @@ #include "node/output/track/tracklist.h" #include "timeline/timelinepoints.h" #include "undo/undocommand.h" +#include "widget/timelinewidget/timelinewidgetselections.h" OLIVE_NAMESPACE_ENTER @@ -613,6 +614,23 @@ private: }; +class TimelineWidget; + +class TimelineSetSelectionsCommand : public QUndoCommand { +public: + TimelineSetSelectionsCommand(TimelineWidget* timeline, const TimelineWidgetSelections& now, const TimelineWidgetSelections& old, QUndoCommand* parent = nullptr); + +protected: + virtual void redo() override; + virtual void undo() override; + +private: + TimelineWidget* timeline_; + TimelineWidgetSelections old_; + TimelineWidgetSelections now_; + +}; + OLIVE_NAMESPACE_EXIT #endif // TIMELINEUNDOABLE_H