/* ide-tree-node.c * * Copyright 2018-2019 Christian Hergert * * 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 3 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, see . * * SPDX-License-Identifier: GPL-3.0-or-later */ #define G_LOG_DOMAIN "ide-tree-node" #include "config.h" #include #include "ide-tree-model.h" #include "ide-tree-node.h" #include "ide-tree-private.h" /** * SECTION:ide-tree-node * @title: IdeTreeNode * @short_description: a node within the tree * * The #IdeTreeNode class is used to represent an item that should * be displayed in the tree of the Ide application. The * #IdeTreeAddin plugins create and maintain these nodes during the * lifetime of the program. * * Plugins that want to add items to the tree should implement the * #IdeTreeAddin interface and register it during plugin * initialization. * * Since: 3.32 */ struct _IdeTreeNode { GObject parent_instance; /* A pointer to the model, which is only set on the root node. */ IdeTreeModel *model; /* * The following are fields containing the values for various properties * on the tree node. Usually, icon, display_name, and item will be set * on all nodes. */ GIcon *icon; GIcon *expanded_icon; gchar *display_name; GObject *item; gchar *tag; GList *emblems; /* * The following items are used to maintain a tree structure of * nodes for which we can use O(1) operations. The link is inserted * into the parents children queue. The parent pointer is unowned, * and set by the parent (cleared upon removal). * * This also allows maintaining the tree structure with zero additional * allocations beyond the nodes themselves. */ IdeTreeNode *parent; GQueue children; GList link; /* Foreground and Background colors */ GdkRGBA background; GdkRGBA foreground; /* Flags for state cell renderer */ IdeTreeNodeFlags flags; /* When did we start loading? This is used to avoid drawing "Loading..." * when the tree loads really quickly. Otherwise, we risk looking janky * when the loads are quite fast. */ gint64 started_loading_at; /* If we're currently loading */ guint is_loading : 1; /* If the node is a header (bold, etc) */ guint is_header : 1; /* If this is a synthesized empty node */ guint is_empty : 1; /* If there are errors associated with the node's item */ guint has_error : 1; /* If the node maybe has children */ guint children_possible : 1; /* If this node needs to have the children built */ guint needs_build_children : 1; /* If true, we remove all children on collapse */ guint reset_on_collapse : 1; /* If pango markup should be used */ guint use_markup : 1; /* If true, we use ide_clear_and_destroy_object() */ guint destroy_item : 1; /* If colors are set */ guint background_set : 1; guint foreground_set : 1; }; G_DEFINE_FINAL_TYPE (IdeTreeNode, ide_tree_node, G_TYPE_OBJECT) enum { PROP_0, PROP_CHILDREN_POSSIBLE, PROP_DESTROY_ITEM, PROP_DISPLAY_NAME, PROP_EXPANDED_ICON, PROP_EXPANDED_ICON_NAME, PROP_HAS_ERROR, PROP_ICON, PROP_ICON_NAME, PROP_IS_HEADER, PROP_ITEM, PROP_RESET_ON_COLLAPSE, PROP_TAG, PROP_USE_MARKUP, N_PROPS }; static GParamSpec *properties [N_PROPS]; static IdeTreeModel * ide_tree_node_get_model (IdeTreeNode *self) { return ide_tree_node_get_root (self)->model; } /** * ide_tree_node_new: * * Create a new #IdeTreeNode. * * Returns: (transfer full): a newly created #IdeTreeNode * * Since: 3.32 */ IdeTreeNode * ide_tree_node_new (void) { return g_object_new (IDE_TYPE_TREE_NODE, NULL); } static void ide_tree_node_emit_changed (IdeTreeNode *self) { g_autoptr(GtkTreePath) path = NULL; IdeTreeModel *model; GtkTreeIter iter = { .user_data = self }; g_assert (IDE_IS_TREE_NODE (self)); if (!(model = ide_tree_node_get_model (self))) return; if ((path = ide_tree_model_get_path_for_node (model, self))) gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter); } static void ide_tree_node_remove_with_dispose (IdeTreeNode *self, IdeTreeNode *child) { g_object_ref (child); ide_tree_node_remove (self, child); g_object_run_dispose (G_OBJECT (child)); g_object_unref (child); } static void ide_tree_node_dispose (GObject *object) { IdeTreeNode *self = (IdeTreeNode *)object; while (self->children.length > 0) ide_tree_node_remove_with_dispose (self, g_queue_peek_nth (&self->children, 0)); if (self->destroy_item && IDE_IS_OBJECT (self->item)) ide_clear_and_destroy_object (&self->item); else g_clear_object (&self->item); g_list_free_full (self->emblems, g_object_unref); self->emblems = NULL; g_clear_object (&self->icon); g_clear_object (&self->expanded_icon); g_clear_pointer (&self->display_name, g_free); g_clear_pointer (&self->tag, g_free); G_OBJECT_CLASS (ide_tree_node_parent_class)->dispose (object); } static void ide_tree_node_finalize (GObject *object) { IdeTreeNode *self = (IdeTreeNode *)object; g_clear_weak_pointer (&self->model); g_assert (self->children.head == NULL); g_assert (self->children.tail == NULL); g_assert (self->children.length == 0); if (self->destroy_item && IDE_IS_OBJECT (self->item)) ide_clear_and_destroy_object (&self->item); else g_clear_object (&self->item); g_clear_object (&self->icon); g_clear_object (&self->expanded_icon); g_clear_pointer (&self->display_name, g_free); g_clear_pointer (&self->tag, g_free); G_OBJECT_CLASS (ide_tree_node_parent_class)->finalize (object); } static void ide_tree_node_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { IdeTreeNode *self = IDE_TREE_NODE (object); switch (prop_id) { case PROP_CHILDREN_POSSIBLE: g_value_set_boolean (value, ide_tree_node_get_children_possible (self)); break; case PROP_DESTROY_ITEM: g_value_set_boolean (value, self->destroy_item); break; case PROP_DISPLAY_NAME: g_value_set_string (value, ide_tree_node_get_display_name (self)); break; case PROP_HAS_ERROR: g_value_set_boolean (value, ide_tree_node_get_has_error (self)); break; case PROP_ICON: g_value_set_object (value, ide_tree_node_get_icon (self)); break; case PROP_IS_HEADER: g_value_set_boolean (value, ide_tree_node_get_is_header (self)); break; case PROP_ITEM: g_value_set_object (value, ide_tree_node_get_item (self)); break; case PROP_RESET_ON_COLLAPSE: g_value_set_boolean (value, ide_tree_node_get_reset_on_collapse (self)); break; case PROP_TAG: g_value_set_string (value, ide_tree_node_get_tag (self)); break; case PROP_USE_MARKUP: g_value_set_boolean (value, ide_tree_node_get_use_markup (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void ide_tree_node_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { IdeTreeNode *self = IDE_TREE_NODE (object); switch (prop_id) { case PROP_CHILDREN_POSSIBLE: ide_tree_node_set_children_possible (self, g_value_get_boolean (value)); break; case PROP_DESTROY_ITEM: self->destroy_item = g_value_get_boolean (value); break; case PROP_DISPLAY_NAME: ide_tree_node_set_display_name (self, g_value_get_string (value)); break; case PROP_EXPANDED_ICON: ide_tree_node_set_expanded_icon (self, g_value_get_object (value)); break; case PROP_EXPANDED_ICON_NAME: ide_tree_node_set_expanded_icon_name (self, g_value_get_string (value)); break; case PROP_HAS_ERROR: ide_tree_node_set_has_error (self, g_value_get_boolean (value)); break; case PROP_ICON: ide_tree_node_set_icon (self, g_value_get_object (value)); break; case PROP_ICON_NAME: ide_tree_node_set_icon_name (self, g_value_get_string (value)); break; case PROP_IS_HEADER: ide_tree_node_set_is_header (self, g_value_get_boolean (value)); break; case PROP_ITEM: ide_tree_node_set_item (self, g_value_get_object (value)); break; case PROP_RESET_ON_COLLAPSE: ide_tree_node_set_reset_on_collapse (self, g_value_get_boolean (value)); break; case PROP_TAG: ide_tree_node_set_tag (self, g_value_get_string (value)); break; case PROP_USE_MARKUP: ide_tree_node_set_use_markup (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void ide_tree_node_class_init (IdeTreeNodeClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = ide_tree_node_dispose; object_class->finalize = ide_tree_node_finalize; object_class->get_property = ide_tree_node_get_property; object_class->set_property = ide_tree_node_set_property; /** * IdeTreeNode:children-possible: * * The "children-possible" property denotes if the node may have children * even if it doesn't have children yet. This is useful for delayed loading * of children nodes. * * Since: 3.32 */ properties [PROP_CHILDREN_POSSIBLE] = g_param_spec_boolean ("children-possible", "Children Possible", "If children are possible for the node", FALSE, (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); /** * IdeTreeNode:destroy-item: * * If %TRUE and #IdeTreeNode:item is an #IdeObject, it will be destroyed * when the node is destroyed. * * Since: 3.32 */ properties [PROP_DESTROY_ITEM] = g_param_spec_boolean ("destroy-item", "Destroy Item", "If the item should be destroyed with the node.", FALSE, (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * IdeTreeNode:display-name: * * The "display-name" property is the name for the node as it should be * displayed in the tree. * * Since: 3.32 */ properties [PROP_DISPLAY_NAME] = g_param_spec_string ("display-name", "Display Name", "Display name for the node in the tree", NULL, (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); /** * IdeTreeNode:expanded-icon: * * The "expanded-icon" property is the icon that should be displayed to the * user in the tree for this node. * * Since: 3.32 */ properties [PROP_EXPANDED_ICON] = g_param_spec_object ("expanded-icon", "Expanded Icon", "The expanded icon to display in the tree", G_TYPE_ICON, (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); /** * IdeTreeNode:expanded-icon-name: * * The "expanded-icon-name" is a convenience property to set the * #IdeTreeNode:expanded-icon property using an icon-name. * * Since: 3.32 */ properties [PROP_EXPANDED_ICON_NAME] = g_param_spec_string ("expanded-icon-name", "Expanded Icon Name", "The expanded icon-name for the GIcon", NULL, (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); /** * IdeTreeNode:has-error: * * The "has-error" property is true if the node should be rendered with * an error styling. This is useful when errors are known by the diagnostics * manager for a given file or folder. * * Since: 3.32 */ properties [PROP_HAS_ERROR] = g_param_spec_boolean ("has-error", "Has Error", "If the node has an error associated with it's item", FALSE, (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); /** * IdeTreeNode:icon: * * The "icon" property is the icon that should be displayed to the * user in the tree for this node. * * Since: 3.32 */ properties [PROP_ICON] = g_param_spec_object ("icon", "Icon", "The icon to display in the tree", G_TYPE_ICON, (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); /** * IdeTreeNode:icon-name: * * The "icon-name" is a convenience property to set the #IdeTreeNode:icon * property using an icon-name. * * Since: 3.32 */ properties [PROP_ICON_NAME] = g_param_spec_string ("icon-name", "Icon Name", "The icon-name for the GIcon", NULL, (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); /** * IdeTreeNode:is-header: * * The "is-header" property denotes the node should be styled as a group * header. * * Since: 3.32 */ properties [PROP_IS_HEADER] = g_param_spec_boolean ("is-header", "Is Header", "If the node is a header", FALSE, (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); /** * IdeTreeNode:item: * * The "item" property is an optional #GObject that can be used to * store information about the node, which is sometimes useful when * creating #IdeTreeAddin plugins. * * Since: 3.32 */ properties [PROP_ITEM] = g_param_spec_object ("item", "Item", "Item", G_TYPE_OBJECT, (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); /** * IdeTreeNode:reset-on-collapse: * * The "reset-on-collapse" denotes that children should be removed when * the node is collapsed. * * Since: 3.32 */ properties [PROP_RESET_ON_COLLAPSE] = g_param_spec_boolean ("reset-on-collapse", "Reset on Collapse", "If the children are removed when the node is collapsed", TRUE, (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); /** * IdeTreeNode:tag: * * The "tag" property can be used to denote the type of node when you do not have an * object to assign to #IdeTreeNode:item. * * See ide_tree_node_is_tag() to match a tag when building. * * Since: 3.32 */ properties [PROP_TAG] = g_param_spec_string ("tag", "Tag", "The tag for the node if any", NULL, (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); /** * IdeTreeNode:use-markup: * * If #TRUE, the "use-markup" property denotes that #IdeTreeNode:display-name * contains pango markup. * * Since: 3.32 */ properties [PROP_USE_MARKUP] = g_param_spec_boolean ("use-markup", "Use Markup", "If pango markup should be used", FALSE, (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (object_class, N_PROPS, properties); } static void ide_tree_node_init (IdeTreeNode *self) { self->reset_on_collapse = TRUE; self->link.data = self; } /** * ide_tree_node_get_display_name: * @self: a #IdeTreeNode * * Gets the #IdeTreeNode:display-name property. * * Returns: (nullable): a string containing the display name * * Since: 3.32 */ const gchar * ide_tree_node_get_display_name (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), NULL); return self->display_name; } /** * ide_tree_node_set_display_name: * * Sets the #IdeTreeNode:display-name property, which is the text to * use when displaying the item in the tree. * * Since: 3.32 */ void ide_tree_node_set_display_name (IdeTreeNode *self, const gchar *display_name) { g_return_if_fail (IDE_IS_TREE_NODE (self)); if (g_strcmp0 (display_name, self->display_name) != 0) { g_free (self->display_name); self->display_name = g_strdup (display_name); ide_tree_node_emit_changed (self); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DISPLAY_NAME]); } } /** * ide_tree_node_get_icon: * @self: a #IdeTree * * Gets the icon associated with the tree node. * * Returns: (transfer none) (nullable): a #GIcon or %NULL * * Since: 3.32 */ GIcon * ide_tree_node_get_icon (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), NULL); return self->icon; } /** * ide_tree_node_set_icon: * @self: a @IdeTreeNode * @icon: (nullable): a #GIcon or %NULL * * Sets the icon for the tree node. * * Since: 3.32 */ void ide_tree_node_set_icon (IdeTreeNode *self, GIcon *icon) { g_return_if_fail (IDE_IS_TREE_NODE (self)); if (g_set_object (&self->icon, icon)) { ide_tree_node_emit_changed (self); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ICON]); } } /** * ide_tree_node_get_expanded_icon: * @self: a #IdeTree * * Gets the expanded icon associated with the tree node. * * Returns: (transfer none) (nullable): a #GIcon or %NULL * * Since: 3.32 */ GIcon * ide_tree_node_get_expanded_icon (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), NULL); return self->expanded_icon ? self->expanded_icon : self->icon; } /** * ide_tree_node_set_expanded_icon: * @self: a @IdeTreeNode * @expanded_icon: (nullable): a #GIcon or %NULL * * Sets the expanded icon for the tree node. * * Since: 3.32 */ void ide_tree_node_set_expanded_icon (IdeTreeNode *self, GIcon *expanded_icon) { g_return_if_fail (IDE_IS_TREE_NODE (self)); if (g_set_object (&self->expanded_icon, expanded_icon)) { ide_tree_node_emit_changed (self); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_EXPANDED_ICON]); } } /** * ide_tree_node_get_item: * @self: a #IdeTreeNode * * Gets the item that has been associated with the node. * * Returns: (transfer none) (type GObject.Object) (nullable): a #GObject * if the item has been previously set. * * Since: 3.32 */ gpointer ide_tree_node_get_item (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), NULL); g_return_val_if_fail (!self->item || G_IS_OBJECT (self->item), NULL); return self->item; } void ide_tree_node_set_item (IdeTreeNode *self, gpointer item) { g_return_if_fail (IDE_IS_TREE_NODE (self)); g_return_if_fail (!item || G_IS_OBJECT (item)); if (g_set_object (&self->item, item)) { ide_tree_node_emit_changed (self); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ITEM]); } } static IdeTreeNodeVisit ide_tree_node_row_inserted_traverse_cb (IdeTreeNode *node, gpointer user_data) { IdeTreeModel *model = user_data; g_autoptr(GtkTreePath) path = NULL; GtkTreeIter iter = { .user_data = node }; g_assert (IDE_IS_TREE_NODE (node)); g_assert (IDE_IS_TREE_MODEL (model)); /* Ignore the root node, nothing to do with that */ if (ide_tree_node_is_root (node)) return IDE_TREE_NODE_VISIT_CHILDREN; /* It would be faster to create our paths as we traverse the tree, * but that complicates the traversal. Generally this path should get * hit very little (as usually it's only a single "child node"). */ if ((path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter))) { gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter); if (ide_tree_node_is_first (node)) { IdeTreeNode *parent = ide_tree_node_get_parent (node); if (!ide_tree_node_is_root (parent)) { iter.user_data = parent; gtk_tree_path_up (path); gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model), path, &iter); } } } return IDE_TREE_NODE_VISIT_CHILDREN; } static void ide_tree_node_row_inserted (IdeTreeNode *self, IdeTreeNode *child) { g_autoptr(GtkTreePath) path = NULL; IdeTreeModel *model; GtkTreeIter iter; g_assert (IDE_IS_MAIN_THREAD ()); g_assert (IDE_IS_TREE_NODE (self)); g_assert (IDE_IS_TREE_NODE (child)); if (!(model = ide_tree_node_get_model (self)) || !ide_tree_model_get_iter_for_node (model, &iter, child) || !(path = ide_tree_model_get_path_for_node (model, child))) return; ide_tree_node_traverse (child, G_PRE_ORDER, G_TRAVERSE_ALL, -1, ide_tree_node_row_inserted_traverse_cb, model); } void _ide_tree_node_set_model (IdeTreeNode *self, IdeTreeModel *model) { g_return_if_fail (IDE_IS_TREE_NODE (self)); g_return_if_fail (!model || IDE_IS_TREE_MODEL (model)); if (g_set_weak_pointer (&self->model, model)) { if (self->model != NULL) ide_tree_node_row_inserted (self, self); } } /** * ide_tree_node_prepend: * @self: a #IdeTreeNode * @child: a #IdeTreeNode * * Prepends @child as a child of @self at the 0 index. * * This operation is O(1). * * Since: 3.32 */ void ide_tree_node_prepend (IdeTreeNode *self, IdeTreeNode *child) { g_return_if_fail (IDE_IS_TREE_NODE (self)); g_return_if_fail (IDE_IS_TREE_NODE (child)); g_return_if_fail (child->parent == NULL); child->parent = self; g_object_ref (child); g_queue_push_head_link (&self->children, &child->link); ide_tree_node_row_inserted (self, child); } /** * ide_tree_node_append: * @self: a #IdeTreeNode * @child: a #IdeTreeNode * * Appends @child as a child of @self at the last position. * * This operation is O(1). * * Since: 3.32 */ void ide_tree_node_append (IdeTreeNode *self, IdeTreeNode *child) { g_return_if_fail (IDE_IS_TREE_NODE (self)); g_return_if_fail (IDE_IS_TREE_NODE (child)); g_return_if_fail (child->parent == NULL); child->parent = self; g_object_ref (child); g_queue_push_tail_link (&self->children, &child->link); ide_tree_node_row_inserted (self, child); } /** * ide_tree_node_insert_sorted: * @self: an #IdeTreeNode * @child: an #IdeTreeNode * @cmpfn: (scope call): an #IdeTreeNodeCompare * * Insert @child as a child of @self at the sorted position determined by @cmpfn * * This operation is O(n). * * Since: 3.32 */ void ide_tree_node_insert_sorted (IdeTreeNode *self, IdeTreeNode *child, IdeTreeNodeCompare cmpfn) { GList *link; g_return_if_fail (IDE_IS_TREE_NODE (self)); g_return_if_fail (IDE_IS_TREE_NODE (child)); g_return_if_fail (child->parent == NULL); link = g_queue_find_custom (&self->children, child, (GCompareFunc)cmpfn); if (link != NULL) ide_tree_node_insert_before (IDE_TREE_NODE (link->data), child); else ide_tree_node_append (self, child); } /** * ide_tree_node_insert_before: * @self: a #IdeTreeNode * @child: a #IdeTreeNode * * Inserts @child directly before @self by adding it to the parent of @self. * * This operation is O(1). * * Since: 3.32 */ void ide_tree_node_insert_before (IdeTreeNode *self, IdeTreeNode *child) { g_return_if_fail (IDE_IS_TREE_NODE (self)); g_return_if_fail (IDE_IS_TREE_NODE (child)); g_return_if_fail (self->parent != NULL); g_return_if_fail (child->parent == NULL); child->parent = self->parent; g_object_ref (child); _g_queue_insert_before_link (&self->parent->children, &self->link, &child->link); ide_tree_node_row_inserted (self, child); } /** * ide_tree_node_insert_after: * @self: a #IdeTreeNode * @child: a #IdeTreeNode * * Inserts @child directly after @self by adding it to the parent of @self. * * This operation is O(1). * * Since: 3.32 */ void ide_tree_node_insert_after (IdeTreeNode *self, IdeTreeNode *child) { g_return_if_fail (IDE_IS_TREE_NODE (self)); g_return_if_fail (IDE_IS_TREE_NODE (child)); g_return_if_fail (self->parent != NULL); g_return_if_fail (child->parent == NULL); child->parent = self->parent; g_object_ref (child); _g_queue_insert_after_link (&self->parent->children, &self->link, &child->link); ide_tree_node_row_inserted (self, child); } /** * ide_tree_node_remove: * @self: a #IdeTreeNode * @child: a #IdeTreeNode * * Removes the child node @child from @self. @self must be the parent of @child. * * This function is O(1). * * Since: 3.32 */ void ide_tree_node_remove (IdeTreeNode *self, IdeTreeNode *child) { g_autoptr(GtkTreePath) path = NULL; IdeTreeModel *model; g_return_if_fail (IDE_IS_TREE_NODE (self)); g_return_if_fail (IDE_IS_TREE_NODE (child)); g_return_if_fail (child->parent == self); if ((model = ide_tree_node_get_model (self))) path = ide_tree_model_get_path_for_node (model, child); child->parent = NULL; g_queue_unlink (&self->children, &child->link); if (path != NULL) gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); g_object_unref (child); } /** * ide_tree_node_get_parent: * @self: a #IdeTreeNode * * Gets the parent node of @self. * * Returns: (transfer none) (nullable): a #IdeTreeNode or %NULL * * Since: 3.32 */ IdeTreeNode * ide_tree_node_get_parent (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), NULL); return self->parent; } /** * ide_tree_node_get_root: * @self: a #IdeTreeNode * * Gets the root #IdeTreeNode by following the #IdeTreeNode:parent * properties of each node. * * Returns: (transfer none) (nullable): a #IdeTreeNode or %NULL * * Since: 3.32 */ IdeTreeNode * ide_tree_node_get_root (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), NULL); while (self->parent != NULL) self = self->parent; g_return_val_if_fail (IDE_IS_TREE_NODE (self), NULL); return self; } /** * ide_tree_node_holds: * @self: a #IdeTreeNode * @type: a #GType * * Checks to see if the #IdeTreeNode:item property matches @type * or is a subclass of @type. * * Returns: %TRUE if @self holds a @type item * * Since: 3.32 */ gboolean ide_tree_node_holds (IdeTreeNode *self, GType type) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE); return G_TYPE_CHECK_INSTANCE_TYPE (self->item, type); } /** * ide_tree_node_get_index: * @self: a #IdeTreeNode * * Gets the position of the @self. * * Returns: the offset of @self with it's siblings. * * Since: 3.32 */ guint ide_tree_node_get_index (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), 0); if (self->parent != NULL) return g_list_position (self->parent->children.head, &self->link); return 0; } /** * ide_tree_node_get_nth_child: * @self: a #IdeTreeNode * @index_: the index of the child * * Gets the @nth child of the tree node or %NULL if it does not exist. * * Returns: (transfer none) (nullable): a #IdeTreeNode or %NULL * * Since: 3.32 */ IdeTreeNode * ide_tree_node_get_nth_child (IdeTreeNode *self, guint index_) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), NULL); return g_queue_peek_nth (&self->children, index_); } /** * ide_tree_node_get_next: * @self: a #IdeTreeNode * * Gets the next sibling after @self. * * Returns: (transfer none) (nullable): a #IdeTreeNode or %NULL * * Since: 3.32 */ IdeTreeNode * ide_tree_node_get_next (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), NULL); if (self->link.next) return self->link.next->data; return NULL; } /** * ide_tree_node_get_previous: * @self: a #IdeTreeNode * * Gets the previous sibling before @self. * * Returns: (transfer none) (nullable): a #IdeTreeNode or %NULL * * Since: 3.32 */ IdeTreeNode * ide_tree_node_get_previous (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), NULL); if (self->link.prev) return self->link.prev->data; return NULL; } /** * ide_tree_node_get_children_possible: * @self: a #IdeTreeNode * * Checks if the node can have children, and if so, returns %TRUE. * It may not actually have children yet. * * Returns: %TRUE if the children may have children * * Since: 3.32 */ gboolean ide_tree_node_get_children_possible (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE); return self->children_possible; } /** * ide_tree_node_set_children_possible: * @self: a #IdeTreeNode * @children_possible: if children are possible * * Sets if the children are possible for the node. * * Since: 3.32 */ void ide_tree_node_set_children_possible (IdeTreeNode *self, gboolean children_possible) { g_return_if_fail (IDE_IS_TREE_NODE (self)); children_possible = !!children_possible; if (children_possible != self->children_possible) { self->children_possible = children_possible; self->needs_build_children = children_possible; if (self->children_possible && self->children.length == 0) { g_autoptr(IdeTreeNode) child = NULL; child = g_object_new (IDE_TYPE_TREE_NODE, "display-name", _("(Empty)"), NULL); child->is_empty = TRUE; ide_tree_node_append (self, child); g_assert (ide_tree_node_has_child (self) == children_possible); } g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CHILDREN_POSSIBLE]); } } /** * ide_tree_node_has_child: * @self: a #IdeTreeNode * * Checks if @self has any children. * * Returns: %TRUE if @self has one or more children. * * Since: 3.32 */ gboolean ide_tree_node_has_child (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE); return self->children.length > 0; } /** * ide_tree_node_get_n_children: * @self: a #IdeTreeNode * * Gets the number of children that @self contains. * * Returns: the number of children * * Since: 3.32 */ guint ide_tree_node_get_n_children (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), 0); return self->children.length; } /** * ide_tree_node_get_is_header: * @self: a #IdeTreeNode * * Gets the #IdeTreeNode:is-header property. * * If this is %TRUE, then the node will be rendered with alternate * styling for group headers. * * Returns: %TRUE if @self is a header. * * Since: 3.32 */ gboolean ide_tree_node_get_is_header (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE); return self->is_header; } /** * ide_tree_node_set_is_header: * @self: a #IdeTreeNode * * Sets the #IdeTreeNode:is-header property. * * Since: 3.32 */ void ide_tree_node_set_is_header (IdeTreeNode *self, gboolean is_header) { g_return_if_fail (IDE_IS_TREE_NODE (self)); is_header = !!is_header; if (self->is_header != is_header) { self->is_header = is_header; g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_IS_HEADER]); } } typedef struct { GTraverseType type; GTraverseFlags flags; gint depth; IdeTreeTraverseFunc callback; gpointer user_data; } IdeTreeTraversal; static inline gboolean can_callback_node (IdeTreeNode *node, GTraverseFlags flags) { return ((flags & G_TRAVERSE_LEAVES) && node->children.length == 0) || ((flags & G_TRAVERSE_NON_LEAVES) && node->children.length > 0); } static gboolean do_traversal (IdeTreeNode *node, IdeTreeTraversal *traversal) { const GList *iter; IdeTreeNodeVisit ret = IDE_TREE_NODE_VISIT_BREAK; if (traversal->depth < 0) return IDE_TREE_NODE_VISIT_CONTINUE; traversal->depth--; if (traversal->type == G_PRE_ORDER && can_callback_node (node, traversal->flags)) { ret = traversal->callback (node, traversal->user_data); if (!ide_tree_node_is_root (node) && (ret == IDE_TREE_NODE_VISIT_CONTINUE || ret == IDE_TREE_NODE_VISIT_BREAK)) goto finish; } iter = node->children.head; while (iter != NULL) { IdeTreeNode *child = iter->data; iter = iter->next; ret = do_traversal (child, traversal); if (ret == IDE_TREE_NODE_VISIT_BREAK) goto finish; } if (traversal->type == G_POST_ORDER && can_callback_node (node, traversal->flags)) ret = traversal->callback (node, traversal->user_data); finish: traversal->depth++; return ret; } /** * ide_tree_node_traverse: * @self: a #IdeTreeNode * @traverse_type: the type of traversal, pre and post supported * @traverse_flags: the flags for what nodes to match * @max_depth: the max depth for the traversal or -1 for all * @traverse_func: (scope call): the callback for each matching node * @user_data: user data for @traverse_func * * Calls @traverse_func for each node that matches the requested * type, flags, and depth. * * Traversal is stopped if @traverse_func returns %TRUE. * * Since: 3.32 */ void ide_tree_node_traverse (IdeTreeNode *self, GTraverseType traverse_type, GTraverseFlags traverse_flags, gint max_depth, IdeTreeTraverseFunc traverse_func, gpointer user_data) { IdeTreeTraversal traverse; g_return_if_fail (IDE_IS_TREE_NODE (self)); g_return_if_fail (traverse_type == G_PRE_ORDER || traverse_type == G_POST_ORDER); g_return_if_fail (traverse_func != NULL); traverse.type = traverse_type; traverse.flags = traverse_flags; traverse.depth = max_depth < 0 ? G_MAXINT : max_depth; traverse.callback = traverse_func; traverse.user_data = user_data; do_traversal (self, &traverse); } /** * ide_tree_node_is_empty: * @self: a #IdeTreeNode * * This function checks if @self is a synthesized "empty" node. * * Empty nodes are added to #IdeTreeNode that may have children in the * future, but are currently empty. It allows the tree to display the * "(Empty)" contents and show a proper expander arrow. * * Returns: %TRUE if @self is a synthesized empty node. * * Since: 3.32 */ gboolean ide_tree_node_is_empty (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE); return self->is_empty; } gboolean _ide_tree_node_get_needs_build_children (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE); return self->needs_build_children; } void _ide_tree_node_set_needs_build_children (IdeTreeNode *self, gboolean needs_build_children) { g_return_if_fail (IDE_IS_TREE_NODE (self)); self->needs_build_children = !!needs_build_children; } /** * ide_tree_node_set_icon_name: * @self: a #IdeTreeNode * @icon_name: (nullable): the name of the icon, or %NULL * * Sets the #IdeTreeNode:icon property using an icon-name. * * Since: 3.32 */ void ide_tree_node_set_icon_name (IdeTreeNode *self, const gchar *icon_name) { g_autoptr(GIcon) icon = NULL; g_return_if_fail (IDE_IS_TREE_NODE (self)); if (icon_name != NULL) icon = g_themed_icon_new (icon_name); ide_tree_node_set_icon (self, icon); } /** * ide_tree_node_set_expanded_icon_name: * @self: a #IdeTreeNode * @expanded_icon_name: (nullable): the name of the icon, or %NULL * * Sets the #IdeTreeNode:icon property using an icon-name. * * Since: 3.32 */ void ide_tree_node_set_expanded_icon_name (IdeTreeNode *self, const gchar *expanded_icon_name) { g_autoptr(GIcon) icon = NULL; g_return_if_fail (IDE_IS_TREE_NODE (self)); if (expanded_icon_name != NULL) icon = g_themed_icon_new (expanded_icon_name); ide_tree_node_set_expanded_icon (self, icon); } /** * ide_tree_node_is_root: * @self: a #IdeTreeNode * * Checks if @self is the root node, meaning it has no parent. * * Returns: %TRUE if @self has no parent. * * Since: 3.32 */ gboolean ide_tree_node_is_root (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE); return self->parent == NULL; } /** * ide_tree_node_is_first: * @self: a #IdeTreeNode * * Checks if @self is the first sibling. * * Returns: %TRUE if @self is the first sibling * * Since: 3.32 */ gboolean ide_tree_node_is_first (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE); return self->link.prev == NULL; } /** * ide_tree_node_is_last: * @self: a #IdeTreeNode * * Checks if @self is the last sibling. * * Returns: %TRUE if @self is the last sibling * * Since: 3.32 */ gboolean ide_tree_node_is_last (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE); return self->link.next == NULL; } static void ide_tree_node_dump_internal (IdeTreeNode *self, gint depth) { g_autofree gchar *space = g_strnfill (depth * 2, ' '); g_print ("%s%s\n", space, ide_tree_node_get_display_name (self)); g_assert (self->children.length == 0 || self->children.head); g_assert (self->children.length == 0 || self->children.tail); g_assert (self->children.length > 0 || !self->children.head); g_assert (self->children.length > 0 || !self->children.tail); for (const GList *iter = self->children.head; iter; iter = iter->next) ide_tree_node_dump_internal (iter->data, depth + 1); } void _ide_tree_node_dump (IdeTreeNode *self) { ide_tree_node_dump_internal (self, 0); } gboolean _ide_tree_node_get_loading (IdeTreeNode *self, gint64 *started_loading_at) { g_assert (IDE_IS_TREE_NODE (self)); g_assert (started_loading_at != NULL); *started_loading_at = self->started_loading_at; return self->is_loading; } void _ide_tree_node_set_loading (IdeTreeNode *self, gboolean loading) { g_return_if_fail (IDE_IS_TREE_NODE (self)); self->is_loading = !!loading; if (self->is_loading) self->started_loading_at = g_get_monotonic_time (); for (const GList *iter = self->children.head; iter; iter = iter->next) { IdeTreeNode *child = iter->data; if (child->is_empty) { if (loading) ide_tree_node_set_display_name (child, _("Loading…")); else ide_tree_node_set_display_name (child, _("(Empty)")); if (self->children.length > 1) ide_tree_node_remove (self, child); break; } } } void _ide_tree_node_remove_all (IdeTreeNode *self) { const GList *iter; g_return_if_fail (IDE_IS_TREE_NODE (self)); iter = self->children.head; while (iter != NULL) { IdeTreeNode *child = iter->data; iter = iter->next; ide_tree_node_remove (self, child); } if (ide_tree_node_get_children_possible (self)) { g_autoptr(IdeTreeNode) child = g_object_new (IDE_TYPE_TREE_NODE, "display-name", _("(Empty)"), NULL); child->is_empty = TRUE; ide_tree_node_append (self, child); _ide_tree_node_set_needs_build_children (self, TRUE); } } /** * ide_tree_node_get_reset_on_collapse: * @self: a #IdeTreeNode * * Checks if the node should have all children removed when collapsed. * * Returns: %TRUE if children are removed on collapse * * Since: 3.32 */ gboolean ide_tree_node_get_reset_on_collapse (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE); return self->reset_on_collapse; } /** * ide_tree_node_set_reset_on_collapse: * @self: a #IdeTreeNode * @reset_on_collapse: if the children should be removed on collapse * * If %TRUE, then children will be removed when the row is collapsed. * * Since: 3.32 */ void ide_tree_node_set_reset_on_collapse (IdeTreeNode *self, gboolean reset_on_collapse) { g_return_if_fail (IDE_IS_TREE_NODE (self)); reset_on_collapse = !!reset_on_collapse; if (reset_on_collapse != self->reset_on_collapse) { self->reset_on_collapse = reset_on_collapse; g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RESET_ON_COLLAPSE]); } } /** * ide_tree_node_get_path: * @self: a #IdeTreeNode * * Gets the path for the tree node. * * Returns: (transfer full) (nullable): a path or %NULL * * Since: 3.32 */ GtkTreePath * ide_tree_node_get_path (IdeTreeNode *self) { IdeTreeModel *model; g_return_val_if_fail (IDE_IS_TREE_NODE (self), NULL); if ((model = ide_tree_node_get_model (self))) return ide_tree_model_get_path_for_node (model, self); return NULL; } static void ide_tree_node_get_area (IdeTreeNode *node, IdeTree *tree, GdkRectangle *area) { GtkTreeViewColumn *column; g_autoptr(GtkTreePath) path = NULL; g_assert (IDE_IS_TREE_NODE (node)); g_assert (IDE_IS_TREE (tree)); g_assert (area != NULL); path = ide_tree_node_get_path (node); column = gtk_tree_view_get_column (GTK_TREE_VIEW (tree), 0); gtk_tree_view_get_cell_area (GTK_TREE_VIEW (tree), path, column, area); } typedef struct { IdeTreeNode *self; IdeTree *tree; GtkPopover *popover; } PopupRequest; static gboolean ide_tree_node_show_popover_timeout_cb (gpointer data) { PopupRequest *popreq = data; GdkRectangle rect; GtkAllocation alloc; g_assert (popreq); g_assert (IDE_IS_TREE_NODE (popreq->self)); g_assert (GTK_IS_POPOVER (popreq->popover)); ide_tree_node_get_area (popreq->self, popreq->tree, &rect); gtk_widget_get_allocation (GTK_WIDGET (popreq->tree), &alloc); if ((rect.x + rect.width) > (alloc.x + alloc.width)) rect.width = (alloc.x + alloc.width) - rect.x; /* FIXME: Wouldn't this be better placed in a theme? */ switch (gtk_popover_get_position (popreq->popover)) { case GTK_POS_BOTTOM: case GTK_POS_TOP: rect.y += 3; rect.height -= 6; break; case GTK_POS_RIGHT: case GTK_POS_LEFT: rect.x += 3; rect.width -= 6; break; default: break; } gtk_popover_set_relative_to (popreq->popover, GTK_WIDGET (popreq->tree)); gtk_popover_set_pointing_to (popreq->popover, &rect); gtk_popover_popup (popreq->popover); g_clear_object (&popreq->self); g_clear_object (&popreq->popover); g_slice_free (PopupRequest, popreq); return G_SOURCE_REMOVE; } void _ide_tree_node_show_popover (IdeTreeNode *self, IdeTree *tree, GtkPopover *popover) { GdkRectangle cell_area; GdkRectangle visible_rect; PopupRequest *popreq; g_return_if_fail (IDE_IS_TREE_NODE (self)); g_return_if_fail (IDE_IS_TREE (tree)); g_return_if_fail (GTK_IS_POPOVER (popover)); gtk_tree_view_get_visible_rect (GTK_TREE_VIEW (tree), &visible_rect); ide_tree_node_get_area (self, tree, &cell_area); gtk_tree_view_convert_bin_window_to_tree_coords (GTK_TREE_VIEW (tree), cell_area.x, cell_area.y, &cell_area.x, &cell_area.y); popreq = g_slice_new0 (PopupRequest); popreq->self = g_object_ref (self); popreq->tree = g_object_ref (tree); popreq->popover = g_object_ref (popover); /* * If the node is not on screen, we need to animate until we get there. */ if ((cell_area.y < visible_rect.y) || ((cell_area.y + cell_area.height) > (visible_rect.y + visible_rect.height))) { GtkTreePath *path; path = ide_tree_node_get_path (self); gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree), path, NULL, FALSE, 0, 0); g_clear_pointer (&path, gtk_tree_path_free); /* * FIXME: Time period comes from gtk animation duration. * Not curently available in pubic API. * We need to be greater than the max timeout it * could take to move, since we must have it * on screen by then. * * One alternative might be to check the result * and if we are still not on screen, then just * pin it to a row-height from the top or bottom. */ g_timeout_add (300, ide_tree_node_show_popover_timeout_cb, popreq); return; } ide_tree_node_show_popover_timeout_cb (g_steal_pointer (&popreq)); } const gchar * ide_tree_node_get_tag (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), NULL); return self->tag; } void ide_tree_node_set_tag (IdeTreeNode *self, const gchar *tag) { g_return_if_fail (IDE_IS_TREE_NODE (self)); if (!ide_str_equal0 (self->tag, tag)) { g_free (self->tag); self->tag = g_strdup (tag); } } gboolean ide_tree_node_is_tag (IdeTreeNode *self, const gchar *tag) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE); return tag && ide_str_equal0 (self->tag, tag); } void ide_tree_node_add_emblem (IdeTreeNode *self, GEmblem *emblem) { g_return_if_fail (IDE_IS_MAIN_THREAD ()); g_return_if_fail (IDE_IS_TREE_NODE (self)); self->emblems = g_list_append (self->emblems, g_object_ref (emblem)); } GIcon * _ide_tree_node_apply_emblems (IdeTreeNode *self, GIcon *base) { g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL); g_return_val_if_fail (IDE_IS_TREE_NODE (self), NULL); if (self->emblems != NULL) { g_autoptr(GIcon) emblemed = g_emblemed_icon_new (base, NULL); for (const GList *iter = self->emblems; iter; iter = iter->next) g_emblemed_icon_add_emblem (G_EMBLEMED_ICON (emblemed), iter->data); return G_ICON (g_steal_pointer (&emblemed)); } return g_object_ref (base); } const GdkRGBA * ide_tree_node_get_foreground_rgba (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), NULL); return self->foreground_set ? &self->foreground : NULL; } void ide_tree_node_set_foreground_rgba (IdeTreeNode *self, const GdkRGBA *foreground_rgba) { g_return_if_fail (IDE_IS_MAIN_THREAD ()); g_return_if_fail (IDE_IS_TREE_NODE (self)); self->foreground_set = !!foreground_rgba; if (foreground_rgba) self->foreground = *foreground_rgba; ide_tree_node_emit_changed (self); } const GdkRGBA * ide_tree_node_get_background_rgba (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), NULL); return self->background_set ? &self->background : NULL; } void ide_tree_node_set_background_rgba (IdeTreeNode *self, const GdkRGBA *background_rgba) { g_return_if_fail (IDE_IS_MAIN_THREAD ()); g_return_if_fail (IDE_IS_TREE_NODE (self)); self->background_set = !!background_rgba; if (background_rgba) self->background = *background_rgba; ide_tree_node_emit_changed (self); } void _ide_tree_node_apply_colors (IdeTreeNode *self, GtkCellRenderer *cell) { PangoAttrList *attrs = NULL; g_return_if_fail (IDE_IS_TREE_NODE (self)); if (self->foreground_set) { if (!attrs) attrs = pango_attr_list_new (); pango_attr_list_insert (attrs, pango_attr_foreground_new (self->foreground.red * 65535, self->foreground.green * 65535, self->foreground.blue * 65535)); } if (self->background_set) { if (!attrs) attrs = pango_attr_list_new (); pango_attr_list_insert (attrs, pango_attr_background_new (self->background.red * 65535, self->background.green * 65535, self->background.blue * 65535)); } g_object_set (cell, "attributes", attrs, NULL); g_clear_pointer (&attrs, pango_attr_list_unref); } gboolean ide_tree_node_is_selected (IdeTreeNode *self) { g_autoptr(GtkTreePath) path = NULL; GtkTreeSelection *selection; IdeTreeModel *model; IdeTree *tree; g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE); if ((path = ide_tree_node_get_path (self)) && (model = ide_tree_node_get_model (self)) && (tree = ide_tree_model_get_tree (model)) && (selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree)))) return gtk_tree_selection_path_is_selected (selection, path); return FALSE; } gboolean ide_tree_node_get_has_error (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE); return self->has_error; } void ide_tree_node_set_has_error (IdeTreeNode *self, gboolean has_error) { g_return_if_fail (IDE_IS_TREE_NODE (self)); has_error = !!has_error; if (has_error != self->has_error) { self->has_error = has_error; ide_tree_node_emit_changed (self); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_ERROR]); } } gboolean ide_tree_node_get_use_markup (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE); return self->use_markup; } void ide_tree_node_set_use_markup (IdeTreeNode *self, gboolean use_markup) { g_return_if_fail (IDE_IS_TREE_NODE (self)); use_markup = !!use_markup; if (use_markup != self->use_markup) { self->use_markup = use_markup; ide_tree_node_emit_changed (self); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_USE_MARKUP]); } } IdeTreeNodeFlags ide_tree_node_get_flags (IdeTreeNode *self) { g_return_val_if_fail (IDE_IS_TREE_NODE (self), 0); return self->flags; } void ide_tree_node_set_flags (IdeTreeNode *self, IdeTreeNodeFlags flags) { g_return_if_fail (IDE_IS_TREE_NODE (self)); if (self->flags != flags) { self->flags = flags; ide_tree_node_emit_changed (self); } }