[pdfmod] Many bookmarks editing improvements



commit 6db447c851a934f7fdd449443110318e8bd0d3e8
Author: Gabriel Burt <gabriel burt gmail com>
Date:   Wed Sep 8 16:49:51 2010 -0500

    Many bookmarks editing improvements
    
    Add undo/redu support, add Bookmarks main menu and context menu.

 src/Makefile.am                          |    1 +
 src/PdfMod/Gui/Actions.cs                |  357 +++++++++++++++++-------------
 src/PdfMod/Gui/BookmarkView.cs           |  358 ++++++++++++++++++++++--------
 src/PdfMod/Gui/Client.cs                 |    2 +-
 src/PdfMod/Pdf/Actions/BaseAction.cs     |   83 +++++++
 src/PdfMod/Pdf/Actions/BasePageAction.cs |   50 ++---
 src/PdfMod/Pdf/Document.cs               |   12 +-
 src/Resources/UIManager.xml              |   16 ++
 8 files changed, 589 insertions(+), 290 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index dbb07e2..6574599 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -41,6 +41,7 @@ FILES =  \
 	PdfMod/Gui/PageListStore.cs \
 	PdfMod/Gui/SelectMatchingBox.cs \
 	PdfMod/Main.cs \
+	PdfMod/Pdf/Actions/BaseAction.cs \
 	PdfMod/Pdf/Actions/BasePageAction.cs \
 	PdfMod/Pdf/Actions/ExportImagesAction.cs \
 	PdfMod/Pdf/Actions/MoveAction.cs \
