2022 lines
51 KiB
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);
|
|
}
|
|
}
|