gem-graph-client/libide/tree/ide-tree-node.c

2022 lines
51 KiB
C

/* ide-tree-node.c
*
* Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "ide-tree-node"
#include "config.h"
#include <glib/gi18n.h>
#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);
}
}