[latexila] StructureModel: a custom Gtk.TreeModel



commit 7dbe79ca9c9fdfceec47a8d046e18f7516d76b2e
Author: Sébastien Wilmet <sebastien wilmet gmail com>
Date:   Sun Jun 5 01:27:57 2011 +0200

    StructureModel: a custom Gtk.TreeModel
    
    Using a separate tree (with GNode's) and then populate this tree into a
    Gtk.TreeStore was not very clean, and the data were stored two times.
    
    A custom TreeModel is a lot better, a bit more complicated, but for the
    update on the fly of the structure (when the document is modified) it
    will be really useful, mainly for performance reasons.

 TODO                        |   33 ++--
 src/document_structure.vala |  186 ++----------------
 src/structure.vala          |  110 ++++------
 src/structure_model.vala    |  475 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 552 insertions(+), 252 deletions(-)
---
diff --git a/TODO b/TODO
index 9f2f247..f7c6496 100644
--- a/TODO
+++ b/TODO
@@ -7,33 +7,36 @@ LaTeXila 2.2
 ============
 
 - Structure (list of chapters, sections, etc. to easily navigate in a document):
-	- Create a custom GtkTreeModel because using a separate tree (with GNodes) becomes
-	  too complicated/hackish for the update on the fly.
-	  If a custom GtkTreeModel is too complicated for our purpose, find a better way than
-	  repopulating all the TreeModel from the GNodes when items are shifted.
-
-	- Update on the fly the structure when the document is modified. An item can be inserted,
-	  deleted or modified. The simplest way I think is to re-run the parsing only on the modified
-	  lines (with a lower and upper bounds) every 2 seconds for example. This way, we simply
-	  delete all items between the two bounds, and the parsing will re-add them correcly.
+	- Doesn't take into account stuff in verbatim environment.
 
 	- Different style (e.g. italic) for commented items.
 
-	- Doesn't take into account stuff in verbatim environment.
+	- Add a show/hide button for sections.
+	  For example, to search the remaining TODOs and FIXMEs, it's boring to have all the sections.
+	
+	- Show/hide buttons: find a way to easily select only one item, and unselect the others.
+	  Also, find a way to easily select all the items.
+	  An idea is to add a menu on a right click on a button.
 
 	- Right click: cut, copy, paste below, select, delete, comment, shift left/right.
 	  Shift left/right is new comparated to Kile. For example we have a big section (with
 	  subsections, etc.) and we want to shift it to the left so it becomes a chapter (the
 	  subsections becomes sections, etc.).
+	
+	(-) Update on the fly the structure when the document is modified. An item can be inserted,
+	    deleted or modified. The simplest way I think is to re-run the parsing only on the modified
+	    lines (with a lower and upper bounds) every 2 seconds for example. This way, we simply
+	    delete all items between the two bounds, and the parsing will re-add them correcly.
 
-	- Scroll to the nearest item in the structure when the document's cursor moves.
+	(-) Scroll to the nearest item in the structure when the document's cursor moves.
 
-	- If the document's cursor is _in_ an item, select it in the structure. This is more
-	  complicated than it sounds because now we only know the beginning of an item, we don't
-	  know where the item ends.
+	(-) If the document's cursor is _in_ an item, select it in the structure. This is more
+	    complicated than it sounds because now we only know the beginning of an item, we don't
+	    know where the item ends.
 
 - Build Tools:
-	- build tools: copy button to make a copy of a build tool, so we can modify the copy and keep the original
+	- build tools: copy button to make a copy of a build tool,
+	  so we can modify the copy and keep the original
 	- right click menu in bottom panel: copy the line to the clipboard
 
 - Cleanup Build Files: if a project is defined, some *.aux files are not removed
diff --git a/src/document_structure.vala b/src/document_structure.vala
index 0007811..5ab1f02 100644
--- a/src/document_structure.vala
+++ b/src/document_structure.vala
@@ -21,19 +21,12 @@ using Gtk;
 
 public class DocumentStructure : GLib.Object
 {
-    private struct DataNode
-    {
-        StructType type;
-        string text;
-        TextMark mark;
-    }
-
     private unowned TextBuffer _doc;
     private int _nb_marks = 0;
     private static const string MARK_NAME_PREFIX = "struct_item_";
 
     private bool _insert_at_end = true;
-    private Node<DataNode?> _tree;
+    private StructureModel _model = null;
 
     private static Regex? _comment_regex = null;
 
@@ -64,8 +57,7 @@ public class DocumentStructure : GLib.Object
     public void parse ()
     {
         // reset
-        DataNode empty_data = {};
-        _tree = new Node<DataNode?> (empty_data);
+        _model = new StructureModel ();
         _in_figure_env = false;
         _in_table_env = false;
         clear_all_structure_marks ();
@@ -77,6 +69,11 @@ public class DocumentStructure : GLib.Object
         });
     }
 
