[pdfmod] First pass at Bookmarks support



commit 637b1cadba6e6ee9005ea519c2f1199a5f2ba778
Author: Gabriel Burt <gabriel burt gmail com>
Date:   Tue Aug 31 15:23:35 2010 -0500

    First pass at Bookmarks support
    
    Adds a sidebar TreeView with that shows the existing bookmarks, letting
    you edit and remove them and add new bookmarks.  Needs more testing and
    polish.  A big step toward closing bgo#627747

 src/Makefile.am                  |    3 +
 src/PdfMod.mdp                   |   82 +++++-----
 src/PdfMod/Core/Configuration.cs |    5 +
 src/PdfMod/Gui/Actions.cs        |   11 +-
 src/PdfMod/Gui/BookmarkView.cs   |  334 ++++++++++++++++++++++++++++++++++++++
 src/PdfMod/Gui/Client.cs         |   27 ++-
 src/PdfMod/Pdf/Document.cs       |   13 ++-
 src/Resources/UIManager.xml      |    1 +
 8 files changed, 421 insertions(+), 55 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 3164485..dbb07e2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -32,6 +32,7 @@ FILES =  \
 	PdfMod/Core/Configuration.cs \
 	PdfMod/Core/Defines.cs \
 	PdfMod/Gui/Actions.cs \
+	PdfMod/Gui/BookmarkView.cs \
 	PdfMod/Gui/CairoCell.cs \
 	PdfMod/Gui/Client.cs \
 	PdfMod/Gui/DocumentIconView.cs \
@@ -68,6 +69,8 @@ REFERENCES =  \
 	System \
 	System.Core
 
+PROJECT_REFERENCES = ../bin/PdfSharp.dll
+
 if USE_BUNDLED_POPPLER
 PROJECT_REFERENCES += ../bin/poppler-sharp.dll
 PROGRAMFILES += $(POPPLER_SHARP_DLL_CONFIG)
diff --git a/src/PdfMod.mdp b/src/PdfMod.mdp
index aa37e31..5b0b84c 100644
--- a/src/PdfMod.mdp
+++ b/src/PdfMod.mdp
@@ -1,48 +1,57 @@
-<Project name="PdfMod" fileversion="2.0" language="C#" targetFramework="3.5" ctype="DotNetProject">
+<Project name="PdfMod" fileversion="2.0" DefaultNamespace="PdfMod" language="C#" targetFramework="3.5" ctype="DotNetProject">
   <Policies>
     <DotNetNamingPolicy DirectoryNamespaceAssociation="Flat" ResourceNamePolicy="FileFormatDefault" />
   </Policies>
+  <GtkDesignInfo gettextClass="Mono.Posix.Catalog" />
+  <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="True" RelativeMakefileName="Makefile.am" BuildTargetName="" CleanTargetName="" SyncReferences="True" IsAutotoolsProject="True" RelativeConfigureInPath="..">
+    <BuildFilesVar Sync="True" Name="FILES" />
+    <DeployFilesVar />
+    <ResourcesVar Sync="True" Name="RESOURCES" />
+    <OthersVar />
+    <GacRefVar Sync="True" Name="REFERENCES" />
+    <AsmRefVar Sync="True" Name="DLL_REFERENCES" />
+    <ProjectRefVar Sync="True" Name="PROJECT_REFERENCES" />
+  </MonoDevelop.Autotools.MakefileInfo>
   <Configurations active="Debug">
     <Configuration name="Debug" ctype="DotNetProjectConfiguration">
       <Output directory="../bin/" assembly="PdfMod" />
       <Build debugmode="True" target="Exe" />
-      <Execution runwithwarnings="True" consolepause="True" runtime="MsNet" />
-      <EnvironmentVariables />
+      <Execution consolepause="True" runwithwarnings="True" runtime="MsNet" />
       <CodeGeneration compiler="Mcs" warninglevel="4" optimize="True" unsafecodeallowed="False" generateoverflowchecks="False" definesymbols="DEBUG" generatexmldocumentation="False" ctype="CSharpCompilerParameters" />
     </Configuration>
     <Configuration name="Release" ctype="DotNetProjectConfiguration">
       <Output directory="../bin/" assembly="PdfMod" />
       <Build debugmode="True" target="Exe" />
