Commit 0801d919 authored by Christian Hergert's avatar Christian Hergert

util: add IdeFancyTreeView

This is a treeview that handles all of the hacks necessary to
get reflowing of text working (using IdeCellRendererFancy) and
make that convenient for consumers.

The places we know we need this today (as GtkListBox is not
designed to handle the use-case) is build warnings and TODOs.
parent c9b37a20
......@@ -149,6 +149,7 @@ G_BEGIN_DECLS
#include "transfers/ide-transfer-button.h"
#include "transfers/ide-transfer-manager.h"
#include "util/ide-cell-renderer-fancy.h"
#include "util/ide-fancy-tree-view.h"
#include "util/ide-flatpak.h"
#include "util/ide-gtk.h"
#include "util/ide-line-reader.h"
......
......@@ -194,6 +194,7 @@ libide_public_headers = [
'transfers/ide-transfers-button.h',
'transfers/ide-transfers-progress-icon.h',
'util/ide-cell-renderer-fancy.h',
'util/ide-fancy-tree-view.h',
'util/ide-flatpak.h',
'util/ide-glib.h',
'util/ide-gtk.h',
......@@ -395,6 +396,7 @@ libide_public_sources = [
'transfers/ide-transfers-button.c',
'transfers/ide-transfers-progress-icon.c',
'util/ide-cell-renderer-fancy.c',
'util/ide-fancy-tree-view.c',
'util/ide-flatpak.c',
'util/ide-glib.c',
'util/ide-gtk.c',
......
/* ide-fancy-tree-view.c
*
* Copyright (C) 2017 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/>.
*/
#define G_LOG_DOMAIN "ide-fancy-tree-view"
#include "ide-macros.h"
#include "util/ide-cell-renderer-fancy.h"
#include "util/ide-fancy-tree-view.h"
/**
* SECTION:ide-fancy-tree-view:
* @title: IdeFancyTreeView
* @short_description: a stylized treeview for use in sidebars
*
* This is a helper #GtkTreeView that matches the style that
* Builder uses for treeviews which can reflow text. It is a
* useful base class because it does all of the hacks necessary
* to make this work without ruining your code.
*
* It only has a single column, and comes setup with a single
* cell (an #IdeCellRendererFancy) to render the conten.
*
* Since: 3.26
*/
typedef struct
{
gint last_width;
guint relayout_source;
} IdeFancyTreeViewPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (IdeFancyTreeView, ide_fancy_tree_view, GTK_TYPE_TREE_VIEW)
static void
ide_fancy_tree_view_destroy (GtkWidget *widget)
{
IdeFancyTreeView *self = (IdeFancyTreeView *)widget;
IdeFancyTreeViewPrivate *priv = ide_fancy_tree_view_get_instance_private (self);
ide_clear_source (&priv->relayout_source);
GTK_WIDGET_CLASS (ide_fancy_tree_view_parent_class)->destroy (widget);
}
static gboolean
queue_relayout_in_idle (gpointer user_data)
{
IdeFancyTreeView *self = user_data;
IdeFancyTreeViewPrivate *priv = ide_fancy_tree_view_get_instance_private (self);
GtkAllocation alloc;
guint n_columns;
g_assert (IDE_IS_FANCY_TREE_VIEW (self));
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
if (alloc.width == priv->last_width)
goto cleanup;
priv->last_width = alloc.width;
n_columns = gtk_tree_view_get_n_columns (GTK_TREE_VIEW (self));
for (guint i = 0; i < n_columns; i++)
{
GtkTreeViewColumn *column;
column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), i);
gtk_tree_view_column_queue_resize (column);
}
cleanup:
priv->relayout_source = 0;
return G_SOURCE_REMOVE;
}
static void
ide_fancy_tree_view_size_allocate (GtkWidget *widget,
GtkAllocation *alloc)
{
IdeFancyTreeView *self = (IdeFancyTreeView *)widget;
IdeFancyTreeViewPrivate *priv = ide_fancy_tree_view_get_instance_private (self);
g_assert (IDE_IS_FANCY_TREE_VIEW (self));
GTK_WIDGET_CLASS (ide_fancy_tree_view_parent_class)->size_allocate (widget, alloc);
if (priv->last_width != alloc->width)
{
/*
* We must perform our queued relayout from an idle callback
* so that we don't affect this draw cycle. If we do that, we
* will get empty content flashes for the current frame. This
* allows us to draw the current frame slightly incorrect but
* fixup on the next frame (which looks much nicer from a user
* point of view).
*/
if (priv->relayout_source == 0)
priv->relayout_source =
gdk_threads_add_idle_full (G_PRIORITY_LOW + 100,
queue_relayout_in_idle,
g_object_ref (self),
g_object_unref);
}
}
static void
ide_fancy_tree_view_class_init (IdeFancyTreeViewClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
widget_class->size_allocate = ide_fancy_tree_view_size_allocate;
widget_class->destroy = ide_fancy_tree_view_destroy;
}
static void
ide_fancy_tree_view_init (IdeFancyTreeView *self)
{
GtkTreeViewColumn *column;
GtkCellRenderer *cell;
g_object_set (self,
"activate-on-single-click", TRUE,
"headers-visible", FALSE,
NULL);
column = g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
"expand", TRUE,
"visible", TRUE,
NULL);
gtk_tree_view_append_column (GTK_TREE_VIEW (self), column);
cell = g_object_new (IDE_TYPE_CELL_RENDERER_FANCY,
"visible", TRUE,
"xalign", 0.0f,
"xpad", 4,
"ypad", 6,
NULL);
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), cell, TRUE);
}
GtkWidget *
ide_fancy_tree_view_new (void)
{
return g_object_new (IDE_TYPE_FANCY_TREE_VIEW, NULL);
}
/**
* ide_fancy_tree_view_set_data_func:
* @self: a #IdeFancyTreeView
* @func: (closure func_data) (scope async) (nullable): a callback
* @func_data: data for @func
* @func_data_destroy: destroy notify for @func_data
*
* Sets the data func to use to update the text for the
* #IdeCellRendererFancy cell renderer.
*
* Since: 3.26
*/
void
ide_fancy_tree_view_set_data_func (IdeFancyTreeView *self,
GtkCellLayoutDataFunc func,
gpointer func_data,
GDestroyNotify func_data_destroy)
{
GtkTreeViewColumn *column;
GList *cells;
g_return_if_fail (IDE_IS_FANCY_TREE_VIEW (self));
column = gtk_tree_view_get_column (GTK_TREE_VIEW (self), 0);
cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
if (cells->data != NULL)
gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (column), cells->data,
func, func_data, func_data_destroy);
g_list_free (cells);
}
/* ide-fancy-tree-view.h
*
* Copyright (C) 2017 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/>.
*/
#pragma once
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define IDE_TYPE_FANCY_TREE_VIEW (ide_fancy_tree_view_get_type())
G_DECLARE_DERIVABLE_TYPE (IdeFancyTreeView, ide_fancy_tree_view, IDE, FANCY_TREE_VIEW, GtkTreeView)
struct _IdeFancyTreeViewClass
{
GtkTreeViewClass parent_class;
gpointer _reserved1;
gpointer _reserved2;
gpointer _reserved3;
gpointer _reserved4;
};
GtkWidget *ide_fancy_tree_view_new (void);
void ide_fancy_tree_view_set_data_func (IdeFancyTreeView *self,
GtkCellLayoutDataFunc func,
gpointer func_data,
GDestroyNotify func_data_destroy);
G_END_DECLS
......@@ -29,9 +29,6 @@ struct _GbpTodoPanel
GtkTreeView *tree_view;
GbpTodoModel *model;
guint last_width;
guint relayout_source;
};
G_DEFINE_TYPE (GbpTodoPanel, gbp_todo_panel, GTK_TYPE_BIN)
......@@ -202,68 +199,6 @@ gbp_todo_panel_query_tooltip (GbpTodoPanel *self,
return FALSE;
}
static gboolean
queue_relayout_in_idle (gpointer user_data)
{
GbpTodoPanel *self = user_data;
GtkAllocation alloc;
guint n_columns;
g_assert (GBP_IS_TODO_PANEL (self));
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
if (alloc.width == self->last_width)
goto cleanup;
self->last_width = alloc.width;
n_columns = gtk_tree_view_get_n_columns (self->tree_view);
for (guint i = 0; i < n_columns; i++)
{
GtkTreeViewColumn *column;
column = gtk_tree_view_get_column (self->tree_view, i);
gtk_tree_view_column_queue_resize (column);
}
cleanup:
self->relayout_source = 0;
return G_SOURCE_REMOVE;
}
static void
gbp_todo_panel_size_allocate (GtkWidget *widget,
GtkAllocation *alloc)
{
GbpTodoPanel *self = (GbpTodoPanel *)widget;
g_assert (GBP_IS_TODO_PANEL (self));
g_assert (alloc != NULL);
GTK_WIDGET_CLASS (gbp_todo_panel_parent_class)->size_allocate (widget, alloc);
if (self->last_width != alloc->width)
{
/*
* We must perform our queued relayout from an idle callback
* so that we don't affect this draw cycle. If we do that, we
* will get empty content flashes for the current frame. This
* allows us to draw the current frame slightly incorrect but
* fixup on the next frame (which looks much nicer from a user
* point of view).
*/
if (self->relayout_source == 0)
self->relayout_source =
gdk_threads_add_idle_full (G_PRIORITY_LOW + 100,
queue_relayout_in_idle,
g_object_ref (self),
g_object_unref);
}
}
static void
gbp_todo_panel_destroy (GtkWidget *widget)
{
......@@ -274,7 +209,6 @@ gbp_todo_panel_destroy (GtkWidget *widget)
if (self->tree_view != NULL)
gtk_tree_view_set_model (self->tree_view, NULL);
ide_clear_source (&self->relayout_source);
g_clear_object (&self->model);
GTK_WIDGET_CLASS (gbp_todo_panel_parent_class)->destroy (widget);
......@@ -328,7 +262,6 @@ gbp_todo_panel_class_init (GbpTodoPanelClass *klass)
object_class->set_property = gbp_todo_panel_set_property;
widget_class->destroy = gbp_todo_panel_destroy;
widget_class->size_allocate = gbp_todo_panel_size_allocate;
properties [PROP_MODEL] =
g_param_spec_object ("model",
......@@ -344,8 +277,6 @@ static void
gbp_todo_panel_init (GbpTodoPanel *self)
{
GtkWidget *scroller;
GtkTreeViewColumn *column;
GtkCellRenderer *cell;
scroller = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
"visible", TRUE,
......@@ -353,10 +284,8 @@ gbp_todo_panel_init (GbpTodoPanel *self)
NULL);
gtk_container_add (GTK_CONTAINER (self), scroller);
self->tree_view = g_object_new (GTK_TYPE_TREE_VIEW,
"activate-on-single-click", TRUE,
self->tree_view = g_object_new (IDE_TYPE_FANCY_TREE_VIEW,
"has-tooltip", TRUE,
"headers-visible", FALSE,
"visible", TRUE,
NULL);
g_signal_connect (self->tree_view,
......@@ -373,24 +302,8 @@ gbp_todo_panel_init (GbpTodoPanel *self)
self);
gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (self->tree_view));
column = g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
"expand", TRUE,
"visible", TRUE,
NULL);
gtk_tree_view_append_column (self->tree_view, column);
cell = g_object_new (IDE_TYPE_CELL_RENDERER_FANCY,
"visible", TRUE,
"xalign", 0.0f,
"xpad", 4,
"ypad", 6,
NULL);
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), cell, TRUE);
gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (column),
cell,
gbp_todo_panel_cell_data_func,
NULL, NULL);
ide_fancy_tree_view_set_data_func (IDE_FANCY_TREE_VIEW (self->tree_view),
gbp_todo_panel_cell_data_func, NULL, NULL);
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment