summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: b21e43b)
raw | patch | inline | side by side (parent: b21e43b)
author | buliabyak <buliabyak@users.sourceforge.net> | |
Thu, 29 Jun 2006 21:27:45 +0000 (21:27 +0000) | ||
committer | buliabyak <buliabyak@users.sourceforge.net> | |
Thu, 29 Jun 2006 21:27:45 +0000 (21:27 +0000) |
src/event-log.cpp | [new file with mode: 0644] | patch | blob |
src/event-log.h | [new file with mode: 0644] | patch | blob |
src/event.h | [new file with mode: 0644] | patch | blob |
src/ui/dialog/undo-history.cpp | [new file with mode: 0644] | patch | blob |
src/ui/dialog/undo-history.h | [new file with mode: 0644] | patch | blob |
diff --git a/src/event-log.cpp b/src/event-log.cpp
--- /dev/null
+++ b/src/event-log.cpp
@@ -0,0 +1,255 @@
+/*
+ * Author:
+ * Gustav Broberg <broberg@kth.se>
+ *
+ * Copyright (c) 2006 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <glibmm/i18n.h>
+
+#include "desktop.h"
+
+#include "event-log.h"
+
+namespace Inkscape {
+
+EventLog::EventLog() :
+ UndoStackObserver(),
+ _connected (false),
+ _event_list_store (Gtk::TreeStore::create(_columns)),
+ _event_list_selection (NULL),
+ _event_list_view (NULL),
+ _curr_event_parent (NULL),
+ _notifications_blocked (false),
+ _callback_connections (NULL)
+{
+ // add initial pseudo event
+ Gtk::TreeRow curr_row = *(_event_list_store->append());
+ _curr_event = _last_event = curr_row;
+
+ curr_row[_columns.description] = _("[Unchanged]");
+ curr_row[_columns.type] = SP_VERB_FILE_NEW;
+}
+
+EventLog::~EventLog() { }
+
+void
+EventLog::notifyUndoEvent(Event* log)
+{
+ if ( !_notifications_blocked ) {
+
+ // if we're on the first child event...
+ if ( _curr_event->parent() &&
+ _curr_event == _curr_event->parent()->children().begin() )
+ {
+ // ...back up to the parent
+ _curr_event = _curr_event->parent();
+ _curr_event_parent = (iterator)NULL;
+
+ } else {
+
+ // if we're about to leave a branch, collapse it
+ if ( !_curr_event->children().empty() && _connected ) {
+ (*_callback_connections)[CALLB_COLLAPSE].block();
+ _event_list_view->collapse_row(_event_list_store->get_path(_curr_event));
+ (*_callback_connections)[CALLB_COLLAPSE].block(false);
+ }
+
+ --_curr_event;
+
+ // if we're entering a branch, move to the end of it
+ if (!_curr_event->children().empty()) {
+ _curr_event_parent = _curr_event;
+ _curr_event = _curr_event->children().end();
+ --_curr_event;
+ }
+ }
+
+ // update the view
+ if (_connected) {
+ (*_callback_connections)[CALLB_SELECTION_CHANGE].block();
+ (*_callback_connections)[CALLB_EXPAND].block();
+
+ Gtk::TreePath curr_path = _event_list_store->get_path(_curr_event);
+ _event_list_view->expand_to_path(curr_path);
+ _event_list_selection->select(curr_path);
+ _event_list_view->scroll_to_row(curr_path);
+
+ (*_callback_connections)[CALLB_EXPAND].block(false);
+ (*_callback_connections)[CALLB_SELECTION_CHANGE].block(false);
+ }
+
+ }
+}
+
+void
+EventLog::notifyRedoEvent(Event* log)
+{
+ if ( !_notifications_blocked ) {
+
+ // if we're on a parent event...
+ if ( !_curr_event->children().empty() ) {
+
+ // ...move to its first child
+ _curr_event_parent = _curr_event;
+ _curr_event = _curr_event->children().begin();
+
+ } else {
+
+ ++_curr_event;
+
+ // if we are about to leave a branch...
+ if ( _curr_event->parent() &&
+ _curr_event == _curr_event->parent()->children().end() )
+ {
+
+ // ...collapse it
+ if (_connected) {
+ (*_callback_connections)[CALLB_SELECTION_CHANGE].block();
+ (*_callback_connections)[CALLB_COLLAPSE].block();
+ _event_list_view->collapse_row(_event_list_store->get_path(_curr_event->parent()));
+ (*_callback_connections)[CALLB_COLLAPSE].block(false);
+ (*_callback_connections)[CALLB_SELECTION_CHANGE].block(false);
+ }
+
+ // ...and move to the next event at parent level
+ _curr_event = _curr_event->parent();
+ _curr_event_parent = (iterator)NULL;
+
+ ++_curr_event;
+ }
+ }
+
+ // update the view
+ if (_connected) {
+ Gtk::TreePath curr_path = _event_list_store->get_path(_curr_event);
+
+ (*_callback_connections)[CALLB_SELECTION_CHANGE].block();
+ (*_callback_connections)[CALLB_EXPAND].block();
+
+ _event_list_view->expand_to_path(curr_path);
+ _event_list_selection->select(curr_path);
+ _event_list_view->scroll_to_row(curr_path);
+
+ (*_callback_connections)[CALLB_EXPAND].block(false);
+ (*_callback_connections)[CALLB_SELECTION_CHANGE].block(false);
+ }
+
+ }
+}
+
+void
+EventLog::notifyUndoCommitEvent(Event* log)
+{
+ // If we're not at the last event in list then erase the previously undone events
+ if ( _last_event != _curr_event ) {
+
+ _last_event = _curr_event;
+
+ if ( !_last_event->children().empty() ) {
+ _last_event = _last_event->children().begin();
+ } else {
+ ++_last_event;
+ }
+
+ while ( _last_event != _event_list_store->children().end() ) {
+
+ if (_last_event->parent()) {
+ while ( _last_event != _last_event->parent()->children().end() ) {
+ _last_event = _event_list_store->erase(_last_event);
+ }
+ _last_event = _last_event->parent();
+
+ (*_last_event)[_columns.child_count] = _last_event->children().size() + 1;
+
+ ++_last_event;
+ } else {
+ _last_event = _event_list_store->erase(_last_event);
+ }
+
+ }
+ }
+
+ const unsigned int event_type = log->type;
+
+ Gtk::TreeRow curr_row;
+
+ // if the new event is of the same type as the previous then create a new branch
+ if ( event_type == (*_curr_event)[_columns.type] ) {
+ if ( !_curr_event_parent ) {
+ _curr_event_parent = _curr_event;
+ }
+ curr_row = *(_event_list_store->append(_curr_event_parent->children()));
+ (*_curr_event_parent)[_columns.child_count] = _curr_event_parent->children().size() + 1;
+ } else {
+ curr_row = *(_event_list_store->append());
+ curr_row[_columns.child_count] = 1;
+
+ _curr_event = _last_event = curr_row;
+
+ // collapse if we're leaving a branch
+ if (_curr_event_parent && _connected) {
+ (*_callback_connections)[CALLB_COLLAPSE].block();
+ _event_list_view->collapse_row(_event_list_store->get_path(_curr_event_parent));
+ (*_callback_connections)[CALLB_COLLAPSE].block(false);
+ }
+
+ _curr_event_parent = (iterator)(NULL);
+ }
+
+ _curr_event = _last_event = curr_row;
+
+ curr_row[_columns.type] = event_type;
+ curr_row[_columns.description] = log->description;
+
+ // update the view
+ if (_connected) {
+ Gtk::TreePath curr_path = _event_list_store->get_path(_curr_event);
+
+ (*_callback_connections)[CALLB_SELECTION_CHANGE].block();
+ (*_callback_connections)[CALLB_EXPAND].block();
+
+ _event_list_view->expand_to_path(curr_path);
+ _event_list_selection->select(curr_path);
+ _event_list_view->scroll_to_row(curr_path);
+
+ (*_callback_connections)[CALLB_EXPAND].block(false);
+ (*_callback_connections)[CALLB_SELECTION_CHANGE].block(false);
+ }
+
+}
+
+void
+EventLog::connectWithDialog(Gtk::TreeView *event_list_view, CallbackMap *callback_connections)
+{
+ _event_list_view = event_list_view;
+ _event_list_selection = event_list_view->get_selection();
+ _event_list_selection->set_mode(Gtk::SELECTION_SINGLE);
+
+ _callback_connections = callback_connections;
+
+ (*_callback_connections)[CALLB_SELECTION_CHANGE].block();
+ (*_callback_connections)[CALLB_EXPAND].block();
+
+ _event_list_view->expand_to_path(_event_list_store->get_path(_curr_event));
+ _event_list_selection->select(_curr_event);
+
+ (*_callback_connections)[CALLB_EXPAND].block(false);
+ (*_callback_connections)[CALLB_SELECTION_CHANGE].block(false);
+
+ _connected = true;
+}
+
+}
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/event-log.h b/src/event-log.h
--- /dev/null
+++ b/src/event-log.h
@@ -0,0 +1,147 @@
+/**
+ * Inkscape::EventLog
+ *
+ * A simple log for maintaining a history of commited, undone and redone events along with their
+ * type. It implements the UndoStackObserver and should be registered with a
+ * CompositeUndoStackObserver for each document. The event log is then notified on all commit, undo
+ * and redo events and will store a representation of them in an internal Gtk::TreeStore.
+ *
+ * Consecutive events of the same type are grouped with the first event as a parent and following
+ * as its children.
+ *
+ * If a Gtk::TreeView is connected to the event log, the TreeView's selection and its nodes
+ * expanded/collapsed state will be updated as events are commited, undone and redone. Whenever
+ * this happens, the event log will block the TreeView's callbacks to prevent circular updates.
+ *
+ * Author:
+ * Gustav Broberg <broberg@kth.se>
+ *
+ * Copyright (c) 2006 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef INKSCAPE_EVENT_LOG_H
+#define INKSCAPE_EVENT_LOG_H
+
+#include <gdkmm/pixbuf.h>
+#include <glibmm/refptr.h>
+#include <gtkmm/treestore.h>
+#include <gtkmm/treeselection.h>
+#include <gtkmm/treeview.h>
+
+#include "undo-stack-observer.h"
+#include "event.h"
+
+namespace Inkscape {
+
+/**
+ *
+ */
+class EventLog : public UndoStackObserver {
+
+public:
+ typedef Gtk::TreeModel::iterator iterator;
+ typedef Gtk::TreeModel::const_iterator const_iterator;
+
+ EventLog();
+ ~EventLog();
+
+ /**
+ * Event datatype
+ */
+
+ struct EventModelColumns : public Gtk::TreeModelColumnRecord
+ {
+ Gtk::TreeModelColumn<unsigned int> type;
+ Gtk::TreeModelColumn<Glib::ustring> description;
+ Gtk::TreeModelColumn<int> child_count;
+
+ EventModelColumns()
+ {
+ add(type); add(description); add(child_count);
+ }
+ };
+
+ /**
+ * Implementation of Inkscape::UndoStackObserver methods
+ * \brief Modifies the log's entries and the view's selection when triggered
+ */
+
+ void notifyUndoEvent(Event *log);
+ void notifyRedoEvent(Event *log);
+ void notifyUndoCommitEvent(Event *log);
+
+ /**
+ * Accessor functions
+ */
+
+ Glib::RefPtr<Gtk::TreeModel> getEventListStore() const { return _event_list_store; }
+ const EventModelColumns& getColumns() const { return _columns; }
+ iterator getCurrEvent() const { return _curr_event; }
+ iterator getCurrEventParent() const { return _curr_event_parent; }
+
+ void setCurrEvent(iterator event) { _curr_event = event; }
+ void setCurrEventParent(iterator event) { _curr_event_parent = event; }
+ void blockNotifications(bool status=true) { _notifications_blocked = status; }
+
+ /*
+ * Callback types for TreeView changes.
+ */
+
+ enum CallbackTypes {
+ CALLB_SELECTION_CHANGE,
+ CALLB_EXPAND,
+ CALLB_COLLAPSE,
+ CALLB_LAST
+ };
+
+ typedef std::map<const CallbackTypes, sigc::connection> CallbackMap;
+
+ /**
+ * Connect with a TreeView.
+ */
+ void connectWithDialog(Gtk::TreeView *event_list_view, CallbackMap *callback_connections);
+
+private:
+ bool _connected; //< connected with dialog
+
+ const EventModelColumns _columns;
+
+ /**
+ * Helper functions for initialization
+ */
+
+ Glib::RefPtr<Gtk::TreeStore> _event_list_store;
+ Glib::RefPtr<Gtk::TreeSelection> _event_list_selection;
+ Gtk::TreeView *_event_list_view;
+
+ iterator _curr_event; //< current event in _event_list_store
+ iterator _last_event; //< end position in _event_list_store
+ iterator _curr_event_parent; //< parent to current event, if any
+
+ bool _notifications_blocked; //< if notifications should be handled
+
+ // Map of connections used to temporary block/unblock callbacks in a TreeView
+ CallbackMap *_callback_connections;
+
+ // noncopyable, nonassignable
+ EventLog(EventLog const &other);
+ EventLog& operator=(EventLog const &other);
+
+};
+
+} // namespace Inkscape
+
+#endif // INKSCAPE_EVENT_LOG_H
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/event.h b/src/event.h
--- /dev/null
+++ b/src/event.h
@@ -0,0 +1,55 @@
+#ifndef INKSCAPE_EVENT_H
+#define INKSCAPE_EVENT_H
+
+/*
+ * Inkscape::Event -- Container for an XML::Event along with some additional information
+* describing it.
+ *
+ * Author:
+ * Gustav Broberg <broberg@kth.se>
+ *
+ * Copyright (c) 2006 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+
+#include <glibmm/ustring.h>
+
+#include "xml/event-fns.h"
+#include "verbs.h"
+
+namespace Inkscape {
+namespace XML {
+class Event;
+}
+}
+
+namespace Inkscape {
+
+struct Event {
+
+ Event(XML::Event *_event, unsigned int _type=SP_VERB_NONE, Glib::ustring _description="")
+ : event (_event), type (_type), description (_description) { }
+
+ ~Event() { sp_repr_free_log (event); }
+
+ XML::Event *event;
+ const unsigned int type;
+ Glib::ustring description;
+};
+
+} // namespace Inkscape
+
+#endif // INKSCAPE_EVENT_H
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/dialog/undo-history.cpp b/src/ui/dialog/undo-history.cpp
--- /dev/null
@@ -0,0 +1,322 @@
+/**
+ * \brief Undo History dialog
+ *
+ * Author:
+ * Gustav Broberg <broberg@kth.se>
+ *
+ * Copyright (C) 2006 Authors
+ *
+ * Released under GNU GPL. Read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <glibmm/i18n.h>
+#include <gtk/gtkimage.h>
+#include <sigc++/sigc++.h>
+
+
+#include "document.h"
+#include "inkscape.h"
+#include "ui/icons.h"
+#include "verbs.h"
+
+#include "undo-history.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Dialog {
+
+/* Rendering functions for custom cell renderers */
+
+void
+CellRendererSPIcon::render_vfunc(const Glib::RefPtr<Gdk::Drawable>& window,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ const Gdk::Rectangle& expose_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 = Inkscape::Verb::get(_property_event_type)->get_image();
+ Gtk::Widget* icon = sp_icon_get_icon(image, Inkscape::ICON_SIZE_MENU);
+
+ if (icon) {
+
+ // check icon type (inkscape, gtk, none)
+ if ( SP_IS_ICON(icon->gobj()) ) {
+ SPIcon* sp_icon = SP_ICON(icon->gobj());
+ sp_icon_fetch_pixbuf(sp_icon);
+ _property_icon = Glib::wrap(sp_icon->pb, true);
+ } else if ( GTK_IS_IMAGE(icon->gobj()) ) {
+ _property_icon = Gtk::Invisible().render_icon(Gtk::StockID(image),
+ Gtk::ICON_SIZE_MENU);
+ } 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(window, widget, background_area,
+ cell_area, expose_area, flags);
+}
+
+
+void
+CellRendererInt::render_vfunc(const Glib::RefPtr<Gdk::Drawable>& window,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ const Gdk::Rectangle& expose_area,
+ Gtk::CellRendererState flags)
+{
+ if( _filter(_property_number) ) {
+ std::ostringstream s;
+ s << _property_number << std::flush;
+ property_text() = s.str();
+ Gtk::CellRendererText::render_vfunc(window, widget, background_area,
+ cell_area, expose_area, flags);
+ }
+}
+
+const CellRendererInt::Filter& CellRendererInt::no_filter = CellRendererInt::NoFilter();
+
+
+UndoHistory::UndoHistory()
+ : Dialog ("dialogs.undo-history", SP_VERB_DIALOG_UNDO_HISTORY),
+ _document (SP_ACTIVE_DOCUMENT),
+ _event_log (_document ? &_document->getEventLog() : NULL),
+ _columns (_event_log ? &_event_log->getColumns() : NULL),
+ _event_list_selection (_event_list_view.get_selection())
+{
+ if( !_document || !_event_log || !_columns ) return;
+
+ set_size_request(300, 400);
+
+ get_vbox()->pack_start(_scrolled_window);
+ _scrolled_window.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
+
+ _event_list_store = _event_log->getEventListStore();
+
+ _event_list_view.set_model(_event_list_store);
+ _event_list_view.set_rules_hint(false);
+ _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() = 8;
+ icon_renderer->property_width() = 36;
+ 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);
+
+ Gtk::CellRendererText* description_renderer = Gtk::manage(new Gtk::CellRendererText());
+
+ 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();
+
+ _event_list_view.set_expander_column( *_event_list_view.get_column(cols_count-1) );
+
+ 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() = 20;
+
+ 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);
+
+ _scrolled_window.add(_event_list_view);
+
+ // connect 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));
+
+ // connect with the event log
+ _event_log->connectWithDialog(&_event_list_view, &_callback_connections);
+
+ _event_list_view.scroll_to_row(_event_list_store->get_path(_event_list_selection->get_selected()));
+
+ show_all_children();
+
+}
+
+UndoHistory::~UndoHistory()
+{
+}
+
+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 ) {
+ sp_document_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 ) {
+
+ sp_document_undo(_document);
+
+ if ( last_selected->parent() &&
+ last_selected == last_selected->parent()->children().begin() )
+ {
+ last_selected = last_selected->parent();
+ _event_log->setCurrEventParent((EventLog::iterator)NULL);
+ } 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);
+
+ } else { // An event after the current one has been selected. Redo to the selected event.
+
+ _event_log->blockNotifications();
+
+ while ( selected != last_selected ) {
+
+ sp_document_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)NULL);
+ }
+ }
+ }
+ _event_log->blockNotifications(false);
+
+ }
+
+ _event_log->setCurrEvent(selected);
+ }
+
+}
+
+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();
+ sp_document_redo(_document);
+
+ for ( --last ; curr_event != last ; ++curr_event ) {
+ sp_document_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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/dialog/undo-history.h b/src/ui/dialog/undo-history.h
--- /dev/null
@@ -0,0 +1,175 @@
+/**
+ * Undo History dialog
+ *
+ * \brief A dialog for presenting an event log of commited, undone and redone events. Allows the
+ * user to undo and redo multiple events in a more convinient way than repateaded ctrl-z,
+ * ctrl-shift-z.
+ *
+ *
+ * Author:
+ * Gustav Broberg <broberg@kth.se>
+ *
+ * Copyright (C) 2006 Authors
+ *
+ * Released under GNU GPL. Read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_DIALOG_UNDO_HISTORY_H
+#define INKSCAPE_UI_DIALOG_UNDO_HISTORY_H
+
+#include <glibmm/refptr.h>
+#include <gtkmm/cellrendererpixbuf.h>
+#include <gtkmm/image.h>
+#include <gtkmm/invisible.h>
+#include <gtkmm/scrolledwindow.h>
+#include <gtkmm/stock.h>
+#include <gtkmm/treemodel.h>
+#include <gtkmm/treeselection.h>
+
+#include <functional>
+#include <sstream>
+
+#include "dialog.h"
+#include "event-log.h"
+
+#include "widgets/icon.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Dialog {
+
+
+/* Custom cell renderers */
+
+class CellRendererSPIcon : public Gtk::CellRendererPixbuf {
+public:
+
+ CellRendererSPIcon() :
+ Glib::ObjectBase(typeid(CellRendererPixbuf)),
+ Gtk::CellRendererPixbuf(),
+ _property_icon(*this, "icon", Glib::RefPtr<Gdk::Pixbuf>(0)),
+ _property_event_type(*this, "event_type", 0)
+ { }
+
+ Glib::PropertyProxy<unsigned int>
+ property_event_type() { return _property_event_type.get_proxy(); }
+
+protected:
+
+ virtual void
+ render_vfunc(const Glib::RefPtr<Gdk::Drawable>& window,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ const Gdk::Rectangle& expose_area,
+ Gtk::CellRendererState flags);
+private:
+
+ Glib::Property<Glib::RefPtr<Gdk::Pixbuf> > _property_icon;
+ Glib::Property<unsigned int> _property_event_type;
+ std::map<const unsigned int, Glib::RefPtr<Gdk::Pixbuf> > _icon_cache;
+};
+
+
+class CellRendererInt : public Gtk::CellRendererText {
+public:
+
+ struct Filter : std::unary_function<int, bool> {
+ virtual ~Filter() {}
+ virtual bool operator() (const int&) const =0;
+ };
+
+ CellRendererInt(const Filter& filter=no_filter) :
+ Glib::ObjectBase(typeid(CellRendererText)),
+ Gtk::CellRendererText(),
+ _property_number(*this, "number", 0),
+ _filter (filter)
+ { }
+
+
+ Glib::PropertyProxy<int>
+ property_number() { return _property_number.get_proxy(); }
+
+ static const Filter& no_filter;
+
+ protected:
+
+ virtual void
+ render_vfunc(const Glib::RefPtr<Gdk::Drawable>& window,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ const Gdk::Rectangle& expose_area,
+ Gtk::CellRendererState flags);
+
+private:
+
+ Glib::Property<int> _property_number;
+ const Filter& _filter;
+
+ struct NoFilter : Filter { bool operator() (const int& x) const { return true; } };
+};
+
+
+/**
+ *
+ */
+
+class UndoHistory : public Dialog {
+public:
+ virtual ~UndoHistory();
+
+ static UndoHistory *create() { return new UndoHistory(); }
+
+protected:
+
+ SPDocument *_document;
+ EventLog *_event_log;
+
+ const EventLog::EventModelColumns *_columns;
+
+ Gtk::ScrolledWindow _scrolled_window;
+
+ Glib::RefPtr<Gtk::TreeModel> _event_list_store;
+ Gtk::TreeView _event_list_view;
+ Glib::RefPtr<Gtk::TreeSelection> _event_list_selection;
+
+ EventLog::CallbackMap _callback_connections;
+
+ void _onListSelectionChange();
+ void _onExpandEvent(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path);
+ void _onCollapseEvent(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path);
+
+private:
+
+ // no default constructor, noncopyable, nonassignable
+ UndoHistory();
+ UndoHistory(UndoHistory const &d);
+ UndoHistory operator=(UndoHistory const &d);
+
+ struct GreaterThan : CellRendererInt::Filter {
+ GreaterThan(int _i) : i (_i) {}
+ bool operator() (const int& x) const { return x > i; }
+ int i;
+ };
+
+ static const CellRendererInt::Filter& greater_than_1;
+};
+
+
+} // namespace Dialog
+} // namespace UI
+} // namespace Inkscape
+
+#endif //INKSCAPE_UI_DIALOG_UNDO_HISTORY_H
+
+/*
+ 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:encoding=utf-8:textwidth=99 :