+    public StructureModel get_model ()
+    {
+        return _model;
+    }
+
     // Parse the document. Returns false if finished, true otherwise.
     private bool parse_impl ()
     {
@@ -111,15 +108,14 @@ public class DocumentStructure : GLib.Object
 
         /* search comments (begin with '%') */
 
-        for (_doc.get_iter_at_line (out iter, _start_parsing_line) ;
+        _doc.get_iter_at_line (out iter, _start_parsing_line);
 
-            iter.forward_search ("%",
+        while (iter.forward_search ("%",
             TextSearchFlags.TEXT_ONLY | TextSearchFlags.VISIBLE_ONLY,
-            null, out iter, limit) ;
-
-            iter.forward_visible_line ())
+            null, out iter, limit))
         {
             search_comment (iter);
+            iter.forward_visible_line ();
         }
 
         if (limit_parsing)
@@ -269,133 +265,15 @@ public class DocumentStructure : GLib.Object
 
     private void add_item (StructType type, string text, TextIter iter)
     {
-        DataNode data = {};
+        StructData data = {};
         data.type = type;
         data.text = text;
         data.mark = create_text_mark_from_iter (iter);
 
         if (_insert_at_end)
-            add_item_at_end (data);
+            _model.add_item_at_end (data);
         else
-            add_item_in_middle (data);
-    }
-
-    private void add_item_at_end (DataNode item)
-    {
-        /* search the parent, based on the type */
-        unowned Node<DataNode?> parent = _tree;
-        int item_depth = item.type;
-
-        while (true)
-        {
-            unowned Node<DataNode?> last_child = parent.last_child ();
-            if (last_child == null)
-                break;
-
-            int cur_depth = last_child.data.type;
-            if (cur_depth >= item_depth || cur_depth > StructType.SUBPARAGRAPH)
-                break;
-
-            parent = last_child;
-        }
-
-        // append the item
-        parent.append_data (item);
-    }
-
-    // In the middle means that we have to find where to insert the data in the tree.
-    // If items have to be shifted (for example: insert a chapter in the middle of
-    // sections), it will be done by insert_item_at_position().
-    private void add_item_in_middle (DataNode item)
-    {
-        // if the tree is empty
-        if (_tree.is_leaf ())
-        {
-            _tree.append_data (item);
-            return;
-        }
-
-        int pos = get_position_from_mark (item.mark);
-        unowned Node<DataNode?> cur_parent = _tree;
-        while (true)
-        {
-            unowned Node<DataNode?> cur_child = cur_parent.first_child ();
-            int child_index = 0;
-            while (true)
-            {
-                int cur_pos = get_position_from_mark (cur_child.data.mark);
-
-                if (cur_pos > pos)
-                {
-                    if (child_index == 0)
-                    {
-                        insert_item_at_position (item, cur_parent, child_index);
-                        return;
-                    }
-
-                    unowned Node<DataNode?> prev_child = cur_child.prev_sibling ();
-                    if (prev_child.is_leaf ())
-                    {
-                        insert_item_at_position (item, cur_parent, child_index);
-                        return;
-                    }
-
-                    cur_parent = prev_child;
-                    break;
-                }
-
-                unowned Node<DataNode?> next_child = cur_child.next_sibling ();
-
-                // current child is the last child
-                if (next_child == null)
-                {
-                    if (cur_child.is_leaf ())
-                    {
-                        insert_item_at_position (item, cur_parent, child_index+1);
-                        return;
-                    }
-
-                    cur_parent = cur_child;
-                    break;
-                }
-
-                cur_child = next_child;
-                child_index++;
-            }
-        }
-    }
-
-    private void insert_item_at_position (DataNode item, Node<DataNode?> parent, int pos)
-    {
-        // If inserting a simple item (not a section) between sections, for example:
-        // chapter
-        //   section 1
-        //   => insert simple item here
-        //   section 2
-        //
-        // The item's parent will 'section 1' instead of 'chapter'.
-        if (pos > 0)
-        {
-            unowned Node<DataNode?> prev = parent.nth_child (pos - 1);
-            bool prev_is_section = prev.data.type <= StructType.SUBPARAGRAPH;
-            bool item_is_section = item.type <= StructType.SUBPARAGRAPH;
-
-            if (prev_is_section && ! item_is_section)
-            {
-                prev.append_data (item);
-                return;
-            }
-        }
-
-        parent.insert_data (pos, item);
-    }
-
-    private static int get_position_from_mark (TextMark mark)
-    {
-        TextIter iter;
-        TextBuffer doc = mark.get_buffer ();
-        doc.get_iter_at_mark (out iter, mark);
-        return iter.get_offset ();
+            _model.add_item_in_middle (data);
     }
 
     private TextMark create_text_mark_from_iter (TextIter iter)
@@ -421,42 +299,6 @@ public class DocumentStructure : GLib.Object
         _nb_marks = 0;
     }
 
-    public void populate_tree_store (TreeStore store)
-    {
-        populate_tree_store_at_node (store, _tree);
-    }
-
-    // The data are first inserted in Gnodes. When the parsing is done, this method is
-    // called to populate the tree store with the data contained in the GNodes.
-    private void populate_tree_store_at_node (TreeStore store, Node<DataNode?> node,
-        TreeIter? parent = null, bool root_node = true)
-    {
-        TreeIter? iter = null;
-        if (! root_node)
-            iter = add_item_to_tree_store (store, parent, node.data);
-
-        node.children_foreach (TraverseFlags.ALL, (child_node) =>
-        {
-            populate_tree_store_at_node (store, child_node, iter, false);
-        });
-    }
-
-    private TreeIter add_item_to_tree_store (TreeStore store, TreeIter? parent,
-        DataNode data)
-    {
-        TreeIter iter;
-        store.append (out iter, parent);
-        store.set (iter,
-            StructItem.PIXBUF, Structure.get_icon_from_type (data.type),
-            StructItem.TYPE, data.type,
-            StructItem.TEXT, data.text,
-            StructItem.TOOLTIP, Structure.get_type_name (data.type),
-            StructItem.MARK, data.mark,
-            -1);
-
-        return iter;
-    }
-
     private StructType? get_type_from_simple_command_name (string name)
     {
         switch (name)
diff --git a/src/structure.vala b/src/structure.vala
index e843f1a..89523a2 100644
--- a/src/structure.vala
+++ b/src/structure.vala
@@ -19,16 +19,6 @@
 
 using Gtk;
 
-public enum StructItem
-{
-    PIXBUF,
-    TYPE,
-    TEXT,
-    TOOLTIP,
-    MARK,
-    N_COLUMNS
-}
-
 public enum StructType
 {
     PART = 0,
@@ -51,7 +41,6 @@ public class Structure : VBox
 {
     private unowned MainWindow _main_window;
     private GLib.Settings _settings;
-    private TreeStore _tree_store;
     private TreeModelFilter _tree_filter;
     private TreeView _tree_view;
     private bool[] _visible_types;
@@ -94,6 +83,8 @@ public class Structure : VBox
 
         _visible_types[StructType.FIXME] =
             _settings.get_boolean ("structure-show-fixme");
+
+        // the other types are initialized in init_choose_min_level()
     }
 
     public void save_state ()
@@ -121,7 +112,7 @@ public class Structure : VBox
         /* save min level */
 
         int min_level = StructType.PART;
-        for (int level = 0 ; level <= StructType.SUBPARAGRAPH ; level++)
+        for (int level = min_level ; is_section ((StructType) level) ; level++)
         {
             if (! _visible_types[level])
                 break;
@@ -138,11 +129,12 @@ public class Structure : VBox
 
         // refresh button
         Button refresh_button = Utils.get_toolbar_button (Stock.REFRESH);
+        refresh_button.tooltip_text = _("Refresh");
         hbox.pack_start (refresh_button);
 
         refresh_button.clicked.connect (() =>
         {
-            parse_document (_main_window.active_document);
+            show_document (_main_window.active_document, true);
         });
 
         // expand all button
@@ -210,24 +202,7 @@ public class Structure : VBox
 
     private void init_tree_view ()
     {
-        _tree_store = new TreeStore (StructItem.N_COLUMNS,
-            typeof (string),     // pixbuf (stock-id)
-            typeof (StructType), // item type
-            typeof (string),     // text
-            typeof (string),     // tooltip
-            typeof (TextMark)    // mark
-            );
-
-        _tree_filter = new TreeModelFilter (_tree_store, null);
-        _tree_filter.set_visible_func ((model, iter) =>
-        {
-            StructType type;
-            model.get (iter, StructItem.TYPE, out type, -1);
-
-            return _visible_types[type];
-        });
-
-        _tree_view = new TreeView.with_model (_tree_filter);
+        _tree_view = new TreeView ();
         _tree_view.headers_visible = false;
 
         TreeViewColumn column = new TreeViewColumn ();
@@ -236,15 +211,15 @@ public class Structure : VBox
         // icon
         CellRendererPixbuf pixbuf_renderer = new CellRendererPixbuf ();
         column.pack_start (pixbuf_renderer, false);
-        column.set_attributes (pixbuf_renderer, "stock-id", StructItem.PIXBUF, null);
+        column.set_attributes (pixbuf_renderer, "stock-id", StructColumn.PIXBUF, null);
 
         // name
         CellRendererText text_renderer = new CellRendererText ();
         column.pack_start (text_renderer, true);
-        column.set_attributes (text_renderer, "text", StructItem.TEXT, null);
+        column.set_attributes (text_renderer, "text", StructColumn.TEXT, null);
 
         // tooltip
-        _tree_view.set_tooltip_column (StructItem.TOOLTIP);
+        _tree_view.set_tooltip_column (StructColumn.TOOLTIP);
 
         // selection
         TreeSelection select = _tree_view.get_selection ();
@@ -305,7 +280,7 @@ public class Structure : VBox
             TreeModel model = (TreeModel) list_store;
             model.get (iter, MinLevelColumn.TYPE, out selected_type, -1);
 
-            for (int type = 0 ; type <= StructType.SUBPARAGRAPH ; type++)
+            for (int type = 0 ; is_section ((StructType) type) ; type++)
                 _visible_types[type] = type <= selected_type;
 
             if (_tree_filter != null)
@@ -331,7 +306,7 @@ public class Structure : VBox
             return false;
 
         TextMark mark;
-        model.get (tree_iter, StructItem.MARK, out mark, -1);
+        model.get (tree_iter, StructColumn.MARK, out mark, -1);
 
         TextBuffer doc = mark.get_buffer ();
         return_val_if_fail (doc == _main_window.active_document, false);
@@ -347,59 +322,63 @@ public class Structure : VBox
         return true;
     }
 
-    // TODO: delete this function when the refresh button is removed
-    private void parse_document (Document? doc)
+    private void show_active_document ()
     {
-        clear ();
-
-        if (doc == null)
-            return;
-
-        DocumentStructure doc_struct = doc.get_structure ();
-        doc_struct.parse ();
-        populate (doc_struct);
+        show_document (_main_window.active_document);
     }
 
-    private void populate_active_document ()
+    private void show_document (Document? doc, bool force_parse = false)
     {
-        clear ();
-
-        Document? doc = _main_window.active_document;
         if (doc == null)
             return;
 
-        populate (doc.get_structure ());
-    }
+        DocumentStructure doc_struct = doc.get_structure ();
 
-    private void clear ()
-    {
-        _tree_store.clear ();
-        _tree_view.columns_autosize ();
+        if (force_parse)
+            doc_struct.parse ();
+
+        set_model (doc_struct.get_model ());
     }
 
-    private void populate (DocumentStructure doc_struct)
+    private void set_model (StructureModel model)
     {
-        Idle.add (() =>
+        _tree_filter = new TreeModelFilter (model, null);
+        _tree_filter.set_visible_func ((mod, iter) =>
         {
-            doc_struct.populate_tree_store (_tree_store);
-            _tree_view.expand_all ();
+            StructType type;
+            mod.get (iter, StructColumn.TYPE, out type, -1);
 
-            // remove the idle source
-            return false;
+            return _visible_types[type];
         });
+
+        _tree_view.set_model (_tree_filter);
+
+        // the flush queue is needed because the expand_all doesn't work without
+        Utils.flush_queue ();
+        _tree_view.expand_all ();
+
+        _tree_view.columns_autosize ();
     }
 
     public void connect_parsing ()
     {
-        _main_window.notify["active-document"].connect (populate_active_document);
-        populate_active_document ();
+        _main_window.notify["active-document"].connect (show_active_document);
+        show_active_document ();
     }
 
     public void disconnect_parsing ()
     {
-        _main_window.notify["active-document"].disconnect (populate_active_document);
+        _main_window.notify["active-document"].disconnect (show_active_document);
+    }
+
+    // Here it's the general meaning of "section" (part -> subparagraph).
+    // A label for example is not a section.
+    public static bool is_section (StructType type)
+    {
+        return type <= StructType.SUBPARAGRAPH;
     }
 
+    // TODO get the icon from an array instead of using a switch
     public static string? get_icon_from_type (StructType type)
     {
         switch (type)
@@ -444,6 +423,7 @@ public class Structure : VBox
         }
     }
 
+    // TODO get the name from an array instead of using a switch
     public static string? get_type_name (StructType type)
     {
         switch (type)
diff --git a/src/structure_model.vala b/src/structure_model.vala
new file mode 100644
index 0000000..ae3d910
--- /dev/null
+++ b/src/structure_model.vala
@@ -0,0 +1,475 @@
+/*
+ * This file is part of LaTeXila.
+ *
+ * Copyright © 2011 Sébastien Wilmet
+ *
+ * LaTeXila 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LaTeXila 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 LaTeXila.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Gtk;
+
+public struct StructData
+{
+    StructType type;
+    string text;
+    TextMark mark;
+}
+
+public enum StructColumn
+{
+    PIXBUF,
+    TYPE,
+    TEXT,
+    TOOLTIP,
+    MARK,
+    N_COLUMNS
+}
+
+public class StructureModel : TreeModel, GLib.Object
+{
+    private Type[] _column_types;
+    private Node<StructData?> _tree;
+    private int _stamp;
+
+    public StructureModel ()
+    {
+        _column_types = new Type[StructColumn.N_COLUMNS];
+        _column_types[StructColumn.PIXBUF]  = typeof (string);
+        _column_types[StructColumn.TYPE]    = typeof (StructType);
+        _column_types[StructColumn.TEXT]    = typeof (string);
+        _column_types[StructColumn.TOOLTIP] = typeof (string);
+        _column_types[StructColumn.MARK]    = typeof (TextMark);
+
+        StructData empty_data = {};
+        _tree = new Node<StructData?> (empty_data);
+
+        new_stamp ();
+    }
+
+    // A new stamp should be generated each time the data in the model change
+    private void new_stamp ()
+    {
+        _stamp = (int) Random.next_int ();
+    }
+
+    private TreeIter? create_iter_at_node (Node<StructData?> node)
+    {
+        return_val_if_fail (node != _tree, null);
+
+        TreeIter new_iter = TreeIter ();
+        new_iter.stamp = _stamp;
+        new_iter.user_data = node;
+        return new_iter;
+    }
+
+    private bool iter_is_valid (TreeIter iter)
+    {
+        if (iter.stamp != _stamp)
+        {
+//            stderr.printf ("iter not valid: bad stamp\n");
+            return false;
+        }
+
+        if (iter.user_data == null)
+        {
+//            stderr.printf ("iter not valid: bad user_data\n");
+            return false;
+        }
+
+        unowned Node<StructData?> node = get_node_from_iter (iter);
+        if (node.data == null)
+        {
+//            stderr.printf ("iter not valid: bad node data\n");
+            return false;
+        }
+
+        StructData data = node.data;
+        if (data.text == null)
+        {
+//            stderr.printf ("iter not valid: text is null\n");
+            return false;
+        }
+
+        return true;
+    }
+
+    private bool column_is_valid (int index)
+    {
+        return 0 <= index && index < StructColumn.N_COLUMNS;
+    }
+
+    // iter must be valid
+    private unowned Node<StructData?> get_node_from_iter (TreeIter iter)
+    {
+        return (Node<StructData?>) iter.user_data;
+    }
+
+    /*************************************************************************/
+    // TreeModel interface implementation, based on a N-ary tree (GNode)
+
+    public Type get_column_type (int index)
+    {
+        return_val_if_fail (column_is_valid (index), Type.INVALID);
+
+        return _column_types[index];
+    }
+
+    public int get_n_columns ()
+    {
+        return StructColumn.N_COLUMNS;
+    }
+
+    public TreeModelFlags get_flags ()
+    {
+        return 0;
+    }
+
+    public bool iter_has_child (TreeIter iter)
+    {
+        return_val_if_fail (iter_is_valid (iter), false);
+
+        unowned Node<StructData?> node = get_node_from_iter (iter);
+        return ! node.is_leaf ();
+    }
+
+    public int iter_n_children (TreeIter? iter)
+    {
+        unowned Node<StructData?> node;
+        if (iter == null)
+            node = _tree;
+        else
+        {
+            return_val_if_fail (iter_is_valid (iter), -1);
+            node = get_node_from_iter (iter);
+        }
+
+        return (int) node.n_children ();
+    }
+
+    public void get_value (TreeIter iter, int column, out Value val)
+    {
+        return_if_fail (iter_is_valid (iter));
+        return_if_fail (column_is_valid (column));
+
+        unowned Node<StructData?> node = get_node_from_iter (iter);
+        StructData data = node.data;
+
+        switch (column)
+        {
+            case StructColumn.TYPE:
+                val = data.type;
+                break;
+
+            case StructColumn.TEXT:
+                val = data.text;
+                break;
+
+            case StructColumn.MARK:
+                val = data.mark;
+                break;
+
+            case StructColumn.PIXBUF:
+                val = Structure.get_icon_from_type (data.type);
+                break;
+
+            case StructColumn.TOOLTIP:
+                val = Structure.get_type_name (data.type);
+                break;
+
+            default:
+                return_if_reached ();
+        }
+    }
+
+    public bool iter_children (out TreeIter iter, TreeIter? parent)
+    {
+        unowned Node<StructData?> node;
+        if (parent == null)
+            node = _tree;
+        else
+        {
+            return_val_if_fail (iter_is_valid (parent), false);
+            node = get_node_from_iter (parent);
+        }
+
+        if (node.is_leaf ())
+            return false;
+
+        // FIXME check if &iter is null?
+        // I think there is an easier method now.
+        iter = create_iter_at_node (node.first_child ());
+        return true;
+    }
+
+    public bool iter_next (ref TreeIter iter)
+    {
+        return_val_if_fail (iter_is_valid (iter), false);
+
+        unowned Node<StructData?> node = get_node_from_iter (iter);
+        unowned Node<StructData?>? next_node = node.next_sibling ();
+        if (next_node == null)
+            return false;
+
+        iter = create_iter_at_node (next_node);
+        return true;
+    }
+
+    public bool iter_nth_child (out TreeIter iter, TreeIter? parent, int n)
+    {
+        unowned Node<StructData?> node;
+        if (parent == null)
+            node = _tree;
+        else
+        {
+            return_val_if_fail (iter_is_valid (parent), false);
+            node = get_node_from_iter (parent);
+        }
+
+        if (node.is_leaf ())
+            return false;
+
+        if (n < 0 || node.n_children () <= n)
+            return false;
+
+        // FIXME check if &iter is null?
+        // I think there is an easier method now.
+        iter = create_iter_at_node (node.nth_child ((uint) n));
+        return true;
+    }
+
+    public bool iter_parent (out TreeIter iter, TreeIter child)
+    {
+        return_val_if_fail (iter_is_valid (child), false);
+
+        unowned Node<StructData?> node = get_node_from_iter (child);
+        unowned Node<StructData?>? parent_node = node.parent;
+
+        // normally, there is always a parent
+        return_val_if_fail (parent_node != null, false);
+
+        // but the root is not a good parent
+        if (parent_node == _tree)
+            return false;
+
+        iter = create_iter_at_node (parent_node);
+        return true;
+    }
+
+    public bool get_iter (out TreeIter iter, TreePath path)
+    {
+        int depth = path.get_depth ();
+        return_val_if_fail (1 <= depth, false);
+
+        unowned int[] indices = path.get_indices ();
+
+        unowned Node<StructData?> node = _tree;
+        for (int cur_depth = 0 ; cur_depth < depth ; cur_depth++)
+        {
+            int indice = indices[cur_depth];
+            if (indice < 0 || node.n_children () <= indice)
+                return false;
+
+            node = node.nth_child (indice);
+        }
+
+        iter = create_iter_at_node (node);
+        return true;
+    }
+
+    // TODO TreePath -> TreePath? when the next version of Vala is released
+    // See https://bugzilla.gnome.org/show_bug.cgi?id=651871
+    public TreePath get_path (TreeIter iter)
+    {
+        return_val_if_fail (iter_is_valid (iter), null);
+
+        TreePath path = new TreePath ();
+        unowned Node<StructData?> node = get_node_from_iter (iter);
+
+        while (! node.is_root ())
+        {
+            int pos = node.parent.child_position (node);
+            path.prepend_index (pos);
+            node = node.parent;
+        }
+
+        return path;
+    }
+
+    // TODO remove (un)ref_node() when the next version of Vala is released
+    // See https://bugzilla.gnome.org/show_bug.cgi?id=651872
+    public void ref_node (TreeIter iter)
+    {
+    }
+
+    public void unref_node (TreeIter iter)
+    {
+    }
+
+
+    /*************************************************************************/
+    // Custom methods (add an item)
+
+    public void add_item_at_end (StructData item)
+    {
+        /* search the parent, based on the type */
+        unowned Node<StructData?> parent = _tree;
+        StructType item_depth = item.type;
+
+        while (true)
+        {
+            unowned Node<StructData?> last_child = parent.last_child ();
+            if (last_child == null)
+                break;
+
+            StructType cur_depth = last_child.data.type;
+            if (item_depth <= cur_depth || ! Structure.is_section (cur_depth))
+                break;
+
+            parent = last_child;
+        }
+
+        append_item (parent, item);
+    }
+
+    // In the middle means that we have to find where to insert the data in the tree.
+    // If items have to be shifted (for example: insert a chapter in the middle of
+    // sections), it will be done by insert_item_at_position().
+    public void add_item_in_middle (StructData item)
+    {
+        // if the tree is empty
+        if (_tree.is_leaf ())
+        {
+            append_item (_tree, item);
+            return;
+        }
+
+        int pos = get_position_from_mark (item.mark);
+        unowned Node<StructData?> cur_parent = _tree;
+        while (true)
+        {
+            unowned Node<StructData?> cur_child = cur_parent.first_child ();
+            int child_index = 0;
+            while (true)
+            {
+                int cur_pos = get_position_from_mark (cur_child.data.mark);
+
+                if (cur_pos > pos)
+                {
+                    if (child_index == 0)
+                    {
+                        insert_item_at_position (item, cur_parent, child_index);
+                        return;
+                    }
+
+                    unowned Node<StructData?> prev_child = cur_child.prev_sibling ();
+                    if (prev_child.is_leaf ())
+                    {
+                        insert_item_at_position (item, cur_parent, child_index);
+                        return;
+                    }
+
+                    cur_parent = prev_child;
+                    break;
+                }
+
+                unowned Node<StructData?> next_child = cur_child.next_sibling ();
+
+                // current child is the last child
+                if (next_child == null)
+                {
+                    if (cur_child.is_leaf ())
+                    {
+                        insert_item_at_position (item, cur_parent, child_index + 1);
+                        return;
+                    }
+
+                    cur_parent = cur_child;
+                    break;
+                }
+
+                cur_child = next_child;
+                child_index++;
+            }
+        }
+    }
+
+    private void insert_item_at_position (StructData item, Node<StructData?> parent,
+        int pos)
+    {
+        // If a simple item (not a section) is inserted between sections. For example:
+        // chapter
+        //   section 1
+        //   => insert simple item here
+        //   section 2
+        //
+        // The item's parent will be 'section 1' instead of 'chapter'.
+        if (pos > 0)
+        {
+            unowned Node<StructData?> prev = parent.nth_child (pos - 1);
+            bool prev_is_section = Structure.is_section (prev.data.type);
+            bool item_is_section = Structure.is_section (item.type);
+
+            if (prev_is_section && ! item_is_section)
+            {
+                append_item (prev, item);
+                return;
+            }
+        }
+
+        insert_item (parent, pos, item);
+    }
+
+    private void append_item (Node<StructData?> parent, StructData item)
+    {
+        insert_item (parent, -1, item);
+    }
+
+    // insert the item, and emits the appropriate signals
+    private void insert_item (Node<StructData?> parent, int pos, StructData item)
+    {
+        return_if_fail (item.text != null);
+
+        unowned Node<StructData?> new_node = parent.insert_data (pos, item);
+
+        new_stamp ();
+
+        TreeIter item_iter = create_iter_at_node (new_node);
+        TreePath item_path = get_path (item_iter);
+        row_inserted (item_path, item_iter);
+
+        // Attention, the row-has-child-toggled signal must be emitted _after_,
+        // else there are strange errors.
+        if (parent != _tree && parent.n_children () == 1)
+        {
+            TreeIter parent_iter = create_iter_at_node (parent);
+            TreePath parent_path = get_path (parent_iter);
+            row_has_child_toggled (parent_path, parent_iter);
+        }
+    }
+
+    private static int get_position_from_mark (TextMark mark)
+    {
+        TextIter iter;
+        TextBuffer doc = mark.get_buffer ();
+        doc.get_iter_at_mark (out iter, mark);
+        return iter.get_offset ();
+    }
+
+//    private void print_item (StructData item)
+//    {
+//        stdout.printf ("\n=== ITEM ===\n");
+//        stdout.printf ("Type: %s\n", Structure.get_type_name (item.type));
+//        stdout.printf ("Text: %s\n\n", item.text);
+//    }
+}



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