+void MainWindow::undo_triggered()
+{
+ // Finish any deferred action.
+ if (defer_timeout->isActive()) {
+ defer_timeout->stop();
+ state_changed(deferred_state);
+ }
+
+ StateProto redo_state;
+ *redo_state.mutable_clip_list() = cliplist_clips->serialize();
+ *redo_state.mutable_play_list() = playlist_clips->serialize();
+ redo_stack.push_back(std::move(redo_state));
+ ui->redo_action->setEnabled(true);
+
+ assert(undo_stack.size() > 1);
+
+ // Pop off the current state, which is always at the top of the stack.
+ undo_stack.pop_back();
+
+ StateProto state = undo_stack.back();
+ ui->undo_action->setEnabled(undo_stack.size() > 1);
+
+ replace_model(ui->clip_list, &cliplist_clips, new ClipList(state.clip_list()), this);
+ replace_model(ui->playlist, &playlist_clips, new PlayList(state.play_list()), this);
+
+ db.store_state(state);
+}
+
+void MainWindow::redo_triggered()
+{
+ assert(!redo_stack.empty());
+
+ ui->undo_action->setEnabled(true);
+ ui->redo_action->setEnabled(true);
+
+ undo_stack.push_back(std::move(redo_stack.back()));
+ redo_stack.pop_back();
+ ui->undo_action->setEnabled(true);
+ ui->redo_action->setEnabled(!redo_stack.empty());
+
+ const StateProto &state = undo_stack.back();
+ replace_model(ui->clip_list, &cliplist_clips, new ClipList(state.clip_list()), this);
+ replace_model(ui->playlist, &playlist_clips, new PlayList(state.play_list()), this);
+
+ db.store_state(state);
+}
+