summaryrefslogtreecommitdiffstats
path: root/src/ui/dialog/undo-history.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/dialog/undo-history.cpp')
-rw-r--r--src/ui/dialog/undo-history.cpp406
1 files changed, 406 insertions, 0 deletions
diff --git a/src/ui/dialog/undo-history.cpp b/src/ui/dialog/undo-history.cpp
new file mode 100644
index 0000000..eae9ea6
--- /dev/null
+++ b/src/ui/dialog/undo-history.cpp
@@ -0,0 +1,406 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Undo History dialog - implementation.
+ */
+/* Author:
+ * Gustav Broberg <broberg@kth.se>
+ * Abhishek Sharma
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2014 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "undo-history.h"
+
+#include "document-undo.h"
+#include "document.h"
+#include "inkscape.h"
+#include "ui/icon-loader.h"
+#include "util/signal-blocker.h"
+
+#include "desktop.h"
+
+
+namespace Inkscape {
+namespace UI {
+namespace Dialog {
+
+/* Rendering functions for custom cell renderers */
+void CellRendererSPIcon::render_vfunc(const Cairo::RefPtr<Cairo::Context>& cr,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ Gtk::CellRendererState flags)
+{
+ // if this event type doesn't have an icon...
+ if ( !Inkscape::Verb::get(_property_event_type)->get_image() ) return;
+
+ // if the icon isn't cached, render it to a pixbuf
+ if ( !_icon_cache[_property_event_type] ) {
+
+ Glib::ustring image_name = Inkscape::Verb::get(_property_event_type)->get_image();
+ Gtk::Image* icon = Gtk::manage(new Gtk::Image());
+ icon = sp_get_icon_image(image_name, Gtk::ICON_SIZE_MENU);
+
+ if (icon) {
+
+ // check icon type (inkscape, gtk, none)
+ if ( GTK_IS_IMAGE(icon->gobj()) ) {
+ _property_icon = sp_get_icon_pixbuf(image_name, 16);
+ } else {
+ delete icon;
+ return;
+ }
+
+ delete icon;
+ property_pixbuf() = _icon_cache[_property_event_type] = _property_icon.get_value();
+ }
+
+ } else {
+ property_pixbuf() = _icon_cache[_property_event_type];
+ }
+
+ Gtk::CellRendererPixbuf::render_vfunc(cr, widget, background_area,
+ cell_area, flags);
+}
+
+
+void CellRendererInt::render_vfunc(const Cairo::RefPtr<Cairo::Context>& cr,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ Gtk::CellRendererState flags)
+{
+ if( _filter(_property_number) ) {
+ std::ostringstream s;
+ s << _property_number << std::flush;
+ property_text() = s.str();
+ Gtk::CellRendererText::render_vfunc(cr, widget, background_area,
+ cell_area, flags);
+ }
+}
+
+const CellRendererInt::Filter& CellRendererInt::no_filter = CellRendererInt::NoFilter();
+
+UndoHistory& UndoHistory::getInstance()
+{
+ return *new UndoHistory();
+}
+
+UndoHistory::UndoHistory()
+ : UI::Widget::Panel("/dialogs/undo-history", SP_VERB_DIALOG_UNDO_HISTORY),
+ _document_replaced_connection(),
+ _desktop(getDesktop()),
+ _document(_desktop ? _desktop->doc() : nullptr),
+ _event_log(_desktop ? _desktop->event_log : nullptr),
+ _columns(_event_log ? &_event_log->getColumns() : nullptr),
+ _scrolled_window(),
+ _event_list_store(),
+ _event_list_selection(_event_list_view.get_selection()),
+ _deskTrack(),
+ _desktopChangeConn(),
+ _callback_connections()
+{
+ if ( !_document || !_event_log || !_columns ) return;
+
+ set_size_request(-1, 95);
+
+ _getContents()->pack_start(_scrolled_window);
+ _scrolled_window.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
+
+ // connect with the EventLog
+ _connectEventLog();
+
+ _event_list_view.set_enable_search(false);
+ _event_list_view.set_headers_visible(false);
+
+ CellRendererSPIcon* icon_renderer = Gtk::manage(new CellRendererSPIcon());
+ icon_renderer->property_xpad() = 2;
+ icon_renderer->property_width() = 24;
+ int cols_count = _event_list_view.append_column("Icon", *icon_renderer);
+
+ Gtk::TreeView::Column* icon_column = _event_list_view.get_column(cols_count-1);
+ icon_column->add_attribute(icon_renderer->property_event_type(), _columns->type);
+
+ CellRendererInt* children_renderer = Gtk::manage(new CellRendererInt(greater_than_1));
+ children_renderer->property_weight() = 600; // =Pango::WEIGHT_SEMIBOLD (not defined in old versions of pangomm)
+ children_renderer->property_xalign() = 1.0;
+ children_renderer->property_xpad() = 2;
+ children_renderer->property_width() = 24;
+
+ cols_count = _event_list_view.append_column("Children", *children_renderer);
+ Gtk::TreeView::Column* children_column = _event_list_view.get_column(cols_count-1);
+ children_column->add_attribute(children_renderer->property_number(), _columns->child_count);
+
+ Gtk::CellRendererText* description_renderer = Gtk::manage(new Gtk::CellRendererText());
+ description_renderer->property_ellipsize() = Pango::ELLIPSIZE_END;
+
+ cols_count = _event_list_view.append_column("Description", *description_renderer);
+ Gtk::TreeView::Column* description_column = _event_list_view.get_column(cols_count-1);
+ description_column->add_attribute(description_renderer->property_text(), _columns->description);
+ description_column->set_resizable();
+ description_column->set_sizing(Gtk::TREE_VIEW_COLUMN_AUTOSIZE);
+ description_column->set_min_width (150);
+
+ _event_list_view.set_expander_column( *_event_list_view.get_column(cols_count-1) );
+
+ _scrolled_window.add(_event_list_view);
+
+ // connect EventLog callbacks
+ _callback_connections[EventLog::CALLB_SELECTION_CHANGE] =
+ _event_list_selection->signal_changed().connect(sigc::mem_fun(*this, &Inkscape::UI::Dialog::UndoHistory::_onListSelectionChange));
+
+ _callback_connections[EventLog::CALLB_EXPAND] =
+ _event_list_view.signal_row_expanded().connect(sigc::mem_fun(*this, &Inkscape::UI::Dialog::UndoHistory::_onExpandEvent));
+
+ _callback_connections[EventLog::CALLB_COLLAPSE] =
+ _event_list_view.signal_row_collapsed().connect(sigc::mem_fun(*this, &Inkscape::UI::Dialog::UndoHistory::_onCollapseEvent));
+
+ _desktopChangeConn = _deskTrack.connectDesktopChanged( sigc::mem_fun(*this, &UndoHistory::setDesktop) );
+ _deskTrack.connect(GTK_WIDGET(gobj()));
+
+ // connect to be informed of document changes
+ signalDocumentReplaced().connect(sigc::mem_fun(*this, &UndoHistory::_handleDocumentReplaced));
+
+ show_all_children();
+
+ // scroll to the selected row
+ _event_list_view.set_cursor(_event_list_store->get_path(_event_log->getCurrEvent()));
+}
+
+UndoHistory::~UndoHistory()
+{
+ _desktopChangeConn.disconnect();
+}
+
+
+void UndoHistory::setDesktop(SPDesktop* desktop)
+{
+ Panel::setDesktop(desktop);
+
+ EventLog *newEventLog = desktop ? desktop->event_log : nullptr;
+ if ((_desktop == desktop) && (_event_log == newEventLog)) {
+ // same desktop set
+ }
+ else
+ {
+ _connectDocument(desktop, desktop ? desktop->doc() : nullptr);
+ }
+}
+
+void UndoHistory::_connectDocument(SPDesktop* desktop, SPDocument * /*document*/)
+{
+ // disconnect from prior
+ if (_event_log) {
+ _event_log->removeDialogConnection(&_event_list_view, &_callback_connections);
+ }
+
+ SignalBlocker blocker(&_callback_connections[EventLog::CALLB_SELECTION_CHANGE]);
+
+ _event_list_view.unset_model();
+
+ // connect to new EventLog/Desktop
+ _desktop = desktop;
+ _event_log = desktop ? desktop->event_log : nullptr;
+ _document = desktop ? desktop->doc() : nullptr;
+ _connectEventLog();
+}
+
+void UndoHistory::_connectEventLog()
+{
+ if (_event_log) {
+ _event_log->add_destroy_notify_callback(this, &_handleEventLogDestroyCB);
+ _event_list_store = _event_log->getEventListStore();
+
+ _event_list_view.set_model(_event_list_store);
+
+ _event_log->addDialogConnection(&_event_list_view, &_callback_connections);
+ _event_list_view.scroll_to_row(_event_list_store->get_path(_event_list_selection->get_selected()));
+ }
+}
+
+void UndoHistory::_handleDocumentReplaced(SPDesktop* desktop, SPDocument *document)
+{
+ if ((desktop != _desktop) || (document != _document)) {
+ _connectDocument(desktop, document);
+ }
+}
+
+void *UndoHistory::_handleEventLogDestroyCB(void *data)
+{
+ void *result = nullptr;
+ if (data) {
+ UndoHistory *self = reinterpret_cast<UndoHistory*>(data);
+ result = self->_handleEventLogDestroy();
+ }
+ return result;
+}
+
+// called *after* _event_log has been destroyed.
+void *UndoHistory::_handleEventLogDestroy()
+{
+ if (_event_log) {
+ SignalBlocker blocker(&_callback_connections[EventLog::CALLB_SELECTION_CHANGE]);
+
+ _event_list_view.unset_model();
+ _event_list_store.reset();
+ _event_log = nullptr;
+ }
+
+ return nullptr;
+}
+
+void
+UndoHistory::_onListSelectionChange()
+{
+
+ EventLog::const_iterator selected = _event_list_selection->get_selected();
+
+ /* If no event is selected in the view, find the right one and select it. This happens whenever
+ * a branch we're currently in is collapsed.
+ */
+ if (!selected) {
+
+ EventLog::iterator curr_event = _event_log->getCurrEvent();
+
+ if (curr_event->parent()) {
+
+ EventLog::iterator curr_event_parent = curr_event->parent();
+ EventLog::iterator last = curr_event_parent->children().end();
+
+ _event_log->blockNotifications();
+ for ( --last ; curr_event != last ; ++curr_event ) {
+ DocumentUndo::redo(_document);
+ }
+ _event_log->blockNotifications(false);
+
+ _event_log->setCurrEvent(curr_event);
+ _event_list_selection->select(curr_event_parent);
+
+ } else { // this should not happen
+ _event_list_selection->select(curr_event);
+ }
+
+ } else {
+
+ EventLog::const_iterator last_selected = _event_log->getCurrEvent();
+
+ /* Selecting a collapsed parent event is equal to selecting the last child
+ * of that parent's branch.
+ */
+
+ if ( !selected->children().empty() &&
+ !_event_list_view.row_expanded(_event_list_store->get_path(selected)) )
+ {
+ selected = selected->children().end();
+ --selected;
+ }
+
+ // An event before the current one has been selected. Undo to the selected event.
+ if ( _event_list_store->get_path(selected) <
+ _event_list_store->get_path(last_selected) )
+ {
+ _event_log->blockNotifications();
+
+ while ( selected != last_selected ) {
+
+ DocumentUndo::undo(_document);
+
+ if ( last_selected->parent() &&
+ last_selected == last_selected->parent()->children().begin() )
+ {
+ last_selected = last_selected->parent();
+ _event_log->setCurrEventParent((EventLog::iterator)nullptr);
+ } else {
+ --last_selected;
+ if ( !last_selected->children().empty() ) {
+ _event_log->setCurrEventParent(last_selected);
+ last_selected = last_selected->children().end();
+ --last_selected;
+ }
+ }
+ }
+ _event_log->blockNotifications(false);
+ _event_log->updateUndoVerbs();
+
+ } else { // An event after the current one has been selected. Redo to the selected event.
+
+ _event_log->blockNotifications();
+
+ while ( selected != last_selected ) {
+
+ DocumentUndo::redo(_document);
+
+ if ( !last_selected->children().empty() ) {
+ _event_log->setCurrEventParent(last_selected);
+ last_selected = last_selected->children().begin();
+ } else {
+ ++last_selected;
+ if ( last_selected->parent() &&
+ last_selected == last_selected->parent()->children().end() )
+ {
+ last_selected = last_selected->parent();
+ ++last_selected;
+ _event_log->setCurrEventParent((EventLog::iterator)nullptr);
+ }
+ }
+ }
+ _event_log->blockNotifications(false);
+
+ }
+
+ _event_log->setCurrEvent(selected);
+ _event_log->updateUndoVerbs();
+ }
+
+}
+
+void
+UndoHistory::_onExpandEvent(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &/*path*/)
+{
+ if ( iter == _event_list_selection->get_selected() ) {
+ _event_list_selection->select(_event_log->getCurrEvent());
+ }
+}
+
+void
+UndoHistory::_onCollapseEvent(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &/*path*/)
+{
+ // Collapsing a branch we're currently in is equal to stepping to the last event in that branch
+ if ( iter == _event_log->getCurrEvent() ) {
+ EventLog::const_iterator curr_event_parent = _event_log->getCurrEvent();
+ EventLog::const_iterator curr_event = curr_event_parent->children().begin();
+ EventLog::const_iterator last = curr_event_parent->children().end();
+
+ _event_log->blockNotifications();
+ DocumentUndo::redo(_document);
+
+ for ( --last ; curr_event != last ; ++curr_event ) {
+ DocumentUndo::redo(_document);
+ }
+ _event_log->blockNotifications(false);
+
+ _event_log->setCurrEvent(curr_event);
+ _event_log->setCurrEventParent(curr_event_parent);
+ _event_list_selection->select(curr_event_parent);
+ }
+}
+
+const CellRendererInt::Filter& UndoHistory::greater_than_1 = UndoHistory::GreaterThan(1);
+
+} // namespace Dialog
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :