[latexila] StructureModel: a custom Gtk.TreeModel
- From: Sébastien Wilmet <swilmet src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [latexila] StructureModel: a custom Gtk.TreeModel
- Date: Fri, 10 Jun 2011 19:43:30 +0000 (UTC)
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]