-      <Execution runwithwarnings="True" consolepause="True" runtime="MsNet" />
-      <EnvironmentVariables />
+      <Execution consolepause="True" runwithwarnings="True" runtime="MsNet" />
       <CodeGeneration compiler="Mcs" warninglevel="4" optimize="True" unsafecodeallowed="False" generateoverflowchecks="False" generatexmldocumentation="False" ctype="CSharpCompilerParameters" />
     </Configuration>
   </Configurations>
   <Contents>
-    <File name="PdfMod/Main.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Core/AssemblyInfo.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Gui/DocumentIconView.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Gui/PageListStore.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Gui/Actions.cs" subtype="Code" buildaction="Compile" />
-    <File name="Resources/UIManager.xml" subtype="Code" buildaction="EmbedAsResource" />
-    <File name="PdfMod/Pdf/Actions/RemoveAction.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Pdf/Document.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Pdf/Page.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Pdf/Actions/RotateAction.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Pdf/Actions/BasePageAction.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Pdf/Actions/ExportImagesAction.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Gui/SelectMatchingBox.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Gui/CairoCell.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Gui/PageCell.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Gui/Client.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Pdf/Actions/MoveAction.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Gui/MetadataEditorBox.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Pdf/PageThumbnail.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Core/Defines.cs.in" subtype="Code" buildaction="Nothing" />
-    <File name="PdfMod/Core/Defines.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Core/Client.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Core/Configuration.cs" subtype="Code" buildaction="Compile" />
-    <File name="PdfMod/Pdf/PageLabels.cs" subtype="Code" buildaction="Compile" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Main.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Core/AssemblyInfo.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Gui/DocumentIconView.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Gui/PageListStore.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Gui/Actions.cs" />
+    <File subtype="Code" buildaction="EmbedAsResource" name="Resources/UIManager.xml" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Pdf/Actions/RemoveAction.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Pdf/Document.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Pdf/Page.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Pdf/Actions/RotateAction.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Pdf/Actions/BasePageAction.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Pdf/Actions/ExportImagesAction.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Gui/SelectMatchingBox.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Gui/CairoCell.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Gui/PageCell.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Gui/Client.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Pdf/Actions/MoveAction.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Gui/MetadataEditorBox.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Pdf/PageThumbnail.cs" />
+    <File subtype="Code" buildaction="Nothing" name="PdfMod/Core/Defines.cs.in" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Core/Defines.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Core/Client.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Core/Configuration.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Pdf/PageLabels.cs" />
+    <File subtype="Code" buildaction="Compile" name="PdfMod/Gui/BookmarkView.cs" />
   </Contents>
   <References>
     <ProjectReference type="Gac" localcopy="True" refto="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
@@ -55,16 +64,9 @@
     <ProjectReference type="Gac" localcopy="True" refto="gconf-sharp, Version=2.24.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
     <ProjectReference type="Gac" localcopy="True" refto="Hyena, Version=0.0.0.0, Culture=neutral" />
     <ProjectReference type="Gac" localcopy="True" refto="Hyena.Gui, Version=0.0.0.0, Culture=neutral" />
+    <ProjectReference type="Gac" localcopy="True" refto="pango-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+    <ProjectReference type="Gac" localcopy="True" refto="atk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+    <ProjectReference type="Gac" localcopy="True" refto="gdk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
     <ProjectReference type="Project" localcopy="True" refto="poppler-sharp" />
   </References>
-  <GtkDesignInfo gettextClass="Mono.Posix.Catalog" />
-  <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="True" RelativeMakefileName="Makefile.am" BuildTargetName="" CleanTargetName="" SyncReferences="True" IsAutotoolsProject="True" RelativeConfigureInPath="..">
-    <BuildFilesVar Sync="True" Name="FILES" />
-    <DeployFilesVar />
-    <ResourcesVar Sync="True" Name="RESOURCES" />
-    <OthersVar />
-    <GacRefVar Sync="True" Name="REFERENCES" />
-    <AsmRefVar Sync="True" Name="DLL_REFERENCES" />
-    <ProjectRefVar Sync="True" Name="PROJECT_REFERENCES" />
-  </MonoDevelop.Autotools.MakefileInfo>
-</Project>
+</Project>
\ No newline at end of file
diff --git a/src/PdfMod/Core/Configuration.cs b/src/PdfMod/Core/Configuration.cs
index 677bbbd..817b9f4 100644
--- a/src/PdfMod/Core/Configuration.cs
+++ b/src/PdfMod/Core/Configuration.cs
@@ -46,6 +46,11 @@ namespace PdfMod.Core
             set { Set<bool> ("show_toolbar", value); }
         }
 
