Code

added files from Gustav Broberg's patch
authorbuliabyak <buliabyak@users.sourceforge.net>
Thu, 29 Jun 2006 21:27:45 +0000 (21:27 +0000)
committerbuliabyak <buliabyak@users.sourceforge.net>
Thu, 29 Jun 2006 21:27:45 +0000 (21:27 +0000)
src/event-log.cpp [new file with mode: 0644]
src/event-log.h [new file with mode: 0644]
src/event.h [new file with mode: 0644]
src/ui/dialog/undo-history.cpp [new file with mode: 0644]
src/ui/dialog/undo-history.h [new file with mode: 0644]

diff --git a/src/event-log.cpp b/src/event-log.cpp
new file mode 100644 (file)
index 0000000..40c8364
--- /dev/null
@@ -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
new file mode 100644 (file)
index 0000000..148d632
--- /dev/null
@@ -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
new file mode 100644 (file)
index 0000000..1e3e233
--- /dev/null
@@ -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
new file mode 100644 (file)
index 0000000..13f6bff
--- /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
new file mode 100644 (file)
index 0000000..19c5740
--- /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 :