diff --git a/src/PdfMod/Gui/Actions.cs b/src/PdfMod/Gui/Actions.cs
index a38f14d..8b70ad0 100644
--- a/src/PdfMod/Gui/Actions.cs
+++ b/src/PdfMod/Gui/Actions.cs
@@ -45,7 +45,8 @@ namespace PdfMod.Gui
 
         static string [] require_doc_actions = new string[] {
             "Save", "SaveAs", "Properties", "Undo", "Redo", "ZoomFit", "OpenInViewer",
-            "SelectAll", "SelectEvens", "SelectOdds", "SelectMatching", "SelectInverse", "InsertFrom", "ExportImages"
+            "SelectAll", "SelectEvens", "SelectOdds", "SelectMatching", "SelectInverse", "InsertFrom", "ExportImages",
+            "AddBookmark", "EditBookmarks"
         };
 
         static string [] require_page_actions = new string[] {
@@ -60,53 +61,64 @@ namespace PdfMod.Gui
             undo_manager = new UndoManager ();
 
             AddImportant (
+                new ActionEntry ("FileMenu", null, Catalog.GetString ("_File"), null, null, null),
                 new ActionEntry ("Open",   Gtk.Stock.Open,   null, "<control>O", Catalog.GetString ("Open a document"), OnOpen),
-                new ActionEntry ("InsertFrom", Gtk.Stock.Add, Catalog.GetString("_Insert From..."), null, Catalog.GetString("Insert pages from another document"), OnInsertFrom),
                 new ActionEntry ("Save",   Gtk.Stock.Save,   null, "<control>S", Catalog.GetString ("Save changes to this document, overwriting the existing file"), OnSave),
                 new ActionEntry ("SaveAs", Gtk.Stock.SaveAs, null, "<control><shift>S", Catalog.GetString ("Save this document to a new file"), OnSaveAs),
-
-                new ActionEntry ("FileMenu", null, Catalog.GetString ("_File"), null, null, null),
                 new ActionEntry ("RecentMenu", null, Catalog.GetString ("Recent _Files"), null, null, null),
-                new ActionEntry ("Close", Gtk.Stock.Close, null, "<control>W", null, OnClose),
-                new ActionEntry ("Remove", Gtk.Stock.Remove, null, "Delete", null, OnRemove),
-                new ActionEntry ("Extract", Gtk.Stock.New, null, null, null, OnExtractPages),
-                new ActionEntry ("RotateRight", null, Catalog.GetString ("Rotate Right"), "bracketright", Catalog.GetString ("Rotate right"), OnRotateRight),
-                new ActionEntry ("RotateLeft", null, Catalog.GetString ("Rotate Left"), "bracketleft", Catalog.GetString ("Rotate left"), OnRotateLeft),
                 new ActionEntry ("ExportImages", null, Catalog.GetString ("Export Images"), null, Catalog.GetString ("Save all images in this document to a new folder"), OnExportImages),
+                new ActionEntry ("InsertFrom", Gtk.Stock.Add, Catalog.GetString("_Insert From..."), null, Catalog.GetString("Insert pages from another document"), OnInsertFrom),
+                new ActionEntry ("Close", Gtk.Stock.Close, null, "<control>W", null, OnClose),
 
                 new ActionEntry ("EditMenu", null, Catalog.GetString ("_Edit"), null, null, null),
+                new ActionEntry ("Undo", Stock.Undo, null, "<control>z", null, OnUndo),
+                new ActionEntry ("Redo", Stock.Redo, null, "<control>y", null, OnRedo),
+                new ActionEntry ("Extract", Gtk.Stock.New, null, null, null, OnExtractPages),
+                new ActionEntry ("Remove", Gtk.Stock.Remove, null, "Delete", null, OnRemove),
+                new ActionEntry ("RotateLeft", null, Catalog.GetString ("Rotate Left"), "bracketleft", Catalog.GetString ("Rotate left"), OnRotateLeft),
+                new ActionEntry ("RotateRight", null, Catalog.GetString ("Rotate Right"), "bracketright", Catalog.GetString ("Rotate right"), OnRotateRight),
                 new ActionEntry ("SelectAll", Stock.SelectAll, null, "<control>A", null, OnSelectAll),
-                new ActionEntry ("SelectEvens", null, Catalog.GetString ("Select Even Pages"), null, null, OnSelectEvens),
                 new ActionEntry ("SelectOdds", null, Catalog.GetString ("Select Odd Pages"), null, null, OnSelectOdds),
+                new ActionEntry ("SelectEvens", null, Catalog.GetString ("Select Even Pages"), null, null, OnSelectEvens),
                 new ActionEntry ("SelectMatching", null, Catalog.GetString ("Select Matching..."), "<control>F", null, OnSelectMatching),
                 new ActionEntry ("SelectInverse", null, Catalog.GetString ("_Invert Selection"), "<shift><control>I", null, OnSelectInverse),
-                new ActionEntry ("Undo", Stock.Undo, null, "<control>z", null, OnUndo),
-                new ActionEntry ("Redo", Stock.Redo, null, "<control>y", null, OnRedo),
 
                 new ActionEntry ("ViewMenu", null, Catalog.GetString ("_View"), null, null, null),
                 new ActionEntry ("ZoomIn", Stock.ZoomIn, null, "<control>plus", null, OnZoomIn),
                 new ActionEntry ("ZoomOut", Stock.ZoomOut, null, "<control>minus", null, OnZoomOut),
+                new ActionEntry ("OpenInViewer", null, Catalog.GetString ("Open in Viewer"), "F5", Catalog.GetString ("Open in viewer"), OnOpenInViewer),
+
+                new ActionEntry ("BookmarksMenu", null, Catalog.GetString ("_Bookmarks"), null, null, null),
+                new ActionEntry ("AddBookmark", null, Catalog.GetString ("_Add Bookmark"), "<control>d", null, null),
+                new ActionEntry ("RenameBookmark", null, Catalog.GetString ("Re_name Bookmark"), "F2", null, null),
+                new ActionEntry ("ChangeBookmarkDest", null, Catalog.GetString ("_Change Bookmark Destination"), null, null, null),
+                new ActionEntry ("RemoveBookmarks", Stock.Remove, Catalog.GetString ("_Remove Bookmark"), null, null, null),
+                new ActionEntry ("EditBookmarks", null, Catalog.GetString ("_Edit Bookmarks"), "<control>B", null, OnEditBookmarks),
 
                 new ActionEntry ("HelpMenu", null, Catalog.GetString ("_Help"), null, null, null),
                 new ActionEntry ("Help", Stock.Help, Catalog.GetString ("_Contents"), "F1", null, OnHelp),
                 new ActionEntry ("About", Stock.About, null, null, null, OnAbout),
 
                 new ActionEntry ("PageContextMenu", null, "", null, null, OnPageContextMenu),
-
-                new ActionEntry ("OpenInViewer", null, Catalog.GetString ("Open in Viewer"), "F5", Catalog.GetString ("Open in viewer"), OnOpenInViewer)
+                new ActionEntry ("BookmarkContextMenu", null, "", null, null, OnBookmarkContextMenu)
             );
 
+            this["AddBookmark"].ShortLabel = Catalog.GetString ("_Add");
+            this["RemoveBookmarks"].ShortLabel = Catalog.GetString ("_Remove");
+
             AddImportant (
                 new ToggleActionEntry ("Properties", Stock.Properties, null, "<alt>Return", Catalog.GetString ("View and edit the title, keywords, and more for this document"), OnProperties, false),
                 new ToggleActionEntry ("ZoomFit", Stock.ZoomFit, null, "<control>0", null, OnZoomFit, true),
                 new ToggleActionEntry ("ViewToolbar", null, Catalog.GetString ("Toolbar"), null, null, OnViewToolbar, Client.Configuration.ShowToolbar),
-                new ToggleActionEntry ("ViewBookmarks", null, Catalog.GetString ("Bookmarks"), "F9", null, OnViewBookmarks, Client.Configuration.ShowBookmarks),
+                new ToggleActionEntry ("ViewBookmarks", null, Catalog.GetString ("Bookmarks"), "<ctrl>B", null, OnViewBookmarks, Client.Configuration.ShowBookmarks),
                 new ToggleActionEntry ("FullScreenView", null, Catalog.GetString ("Fullscreen"), "F11", null, OnFullScreenView, false)
             );
 
             this["RotateRight"].IconName = "object-rotate-right";
             this["RotateLeft"].IconName = "object-rotate-left";
             this["ExportImages"].IconName = "image-x-generic";
+            this["ViewBookmarks"].IconName = "user-bookmarks";
+            this["AddBookmark"].IconName = "bookmark-new";
 
             UpdateAction ("Help", true);
 
@@ -123,19 +135,10 @@ namespace PdfMod.Gui
             Register ();
 
             // Add additional menu item keybindings
-            var item = ActionManager.UIManager.GetWidget ("/MainMenu/ViewMenu/ZoomIn");
-            item.AddAccelerator ("activate", ActionManager.UIManager.AccelGroup, (uint) Gdk.Key.KP_Add, Gdk.ModifierType.ControlMask, Gtk.AccelFlags.Visible);
-            item.AddAccelerator ("activate", ActionManager.UIManager.AccelGroup, (uint) Gdk.Key.equal, Gdk.ModifierType.ControlMask, Gtk.AccelFlags.Visible);
-
-            item = ActionManager.UIManager.GetWidget ("/MainMenu/ViewMenu/ZoomOut");
-            item.AddAccelerator ("activate", ActionManager.UIManager.AccelGroup, (uint) Gdk.Key.KP_Subtract, Gdk.ModifierType.ControlMask, Gtk.AccelFlags.Visible);
-            item.AddAccelerator ("activate", ActionManager.UIManager.AccelGroup, (uint) Gdk.Key.underscore, Gdk.ModifierType.ControlMask, Gtk.AccelFlags.Visible);
-
-            item = ActionManager.UIManager.GetWidget ("/MainMenu/FileMenu/Close");
-            item.AddAccelerator ("activate", ActionManager.UIManager.AccelGroup, (uint) Gdk.Key.q, Gdk.ModifierType.ControlMask, Gtk.AccelFlags.Visible);
-
-            item = ActionManager.UIManager.GetWidget ("/MainMenu/EditMenu/Redo");
-            item.AddAccelerator ("activate", ActionManager.UIManager.AccelGroup, (uint) Gdk.Key.z, Gdk.ModifierType.ControlMask | Gdk.ModifierType.ShiftMask, Gtk.AccelFlags.Visible);
+            AddAccel ("/MainMenu/ViewMenu/ZoomIn",  Gdk.ModifierType.ControlMask, Gdk.Key.KP_Add, Gdk.Key.equal);
+            AddAccel ("/MainMenu/ViewMenu/ZoomOut", Gdk.ModifierType.ControlMask, Gdk.Key.KP_Subtract, Gdk.Key.underscore);
+            AddAccel ("/MainMenu/FileMenu/Close",   Gdk.ModifierType.ControlMask, Gdk.Key.q);
+            AddAccel ("/MainMenu/EditMenu/Redo",    Gdk.ModifierType.ControlMask | Gdk.ModifierType.ShiftMask, Gdk.Key.z);
 
             // Set up recent documents menu
             MenuItem recent_item = ActionManager.UIManager.GetWidget ("/MainMenu/FileMenu/RecentMenu") as MenuItem;
@@ -150,6 +153,14 @@ namespace PdfMod.Gui
             recent_item.Submenu = recent_chooser_item;
         }
 
