diff options
Diffstat (limited to 'src/ui/dialog/undo-history.cpp')
-rw-r--r-- | src/ui/dialog/undo-history.cpp | 406 |
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 : |