+        public bool ShowBookmarks {
+            get { return Get<bool> ("show_bookmarks", false); }
+            set { Set<bool> ("show_bookmarks", value); }
+        }
+
         public string LastOpenFolder {
             get { return Get<string> ("last_folder", System.Environment.GetFolderPath (System.Environment.SpecialFolder.Desktop)); }
             set {
diff --git a/src/PdfMod/Gui/Actions.cs b/src/PdfMod/Gui/Actions.cs
index c934136..a110455 100644
--- a/src/PdfMod/Gui/Actions.cs
+++ b/src/PdfMod/Gui/Actions.cs
@@ -100,6 +100,7 @@ namespace PdfMod.Gui
                 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 ("FullScreenView", null, Catalog.GetString ("Fullscreen"), "F11", null, OnFullScreenView, false)
             );
 
@@ -177,7 +178,7 @@ namespace PdfMod.Gui
                 ? Catalog.GetString ("_Redo")
                 : String.Format (Catalog.GetString ("Redo {0}"), redo.Description);
 
-            UpdateActions (true, have_doc && app.Document.HasUnsavedChanged, "Save", "SaveAs");
+            UpdateActions (true, have_doc && app.Document.HasUnsavedChanges, "Save", "SaveAs");
             UpdateAction ("ZoomIn", true, have_doc && app.IconView.CanZoomIn);
             UpdateAction ("ZoomOut", true, have_doc && app.IconView.CanZoomOut);
 
@@ -337,8 +338,6 @@ namespace PdfMod.Gui
 
             var export_path_base = Path.Combine (
                 Path.GetDirectoryName (app.Document.SuggestedSavePath),
-                // Translators: This is used for creating a folder name, be careful!
-                //String.Format (Catalog.GetString ("{0} - Images for {1}"), app.Document.Title ?? app.Document.Filename, GetPageSummary (pages, 10))
                 Hyena.StringUtil.EscapeFilename (
                     app.Document.Title ?? System.IO.Path.GetFileNameWithoutExtension (app.Document.Filename))
             );
@@ -535,6 +534,12 @@ namespace PdfMod.Gui
             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;
+        }
+
         void OnRotateRight (object o, EventArgs args)
         {
             Rotate (90);
diff --git a/src/PdfMod/Gui/BookmarkView.cs b/src/PdfMod/Gui/BookmarkView.cs
new file mode 100644
index 0000000..f9d20e7
--- /dev/null
+++ b/src/PdfMod/Gui/BookmarkView.cs
@@ -0,0 +1,334 @@
+// Copyright (C) 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.Linq;
+
+using Gtk;
+
+using PdfSharp.Pdf;
+using PdfMod.Pdf;
+using System.Collections.Generic;
+using Mono.Unix;
+using PdfSharp.Pdf.Advanced;
+
+namespace PdfMod.Gui
+{
+    public class BookmarkView : VBox
+    {
+        TreeView tree_view;
+        TreeStore model;
+        Document document;
+
+        public bool IsModified { get; set; }
+
+        public BookmarkView ()
+        {
+            BuildTreeView ();
+            BuildButtonBar ();
+
+            WidthRequest = 200;
+            Spacing = 6;
+            ShowAll ();
+        }
+
+        public void SetDocument (Document new_doc)
+        {
+            if (document != null) {
+                document.PagesAdded   -= OnPagesAdded;
+                document.PagesChanged -= OnPagesChanged;
+                document.PagesRemoved -= OnPagesRemoved;
+                document.PagesMoved   -= OnPagesMoved;
+            }
+
+            document = new_doc;
+            document.PagesAdded   += OnPagesAdded;
+            document.PagesChanged += OnPagesChanged;
+            document.PagesRemoved += OnPagesRemoved;
+            document.PagesMoved   += OnPagesMoved;
+
+            model.Clear ();
+            AddOutlineCollection (document, document.Pdf.Outlines, TreeIter.Zero);
+        }
+
+        void OnPagesAdded (int index, Page [] pages)
+        {
+            UpdateModel ();
+        }
+
+        void OnPagesChanged (Page [] pages)
+        {
+            UpdateModel ();
+        }
+
+        void OnPagesRemoved (Page [] pages)
+        {
+            var pdf_pages = pages.Select (p => p.Pdf).ToList ();
+
+            var to_remove = new List<TreeIter> ();
+            // Remove bookmarks that point to removed pages
+            model.Foreach ((m, path, iter) => {
+                var outline = GetOutline (iter);
+                if (pdf_pages.Contains (outline.DestinationPage)) {
+                    to_remove.Add (iter);
+                }
+                return false;
+            });
+
+            while (to_remove.Count > 0) {
+                var iter = to_remove[0];
+                to_remove.Remove (iter);
+
+                var outline = GetOutline (iter);
+                outline.Remove ();
+                model.Remove (ref iter);
+
+                Hyena.Log.DebugFormat ("Removing bookmark '{0}' since its page was removed", outline.Title);
+            }
+
+            UpdateModel ();
+        }
+
+        void OnPagesMoved ()
+        {
+            UpdateModel ();
+        }
+
+        public IEnumerable<PdfOutline> Outlines {
+            get {
+                TreeIter iter;
+                if (model.GetIterFirst (out iter)) {
+                    do {
+                        yield return GetOutline (iter);
+                    } while (model.IterNext (ref iter));
+                }
+            }
+        }
+
+        private 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 TreeView () {
+                Model = model,
+                SearchColumn = (int)ModelColumns.Title,
+                TooltipColumn = (int)ModelColumns.Tooltip,
+                EnableSearch = true,
+                EnableTreeLines = true,
+                HeadersVisible = false,
+                Reorderable = false,
+                ShowExpanders = true
+            };
+            tree_view.Selection.Mode = SelectionMode.Browse;
+
+            var title = new CellRendererText () {
+                Editable = true,
+                Ellipsize = Pango.EllipsizeMode.End
+            };
+            title.Edited += delegate(object o, EditedArgs args) {
+                TreeIter iter;
+                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 ();
+                    } else {
+                        args.RetVal = false;
+                    }
+                }
+            };
+            var title_col = tree_view.AppendColumn ("", title, "text", ModelColumns.Title);
+            title_col.Expand = true;
+
+            var page = new CellRendererText () {
+                Editable = true,
+                Style = Pango.Style.Italic
+            };
+            page.Edited += delegate(object o, EditedArgs args) {
+                TreeIter iter;
+                if (model.GetIterFromString (out iter, args.Path)) {
+                    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 ();
+                    } else {
+                        args.RetVal = false;
+                    }
+                }
+            };
+            var num_col = tree_view.AppendColumn ("", page, "text", ModelColumns.PageNumber);
+            num_col.Alignment = 1.0f;
+            num_col.Expand = false;
+
+            var label = new Label (Catalog.GetString ("_Bookmarks")) { Xalign = 0f, MnemonicWidget = tree_view };
+            PackStart (label, false, false, 0);
+
+            var sw = new Gtk.ScrolledWindow () {
+                HscrollbarPolicy = PolicyType.Never,
+                VscrollbarPolicy = PolicyType.Automatic,
+                Child = tree_view
+            };
+            PackStart (sw, true, true, 0);
+        }
+
+        private 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);
+                }
+
+                // 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) => {
+                TreeIter iter;
+                if (tree_view.Selection.GetSelected (out iter)) {
+                    var outline = GetOutline (iter);
+                    Hyena.Log.DebugFormat ("Removing bookmark '{0}'", outline.Title);
+                    outline.Remove ();
+                    model.Remove (ref iter);
+                    MarkModified ();
+                }
+            };
+
+            box.PackStart (add_button, false, false, 0);
+            box.PackStart (remove_button, false, false, 0);
+
+            PackStart (box, false, false, 0);
+        }
+
+        private void UpdateModel ()
+        {
+            model.Foreach ((m, path, iter) => {
+                model.SetValues (iter, GetValuesFor (GetOutline (iter)));
+                return false;
+            });
+        }
+
+        private void MarkModified ()
+        {
+            IsModified = true;
+            document.HasUnsavedChanges = true;
+        }
+
+        private 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)
+        {
+            int dest_num = GetDestIndex (outline);
+
+            return new object [] { outline, outline.Opened, outline.Title, dest_num + 1,
+                String.Format (Catalog.GetString ("Bookmark links to page {0}"), dest_num + 1) };
+        }
+
+        private int GetDestIndex (PdfOutline outline)
+        {
+            if (outline.DestinationPage == null)
+                return -1;
+            else
+                return document.Pages.Select (p => p.Pdf).IndexOf (outline.DestinationPage);
+        }
+
+        private 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)
+        {
+            if (outlines != null) {
+                foreach (PdfOutline outline in outlines) {
+                    var iter = AddOutline (parent, outline);
+
+                    // Recursively add this item's children, if any
+                    AddOutlineCollection (document, outline.Outlines, iter);
+                }
+            }
+        }
+
+        private PdfOutline GetOutline (TreeIter iter)
+        {
+            return (PdfOutline) model.GetValue (iter, (int)ModelColumns.Bookmark);
+        }
+
+        private enum ModelColumns : int {
+            Bookmark,
+            IsExpanded,
+            Title,
+            PageNumber,
+            Tooltip
+        };
+    }
+
+    internal static class Extensions
+    {
+        public static int IndexOf<T> (this IEnumerable<T> enumerable, T item)
+        {
+            int i = 0;
+            foreach (var a in enumerable) {
+                if (item.Equals (a)) {
+                    return i;
+                } else {
+                    i++;
+                }
+            }
+
+            return -1;
+        }
+    }
+}
diff --git a/src/PdfMod/Gui/Client.cs b/src/PdfMod/Gui/Client.cs
index c7f8af0..8eba73c 100644
--- a/src/PdfMod/Gui/Client.cs
+++ b/src/PdfMod/Gui/Client.cs
@@ -55,6 +55,7 @@ namespace PdfMod.Gui
         public Gtk.Window        Window        { get; private set; }
         public DocumentIconView  IconView      { get; private set; }
         public MetadataEditorBox EditorBox     { get; private set; }