+        private void AddAccel (string path, Gdk.ModifierType modifier, params Gdk.Key [] keys)
+        {
+            var item = ActionManager.UIManager.GetWidget (path);
+            foreach (var key in keys) {
+                item.AddAccelerator ("activate", ActionManager.UIManager.AccelGroup, (uint)key, modifier, Gtk.AccelFlags.Visible);
+            }
+        }
+
         void OnChanged (object o, EventArgs args)
         {
             Update ();
@@ -199,6 +210,8 @@ namespace PdfMod.Gui
 
 #region Action Handlers
 
+        // File menu actions
+
         void OnOpen (object o, EventArgs args)
         {
             var chooser = app.CreateChooser (Catalog.GetString ("Select PDF"), FileChooserAction.Open);
@@ -224,22 +237,62 @@ namespace PdfMod.Gui
             }
         }
 
-        void OnOpenInViewer (object o, EventArgs args)
+        void OnSave (object o, EventArgs args)
         {
-            System.Diagnostics.Process.Start (app.Document.CurrentStateUri);
+            app.Document.Save (app.Document.SuggestedSavePath);
+            undo_manager.Clear ();
         }
 
-        void OnFullScreenView (object o, EventArgs args)
+        void OnSaveAs (object o, EventArgs args)
         {
-            bool fullscreen = (this["FullScreenView"] as ToggleAction).Active;
+            var chooser = app.CreateChooser (Catalog.GetString ("Save as..."), FileChooserAction.Save);
+            chooser.SelectMultiple = false;
+            chooser.DoOverwriteConfirmation = true;
+            chooser.CurrentName = System.IO.Path.GetFileName (app.Document.SuggestedSavePath);
+            chooser.AddButton (Stock.SaveAs, ResponseType.Ok);
+            chooser.SetCurrentFolder (System.IO.Path.GetDirectoryName (app.Document.SuggestedSavePath));
 
-            if (fullscreen) {
-                app.Window.Fullscreen ();
-            } else {
-                app.Window.Unfullscreen ();
+            var response = chooser.Run ();
+            string filename = chooser.Filename;
+            chooser.Destroy ();
+
+            if (response == (int)ResponseType.Ok) {
+                Log.DebugFormat ("Saving {0} to {1}", app.Document.Uri, filename);
+                app.Document.Save (filename);
+                undo_manager.Clear ();
             }
         }
 
+        void OnExportImages (object o, EventArgs args)
+        {
+            var action = new ExportImagesAction (app.Document, app.Document.Pages);
+            if (action.ExportableImageCount == 0) {
+                Log.Information ("Found zero exportable images in the selected pages");
+                return;
+            }
+
+            var export_path_base = Path.Combine (
+                Path.GetDirectoryName (app.Document.SuggestedSavePath),
+                Hyena.StringUtil.EscapeFilename (
+                    app.Document.Title ?? System.IO.Path.GetFileNameWithoutExtension (app.Document.Filename))
+            );
+
+            var export_path = export_path_base;
+            int i = 1;
+            while (Directory.Exists (export_path) && i < 100) {
+                export_path = String.Format ("{0} ({1})", export_path_base, i++);
+            }
+
+            try {
+                Directory.CreateDirectory (export_path);
+            } catch (Exception e) {
+                Hyena.Log.Exception (e);
+            }
+
+            action.Do (export_path);
+            System.Diagnostics.Process.Start (export_path);
+        }
+
         void OnInsertFrom (object o, EventArgs args)
         {
             var chooser = app.CreateChooser (Catalog.GetString ("Select PDF"), FileChooserAction.Open);
@@ -266,46 +319,29 @@ namespace PdfMod.Gui
             }
         }
 
-        void OnSave (object o, EventArgs args)
+        void OnProperties (object o, EventArgs args)
         {
-            app.Document.Save (app.Document.SuggestedSavePath);
-            undo_manager.Clear ();
+            app.EditorBox.Visible = (this["Properties"] as ToggleAction).Active;
+            if (app.EditorBox.Visible) {
+                app.EditorBox.GrabFocus ();
+            }
         }
 
-        void OnSaveAs (object o, EventArgs args)
+        void OnClose (object o, EventArgs args)
         {
-            var chooser = app.CreateChooser (Catalog.GetString ("Save as..."), FileChooserAction.Save);
-            chooser.SelectMultiple = false;
-            chooser.DoOverwriteConfirmation = true;
-            chooser.CurrentName = System.IO.Path.GetFileName (app.Document.SuggestedSavePath);
-            chooser.AddButton (Stock.SaveAs, ResponseType.Ok);
-            chooser.SetCurrentFolder (System.IO.Path.GetDirectoryName (app.Document.SuggestedSavePath));
-
-            var response = chooser.Run ();
-            string filename = chooser.Filename;
-            chooser.Destroy ();
-
-            if (response == (int)ResponseType.Ok) {
-                Log.DebugFormat ("Saving {0} to {1}", app.Document.Uri, filename);
-                app.Document.Save (filename);
-                undo_manager.Clear ();
-            }
+            app.Quit ();
         }
 
-        void OnProperties (object o, EventArgs args)
+        // Edit menu actions
+
+        void OnUndo (object o, EventArgs args)
         {
-            app.EditorBox.Visible = (this["Properties"] as ToggleAction).Active;
-            if (app.EditorBox.Visible) {
-                app.EditorBox.GrabFocus ();
-            }
+            undo_manager.Undo ();
         }
 
-        void OnRemove (object o, EventArgs args)
+        void OnRedo (object o, EventArgs args)
         {
-            var action = new RemoveAction (app.Document, app.IconView.SelectedPages);
-            action.Do ();
-            // Undo isn't working yet
-            //undo_manager.AddUndoAction (action);
+            undo_manager.Redo ();
         }
 
         void OnExtractPages (object o, EventArgs args)
@@ -328,58 +364,96 @@ namespace PdfMod.Gui
             ));
         }
 
-        void OnExportImages (object o, EventArgs args)
+        void OnRemove (object o, EventArgs args)
         {
-            var action = new ExportImagesAction (app.Document, app.Document.Pages);
-            if (action.ExportableImageCount == 0) {
-                Log.Information ("Found zero exportable images in the selected pages");
-                return;
-            }
+            var action = new RemoveAction (app.Document, app.IconView.SelectedPages);
+            action.Do ();
+            // Undo isn't working yet
+            //undo_manager.AddUndoAction (action);
+        }
 
