diff --git a/app/core.cpp b/app/core.cpp index a00d8b608..dae6b1273 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -29,6 +29,7 @@ #include #include #include +#include "window/mainwindow/mainwindowundo.h" #ifdef Q_OS_WINDOWS #include #endif @@ -450,14 +451,13 @@ void Core::CreateNewSequence() command->add_child(new NodeAddCommand(active_project, new_sequence)); command->add_child(new FolderAddChild(GetSelectedFolderInActiveProject(), new_sequence)); command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, Node::Position())); + command->add_child(new OpenSequenceCommand(new_sequence)); // Create and connect default nodes to new sequence new_sequence->add_default_nodes(command); Core::instance()->undo_stack()->push(command); - Core::instance()->main_window()->OpenSequence(new_sequence); - } else { // If the dialog was accepted, ownership goes to the AddItemCommand. But if we get here, just delete @@ -575,7 +575,7 @@ void Core::ImportTaskComplete(Task* task) d.exec(); } - undo_stack_.pushIfHasChildren(command); + undo_stack_.push(command); main_window_->SelectFootage(import_task->GetImportedFootage()); } diff --git a/app/dialog/configbase/configdialogbase.cpp b/app/dialog/configbase/configdialogbase.cpp index 13bbafbb5..1d10e099e 100644 --- a/app/dialog/configbase/configdialogbase.cpp +++ b/app/dialog/configbase/configdialogbase.cpp @@ -73,7 +73,7 @@ void ConfigDialogBase::accept() tab->Accept(command); } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); AcceptEvent(); diff --git a/app/dialog/footageproperties/footageproperties.cpp b/app/dialog/footageproperties/footageproperties.cpp index ece3e5688..aaf34bc06 100644 --- a/app/dialog/footageproperties/footageproperties.cpp +++ b/app/dialog/footageproperties/footageproperties.cpp @@ -192,7 +192,7 @@ void FootagePropertiesDialog::accept() static_cast(stacked_widget_->widget(i))->Accept(command); } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); QDialog::accept(); } diff --git a/app/dialog/keyframeproperties/keyframeproperties.cpp b/app/dialog/keyframeproperties/keyframeproperties.cpp index 45fa39d32..39311f5e7 100644 --- a/app/dialog/keyframeproperties/keyframeproperties.cpp +++ b/app/dialog/keyframeproperties/keyframeproperties.cpp @@ -219,7 +219,7 @@ void KeyframePropertiesDialog::accept() } } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); QDialog::accept(); } diff --git a/app/dialog/markerproperties/markerpropertiesdialog.cpp b/app/dialog/markerproperties/markerpropertiesdialog.cpp index 6809b3385..db6ce01ad 100644 --- a/app/dialog/markerproperties/markerpropertiesdialog.cpp +++ b/app/dialog/markerproperties/markerpropertiesdialog.cpp @@ -147,7 +147,7 @@ void MarkerPropertiesDialog::accept() command->add_child(new MarkerChangeTimeCommand(markers_.front(), TimeRange(in_slider_->GetValue(), out_slider_->GetValue()))); } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); super::accept(); } diff --git a/app/node/gizmo/text.cpp b/app/node/gizmo/text.cpp index 7b966ee40..5212a884f 100644 --- a/app/node/gizmo/text.cpp +++ b/app/node/gizmo/text.cpp @@ -43,7 +43,7 @@ void TextGizmo::UpdateInputHtml(const QString &s, const rational &time) if (input_.IsValid()) { MultiUndoCommand *command = new MultiUndoCommand(); Node::SetValueAtTime(input_.input(), time, s, input_.track(), command, true); - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } } diff --git a/app/panel/CMakeLists.txt b/app/panel/CMakeLists.txt index eb26a7e58..0f4c9348f 100644 --- a/app/panel/CMakeLists.txt +++ b/app/panel/CMakeLists.txt @@ -17,6 +17,7 @@ add_subdirectory(audiomonitor) add_subdirectory(curve) add_subdirectory(footageviewer) +add_subdirectory(history) add_subdirectory(multicam) add_subdirectory(node) add_subdirectory(param) diff --git a/app/panel/history/CMakeLists.txt b/app/panel/history/CMakeLists.txt new file mode 100644 index 000000000..811a1333d --- /dev/null +++ b/app/panel/history/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2023 Olive Studios LLC +# +# 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} + panel/history/historypanel.h + panel/history/historypanel.cpp + PARENT_SCOPE +) diff --git a/app/panel/history/historypanel.cpp b/app/panel/history/historypanel.cpp new file mode 100644 index 000000000..6ab2df8fd --- /dev/null +++ b/app/panel/history/historypanel.cpp @@ -0,0 +1,40 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "historypanel.h" + +#include "widget/history/historywidget.h" + +namespace olive { + +HistoryPanel::HistoryPanel() : + PanelWidget(QStringLiteral("HistoryPanel")) +{ + SetWidgetWithPadding(new HistoryWidget(this)); + + Retranslate(); +} + +void HistoryPanel::Retranslate() +{ + SetTitle(tr("History")); +} + +} diff --git a/app/panel/history/historypanel.h b/app/panel/history/historypanel.h new file mode 100644 index 000000000..6f89d6267 --- /dev/null +++ b/app/panel/history/historypanel.h @@ -0,0 +1,41 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 HISTORYPANEL_H +#define HISTORYPANEL_H + +#include "panel/panel.h" + +namespace olive { + +class HistoryPanel : public PanelWidget +{ + Q_OBJECT +public: + HistoryPanel(); + +protected: + virtual void Retranslate() override; + +}; + +} + +#endif // HISTORYPANEL_H diff --git a/app/undo/undostack.cpp b/app/undo/undostack.cpp index 157ebda26..8cf317a28 100644 --- a/app/undo/undostack.cpp +++ b/app/undo/undostack.cpp @@ -26,6 +26,19 @@ namespace olive { const int UndoStack::kMaxUndoCommands = 200; +class EmptyCommand : public UndoCommand +{ +public: + EmptyCommand(){} + + virtual Project* GetRelevantProject() const override {return nullptr;} + +protected: + virtual void redo() override {} + virtual void undo() override {} + +}; + UndoStack::UndoStack() { undo_action_ = new QAction(); @@ -34,6 +47,7 @@ UndoStack::UndoStack() redo_action_ = new QAction(); connect(redo_action_, &QAction::triggered, this, &UndoStack::redo); + clear(); UpdateActions(); } @@ -45,35 +59,51 @@ UndoStack::~UndoStack() delete redo_action_; } -void UndoStack::pushIfHasChildren(MultiUndoCommand *command) -{ - if (command->child_count() > 0) { - push(command); - } else { - delete command; - } -} - void UndoStack::push(UndoCommand *command) { - command->redo_and_set_modified(); - commands_.push_back(command); - - if (commands_.size() > kMaxUndoCommands) { - delete commands_.front(); - commands_.pop_front(); + MultiUndoCommand *mcu = dynamic_cast(command); + if (mcu && mcu->child_count() == 0) { + delete command; + return; } + // Clear any redoable commands + this->beginRemoveRows(QModelIndex(), commands_.size(), commands_.size() + undone_commands_.size()); if (CanRedo()) { for (auto it=undone_commands_.cbegin(); it!=undone_commands_.cend(); it++) { delete (*it); } undone_commands_.clear(); } + this->endRemoveRows(); + + // Do command and push + this->beginInsertRows(QModelIndex(), commands_.size(), commands_.size()); + command->redo_and_set_modified(); + commands_.push_back(command); + this->endInsertRows(); + + // Delete oldest + if (commands_.size() > kMaxUndoCommands) { + this->beginRemoveRows(QModelIndex(), 0, 0); + delete commands_.front(); + commands_.pop_front(); + this->endRemoveRows(); + } UpdateActions(); } +void UndoStack::jump(size_t index) +{ + while (commands_.size() > index) { + undo(); + } + while (commands_.size() < index) { + redo(); + } +} + void UndoStack::undo() { if (CanUndo()) { @@ -110,10 +140,27 @@ void UndoStack::redo() void UndoStack::clear() { + this->beginResetModel(); + for (auto it=commands_.cbegin(); it!=commands_.cend(); it++) { delete (*it); } commands_.clear(); + for (auto it=undone_commands_.cbegin(); it!=undone_commands_.cend(); it++) { + delete (*it); + } + undone_commands_.clear(); + + this->endResetModel(); + + EmptyCommand *e = new EmptyCommand(); + e->set_name(tr("New/Open Project")); + push(e); +} + +bool UndoStack::CanUndo() const +{ + return !commands_.empty() && !dynamic_cast(commands_.back()); } void UndoStack::UpdateActions() @@ -123,6 +170,86 @@ void UndoStack::UpdateActions() undo_action_->setText(QCoreApplication::translate("UndoStack", "Undo %1").arg(CanUndo() ? commands_.back()->name() : QString())); redo_action_->setText(QCoreApplication::translate("UndoStack", "Redo %1").arg(CanRedo() ? undone_commands_.front()->name() : QString())); + + emit indexChanged(commands_.size()); +} + +int UndoStack::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + return 2; +} + +QVariant UndoStack::data(const QModelIndex &index, int role) const +{ + if (role == Qt::DisplayRole) { + switch (index.column()) { + case 0: + return index.row() + 1; + case 1: + { + std::list::const_iterator it; + size_t real_index = index.row(); + if (real_index < commands_.size()) { + it = commands_.cbegin(); + } else { + real_index -= commands_.size(); + it = undone_commands_.cbegin(); + } + for (size_t i = 0; i < real_index; i++) { + it++; + } + const QString &name = (*it)->name(); + return (name.isEmpty()) ? QStringLiteral("Command") : name; + } + } + } else if (role == Qt::ForegroundRole) { + if (size_t(index.row()) >= commands_.size()) { + return QVariant(QColor(Qt::gray)); + } + } + + return QVariant(); +} + +QModelIndex UndoStack::index(int row, int column, const QModelIndex &parent) const +{ + return createIndex(row, column, nullptr); +} + +QModelIndex UndoStack::parent(const QModelIndex &index) const +{ + return QModelIndex(); +} + +int UndoStack::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return commands_.size() + undone_commands_.size(); +} + +QVariant UndoStack::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::DisplayRole) { + switch (section) { + case 0: + return QStringLiteral("Number"); + case 1: + return QStringLiteral("Action"); + } + } + + return QVariant(); +} + +bool UndoStack::hasChildren(const QModelIndex &parent) const +{ + return !parent.isValid(); } } diff --git a/app/undo/undostack.h b/app/undo/undostack.h index bc1550e7d..46e31d998 100644 --- a/app/undo/undostack.h +++ b/app/undo/undostack.h @@ -22,13 +22,14 @@ #define UNDOSTACK_H #include +#include #include "common/define.h" #include "undo/undocommand.h" namespace olive { -class UndoStack : public QObject +class UndoStack : public QAbstractItemModel { Q_OBJECT public: @@ -36,21 +37,13 @@ public: virtual ~UndoStack() override; - /** - * @brief A wrapper for push() that either pushes if the command has children or deletes if not - * - * This function takes ownership of `command`, and may delete it so it should never be accessed after this call. - */ - void pushIfHasChildren(MultiUndoCommand* command); - void push(UndoCommand* command); + void jump(size_t index); + void clear(); - bool CanUndo() const - { - return !commands_.empty(); - } + bool CanUndo() const; bool CanRedo() const { @@ -69,6 +62,17 @@ public: return redo_action_; } + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + virtual QModelIndex parent(const QModelIndex &index) const override; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + virtual bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; + +signals: + void indexChanged(int i); + public slots: void undo(); diff --git a/app/widget/CMakeLists.txt b/app/widget/CMakeLists.txt index 1c075c9b6..26f2b10e8 100644 --- a/app/widget/CMakeLists.txt +++ b/app/widget/CMakeLists.txt @@ -27,6 +27,7 @@ add_subdirectory(filefield) add_subdirectory(flowlayout) add_subdirectory(focusablelineedit) add_subdirectory(handmovableview) +add_subdirectory(history) add_subdirectory(keyframeview) add_subdirectory(manageddisplay) add_subdirectory(menu) diff --git a/app/widget/history/CMakeLists.txt b/app/widget/history/CMakeLists.txt new file mode 100644 index 000000000..1ccd89717 --- /dev/null +++ b/app/widget/history/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2023 Olive Studios LLC +# +# 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/history/historywidget.cpp + widget/history/historywidget.h + PARENT_SCOPE +) diff --git a/app/widget/history/historywidget.cpp b/app/widget/history/historywidget.cpp new file mode 100644 index 000000000..8a9deb5dc --- /dev/null +++ b/app/widget/history/historywidget.cpp @@ -0,0 +1,49 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "historywidget.h" + +#include "core.h" + +namespace olive { + +HistoryWidget::HistoryWidget(QWidget *parent) : + QTreeView(parent) +{ + stack_ = Core::instance()->undo_stack(); + + this->setModel(stack_); + this->setRootIsDecorated(false); + connect(stack_, &UndoStack::indexChanged, this, &HistoryWidget::indexChanged); + connect(this->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &HistoryWidget::currentRowChanged); +} + +void HistoryWidget::indexChanged(int i) +{ + this->selectionModel()->select(this->model()->index(i-1, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); +} + +void HistoryWidget::currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + size_t jump_to = (current.row() + 1); + stack_->jump(jump_to); +} + +} diff --git a/app/widget/history/historywidget.h b/app/widget/history/historywidget.h new file mode 100644 index 000000000..0b0f28fc8 --- /dev/null +++ b/app/widget/history/historywidget.h @@ -0,0 +1,50 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 HISTORYWIDGET_H +#define HISTORYWIDGET_H + +#include + +#include "undo/undostack.h" + +namespace olive { + +class HistoryWidget : public QTreeView +{ + Q_OBJECT +public: + HistoryWidget(QWidget *parent = nullptr); + +private: + UndoStack *stack_; + + size_t current_row_; + +private slots: + void indexChanged(int i); + + void currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous); + +}; + +} + +#endif // HISTORYWIDGET_H diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index 1ff6618fb..c9c103b3d 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -61,7 +61,7 @@ void KeyframeView::DeleteSelected() command->add_child(new NodeParamRemoveKeyframeCommand(key)); } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } } @@ -247,7 +247,7 @@ bool KeyframeView::Paste(std::function find_node_functi } } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); return true; } diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 4060b8137..f7ec0cbe8 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -646,7 +646,7 @@ bool NodeParamView::Paste(QWidget *parent, std::function(co } } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); return true; } diff --git a/app/widget/nodeparamview/nodeparamviewcontext.cpp b/app/widget/nodeparamview/nodeparamviewcontext.cpp index d4a0adeb5..05a4ee659 100644 --- a/app/widget/nodeparamview/nodeparamviewcontext.cpp +++ b/app/widget/nodeparamview/nodeparamviewcontext.cpp @@ -180,7 +180,7 @@ void NodeParamViewContext::AddEffectMenuItemTriggered(QAction *a) command->add_child(new NodeEdgeAddCommand(n, ctx_input)); } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } } diff --git a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp index 2dd98aa7a..7adfeb085 100644 --- a/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp +++ b/app/widget/nodeparamview/nodeparamviewkeyframecontrol.cpp @@ -176,7 +176,7 @@ void NodeParamViewKeyframeControl::ToggleKeyframe(bool e) } } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } void NodeParamViewKeyframeControl::UpdateState() @@ -276,7 +276,7 @@ void NodeParamViewKeyframeControl::KeyframeEnableBtnClicked(bool e) } } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } void NodeParamViewKeyframeControl::KeyframeEnableChanged(const NodeInput &input, bool e) diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 29d55bd23..37322ed30 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -190,7 +190,7 @@ void NodeParamViewWidgetBridge::SetInputValue(const QVariant &value, int track) SetInputValueInternal(value, track, command, true); - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } void NodeParamViewWidgetBridge::SetInputValueInternal(const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key) @@ -314,7 +314,7 @@ void NodeParamViewWidgetBridge::WidgetCallback() n->SetInputProperty(GetInnerInput().input(), QStringLiteral("col_look"), c.color_output().look()); n->blockSignals(false); - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); break; } case NodeValue::kText: diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 17ab4fd45..821c810df 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -411,7 +411,7 @@ void NodeView::keyPressEvent(QKeyEvent *event) } } } - Core::instance()->undo_stack()->pushIfHasChildren(pos_command); + Core::instance()->undo_stack()->push(pos_command); break; } case Qt::Key_Escape: @@ -581,7 +581,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } dragging_items_.clear(); - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); if (!had_attached_items) { super::mouseReleaseEvent(event); @@ -674,7 +674,7 @@ void NodeView::dropEvent(QDropEvent *event) if (Node *drop_ctx = GetContextAtMousePos(event->pos())) { MultiUndoCommand *command = new MultiUndoCommand(); QVector select_nodes = ProcessDroppingAttachedNodes(command, drop_ctx, event->pos()); - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); DeselectAll(); scene_.context_map().value(drop_ctx)->Select(select_nodes); @@ -1680,7 +1680,7 @@ void NodeView::EndEdgeDrag(bool cancel) } create_edge_expanded_items_.clear(); - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } void NodeView::PostPaste(const QVector &new_nodes, const Node::PositionMap &map) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 70a95f874..512541db1 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -666,7 +666,7 @@ void ProjectExplorer::DeleteSelected() bool check_if_item_is_in_use = true; if (DeleteItemsInternal(selected, check_if_item_is_in_use, command)) { - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } else { delete command; } diff --git a/app/widget/projectexplorer/projectviewmodel.cpp b/app/widget/projectexplorer/projectviewmodel.cpp index 8efe73014..472a1da0d 100644 --- a/app/widget/projectexplorer/projectviewmodel.cpp +++ b/app/widget/projectexplorer/projectviewmodel.cpp @@ -374,7 +374,7 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action } } - Core::instance()->undo_stack()->pushIfHasChildren(move_command); + Core::instance()->undo_stack()->push(move_command); return true; diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 914796e15..0e1b53e81 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -545,7 +545,7 @@ void TimelineWidget::DeleteSelected(bool ripple) command->add_child(ripple_command); } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); // Ensures any current drag operations are cancelled ClearGhosts(); @@ -773,7 +773,7 @@ void TimelineWidget::ToggleSelectedEnabled() !i->is_enabled())); } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } void TimelineWidget::SetColorLabel(int index) @@ -845,7 +845,7 @@ void TimelineWidget::RecordingCallback(const QString &filename, const TimeRange import_command->add_child(subimport_command); import_tool_->PlaceAt({task.GetImportedFootage().front()}, time.in(), false, import_command, track.index()); - Core::instance()->undo_stack()->pushIfHasChildren(import_command); + Core::instance()->undo_stack()->push(import_command); } } @@ -1452,7 +1452,7 @@ void TimelineWidget::RenameSelectedBlocks() } Core::instance()->LabelNodes(nodes); - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } void TimelineWidget::TrackAboutToBeDeleted(Track *track) @@ -1475,7 +1475,7 @@ void TimelineWidget::SetSelectedClipsAutocaching(bool e) } } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } void TimelineWidget::CacheClips() @@ -1574,7 +1574,7 @@ void TimelineWidget::MulticamEnabledTriggered(bool e) } } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } void TimelineWidget::ForceUpdateRubberBand() @@ -1918,7 +1918,7 @@ void TimelineWidget::EditTo(Timeline::MovementMode mode) } } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } void TimelineWidget::UpdateViewports(const Track::Type &type) @@ -1953,8 +1953,6 @@ bool TimelineWidget::PasteInternal(bool insert) } } - qDebug() << "pasing" << res.GetLoadData().nodes.size() << "nodes"; - for (auto it = res.GetLoadData().promised_connections.cbegin(); it != res.GetLoadData().promised_connections.cend(); it++) { auto oc = *it; command->add_child(new NodeEdgeAddCommand(oc.first, oc.second)); @@ -1988,7 +1986,7 @@ bool TimelineWidget::PasteInternal(bool insert) paste_start + in)); } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); return true; } diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index 2cc1248d5..1c2795c63 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -184,7 +184,7 @@ void ImportTool::DragDrop(TimelineViewMouseEvent *event) if (!dragged_footage_.isEmpty()) { auto command = new MultiUndoCommand(); DropGhosts(event->GetModifiers() & Qt::ControlModifier, command); - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); event->accept(); } else { diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 33adebd93..1d1d30662 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -815,7 +815,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) } } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } Timeline::MovementMode PointerTool::IsCursorInTrimHandle(Block *block, qreal cursor_x) diff --git a/app/widget/timelinewidget/tool/slip.cpp b/app/widget/timelinewidget/tool/slip.cpp index af267eae4..0a7c5d929 100644 --- a/app/widget/timelinewidget/tool/slip.cpp +++ b/app/widget/timelinewidget/tool/slip.cpp @@ -80,7 +80,7 @@ void SlipTool::FinishDrag(TimelineViewMouseEvent *event) } } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } } diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index a5386031f..d7cc2b668 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -116,7 +116,7 @@ void SeekableWidget::DeleteSelected() command->add_child(new MarkerRemoveCommand(marker)); } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } } @@ -249,7 +249,7 @@ void SeekableWidget::mouseReleaseEvent(QMouseEvent *event) if (selection_manager_.IsDragging()) { MultiUndoCommand *command = new MultiUndoCommand(); selection_manager_.DragStop(command); - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } if (GetSnapService()) { @@ -364,7 +364,7 @@ void SeekableWidget::SetMarkerColor(int c) command->add_child(new MarkerChangeColorCommand(marker, c)); } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } void SeekableWidget::ShowMarkerProperties() @@ -614,7 +614,7 @@ void SeekableWidget::CommitResizeHandle() command->add_child(new WorkareaSetRangeCommand(workarea, workarea->range(), resize_item_range_)); } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); } } diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 0e4f25aa9..33a8faf3c 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -557,7 +557,7 @@ void ViewerWidget::CreateAddableAt(const QRectF &f) shape->SetRect(f, s->GetVideoParams(), command); } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); SetGizmos(clip); } } diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 2a78df02c..7c6b78555 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -946,7 +946,7 @@ bool ViewerDisplayWidget::OnMouseRelease(QMouseEvent *e) if (DraggableGizmo *draggable = dynamic_cast(current_gizmo_)) { draggable->DragEnd(command); } - Core::instance()->undo_stack()->pushIfHasChildren(command); + Core::instance()->undo_stack()->push(command); gizmo_drag_started_ = false; } current_gizmo_ = nullptr; diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 5354bb313..2f762b9ef 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -85,6 +85,7 @@ MainWindow::MainWindow(QWidget *parent) : AppendTimelinePanel(); audio_monitor_panel_ = new AudioMonitorPanel(); scope_panel_ = new ScopePanel(); + history_panel_ = new HistoryPanel(); // HACK: The pixel sampler is closed by default, which signals to Core that // it's no longer visible. However KDDockWidgets doesn't appear to @@ -845,6 +846,8 @@ void MainWindow::SetDefaultLayout() // Bottom left - project panel addDockWidget(project_panel_, KDDockWidgets::Location_OnLeft, tool_panel_); + project_panel_->addDockWidgetAsTab(history_panel_); + project_panel_->raise(); // Hidden panels pixel_sampler_panel_->close(); diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index a624c965e..7fbb81fc4 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -30,6 +30,7 @@ #include "panel/panelmanager.h" #include "panel/audiomonitor/audiomonitor.h" #include "panel/curve/curve.h" +#include "panel/history/historypanel.h" #include "panel/node/node.h" #include "panel/param/param.h" #include "panel/project/project.h" @@ -157,6 +158,7 @@ private: ScopePanel* scope_panel_; QList viewer_panels_; MulticamPanel *multicam_panel_; + HistoryPanel *history_panel_; #ifdef Q_OS_WINDOWS unsigned int taskbar_btn_id_;