+        public BookmarkView      BookmarkView  { get; private set; }
 
         static Client ()
         {
@@ -121,12 +122,22 @@ namespace PdfMod.Gui
             HeaderToolbar.NoShowAll = true;
             HeaderToolbar.Visible = Configuration.ShowToolbar;
 
+            // BookmarksView
+            BookmarkView = new BookmarkView ();
+            BookmarkView.NoShowAll = true;
+            BookmarkView.Visible = Configuration.ShowBookmarks;
+
             var vbox = new VBox ();
             vbox.PackStart (menu_bar, false, false, 0);
             vbox.PackStart (HeaderToolbar, false, false, 0);
             vbox.PackStart (EditorBox, false, false, 0);
             vbox.PackStart (query_box, false, false, 0);
-            vbox.PackStart (iconview_sw, true, true, 0);
+
+            var hbox = new HPaned ();
+            hbox.Add1 (BookmarkView);
+            hbox.Add2 (iconview_sw);
+            vbox.PackStart (hbox, true, true, 0);
+
             vbox.PackStart (StatusBar, false, true, 0);
             Window.Add (vbox);
 
@@ -184,7 +195,7 @@ namespace PdfMod.Gui
 
         bool PromptIfUnsavedChanges ()
         {
-            if (Document != null && Document.HasUnsavedChanged) {
+            if (Document != null && Document.HasUnsavedChanges) {
                 var dialog = new Hyena.Widgets.HigMessageDialog (
                     Window, DialogFlags.Modal, MessageType.Warning, ButtonsType.None,
                     Catalog.GetString ("Save the changes made to this document?"),
@@ -268,7 +279,9 @@ namespace PdfMod.Gui
 
                     ThreadAssist.BlockingProxyToMain (delegate {
                         IconView.SetDocument (Document);
+                        BookmarkView.SetDocument (Document);
                         RecentManager.Default.AddItem (Document.Uri);
+
                         Document.Changed += UpdateForDocument;
                         UpdateForDocument ();
                         OnDocumentLoaded ();
@@ -302,6 +315,7 @@ namespace PdfMod.Gui
 
         void UpdateForDocument ()
         {
+            ThreadAssist.AssertInMainThread ();
             var current_size = Document.FileSize;
             string size_str = null;
             if (original_size_string == null) {
@@ -326,7 +340,7 @@ namespace PdfMod.Gui
 
             var title = Document.Title;
             var filename = Document.Filename;
-            if (Document.HasUnsavedChanged) {
+            if (Document.HasUnsavedChanges) {
                 filename = "*" + filename;
             }
             Window.Title = title == null ? filename : String.Format ("{0} - {1}", filename, title);
@@ -337,9 +351,7 @@ namespace PdfMod.Gui
             // This method is called from some random thread, but we need
             // to do the dialog on the GUI thread; use the reset_event
             // to block this thread until the user is done with the dialog.
-            var reset_event = new System.Threading.ManualResetEvent (false);
-
-            ThreadAssist.ProxyToMain (delegate {
+            ThreadAssist.BlockingProxyToMain (delegate {
                 Log.Debug ("Password requested to open document");
                 var dialog = new Hyena.Widgets.HigMessageDialog (
                     Window, DialogFlags.Modal, MessageType.Question, ButtonsType.None,
@@ -365,10 +377,7 @@ namespace PdfMod.Gui
                     Log.Information ("Password dialog cancelled");
                     args.Abort = true;
                 }
-                reset_event.Set ();
             });
-
-            reset_event.WaitOne ();
         }
 
         public Gtk.FileChooserDialog CreateChooser (string title, FileChooserAction action)
diff --git a/src/PdfMod/Pdf/Document.cs b/src/PdfMod/Pdf/Document.cs
index be45113..9515c1c 100644
--- a/src/PdfMod/Pdf/Document.cs
+++ b/src/PdfMod/Pdf/Document.cs
@@ -144,7 +144,8 @@ namespace PdfMod.Pdf
             Uri = uri_obj.AbsoluteUri;
             SuggestedSavePath = Path = uri_obj.LocalPath;
 
-            pdf_document = PdfSharp.Pdf.IO.PdfReader.Open (Path, PdfDocumentOpenMode.Modify | PdfDocumentOpenMode.Import, passwordProvider);
+            pdf_document = PdfSharp.Pdf.IO.PdfReader.Open (Path, PdfDocumentOpenMode.Modify, passwordProvider);
+
             for (int i = 0; i < pdf_document.PageCount; i++) {
                 var page = new Page (pdf_document.Pages[i]) {
                     Document = this,
@@ -158,8 +159,13 @@ namespace PdfMod.Pdf
             OnChanged ();
         }
 
-        public bool HasUnsavedChanged {
-            get { return tmp_uri != null || save_timeout_id != 0; }
+        private bool has_unsaved_changes;
+        public bool HasUnsavedChanges {
+            get { return has_unsaved_changes || tmp_uri != null || save_timeout_id != 0; }
+            set {
+                has_unsaved_changes = value;
+                OnChanged ();
+            }
         }
 
         public long FileSize {
@@ -258,6 +264,7 @@ namespace PdfMod.Pdf
         public void Save (string uri)
         {
             Pdf.Save (uri);
+            has_unsaved_changes = false;
             Log.DebugFormat ("Saved to {0}", uri);
             Uri = uri;
 
diff --git a/src/Resources/UIManager.xml b/src/Resources/UIManager.xml
index a8c68dd..b6cc7a0 100644
--- a/src/Resources/UIManager.xml
+++ b/src/Resources/UIManager.xml
@@ -40,6 +40,7 @@
       <separator/>
       <menuitem action="FullScreenView"/>
       <menuitem action="ViewToolbar"/>
+      <menuitem action="ViewBookmarks"/>
     </menu>
     <menu action="HelpMenu">
       <menuitem action="Help"/>



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