-            var export_path_base = Path.Combine (
-                Path.GetDirectoryName (app.Document.SuggestedSavePath),
-                Hyena.StringUtil.EscapeFilename (
-                    app.Document.Title ?? System.IO.Path.GetFileNameWithoutExtension (app.Document.Filename))
-            );
+        void OnRotateLeft (object o, EventArgs args)
+        {
+            Rotate (-90);
+        }
 
-            var export_path = export_path_base;
-            int i = 1;
-            while (Directory.Exists (export_path) && i < 100) {
-                export_path = String.Format ("{0} ({1})", export_path_base, i++);
-            }
+        void OnRotateRight (object o, EventArgs args)
+        {
+            Rotate (90);
+        }
 
-            try {
-                Directory.CreateDirectory (export_path);
-            } catch (Exception e) {
-                Hyena.Log.Exception (e);
-            }
+        void OnSelectAll (object o, EventArgs args)
+        {
+            app.IconView.SetPageSelectionMode (PageSelectionMode.All);
+        }
 
-            action.Do (export_path);
-            System.Diagnostics.Process.Start (export_path);
+        void OnSelectOdds (object o, EventArgs args)
+        {
+            app.IconView.SetPageSelectionMode (PageSelectionMode.Odds);
         }
 
-        void OnUndo (object o, EventArgs args)
+        void OnSelectEvens (object o, EventArgs args)
         {
-            undo_manager.Undo ();
+            app.IconView.SetPageSelectionMode (PageSelectionMode.Evens);
         }
 
-        void OnRedo (object o, EventArgs args)
+        void OnSelectMatching (object o, EventArgs args)
         {
-            undo_manager.Redo ();
+            app.ToggleMatchQuery ();
         }
 
-        [DllImport ("glib-2.0.dll")]
-        static extern IntPtr g_get_language_names ();
+        void OnSelectInverse (object o, EventArgs args)
+        {
+            app.IconView.SetPageSelectionMode (PageSelectionMode.Inverse);
+        }
 
-        string CombinePaths (params string [] parts)
+        // View menu actions
+
+        void OnZoomIn (object o, EventArgs args)
         {
-            string path = parts[0];
-            for (int i = 1; i < parts.Length; i++) {
-                path = System.IO.Path.Combine (path, parts[i]);
+            app.IconView.Zoom (10);
+        }
+
+        void OnZoomOut (object o, EventArgs args)
+        {
+            app.IconView.Zoom (-10);
+        }
+
+        void OnZoomFit (object o, EventArgs args)
+        {
+            app.IconView.ZoomFit ();
+        }
+
+        void OnOpenInViewer (object o, EventArgs args)
+        {
+            System.Diagnostics.Process.Start (app.Document.CurrentStateUri);
+        }
+
+        void OnFullScreenView (object o, EventArgs args)
+        {
+            bool fullscreen = (this["FullScreenView"] as ToggleAction).Active;
+
+            if (fullscreen) {
+                app.Window.Fullscreen ();
+            } else {
+                app.Window.Unfullscreen ();
             }
-            return path;
         }
 
+        void OnViewToolbar (object o, EventArgs args)
+        {
+            bool show = (this["ViewToolbar"] as ToggleAction).Active;
+            Client.Configuration.ShowToolbar = app.HeaderToolbar.Visible = show;
+        }
+
+        void OnViewBookmarks (object o, EventArgs args)
+        {
+            bool show = (this["ViewBookmarks"] as ToggleAction).Active;
+            Client.Configuration.ShowBookmarks = app.BookmarkView.Visible = show;
+        }
+
+        // Help menu actions
+
         void OnHelp (object o, EventArgs args)
         {
             bool shown = false;
@@ -488,67 +562,23 @@ namespace PdfMod.Gui
             ShowContextMenu ("/PageContextMenu");
         }
 
-        void OnSelectAll (object o, EventArgs args)
-        {
-            app.IconView.SetPageSelectionMode (PageSelectionMode.All);
-        }
-
-        void OnSelectEvens (object o, EventArgs args)
-        {
-            app.IconView.SetPageSelectionMode (PageSelectionMode.Evens);
-        }
-
-        void OnSelectOdds (object o, EventArgs args)
-        {
-            app.IconView.SetPageSelectionMode (PageSelectionMode.Odds);
-        }
-
-        void OnSelectMatching (object o, EventArgs args)
-        {
-            app.ToggleMatchQuery ();
-        }
-
-        void OnSelectInverse (object o, EventArgs args)
-        {
-            app.IconView.SetPageSelectionMode (PageSelectionMode.Inverse);
-        }
-
-        void OnZoomIn (object o, EventArgs args)
-        {
-            app.IconView.Zoom (10);
-        }
-
-        void OnZoomOut (object o, EventArgs args)
-        {
-            app.IconView.Zoom (-10);
-        }
-
-        void OnZoomFit (object o, EventArgs args)
-        {
-            app.IconView.ZoomFit ();
-        }
+        // Bookmark actions
 
-        void OnViewToolbar (object o, EventArgs args)
+        void OnEditBookmarks (object o, EventArgs args)
         {
-            bool show = (this["ViewToolbar"] as ToggleAction).Active;
-            Client.Configuration.ShowToolbar = app.HeaderToolbar.Visible = show;
+            if (!app.BookmarkView.Visible) {
+                (this["ViewBookmarks"] as ToggleAction).Active = true;
+            }
         }
 
-        void OnViewBookmarks (object o, EventArgs args)
+        void OnBookmarkContextMenu (object o, EventArgs args)
         {
-            bool show = (this["ViewBookmarks"] as ToggleAction).Active;
-            Client.Configuration.ShowBookmarks = app.BookmarkView.Visible = show;
+            ShowContextMenu ("/BookmarkContextMenu");
         }
 
-        void OnRotateRight (object o, EventArgs args)
-        {
-            Rotate (90);
-        }
+#endregion
 
-        void OnRotateLeft (object o, EventArgs args)
-        {
-            Rotate (-90);
-        }
+#region Utility methods
 
         void Rotate (int degrees)
         {
@@ -559,12 +589,29 @@ namespace PdfMod.Gui
             }
         }
 
-        void OnClose (object o, EventArgs args)
+        string CombinePaths (params string [] parts)
         {
-            app.Quit ();
+            string path = parts[0];
+            for (int i = 1; i < parts.Length; i++) {
+                path = System.IO.Path.Combine (path, parts[i]);
+            }
+            return path;
         }
 
+        [DllImport ("glib-2.0.dll")]
+        static extern IntPtr g_get_language_names ();
+
 #endregion
 
     }
+
+    public static class ActionExtensions
+    {
+        public static Hyena.Widgets.ImageButton CreateImageButton (this Gtk.Action action)
+        {
+            var button = new Hyena.Widgets.ImageButton (action.ShortLabel, action.IconName);
+            action.ConnectProxy (button);
+            return button;
+        }
+    }
 }
diff --git a/src/PdfMod/Gui/BookmarkView.cs b/src/PdfMod/Gui/BookmarkView.cs
index f7c64e8..bdf62e1 100644
--- a/src/PdfMod/Gui/BookmarkView.cs
+++ b/src/PdfMod/Gui/BookmarkView.cs
@@ -21,6 +21,7 @@ using Gtk;
 
 using PdfSharp.Pdf;
 using PdfMod.Pdf;
+using PdfMod.Pdf.Actions;
 using System.Collections.Generic;
 using Mono.Unix;
 using PdfSharp.Pdf.Advanced;
@@ -29,20 +30,25 @@ namespace PdfMod.Gui
 {
     public class BookmarkView : VBox
     {
+        Client app;
         TreeView tree_view;
         TreeStore model;
         Document document;
 
-        public bool IsModified { get; set; }
-
-        public BookmarkView ()
+        public BookmarkView (Client app)
         {
+            this.app = app;
             BuildTreeView ();
             BuildButtonBar ();
 
             WidthRequest = 200;
             Spacing = 6;
             ShowAll ();
+
+            app.Actions["AddBookmark"].Activated += OnAdd;
+            app.Actions["RenameBookmark"].Activated += OnRename;
+            app.Actions["ChangeBookmarkDest"].Activated += OnChangeDest;
+            app.Actions["RemoveBookmarks"].Activated += OnRemove;
         }
 
         public void SetDocument (Document new_doc)
@@ -62,8 +68,142 @@ namespace PdfMod.Gui
 
             model.Clear ();
             AddOutlineCollection (document, document.Pdf.Outlines, TreeIter.Zero);
+            UpdateActions ();
+        }
+
+        // Bookmark action handlers
+
+        void OnAdd (object o, EventArgs args)
+        {
+            // Figure out if there's a parent to put it under
+            TreeIter parent_iter = TreeIter.Zero;
+            if (tree_view.Selection.CountSelectedRows () > 0) {
+                TreePath parent_path;
+                TreeViewColumn col;
+                tree_view.GetCursor (out parent_path, out col);
+                model.GetIter (out parent_iter, parent_path);
+            }
+
+            // Add it to the PDF document
+            var outline = new PdfOutline (Catalog.GetString ("New bookmark"), document.Pages.First ().Pdf, true);
+            if (!TreeIter.Zero.Equals (parent_iter)) {
+                var parent = GetOutline (parent_iter);
+                SetDestIndex (outline, GetDestIndex (parent) + 1);
+                parent.Outlines.Add (outline);
+            } else {
+                document.Pdf.Outlines.Add (outline);
+            }
+
+            // Add it to our TreeView
+            var iter = AddOutline (parent_iter, outline);
+
+            // Make sure it is visible
+            tree_view.ExpandToPath (model.GetPath (iter));
+
+            MarkModified ();
+
+            // Begin editing its name
+            tree_view.SetCursor (model.GetPath (iter), tree_view.Columns[0], true);
+            Hyena.Log.Debug ("Added bookmark");
+
+            // Create an IUndo action so it can be undone
+            var action = CreateAddRemoveAction (true, iter);
+            action.Description = Catalog.GetString ("Add Bookmark");
+            app.Actions.UndoManager.AddUndoAction (action);
+        }
+
+        void OnRename (object o, EventArgs args)
+        {
+            tree_view.SetCursor (tree_view.Selection.GetSelectedRows ().First (), tree_view.Columns[0], true);
+        }
+
+        void OnChangeDest (object o, EventArgs args)
+        {
+            tree_view.SetCursor (tree_view.Selection.GetSelectedRows ().First (), tree_view.Columns[1], true);
+        }
+
+        void OnRemove (object o, EventArgs args)
+        {
+            TreeIter iter;
+            var iters = tree_view.Selection.GetSelectedRows ().Select (p => { model.GetIter (out iter, p); return iter; }).ToArray ();
+            var action = CreateAddRemoveAction (false, iters);
+            action.Description = String.Format (Catalog.GetPluralString ("Remove Bookmark", "Remove {0} Bookmarks", iters.Length), iters.Length);
+            action.Do ();
+            app.Actions.UndoManager.AddUndoAction (action);
+        }
+
+        class ActionContext {
+            public TreeIter Iter;
+            public PdfOutline Bookmark;
+            public PdfOutline Parent;
+        }
+
+        TreeIter IterForBookmark (PdfOutline bookmark)
+        {
+            var iter = TreeIter.Zero;
+            model.Foreach ((m, path, i) => {
+                if (GetOutline (i) == bookmark) {
+                    iter = i;
+                    return true;
+                }
+                return false;
+            });
+            return iter;
+        }
+
+        DelegateAction CreateAddRemoveAction (bool added, params TreeIter [] iters)
+        {
+            TreeIter iter;
+            var items = iters.Select (i => new ActionContext () {
+                                  Iter = i,
+                                  Bookmark = GetOutline (i),
+                                  Parent = model.IterParent (out iter, i) ? GetOutline (iter) : null
+                              })
+                             .ToList ();
+
+            var add_action = new System.Action (() => {
+                for (int i = 0; i < items.Count; i++) {
+                    var item = items[i];
+                    TreeIter parent_iter = TreeIter.Zero;
+                    if (item.Parent != null) {
+                        item.Parent.Outlines.Add (item.Bookmark);
+                        parent_iter = IterForBookmark (item.Parent);
+                    } else {
+                        document.Pdf.Outlines.Add (item.Bookmark);
+                    }
+
+                    // Add it to our TreeView
+                    item.Iter = AddOutline (parent_iter, item.Bookmark);
+                    tree_view.ExpandToPath (model.GetPath (item.Iter));
+                    Hyena.Log.DebugFormat ("Added back bookmark '{0}'", item.Bookmark.Title);
+                }
+            });
+
+            var remove_action = new System.Action (() => {
+                items.Reverse ();
+                foreach (var item in items) {
+                    item.Bookmark.Remove ();
+                    TreeIter i = item.Iter;
+                    model.Remove (ref i);
+                    Hyena.Log.DebugFormat ("Removed bookmark '{0}'", item.Bookmark.Title);
+                }
+                items.Reverse ();
+            });
+
+            return new DelegateAction (document) {
+                UndoAction = delegate {
+                    if (added) remove_action (); else add_action ();
+                    RemoveModifiedMark ();
+                },
+                RedoAction = delegate {
+                    if (added)  add_action (); else remove_action ();
+                    MarkModified ();
+                }
+            };
         }
 
+        // Document event handlers
+
         void OnPagesAdded (int index, Page [] pages)
         {
             UpdateModel ();
@@ -78,8 +218,8 @@ namespace PdfMod.Gui
         {
             var pdf_pages = pages.Select (p => p.Pdf).ToList ();
 
-            var to_remove = new List<TreeIter> ();
             // Remove bookmarks that point to removed pages
+            var to_remove = new List<TreeIter> ();
             model.Foreach ((m, path, iter) => {
                 var outline = GetOutline (iter);
                 if (pdf_pages.Contains (outline.DestinationPage)) {
@@ -96,7 +236,7 @@ namespace PdfMod.Gui
                 outline.Remove ();
                 model.Remove (ref iter);
 
-                Hyena.Log.DebugFormat ("Removing bookmark '{0}' since its page was removed", outline.Title);
+                Hyena.Log.DebugFormat ("Removed bookmark '{0}' since its page was removed", outline.Title);
             }
 
             UpdateModel ();
@@ -107,37 +247,15 @@ namespace PdfMod.Gui
             UpdateModel ();
         }
 
-        public IEnumerable<PdfOutline> Outlines {
-            get {
-                TreeIter iter;
-                if (model.GetIterFirst (out iter)) {
-                    do {
-                        yield return GetOutline (iter);
-                    } while (model.IterNext (ref iter));
-                }
-            }
-        }
+        // Widget construction utility methods
 
-        private class BookmarkTreeView : TreeView
-        {
-            protected override bool OnButtonPressEvent (Gdk.EventButton press)
-            {
-                TreePath path;
-                if (!GetPathAtPos ((int)press.X, (int)press.Y, out path)) {
-                    Selection.UnselectAll ();
-                    return true;
-                } else {
-                    return base.OnButtonPressEvent (press);
-                }
-            }
-        }
-
-        private void BuildTreeView ()
+        void BuildTreeView ()
         {
             // outline, expanded/opened, title, page # destination, tooltip
             model = new TreeStore (typeof(PdfSharp.Pdf.PdfOutline), typeof(bool), typeof(string), typeof(int), typeof(string));
 
             tree_view = new BookmarkTreeView () {
+                App = app,
                 Model = model,
                 SearchColumn = (int)ModelColumns.Title,
                 TooltipColumn = (int)ModelColumns.Tooltip,
@@ -158,9 +276,25 @@ namespace PdfMod.Gui
                 if (model.GetIterFromString (out iter, args.Path)) {
                     if (!String.IsNullOrEmpty (args.NewText)) {
                         var bookmark = GetOutline (iter);
-                        bookmark.Title = args.NewText;
-                        model.SetValue (iter, (int)ModelColumns.Title, bookmark.Title);
-                        MarkModified ();
+                        string new_name = args.NewText;
+                        string old_name = bookmark.Title;
+                        var action = new DelegateAction (document) {
+                            Description = Catalog.GetString ("Rename Bookmark"),
+                            UndoAction = delegate {
+                                var i = IterForBookmark (bookmark);
+                                bookmark.Title = old_name;
+                                model.SetValue (i, (int)ModelColumns.Title, bookmark.Title);
+                                RemoveModifiedMark ();
+                            },
+                            RedoAction = delegate {
+                                var i = IterForBookmark (bookmark);
+                                bookmark.Title = new_name;
+                                model.SetValue (i, (int)ModelColumns.Title, bookmark.Title);
+                                MarkModified ();
+                            }
+                        };
+                        action.Do ();
+                        app.Actions.UndoManager.AddUndoAction (action);
                     } else {
                         args.RetVal = false;
                     }
@@ -179,9 +313,24 @@ namespace PdfMod.Gui
                     var bookmark = GetOutline (iter);
                     int i = -1;
                     if (Int32.TryParse (args.NewText, out i) && i >= 1 && i <= document.Count && i != (GetDestIndex (bookmark) + 1)) {
-                        SetDestIndex (bookmark, i - 1);
-                        model.SetValue (iter, (int)ModelColumns.PageNumber, i);
-                        MarkModified ();
+                        int old_dest = GetDestIndex (bookmark);
+                        int new_dest = i - 1;
+
+                        var action = new DelegateAction (document) {
+                            Description = Catalog.GetString ("Rename Bookmark"),
+                            UndoAction = delegate {
+                                SetDestIndex (bookmark, old_dest);
+                                model.SetValue (iter, (int)ModelColumns.PageNumber, old_dest + 1);
+                                RemoveModifiedMark ();
+                            },
+                            RedoAction = delegate {
+                                SetDestIndex (bookmark, new_dest);
+                                model.SetValue (iter, (int)ModelColumns.PageNumber, new_dest + 1);
+                                MarkModified ();
+                            }
+                        };
+                        action.Do ();
+                        app.Actions.UndoManager.AddUndoAction (action);
                     } else {
                         args.RetVal = false;
                     }
@@ -202,51 +351,16 @@ namespace PdfMod.Gui
             PackStart (sw, true, true, 0);
         }
 
-        private void BuildButtonBar ()
+        void BuildButtonBar ()
         {
             var box = new HBox () { Spacing = 6 };
-            var add_button = new Button (Gtk.Stock.Add);
-            add_button.Clicked += (o, a) => {
-                TreeIter parent_iter;
-                if (!tree_view.Selection.GetSelected (out parent_iter) && !model.GetIterFirst (out parent_iter)) {
-                    parent_iter = TreeIter.Zero;
-                }
-
-                // Add it to the PDF document
-                var outline = new PdfOutline (Catalog.GetString ("New bookmark"), document.Pages.First ().Pdf, true);
-                if (!TreeIter.Zero.Equals (parent_iter)) {
-                    var parent = GetOutline (parent_iter);
-                    SetDestIndex (outline, GetDestIndex (parent) + 1);
-                    parent.Outlines.Add (outline);
-
-                    tree_view.ExpandToPath (model.GetPath (parent_iter));
-                } else {
-                    document.Pdf.Outlines.Add (outline);
-                }
+            var add_action = app.Actions["AddBookmark"];
+            var add_button = add_action.CreateImageButton ();
 
-                // Add it to our TreeView
-                var iter = AddOutline (parent_iter, outline);
-                MarkModified ();
-
-                // Begin editing its name
-                tree_view.SetCursor (model.GetPath (iter), tree_view.Columns[0], true);
-                Hyena.Log.Debug ("Added bookmark");
-            };
-
-            var remove_button = new Button (Gtk.Stock.Remove);
-            remove_button.Clicked += (o, a) => {
-                foreach (var path in tree_view.Selection.GetSelectedRows ()) {
-                    TreeIter iter;
-                    model.GetIter (out iter, path);
-                    var outline = GetOutline (iter);
-                    Hyena.Log.DebugFormat ("Removing bookmark '{0}'", outline.Title);
-                    outline.Remove ();
-                    model.Remove (ref iter);
-                }
-                MarkModified ();
-            };
-            tree_view.Selection.Changed += (o, a) => remove_button.Sensitive = tree_view.Selection.CountSelectedRows () > 0;
-            remove_button.Sensitive = tree_view.Selection.CountSelectedRows () > 0;
+            var remove_action = app.Actions["RemoveBookmarks"];
+            var remove_button = remove_action.CreateImageButton ();
+            tree_view.Selection.Changed += (o, a) => UpdateActions ();
+            UpdateActions ();
 
             box.PackStart (add_button, false, false, 0);
             box.PackStart (remove_button, false, false, 0);
@@ -254,28 +368,44 @@ namespace PdfMod.Gui
             PackStart (box, false, false, 0);
         }
 
-        private void UpdateModel ()
+        string [] selection_actions = new string [] { "RenameBookmark", "RemoveBookmarks", "ChangeBookmarkDest" };
+        void UpdateActions ()
+        {
+            int count = tree_view.Selection.CountSelectedRows ();
+            bool have_doc_and_selection = count > 0 && document != null;
+            foreach (var action in selection_actions) {
+                app.Actions.UpdateAction (action, true, have_doc_and_selection);
+            }
+            app.Actions["RemoveBookmarks"].Label = String.Format (Catalog.GetPluralString ("_Remove Bookmark", "_Remove {0} Bookmarks", count), count);
+        }
+
+        void UpdateModel ()
         {
             model.Foreach ((m, path, iter) => {
                 model.SetValues (iter, GetValuesFor (GetOutline (iter)));
                 return false;
             });
+            UpdateActions ();
+        }
+
+        void RemoveModifiedMark ()
+        {
+            document.UnsavedChanges--;
         }
 
-        private void MarkModified ()
+        void MarkModified ()
         {
-            IsModified = true;
-            document.HasUnsavedChanges = true;
+            document.UnsavedChanges++;
         }
 
-        private TreeIter AddOutline (TreeIter parent, PdfOutline outline)
+        TreeIter AddOutline (TreeIter parent, PdfOutline outline)
         {
             return TreeIter.Zero.Equals (parent)
                 ? model.AppendValues (GetValuesFor (outline))
                 : model.AppendValues (parent, GetValuesFor (outline));
         }
 
-        private object [] GetValuesFor (PdfOutline outline)
+        object [] GetValuesFor (PdfOutline outline)
         {
             int dest_num = GetDestIndex (outline);
 
@@ -283,7 +413,7 @@ namespace PdfMod.Gui
                 String.Format (Catalog.GetString ("Bookmark links to page {0}"), dest_num + 1) };
         }
 
-        private int GetDestIndex (PdfOutline outline)
+        int GetDestIndex (PdfOutline outline)
         {
             if (outline.DestinationPage == null)
                 return -1;
@@ -291,26 +421,21 @@ namespace PdfMod.Gui
                 return document.Pages.Select (p => p.Pdf).IndexOf (outline.DestinationPage);
         }
 
-        private void SetDestIndex (PdfOutline outline, int i)
+        void SetDestIndex (PdfOutline outline, int i)
         {
             if (i >= 0 && i < document.Count) {
                 outline.DestinationPage = document.Pages.Skip (i).First ().Pdf;
             }
         }
 
-        private PdfOutline GetSelected ()
-        {
-            TreeIter iter;
-            if (tree_view.Selection.GetSelected (out iter))
-                return GetOutline (iter);
-            return null;
-        }
-
-        private void AddOutlineCollection (Document document, PdfOutline.PdfOutlineCollection outlines, TreeIter parent)
+        void AddOutlineCollection (Document document, PdfOutline.PdfOutlineCollection outlines, TreeIter parent)
         {
             if (outlines != null) {
                 foreach (PdfOutline outline in outlines) {
                     var iter = AddOutline (parent, outline);
+                    if (outline.Opened) {
+                        tree_view.ExpandRow (model.GetPath (iter), false);
+                    }
 
                     // Recursively add this item's children, if any
                     AddOutlineCollection (document, outline.Outlines, iter);
@@ -318,18 +443,57 @@ namespace PdfMod.Gui
             }
         }
 
-        private PdfOutline GetOutline (TreeIter iter)
+        PdfOutline GetOutline (TreeIter iter)
         {
             return (PdfOutline) model.GetValue (iter, (int)ModelColumns.Bookmark);
         }
 
-        private enum ModelColumns : int {
+        enum ModelColumns : int {
             Bookmark,
             IsExpanded,
             Title,
             PageNumber,
             Tooltip
         };
+
+        class BookmarkTreeView : TreeView
+        {
+            public Client App;
+
+            protected override bool OnButtonPressEvent (Gdk.EventButton press)
+            {
+                TreePath path;
+                if (!GetPathAtPos ((int)press.X, (int)press.Y, out path)) {
+                    Selection.UnselectAll ();
+                }
+
+                bool call_parent = true;
+                if (press.Button == 3 && path != null && Selection.PathIsSelected (path)) {
+                    // Calling the parent in this case would unselect any other items than
+                    // this path, which we don't want to do - they should stay selected and the
+                    // context menu should pop up.
+                    call_parent = false;
+                }
+
+                bool ret = false;
+                if (call_parent) {
+                    ret = base.OnButtonPressEvent (press);
+                }
+
+                if (press.Button == 3) {
+                    ret = OnPopupMenu ();
+                }
+
+                return ret;
+            }
+
+            protected override bool OnPopupMenu ()
+            {
+                App.Actions["BookmarkContextMenu"].Activate ();
+                return true;
+            }
+        }
+
     }
 
     internal static class Extensions
diff --git a/src/PdfMod/Gui/Client.cs b/src/PdfMod/Gui/Client.cs
index fdd2021..511388b 100644
--- a/src/PdfMod/Gui/Client.cs
+++ b/src/PdfMod/Gui/Client.cs
@@ -124,7 +124,7 @@ namespace PdfMod.Gui
             HeaderToolbar.Visible = Configuration.ShowToolbar;
 
             // BookmarksView
-            BookmarkView = new BookmarkView ();
+            BookmarkView = new BookmarkView (this);
             BookmarkView.NoShowAll = true;
             BookmarkView.Visible = Configuration.ShowBookmarks;
 
diff --git a/src/PdfMod/Pdf/Actions/BaseAction.cs b/src/PdfMod/Pdf/Actions/BaseAction.cs
new file mode 100644
index 0000000..8815310
--- /dev/null
+++ b/src/PdfMod/Pdf/Actions/BaseAction.cs
@@ -0,0 +1,83 @@
+// Copyright (C) 2009-2010 Novell, Inc.
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+using System;
+using System.Collections.Generic;
+
+using Hyena;
+
+namespace PdfMod.Pdf.Actions
+{
+    public interface IDescribedUndoAction : IUndoAction
+    {
+        string Description { get; }
+    }
+
+    public abstract class BaseAction : IDescribedUndoAction
+    {
+        protected Document Document { get; private set; }
+        public string Description { get; set; }
+
+        public BaseAction (Document document) : this (document, null) {}
+
+        public BaseAction (Document document, string description)
+        {
+            Document = document;
+            Description = description;
+        }
+
+        public void Do ()
+        {
+            Redo ();
+        }
+
+        #region IUndoAction implementation
+
+        public abstract void Undo ();
+
+        public abstract void Redo ();
+
+        public virtual void Merge (IUndoAction action)
+        {
+            throw new System.NotImplementedException();
+        }
+
+        public virtual bool CanMerge (IUndoAction action)
+        {
+            return false;
+        }
+
+        #endregion
+    }
+
+    public class DelegateAction : BaseAction
+    {
+        public DelegateAction (Document document) : base (document) {}
+
+        public System.Action UndoAction { get; set; }
+        public System.Action RedoAction { get; set; }
+
+        public override void Undo ()
+        {
+            UndoAction ();
+        }
+
+        public override void Redo ()
+        {
+            RedoAction ();
+        }
+    }
+}
diff --git a/src/PdfMod/Pdf/Actions/BasePageAction.cs b/src/PdfMod/Pdf/Actions/BasePageAction.cs
index 7e7a6d7..a12a465 100644
--- a/src/PdfMod/Pdf/Actions/BasePageAction.cs
+++ b/src/PdfMod/Pdf/Actions/BasePageAction.cs
@@ -1,3 +1,18 @@
+// Copyright (C) 2009-2010 Novell, Inc.
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 using System;
 using System.Collections.Generic;
@@ -6,44 +21,13 @@ using Hyena;
 
 namespace PdfMod.Pdf.Actions
 {
-    public interface IDescribedUndoAction : IUndoAction
+    public abstract class BasePageAction : BaseAction
     {
-        string Description { get; }
-    }
-
-    public abstract class BasePageAction : IDescribedUndoAction
-    {
-        protected Document Document { get; private set; }
         protected List<Page> Pages { get; private set; }
-        public string Description { get; protected set; }
 
-        public BasePageAction (Document document, IEnumerable<Page> to_remove)
+        public BasePageAction (Document document, IEnumerable<Page> to_remove) : base (document)
         {
-            Document = document;
             Pages = new List<Page> (to_remove);
         }
-
-        public void Do ()
-        {
-            Redo ();
-        }
-
-        #region IUndoAction implementation
-
-        public abstract void Undo ();
-
-        public abstract void Redo ();
-
-        public virtual void Merge (IUndoAction action)
-        {
-            throw new System.NotImplementedException();
-        }
-
-        public virtual bool CanMerge (IUndoAction action)
-        {
-            return false;
-        }
-
-        #endregion
     }
 }
diff --git a/src/PdfMod/Pdf/Document.cs b/src/PdfMod/Pdf/Document.cs
index 9515c1c..8401c9f 100644
--- a/src/PdfMod/Pdf/Document.cs
+++ b/src/PdfMod/Pdf/Document.cs
@@ -159,11 +159,15 @@ namespace PdfMod.Pdf
             OnChanged ();
         }
 
-        private bool has_unsaved_changes;
         public bool HasUnsavedChanges {
-            get { return has_unsaved_changes || tmp_uri != null || save_timeout_id != 0; }
+            get { return UnsavedChanges > 0 || tmp_uri != null || save_timeout_id != 0; }
+        }
+
+        int unsaved_changes = 0;
+        public int UnsavedChanges {
+            get { return unsaved_changes; }
             set {
-                has_unsaved_changes = value;
+                unsaved_changes = value;
                 OnChanged ();
             }
         }
@@ -264,7 +268,7 @@ namespace PdfMod.Pdf
         public void Save (string uri)
         {
             Pdf.Save (uri);
-            has_unsaved_changes = false;
+            UnsavedChanges = 0;
             Log.DebugFormat ("Saved to {0}", uri);
             Uri = uri;
 
diff --git a/src/Resources/UIManager.xml b/src/Resources/UIManager.xml
index b6cc7a0..035eb05 100644
--- a/src/Resources/UIManager.xml
+++ b/src/Resources/UIManager.xml
@@ -42,6 +42,14 @@
       <menuitem action="ViewToolbar"/>
       <menuitem action="ViewBookmarks"/>
     </menu>
+    <menu action="BookmarksMenu">
+      <menuitem action="AddBookmark"/>
+      <menuitem action="RenameBookmark"/>
+      <menuitem action="ChangeBookmarkDest"/>
+      <menuitem action="RemoveBookmarks"/>
+      <separator/>
+      <menuitem action="EditBookmarks"/>
+    </menu>
     <menu action="HelpMenu">
       <menuitem action="Help"/>
       <menuitem action="About"/>
@@ -53,6 +61,7 @@
     <toolitem action="SaveAs"/>
     <separator/>
     <toolitem action="Properties"/>
+    <toolitem action="ViewBookmarks"/>
     <toolitem action="ExportImages"/>
     <toolitem action="InsertFrom"/>
     <separator/>
@@ -78,4 +87,11 @@
     <menuitem action="SelectMatching"/>
     <separator/>
   </popup>
+
+  <popup action="BookmarkContextMenu">
+    <menuitem action="AddBookmark"/>
+    <menuitem action="RenameBookmark"/>
+    <menuitem action="ChangeBookmarkDest"/>
+    <menuitem action="RemoveBookmarks"/>
+  </popup>
 </ui>



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]