gtk2/gtk/gtktoolitemgroup.c
Benjamin Otte 43c212ac28 build: Enable -Wswitch-enum and -Wswitch-default
This patch makes that work using 1 of 2 options:

1. Add all missing enums to the switch statement
  or
2. Cast the switch argument to a uint to avoid having to do that (mostly
   for GdkEventType).

I even found a bug while doing that: clearing a GtkImage with a surface
did not notify thae surface property.

The reason for enabling this flag even though it is tedious at times is
that it is very useful when adding values to an enum, because it makes
GTK immediately warn about all the switch statements where this enum is
relevant.
And I expect changes to enums to be frequent during the GTK4 development
cycle.
2017-10-06 21:23:39 +02:00

2349 lines
70 KiB
C

/* GtkToolPalette -- A tool palette with categories and DnD support
* Copyright (C) 2008 Openismus GmbH
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Mathias Hasselmann
* Jan Arne Petersen
*/
#include "config.h"
#include <math.h>
#include <string.h>
#include "gtktoolpaletteprivate.h"
#include "gtktypebuiltins.h"
#include "gtkprivate.h"
#include "gtkintl.h"
#include "gtkcssnodeprivate.h"
#include "gtkstylecontextprivate.h"
#include "gtkwidgetprivate.h"
#define ANIMATION_TIMEOUT 50
#define ANIMATION_DURATION (ANIMATION_TIMEOUT * 4)
#define DEFAULT_ANIMATION_STATE TRUE
#define DEFAULT_EXPANDER_SIZE 16
#define DEFAULT_HEADER_SPACING 2
#define DEFAULT_LABEL ""
#define DEFAULT_COLLAPSED FALSE
#define DEFAULT_ELLIPSIZE PANGO_ELLIPSIZE_NONE
/**
* SECTION:gtktoolitemgroup
* @Short_description: A sub container used in a tool palette
* @Title: GtkToolItemGroup
*
* A #GtkToolItemGroup is used together with #GtkToolPalette to add
* #GtkToolItems to a palette like container with different
* categories and drag and drop support.
*
* # CSS nodes
*
* GtkToolItemGroup has a single CSS node named toolitemgroup.
*
* Since: 2.20
*/
enum
{
PROP_NONE,
PROP_LABEL,
PROP_LABEL_WIDGET,
PROP_COLLAPSED,
PROP_ELLIPSIZE,
PROP_RELIEF
};
enum
{
CHILD_PROP_NONE,
CHILD_PROP_HOMOGENEOUS,
CHILD_PROP_EXPAND,
CHILD_PROP_FILL,
CHILD_PROP_NEW_ROW,
CHILD_PROP_POSITION,
};
typedef struct _GtkToolItemGroupChild GtkToolItemGroupChild;
struct _GtkToolItemGroupPrivate
{
GtkWidget *header;
GtkWidget *label_widget;
GtkCssNode *arrow_node;
GList *children;
gint64 animation_start;
GSource *animation_timeout;
gulong focus_set_id;
GtkWidget *toplevel;
GtkSettings *settings;
gulong settings_connection;
PangoEllipsizeMode ellipsize;
guint animation : 1;
guint collapsed : 1;
};
struct _GtkToolItemGroupChild
{
GtkToolItem *item;
guint homogeneous : 1;
guint expand : 1;
guint fill : 1;
guint new_row : 1;
};
static void gtk_tool_item_group_tool_shell_init (GtkToolShellIface *iface);
G_DEFINE_TYPE_WITH_CODE (GtkToolItemGroup, gtk_tool_item_group, GTK_TYPE_CONTAINER,
G_ADD_PRIVATE (GtkToolItemGroup)
G_IMPLEMENT_INTERFACE (GTK_TYPE_TOOL_SHELL,
gtk_tool_item_group_tool_shell_init));
static GtkWidget*
gtk_tool_item_group_get_frame (GtkToolItemGroup *group)
{
return gtk_bin_get_child (GTK_BIN (group->priv->header));
}
static GtkOrientation
gtk_tool_item_group_get_orientation (GtkToolShell *shell)
{
GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (shell));
if (GTK_IS_TOOL_PALETTE (parent))
return gtk_orientable_get_orientation (GTK_ORIENTABLE (parent));
return GTK_ORIENTATION_VERTICAL;
}
static GtkToolbarStyle
gtk_tool_item_group_get_style (GtkToolShell *shell)
{
GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (shell));
if (GTK_IS_TOOL_PALETTE (parent))
return gtk_tool_palette_get_style (GTK_TOOL_PALETTE (parent));
return GTK_TOOLBAR_ICONS;
}
static GtkIconSize
gtk_tool_item_group_get_icon_size (GtkToolShell *shell)
{
GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (shell));
if (GTK_IS_TOOL_PALETTE (parent))
return gtk_tool_palette_get_icon_size (GTK_TOOL_PALETTE (parent));
return GTK_ICON_SIZE_SMALL_TOOLBAR;
}
static PangoEllipsizeMode
gtk_tool_item_group_get_ellipsize_mode (GtkToolShell *shell)
{
return GTK_TOOL_ITEM_GROUP (shell)->priv->ellipsize;
}
static gfloat
gtk_tool_item_group_get_text_alignment (GtkToolShell *shell)
{
if (GTK_TOOLBAR_TEXT == gtk_tool_item_group_get_style (shell) ||
GTK_TOOLBAR_BOTH_HORIZ == gtk_tool_item_group_get_style (shell))
return 0.0;
return 0.5;
}
static GtkOrientation
gtk_tool_item_group_get_text_orientation (GtkToolShell *shell)
{
return GTK_ORIENTATION_HORIZONTAL;
}
static GtkSizeGroup *
gtk_tool_item_group_get_text_size_group (GtkToolShell *shell)
{
GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (shell));
if (GTK_IS_TOOL_PALETTE (parent))
return _gtk_tool_palette_get_size_group (GTK_TOOL_PALETTE (parent));
return NULL;
}
static void
animation_change_notify (GtkToolItemGroup *group)
{
GtkSettings *settings = group->priv->settings;
gboolean animation;
if (settings)
g_object_get (settings,
"gtk-enable-animations", &animation,
NULL);
else
animation = DEFAULT_ANIMATION_STATE;
group->priv->animation = animation;
}
static void
gtk_tool_item_group_settings_change_notify (GtkSettings *settings,
const GParamSpec *pspec,
GtkToolItemGroup *group)
{
if (strcmp (pspec->name, "gtk-enable-animations") == 0)
animation_change_notify (group);
}
static void
gtk_tool_item_group_screen_changed (GtkWidget *widget,
GdkScreen *previous_screen)
{
GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (widget);
GtkToolItemGroupPrivate* priv = group->priv;
GtkSettings *old_settings = priv->settings;
GtkSettings *settings;
if (gtk_widget_has_screen (GTK_WIDGET (group)))
settings = gtk_widget_get_settings (GTK_WIDGET (group));
else
settings = NULL;
if (settings == old_settings)
return;
if (old_settings)
{
g_signal_handler_disconnect (old_settings, priv->settings_connection);
priv->settings_connection = 0;
g_object_unref (old_settings);
}
if (settings)
{
priv->settings_connection =
g_signal_connect (settings, "notify",
G_CALLBACK (gtk_tool_item_group_settings_change_notify),
group);
priv->settings = g_object_ref (settings);
}
else
priv->settings = NULL;
animation_change_notify (group);
}
static void
gtk_tool_item_group_tool_shell_init (GtkToolShellIface *iface)
{
iface->get_icon_size = gtk_tool_item_group_get_icon_size;
iface->get_orientation = gtk_tool_item_group_get_orientation;
iface->get_style = gtk_tool_item_group_get_style;
iface->get_text_alignment = gtk_tool_item_group_get_text_alignment;
iface->get_text_orientation = gtk_tool_item_group_get_text_orientation;
iface->get_text_size_group = gtk_tool_item_group_get_text_size_group;
iface->get_ellipsize_mode = gtk_tool_item_group_get_ellipsize_mode;
}
static gboolean
gtk_tool_item_group_header_draw_cb (GtkWidget *widget,
cairo_t *cr,
gpointer data)
{
GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (data);
GtkToolItemGroupPrivate* priv = group->priv;
GtkOrientation orientation;
gint x, y, width, height;
GtkTextDirection direction;
GtkStyleContext *context;
orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group));
direction = gtk_widget_get_direction (widget);
width = gtk_widget_get_allocated_width (widget);
height = gtk_widget_get_allocated_height (widget);
context = gtk_widget_get_style_context (widget);
gtk_style_context_save_to_node (context, priv->arrow_node);
if (GTK_ORIENTATION_VERTICAL == orientation)
{
gtk_style_context_add_class (context, GTK_STYLE_CLASS_VERTICAL);
if (GTK_TEXT_DIR_RTL == direction)
x = width;
else
x = 0;
y = height / 2 - (DEFAULT_EXPANDER_SIZE / 2);
}
else
{
gtk_style_context_add_class (context, GTK_STYLE_CLASS_HORIZONTAL);
x = width / 2 - (DEFAULT_EXPANDER_SIZE / 2);
y = 0;
}
gtk_render_expander (context, cr, x, y,
DEFAULT_EXPANDER_SIZE,
DEFAULT_EXPANDER_SIZE);
gtk_style_context_restore (context);
return FALSE;
}
static void
gtk_tool_item_group_header_clicked_cb (GtkButton *button,
gpointer data)
{
GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (data);
GtkToolItemGroupPrivate* priv = group->priv;
GtkWidget *parent = gtk_widget_get_parent (data);
if (priv->collapsed ||
!GTK_IS_TOOL_PALETTE (parent) ||
!gtk_tool_palette_get_exclusive (GTK_TOOL_PALETTE (parent), data))
gtk_tool_item_group_set_collapsed (group, !priv->collapsed);
}
static void
gtk_tool_item_group_header_adjust_style (GtkToolItemGroup *group)
{
GtkWidget *frame = gtk_bin_get_child (GTK_BIN (group->priv->header));
GtkWidget *label_widget = gtk_bin_get_child (GTK_BIN (frame));
GtkToolItemGroupPrivate* priv = group->priv;
gint dx = 0, dy = 0;
switch (gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group)))
{
case GTK_ORIENTATION_HORIZONTAL:
dy = DEFAULT_HEADER_SPACING + DEFAULT_EXPANDER_SIZE;
if (GTK_IS_LABEL (label_widget))
{
gtk_label_set_ellipsize (GTK_LABEL (label_widget), PANGO_ELLIPSIZE_NONE);
}
break;
case GTK_ORIENTATION_VERTICAL:
dx = DEFAULT_HEADER_SPACING + DEFAULT_EXPANDER_SIZE;
if (GTK_IS_LABEL (label_widget))
{
gtk_label_set_ellipsize (GTK_LABEL (label_widget), priv->ellipsize);
}
break;
default:
g_assert_not_reached ();
break;
}
gtk_widget_set_margin_start (frame, dx);
gtk_widget_set_margin_top (frame, dy);
}
static void
update_arrow_state (GtkToolItemGroup *group)
{
GtkToolItemGroupPrivate *priv = group->priv;
GtkStateFlags state;
state = gtk_widget_get_state_flags (GTK_WIDGET (group));
if (priv->collapsed)
state &= ~GTK_STATE_FLAG_CHECKED;
else
state |= GTK_STATE_FLAG_CHECKED;
gtk_css_node_set_state (priv->arrow_node, state);
}
static void
gtk_tool_item_group_init (GtkToolItemGroup *group)
{
GtkWidget *frame;
GtkToolItemGroupPrivate* priv;
GtkCssNode *widget_node;
gtk_widget_set_has_window (GTK_WIDGET (group), FALSE);
group->priv = priv = gtk_tool_item_group_get_instance_private (group);
priv->children = NULL;
priv->collapsed = DEFAULT_COLLAPSED;
priv->label_widget = gtk_label_new (NULL);
gtk_widget_set_halign (priv->label_widget, GTK_ALIGN_START);
gtk_widget_set_valign (priv->label_widget, GTK_ALIGN_CENTER);
frame = gtk_frame_new (NULL);
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
gtk_container_add (GTK_CONTAINER (frame), priv->label_widget);
priv->header = gtk_button_new ();
g_object_ref_sink (priv->header);
gtk_widget_set_focus_on_click (priv->header, FALSE);
gtk_container_add (GTK_CONTAINER (priv->header), frame);
gtk_widget_set_parent (priv->header, GTK_WIDGET (group));
gtk_tool_item_group_header_adjust_style (group);
g_signal_connect_after (frame, "draw",
G_CALLBACK (gtk_tool_item_group_header_draw_cb),
group);
g_signal_connect (priv->header, "clicked",
G_CALLBACK (gtk_tool_item_group_header_clicked_cb),
group);
widget_node = gtk_widget_get_css_node (GTK_WIDGET (group));
priv->arrow_node = gtk_css_node_new ();
gtk_css_node_set_name (priv->arrow_node, I_("arrow"));
gtk_css_node_set_parent (priv->arrow_node, widget_node);
gtk_css_node_set_state (priv->arrow_node, gtk_css_node_get_state (widget_node));
g_object_unref (priv->arrow_node);
update_arrow_state (group);
}
static void
gtk_tool_item_group_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (object);
switch (prop_id)
{
case PROP_LABEL:
gtk_tool_item_group_set_label (group, g_value_get_string (value));
break;
case PROP_LABEL_WIDGET:
gtk_tool_item_group_set_label_widget (group, g_value_get_object (value));
break;
case PROP_COLLAPSED:
gtk_tool_item_group_set_collapsed (group, g_value_get_boolean (value));
break;
case PROP_ELLIPSIZE:
gtk_tool_item_group_set_ellipsize (group, g_value_get_enum (value));
break;
case PROP_RELIEF:
gtk_tool_item_group_set_header_relief (group, g_value_get_enum(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_tool_item_group_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (object);
switch (prop_id)
{
case PROP_LABEL:
g_value_set_string (value, gtk_tool_item_group_get_label (group));
break;
case PROP_LABEL_WIDGET:
g_value_set_object (value,
gtk_tool_item_group_get_label_widget (group));
break;
case PROP_COLLAPSED:
g_value_set_boolean (value, gtk_tool_item_group_get_collapsed (group));
break;
case PROP_ELLIPSIZE:
g_value_set_enum (value, gtk_tool_item_group_get_ellipsize (group));
break;
case PROP_RELIEF:
g_value_set_enum (value, gtk_tool_item_group_get_header_relief (group));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_tool_item_group_finalize (GObject *object)
{
GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (object);
g_list_free (group->priv->children);
G_OBJECT_CLASS (gtk_tool_item_group_parent_class)->finalize (object);
}
static void
gtk_tool_item_group_dispose (GObject *object)
{
GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (object);
GtkToolItemGroupPrivate* priv = group->priv;
if (priv->toplevel)
{
/* disconnect focus tracking handler */
g_signal_handler_disconnect (priv->toplevel,
priv->focus_set_id);
priv->focus_set_id = 0;
priv->toplevel = NULL;
}
if (priv->settings_connection > 0)
{
g_signal_handler_disconnect (priv->settings, priv->settings_connection);
priv->settings_connection = 0;
}
g_clear_object (&priv->settings);
if (priv->header)
gtk_widget_unparent (priv->header);
g_clear_object (&priv->header);
G_OBJECT_CLASS (gtk_tool_item_group_parent_class)->dispose (object);
}
static void
gtk_tool_item_group_get_item_size (GtkToolItemGroup *group,
GtkRequisition *item_size,
gboolean homogeneous_only,
gint *requested_rows)
{
GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (group));
if (GTK_IS_TOOL_PALETTE (parent))
_gtk_tool_palette_get_item_size (GTK_TOOL_PALETTE (parent), item_size, homogeneous_only, requested_rows);
else
_gtk_tool_item_group_item_size_request (group, item_size, homogeneous_only, requested_rows);
}
static void
gtk_tool_item_group_size_request (GtkWidget *widget,
GtkRequisition *requisition)
{
GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (widget);
GtkToolItemGroupPrivate* priv = group->priv;
GtkOrientation orientation;
GtkRequisition item_size;
gint requested_rows;
if (priv->children && gtk_tool_item_group_get_label_widget (group))
{
gtk_widget_get_preferred_size (priv->header,
requisition, NULL);
gtk_widget_show (priv->header);
}
else
{
requisition->width = requisition->height = 0;
gtk_widget_hide (priv->header);
}
gtk_tool_item_group_get_item_size (group, &item_size, FALSE, &requested_rows);
orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group));
if (GTK_ORIENTATION_VERTICAL == orientation)
requisition->width = MAX (requisition->width, item_size.width);
else
requisition->height = MAX (requisition->height, item_size.height * requested_rows);
}
static void
gtk_tool_item_group_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkRequisition requisition;
gtk_tool_item_group_size_request (widget, &requisition);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
*minimum = *natural = requisition.width;
else
*minimum = *natural = requisition.height;
}
static gboolean
gtk_tool_item_group_is_item_visible (GtkToolItemGroup *group,
GtkToolItemGroupChild *child)
{
GtkToolbarStyle style;
GtkOrientation orientation;
orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group));
style = gtk_tool_shell_get_style (GTK_TOOL_SHELL (group));
/* horizontal tool palettes with text style support only homogeneous items */
if (!child->homogeneous &&
GTK_ORIENTATION_HORIZONTAL == orientation &&
GTK_TOOLBAR_TEXT == style)
return FALSE;
return
(gtk_widget_get_visible (GTK_WIDGET (child->item))) &&
(GTK_ORIENTATION_VERTICAL == orientation ?
gtk_tool_item_get_visible_vertical (child->item) :
gtk_tool_item_get_visible_horizontal (child->item));
}
static inline unsigned
udiv (unsigned x,
unsigned y)
{
return (x + y - 1) / y;
}
static void
gtk_tool_item_group_real_size_query (GtkWidget *widget,
GtkAllocation *allocation,
GtkRequisition *inquery)
{
GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (widget);
GtkToolItemGroupPrivate* priv = group->priv;
GtkRequisition item_size;
GtkAllocation item_area;
GtkOrientation orientation;
gint min_rows;
orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group));
/* figure out the size of homogeneous items */
gtk_tool_item_group_get_item_size (group, &item_size, TRUE, &min_rows);
if (GTK_ORIENTATION_VERTICAL == orientation)
item_size.width = MIN (item_size.width, allocation->width);
else
item_size.height = MIN (item_size.height, allocation->height);
item_size.width = MAX (item_size.width, 1);
item_size.height = MAX (item_size.height, 1);
item_area.width = 0;
item_area.height = 0;
/* figure out the required columns (n_columns) and rows (n_rows)
* to place all items
*/
if (!priv->collapsed || !priv->animation || priv->animation_timeout)
{
guint n_columns;
gint n_rows;
GList *it;
if (GTK_ORIENTATION_VERTICAL == orientation)
{
gboolean new_row = FALSE;
gint row = -1;
guint col = 0;
item_area.width = allocation->width;
n_columns = MAX (item_area.width / item_size.width, 1);
/* calculate required rows for n_columns columns */
for (it = priv->children; it != NULL; it = it->next)
{
GtkToolItemGroupChild *child = it->data;
if (!gtk_tool_item_group_is_item_visible (group, child))
continue;
if (new_row || child->new_row)
{
new_row = FALSE;
row++;
col = 0;
}
if (child->expand)
new_row = TRUE;
if (child->homogeneous)
{
col++;
if (col >= n_columns)
new_row = TRUE;
}
else
{
GtkRequisition req = {0, 0};
guint width;
gtk_widget_get_preferred_size (GTK_WIDGET (child->item),
&req, NULL);
width = udiv (req.width, item_size.width);
col += width;
if (col > n_columns)
row++;
col = width;
if (col >= n_columns)
new_row = TRUE;
}
}
n_rows = row + 2;
}
else
{
guint *row_min_width;
gint row = -1;
gboolean new_row = TRUE;
guint col = 0, min_col, max_col = 0, all_items = 0;
gint i;
item_area.height = allocation->height;
n_rows = MAX (item_area.height / item_size.height, min_rows);
row_min_width = g_new0 (guint, n_rows);
/* calculate minimal and maximal required cols and minimal
* required rows
*/
for (it = priv->children; it != NULL; it = it->next)
{
GtkToolItemGroupChild *child = it->data;
if (!gtk_tool_item_group_is_item_visible (group, child))
continue;
if (new_row || child->new_row)
{
new_row = FALSE;
row++;
col = 0;
row_min_width[row] = 1;
}
if (child->expand)
new_row = TRUE;
if (child->homogeneous)
{
col++;
all_items++;
}
else
{
GtkRequisition req = {0, 0};
guint width;
gtk_widget_get_preferred_size (GTK_WIDGET (child->item),
&req, NULL);
width = udiv (req.width, item_size.width);
col += width;
all_items += width;
row_min_width[row] = MAX (row_min_width[row], width);
}
max_col = MAX (max_col, col);
}
/* calculate minimal required cols */
min_col = udiv (all_items, n_rows);
for (i = 0; i <= row; i++)
{
min_col = MAX (min_col, row_min_width[i]);
}
/* simple linear search for minimal required columns
* for the given maximal number of rows (n_rows)
*/
for (n_columns = min_col; n_columns < max_col; n_columns ++)
{
new_row = TRUE;
row = -1;
/* calculate required rows for n_columns columns */
for (it = priv->children; it != NULL; it = it->next)
{
GtkToolItemGroupChild *child = it->data;
if (!gtk_tool_item_group_is_item_visible (group, child))
continue;
if (new_row || child->new_row)
{
new_row = FALSE;
row++;
col = 0;
}
if (child->expand)
new_row = TRUE;
if (child->homogeneous)
{
col++;
if (col >= n_columns)
new_row = TRUE;
}
else
{
GtkRequisition req = {0, 0};
guint width;
gtk_widget_get_preferred_size (GTK_WIDGET (child->item),
&req, NULL);
width = udiv (req.width, item_size.width);
col += width;
if (col > n_columns)
row++;
col = width;
if (col >= n_columns)
new_row = TRUE;
}
}
if (row < n_rows)
break;
}
}
item_area.width = item_size.width * n_columns;
item_area.height = item_size.height * n_rows;
}
inquery->width = 0;
inquery->height = 0;
/* figure out header widget size */
if (gtk_widget_get_visible (priv->header))
{
GtkRequisition child_requisition;
gtk_widget_get_preferred_size (priv->header,
&child_requisition, NULL);
if (GTK_ORIENTATION_VERTICAL == orientation)
inquery->height += child_requisition.height;
else
inquery->width += child_requisition.width;
}
/* report effective widget size */
inquery->width += item_area.width;
inquery->height += item_area.height;
}
static void
gtk_tool_item_group_real_size_allocate (GtkWidget *widget,
const GtkAllocation *allocation,
int baseline,
GtkAllocation *out_clip)
{
GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (widget);
GtkToolItemGroupPrivate* priv = group->priv;
GtkRequisition child_requisition;
GtkAllocation child_allocation;
GtkRequisition item_size;
GtkAllocation item_area;
GtkOrientation orientation;
GList *it;
gint n_columns, n_rows = 1;
gint min_rows;
GtkTextDirection direction;
direction = gtk_widget_get_direction (widget);
orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group));
/* chain up */
GTK_WIDGET_CLASS (gtk_tool_item_group_parent_class)->size_allocate (widget, allocation,
baseline, out_clip);
child_allocation.x = 0;
child_allocation.y = 0;
/* place the header widget */
if (gtk_widget_get_visible (priv->header))
{
gtk_widget_get_preferred_size (priv->header,
&child_requisition, NULL);
if (GTK_ORIENTATION_VERTICAL == orientation)
{
child_allocation.width = allocation->width;
child_allocation.height = child_requisition.height;
}
else
{
child_allocation.width = child_requisition.width;
child_allocation.height = allocation->height;
if (GTK_TEXT_DIR_RTL == direction)
child_allocation.x = allocation->width - child_allocation.width;
}
gtk_widget_size_allocate (priv->header, &child_allocation, -1, out_clip);
if (GTK_ORIENTATION_VERTICAL == orientation)
child_allocation.y += child_allocation.height;
else if (GTK_TEXT_DIR_RTL != direction)
child_allocation.x += child_allocation.width;
else
child_allocation.x = 0;
}
else
child_requisition.width = child_requisition.height = 0;
/* figure out the size of homogeneous items */
gtk_tool_item_group_get_item_size (group, &item_size, TRUE, &min_rows);
item_size.width = MAX (item_size.width, 1);
item_size.height = MAX (item_size.height, 1);
/* figure out the available columns and size of item_area */
if (GTK_ORIENTATION_VERTICAL == orientation)
{
item_size.width = MIN (item_size.width, allocation->width);
item_area.width = allocation->width;
item_area.height = allocation->height - child_requisition.height;
n_columns = MAX (item_area.width / item_size.width, 1);
item_size.width = item_area.width / n_columns;
}
else
{
item_size.height = MIN (item_size.height, allocation->height);
item_area.width = allocation->width - child_requisition.width;
item_area.height = allocation->height;
n_columns = MAX (item_area.width / item_size.width, 1);
n_rows = MAX (item_area.height / item_size.height, min_rows);
item_size.height = item_area.height / n_rows;
}
item_area.x = child_allocation.x;
item_area.y = child_allocation.y;
/* when expanded or in transition, place the tool items in a grid like layout */
if (!priv->collapsed || !priv->animation || priv->animation_timeout)
{
gint col = 0, row = 0;
for (it = priv->children; it != NULL; it = it->next)
{
GtkToolItemGroupChild *child = it->data;
gint col_child;
if (!gtk_tool_item_group_is_item_visible (group, child))
{
gtk_widget_set_child_visible (GTK_WIDGET (child->item), FALSE);
continue;
}
/* for non homogeneous widgets request the required size */
child_requisition.width = 0;
if (!child->homogeneous)
{
gtk_widget_get_preferred_size (GTK_WIDGET (child->item),
&child_requisition, NULL);
child_requisition.width = MIN (child_requisition.width, item_area.width);
}
/* select next row if at end of row */
if (col > 0 && (child->new_row || (col * item_size.width) + MAX (child_requisition.width, item_size.width) > item_area.width))
{
row++;
col = 0;
child_allocation.y += child_allocation.height;
}
col_child = col;
/* calculate the position and size of the item */
if (!child->homogeneous)
{
gint col_width;
gint width;
if (!child->expand)
col_width = udiv (child_requisition.width, item_size.width);
else
col_width = n_columns - col;
width = col_width * item_size.width;
if (GTK_TEXT_DIR_RTL == direction)
col_child = (n_columns - col - col_width);
if (child->fill)
{
child_allocation.x = item_area.x + col_child * item_size.width;
child_allocation.width = width;
}
else
{
child_allocation.x =
(item_area.x + col_child * item_size.width +
(width - child_requisition.width) / 2);
child_allocation.width = child_requisition.width;
}
col += col_width;
}
else
{
if (GTK_TEXT_DIR_RTL == direction)
col_child = (n_columns - col - 1);
child_allocation.x = item_area.x + col_child * item_size.width;
child_allocation.width = item_size.width;
col++;
}
child_allocation.height = item_size.height;
gtk_widget_size_allocate (GTK_WIDGET (child->item), &child_allocation, -1, out_clip);
gtk_widget_set_child_visible (GTK_WIDGET (child->item), TRUE);
}
child_allocation.y += item_size.height;
}
/* or just hide all items, when collapsed */
else
{
for (it = priv->children; it != NULL; it = it->next)
{
GtkToolItemGroupChild *child = it->data;
gtk_widget_set_child_visible (GTK_WIDGET (child->item), FALSE);
}
}
}
static void
gtk_tool_item_group_size_allocate (GtkWidget *widget,
const GtkAllocation *allocation,
int baseline,
GtkAllocation *out_clip)
{
gtk_tool_item_group_real_size_allocate (widget, allocation, baseline, out_clip);
}
static void
gtk_tool_item_group_set_focus_cb (GtkWidget *window,
GtkWidget *widget,
gpointer user_data)
{
GtkAdjustment *adjustment;
GtkAllocation allocation, p_allocation;
GtkWidget *p;
/* Find this group's parent widget in the focused widget's anchestry. */
for (p = widget; p; p = gtk_widget_get_parent (p))
if (p == user_data)
{
p = gtk_widget_get_parent (p);
break;
}
if (GTK_IS_TOOL_PALETTE (p))
{
/* Check that the focused widgets is fully visible within
* the group's parent widget and make it visible otherwise. */
adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (p));
if (adjustment)
{
int y;
gtk_widget_get_allocation (widget, &allocation);
gtk_widget_get_allocation (p, &p_allocation);
/* Handle vertical adjustment. */
if (gtk_widget_translate_coordinates
(widget, p, 0, 0, NULL, &y) && y < 0)
{
y += gtk_adjustment_get_value (adjustment);
gtk_adjustment_clamp_page (adjustment, y, y + allocation.height);
}
else if (gtk_widget_translate_coordinates (widget, p, 0, allocation.height, NULL, &y) &&
y > p_allocation.height)
{
y += gtk_adjustment_get_value (adjustment);
gtk_adjustment_clamp_page (adjustment, y - allocation.height, y);
}
}
adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (p));
if (adjustment)
{
int x;
gtk_widget_get_allocation (widget, &allocation);
gtk_widget_get_allocation (p, &p_allocation);
/* Handle horizontal adjustment. */
if (gtk_widget_translate_coordinates
(widget, p, 0, 0, &x, NULL) && x < 0)
{
x += gtk_adjustment_get_value (adjustment);
gtk_adjustment_clamp_page (adjustment, x, x + allocation.width);
}
else if (gtk_widget_translate_coordinates (widget, p, allocation.width, 0, &x, NULL) &&
x > p_allocation.width)
{
x += gtk_adjustment_get_value (adjustment);
gtk_adjustment_clamp_page (adjustment, x - allocation.width, x);
}
return;
}
}
}
static void
gtk_tool_item_group_set_toplevel_window (GtkToolItemGroup *group,
GtkWidget *toplevel)
{
GtkToolItemGroupPrivate* priv = group->priv;
if (toplevel != priv->toplevel)
{
if (priv->toplevel)
{
/* Disconnect focus tracking handler. */
g_signal_handler_disconnect (priv->toplevel,
priv->focus_set_id);
priv->focus_set_id = 0;
priv->toplevel = NULL;
}
if (toplevel)
{
/* Install focus tracking handler. We connect to the window's
* set-focus signal instead of connecting to the focus signal of
* each child to:
*
* 1) Reduce the number of signal handlers used.
* 2) Avoid special handling for group headers.
* 3) Catch focus grabs not only for direct children,
* but also for nested widgets.
*/
priv->focus_set_id =
g_signal_connect (toplevel, "set-focus",
G_CALLBACK (gtk_tool_item_group_set_focus_cb),
group);
priv->toplevel = toplevel;
}
}
}
static void
gtk_tool_item_group_realize (GtkWidget *widget)
{
GtkWidget *toplevel_window;
GTK_WIDGET_CLASS (gtk_tool_item_group_parent_class)->realize (widget);
toplevel_window = gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW);
gtk_tool_item_group_set_toplevel_window (GTK_TOOL_ITEM_GROUP (widget),
toplevel_window);
}
static void
gtk_tool_item_group_unrealize (GtkWidget *widget)
{
gtk_tool_item_group_set_toplevel_window (GTK_TOOL_ITEM_GROUP (widget), NULL);
GTK_WIDGET_CLASS (gtk_tool_item_group_parent_class)->unrealize (widget);
}
static void
gtk_tool_item_group_style_updated (GtkWidget *widget)
{
gtk_tool_item_group_header_adjust_style (GTK_TOOL_ITEM_GROUP (widget));
GTK_WIDGET_CLASS (gtk_tool_item_group_parent_class)->style_updated (widget);
}
static void
gtk_tool_item_group_state_flags_changed (GtkWidget *widget,
GtkStateFlags previous_flags)
{
update_arrow_state (GTK_TOOL_ITEM_GROUP (widget));
}
static void
gtk_tool_item_group_add (GtkContainer *container,
GtkWidget *widget)
{
g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (container));
g_return_if_fail (GTK_IS_TOOL_ITEM (widget));
gtk_tool_item_group_insert (GTK_TOOL_ITEM_GROUP (container),
GTK_TOOL_ITEM (widget), -1);
}
static void
gtk_tool_item_group_remove (GtkContainer *container,
GtkWidget *child)
{
GtkToolItemGroup *group;
GtkToolItemGroupPrivate* priv;
GList *it;
g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (container));
group = GTK_TOOL_ITEM_GROUP (container);
priv = group->priv;
for (it = priv->children; it != NULL; it = it->next)
{
GtkToolItemGroupChild *child_info = it->data;
if ((GtkWidget *)child_info->item == child)
{
g_object_unref (child);
gtk_widget_unparent (child);
g_free (child_info);
priv->children = g_list_delete_link (priv->children, it);
gtk_widget_queue_resize (GTK_WIDGET (container));
break;
}
}
}
static void
gtk_tool_item_group_forall (GtkContainer *container,
GtkCallback callback,
gpointer callback_data)
{
GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (container);
GtkToolItemGroupPrivate* priv = group->priv;
GList *children;
children = priv->children;
while (children)
{
GtkToolItemGroupChild *child = children->data;
children = children->next; /* store pointer before call to callback
because the child pointer is invalid if the
child->item is removed from the item group
in callback */
callback (GTK_WIDGET (child->item), callback_data);
}
}
static GType
gtk_tool_item_group_child_type (GtkContainer *container)
{
return GTK_TYPE_TOOL_ITEM;
}
static GtkToolItemGroupChild *
gtk_tool_item_group_get_child (GtkToolItemGroup *group,
GtkToolItem *item,
gint *position,
GList **link)
{
guint i;
GList *it;
g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), NULL);
g_return_val_if_fail (GTK_IS_TOOL_ITEM (item), NULL);
for (it = group->priv->children, i = 0; it != NULL; it = it->next, ++i)
{
GtkToolItemGroupChild *child = it->data;
if (child->item == item)
{
if (position)
*position = i;
if (link)
*link = it;
return child;
}
}
return NULL;
}
static void
gtk_tool_item_group_get_item_packing (GtkToolItemGroup *group,
GtkToolItem *item,
gboolean *homogeneous,
gboolean *expand,
gboolean *fill,
gboolean *new_row)
{
GtkToolItemGroupChild *child;
g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group));
g_return_if_fail (GTK_IS_TOOL_ITEM (item));
child = gtk_tool_item_group_get_child (group, item, NULL, NULL);
if (!child)
return;
if (expand)
*expand = child->expand;
if (homogeneous)
*homogeneous = child->homogeneous;
if (fill)
*fill = child->fill;
if (new_row)
*new_row = child->new_row;
}
static void
gtk_tool_item_group_set_item_packing (GtkToolItemGroup *group,
GtkToolItem *item,
gboolean homogeneous,
gboolean expand,
gboolean fill,
gboolean new_row)
{
GtkToolItemGroupChild *child;
gboolean changed = FALSE;
g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group));
g_return_if_fail (GTK_IS_TOOL_ITEM (item));
child = gtk_tool_item_group_get_child (group, item, NULL, NULL);
if (!child)
return;
gtk_widget_freeze_child_notify (GTK_WIDGET (item));
if (child->homogeneous != homogeneous)
{
child->homogeneous = homogeneous;
changed = TRUE;
gtk_widget_child_notify (GTK_WIDGET (item), "homogeneous");
}
if (child->expand != expand)
{
child->expand = expand;
changed = TRUE;
gtk_widget_child_notify (GTK_WIDGET (item), "expand");
}
if (child->fill != fill)
{
child->fill = fill;
changed = TRUE;
gtk_widget_child_notify (GTK_WIDGET (item), "fill");
}
if (child->new_row != new_row)
{
child->new_row = new_row;
changed = TRUE;
gtk_widget_child_notify (GTK_WIDGET (item), "new-row");
}
gtk_widget_thaw_child_notify (GTK_WIDGET (item));
if (changed
&& gtk_widget_get_visible (GTK_WIDGET (group))
&& gtk_widget_get_visible (GTK_WIDGET (item)))
gtk_widget_queue_resize (GTK_WIDGET (group));
}
static void
gtk_tool_item_group_set_child_property (GtkContainer *container,
GtkWidget *child,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (container);
GtkToolItem *item = GTK_TOOL_ITEM (child);
gboolean homogeneous, expand, fill, new_row;
if (prop_id != CHILD_PROP_POSITION)
gtk_tool_item_group_get_item_packing (group, item,
&homogeneous,
&expand,
&fill,
&new_row);
switch (prop_id)
{
case CHILD_PROP_HOMOGENEOUS:
gtk_tool_item_group_set_item_packing (group, item,
g_value_get_boolean (value),
expand,
fill,
new_row);
break;
case CHILD_PROP_EXPAND:
gtk_tool_item_group_set_item_packing (group, item,
homogeneous,
g_value_get_boolean (value),
fill,
new_row);
break;
case CHILD_PROP_FILL:
gtk_tool_item_group_set_item_packing (group, item,
homogeneous,
expand,
g_value_get_boolean (value),
new_row);
break;
case CHILD_PROP_NEW_ROW:
gtk_tool_item_group_set_item_packing (group, item,
homogeneous,
expand,
fill,
g_value_get_boolean (value));
break;
case CHILD_PROP_POSITION:
gtk_tool_item_group_set_item_position (group, item, g_value_get_int (value));
break;
default:
GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
break;
}
}
static void
gtk_tool_item_group_get_child_property (GtkContainer *container,
GtkWidget *child,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (container);
GtkToolItem *item = GTK_TOOL_ITEM (child);
gboolean homogeneous, expand, fill, new_row;
if (prop_id != CHILD_PROP_POSITION)
gtk_tool_item_group_get_item_packing (group, item,
&homogeneous,
&expand,
&fill,
&new_row);
switch (prop_id)
{
case CHILD_PROP_HOMOGENEOUS:
g_value_set_boolean (value, homogeneous);
break;
case CHILD_PROP_EXPAND:
g_value_set_boolean (value, expand);
break;
case CHILD_PROP_FILL:
g_value_set_boolean (value, fill);
break;
case CHILD_PROP_NEW_ROW:
g_value_set_boolean (value, new_row);
break;
case CHILD_PROP_POSITION:
g_value_set_int (value, gtk_tool_item_group_get_item_position (group, item));
break;
default:
GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
break;
}
}
static void
gtk_tool_item_group_class_init (GtkToolItemGroupClass *cls)
{
GObjectClass *oclass = G_OBJECT_CLASS (cls);
GtkWidgetClass *wclass = GTK_WIDGET_CLASS (cls);
GtkContainerClass *cclass = GTK_CONTAINER_CLASS (cls);
oclass->set_property = gtk_tool_item_group_set_property;
oclass->get_property = gtk_tool_item_group_get_property;
oclass->finalize = gtk_tool_item_group_finalize;
oclass->dispose = gtk_tool_item_group_dispose;
wclass->measure = gtk_tool_item_group_measure;
wclass->size_allocate = gtk_tool_item_group_size_allocate;
wclass->realize = gtk_tool_item_group_realize;
wclass->unrealize = gtk_tool_item_group_unrealize;
wclass->style_updated = gtk_tool_item_group_style_updated;
wclass->screen_changed = gtk_tool_item_group_screen_changed;
wclass->state_flags_changed = gtk_tool_item_group_state_flags_changed;
cclass->add = gtk_tool_item_group_add;
cclass->remove = gtk_tool_item_group_remove;
cclass->forall = gtk_tool_item_group_forall;
cclass->child_type = gtk_tool_item_group_child_type;
cclass->set_child_property = gtk_tool_item_group_set_child_property;
cclass->get_child_property = gtk_tool_item_group_get_child_property;
g_object_class_install_property (oclass, PROP_LABEL,
g_param_spec_string ("label",
P_("Label"),
P_("The human-readable title of this item group"),
DEFAULT_LABEL,
GTK_PARAM_READWRITE));
g_object_class_install_property (oclass, PROP_LABEL_WIDGET,
g_param_spec_object ("label-widget",
P_("Label widget"),
P_("A widget to display in place of the usual label"),
GTK_TYPE_WIDGET,
GTK_PARAM_READWRITE));
g_object_class_install_property (oclass, PROP_COLLAPSED,
g_param_spec_boolean ("collapsed",
P_("Collapsed"),
P_("Whether the group has been collapsed and items are hidden"),
DEFAULT_COLLAPSED,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
g_object_class_install_property (oclass, PROP_ELLIPSIZE,
g_param_spec_enum ("ellipsize",
P_("ellipsize"),
P_("Ellipsize for item group headers"),
PANGO_TYPE_ELLIPSIZE_MODE, DEFAULT_ELLIPSIZE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
g_object_class_install_property (oclass, PROP_RELIEF,
g_param_spec_enum ("header-relief",
P_("Header Relief"),
P_("Relief of the group header button"),
GTK_TYPE_RELIEF_STYLE, GTK_RELIEF_NORMAL,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
gtk_container_class_install_child_property (cclass, CHILD_PROP_HOMOGENEOUS,
g_param_spec_boolean ("homogeneous",
P_("Homogeneous"),
P_("Whether the item should be the same size as other homogeneous items"),
TRUE,
GTK_PARAM_READWRITE));
gtk_container_class_install_child_property (cclass, CHILD_PROP_EXPAND,
g_param_spec_boolean ("expand",
P_("Expand"),
P_("Whether the item should receive extra space when the group grows"),
FALSE,
GTK_PARAM_READWRITE));
gtk_container_class_install_child_property (cclass, CHILD_PROP_FILL,
g_param_spec_boolean ("fill",
P_("Fill"),
P_("Whether the item should fill the available space"),
TRUE,
GTK_PARAM_READWRITE));
gtk_container_class_install_child_property (cclass, CHILD_PROP_NEW_ROW,
g_param_spec_boolean ("new-row",
P_("New Row"),
P_("Whether the item should start a new row"),
FALSE,
GTK_PARAM_READWRITE));
gtk_container_class_install_child_property (cclass, CHILD_PROP_POSITION,
g_param_spec_int ("position",
P_("Position"),
P_("Position of the item within this group"),
0,
G_MAXINT,
0,
GTK_PARAM_READWRITE));
gtk_widget_class_set_css_name (wclass, "toolitemgroup");
}
/**
* gtk_tool_item_group_new:
* @label: the label of the new group
*
* Creates a new tool item group with label @label.
*
* Returns: a new #GtkToolItemGroup.
*
* Since: 2.20
*/
GtkWidget*
gtk_tool_item_group_new (const gchar *label)
{
return g_object_new (GTK_TYPE_TOOL_ITEM_GROUP, "label", label, NULL);
}
/**
* gtk_tool_item_group_set_label:
* @group: a #GtkToolItemGroup
* @label: the new human-readable label of of the group
*
* Sets the label of the tool item group. The label is displayed in the header
* of the group.
*
* Since: 2.20
*/
void
gtk_tool_item_group_set_label (GtkToolItemGroup *group,
const gchar *label)
{
g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group));
if (!label)
gtk_tool_item_group_set_label_widget (group, NULL);
else
{
GtkWidget *child = gtk_label_new (label);
gtk_widget_show (child);
gtk_tool_item_group_set_label_widget (group, child);
}
g_object_notify (G_OBJECT (group), "label");
}
/**
* gtk_tool_item_group_set_label_widget:
* @group: a #GtkToolItemGroup
* @label_widget: the widget to be displayed in place of the usual label
*
* Sets the label of the tool item group.
* The label widget is displayed in the header of the group, in place
* of the usual label.
*
* Since: 2.20
*/
void
gtk_tool_item_group_set_label_widget (GtkToolItemGroup *group,
GtkWidget *label_widget)
{
GtkToolItemGroupPrivate* priv;
GtkWidget *frame;
g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group));
g_return_if_fail (label_widget == NULL || GTK_IS_WIDGET (label_widget));
g_return_if_fail (label_widget == NULL || gtk_widget_get_parent (label_widget) == NULL);
priv = group->priv;
if (priv->label_widget == label_widget)
return;
frame = gtk_tool_item_group_get_frame (group);
if (priv->label_widget)
{
gtk_widget_set_state_flags (priv->label_widget, 0, TRUE);
gtk_container_remove (GTK_CONTAINER (frame), priv->label_widget);
}
if (label_widget)
gtk_container_add (GTK_CONTAINER (frame), label_widget);
priv->label_widget = label_widget;
if (gtk_widget_get_visible (GTK_WIDGET (group)))
gtk_widget_queue_resize (GTK_WIDGET (group));
/* Only show the header widget if the group has children: */
if (label_widget && priv->children)
gtk_widget_show (priv->header);
else
gtk_widget_hide (priv->header);
g_object_freeze_notify (G_OBJECT (group));
g_object_notify (G_OBJECT (group), "label-widget");
g_object_notify (G_OBJECT (group), "label");
g_object_thaw_notify (G_OBJECT (group));
}
/**
* gtk_tool_item_group_set_header_relief:
* @group: a #GtkToolItemGroup
* @style: the #GtkReliefStyle
*
* Set the button relief of the group header.
* See gtk_button_set_relief() for details.
*
* Since: 2.20
*/
void
gtk_tool_item_group_set_header_relief (GtkToolItemGroup *group,
GtkReliefStyle style)
{
g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group));
if (gtk_button_get_relief (GTK_BUTTON (group->priv->header)) != style)
{
gtk_button_set_relief (GTK_BUTTON (group->priv->header), style);
g_object_notify (G_OBJECT (group), "header-relief");
}
}
static gint64
gtk_tool_item_group_get_animation_timestamp (GtkToolItemGroup *group)
{
return (g_source_get_time (group->priv->animation_timeout) -
group->priv->animation_start) / 1000;
}
static void
gtk_tool_item_group_force_expose (GtkToolItemGroup *group)
{
GtkToolItemGroupPrivate* priv = group->priv;
GtkWidget *widget = GTK_WIDGET (group);
if (gtk_widget_get_realized (priv->header))
{
GtkAllocation frame_allocation;
GtkWidget *frame = gtk_tool_item_group_get_frame (group);
/* Find the header button's arrow area and
* invalidate it to get it animated. */
gtk_widget_get_allocation (frame, &frame_allocation);
gtk_widget_queue_draw_area (priv->header,
frame_allocation.x,
frame_allocation.y + (frame_allocation.height - DEFAULT_EXPANDER_SIZE) / 2,
DEFAULT_EXPANDER_SIZE,
DEFAULT_EXPANDER_SIZE);
}
if (gtk_widget_get_realized (widget))
{
GtkAllocation allocation;
GtkWidget *parent = gtk_widget_get_parent (widget);
int x, y, width, height;
/* Find the tool item area button's arrow area... */
gtk_widget_get_allocation (widget, &allocation);
width = allocation.width;
height = allocation.height;
gtk_widget_translate_coordinates (widget, parent, 0, 0, &x, &y);
if (gtk_widget_get_visible (priv->header))
{
GtkAllocation header_allocation;
gtk_widget_get_allocation (priv->header, &header_allocation);
height -= header_allocation.height;
y += header_allocation.height;
}
/* ... and invalidated it to get it animated. */
gtk_widget_queue_draw_area (parent, x, y, width, height);
}
}
static gboolean
gtk_tool_item_group_animation_cb (gpointer data)
{
GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (data);
GtkToolItemGroupPrivate* priv = group->priv;
gint64 timestamp = gtk_tool_item_group_get_animation_timestamp (group);
gboolean retval;
gdk_threads_enter ();
/* Enque this early to reduce number of expose events. */
gtk_widget_queue_resize_no_redraw (GTK_WIDGET (group));
gtk_tool_item_group_force_expose (group);
/* Finish animation when done. */
if (timestamp >= ANIMATION_DURATION)
priv->animation_timeout = NULL;
retval = (priv->animation_timeout != NULL);
gdk_threads_leave ();
return retval;
}
/**
* gtk_tool_item_group_set_collapsed:
* @group: a #GtkToolItemGroup
* @collapsed: whether the @group should be collapsed or expanded
*
* Sets whether the @group should be collapsed or expanded.
*
* Since: 2.20
*/
void
gtk_tool_item_group_set_collapsed (GtkToolItemGroup *group,
gboolean collapsed)
{
GtkWidget *parent;
GtkToolItemGroupPrivate* priv;
g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group));
priv = group->priv;
parent = gtk_widget_get_parent (GTK_WIDGET (group));
if (GTK_IS_TOOL_PALETTE (parent) && !collapsed)
_gtk_tool_palette_set_expanding_child (GTK_TOOL_PALETTE (parent),
GTK_WIDGET (group));
if (collapsed != priv->collapsed)
{
if (priv->animation)
{
if (priv->animation_timeout)
g_source_destroy (priv->animation_timeout);
priv->animation_start = g_get_monotonic_time ();
priv->animation_timeout = g_timeout_source_new (ANIMATION_TIMEOUT);
g_source_set_callback (priv->animation_timeout,
gtk_tool_item_group_animation_cb,
group, NULL);
g_source_attach (priv->animation_timeout, NULL);
}
else
gtk_tool_item_group_force_expose (group);
priv->collapsed = collapsed;
update_arrow_state (group);
g_object_notify (G_OBJECT (group), "collapsed");
}
}
/**
* gtk_tool_item_group_set_ellipsize:
* @group: a #GtkToolItemGroup
* @ellipsize: the #PangoEllipsizeMode labels in @group should use
*
* Sets the ellipsization mode which should be used by labels in @group.
*
* Since: 2.20
*/
void
gtk_tool_item_group_set_ellipsize (GtkToolItemGroup *group,
PangoEllipsizeMode ellipsize)
{
g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group));
if (ellipsize != group->priv->ellipsize)
{
group->priv->ellipsize = ellipsize;
gtk_tool_item_group_header_adjust_style (group);
g_object_notify (G_OBJECT (group), "ellipsize");
_gtk_tool_item_group_palette_reconfigured (group);
}
}
/**
* gtk_tool_item_group_get_label:
* @group: a #GtkToolItemGroup
*
* Gets the label of @group.
*
* Returns: the label of @group. The label is an internal string of @group
* and must not be modified. Note that %NULL is returned if a custom
* label has been set with gtk_tool_item_group_set_label_widget()
*
* Since: 2.20
*/
const gchar*
gtk_tool_item_group_get_label (GtkToolItemGroup *group)
{
GtkToolItemGroupPrivate *priv;
g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), NULL);
priv = group->priv;
if (GTK_IS_LABEL (priv->label_widget))
return gtk_label_get_label (GTK_LABEL (priv->label_widget));
else
return NULL;
}
/**
* gtk_tool_item_group_get_label_widget:
* @group: a #GtkToolItemGroup
*
* Gets the label widget of @group.
* See gtk_tool_item_group_set_label_widget().
*
* Returns: (transfer none): the label widget of @group
*
* Since: 2.20
*/
GtkWidget*
gtk_tool_item_group_get_label_widget (GtkToolItemGroup *group)
{
GtkWidget *frame = gtk_tool_item_group_get_frame (group);
return gtk_bin_get_child (GTK_BIN (frame));
}
/**
* gtk_tool_item_group_get_collapsed:
* @group: a GtkToolItemGroup
*
* Gets whether @group is collapsed or expanded.
*
* Returns: %TRUE if @group is collapsed, %FALSE if it is expanded
*
* Since: 2.20
*/
gboolean
gtk_tool_item_group_get_collapsed (GtkToolItemGroup *group)
{
g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), DEFAULT_COLLAPSED);
return group->priv->collapsed;
}
/**
* gtk_tool_item_group_get_ellipsize:
* @group: a #GtkToolItemGroup
*
* Gets the ellipsization mode of @group.
*
* Returns: the #PangoEllipsizeMode of @group
*
* Since: 2.20
*/
PangoEllipsizeMode
gtk_tool_item_group_get_ellipsize (GtkToolItemGroup *group)
{
g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), DEFAULT_ELLIPSIZE);
return group->priv->ellipsize;
}
/**
* gtk_tool_item_group_get_header_relief:
* @group: a #GtkToolItemGroup
*
* Gets the relief mode of the header button of @group.
*
* Returns: the #GtkReliefStyle
*
* Since: 2.20
*/
GtkReliefStyle
gtk_tool_item_group_get_header_relief (GtkToolItemGroup *group)
{
g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), GTK_RELIEF_NORMAL);
return gtk_button_get_relief (GTK_BUTTON (group->priv->header));
}
/**
* gtk_tool_item_group_insert:
* @group: a #GtkToolItemGroup
* @item: the #GtkToolItem to insert into @group
* @position: the position of @item in @group, starting with 0.
* The position -1 means end of list.
*
* Inserts @item at @position in the list of children of @group.
*
* Since: 2.20
*/
void
gtk_tool_item_group_insert (GtkToolItemGroup *group,
GtkToolItem *item,
gint position)
{
GtkWidget *parent, *child_widget;
GtkToolItemGroupChild *child;
g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group));
g_return_if_fail (GTK_IS_TOOL_ITEM (item));
g_return_if_fail (position >= -1);
parent = gtk_widget_get_parent (GTK_WIDGET (group));
child = g_new (GtkToolItemGroupChild, 1);
child->item = g_object_ref_sink (item);
child->homogeneous = TRUE;
child->expand = FALSE;
child->fill = TRUE;
child->new_row = FALSE;
group->priv->children = g_list_insert (group->priv->children, child, position);
if (GTK_IS_TOOL_PALETTE (parent))
_gtk_tool_palette_child_set_drag_source (GTK_WIDGET (item), parent);
child_widget = gtk_bin_get_child (GTK_BIN (item));
gtk_widget_set_focus_on_click (child_widget, TRUE);
gtk_widget_set_parent (GTK_WIDGET (item), GTK_WIDGET (group));
}
/**
* gtk_tool_item_group_set_item_position:
* @group: a #GtkToolItemGroup
* @item: the #GtkToolItem to move to a new position, should
* be a child of @group.
* @position: the new position of @item in @group, starting with 0.
* The position -1 means end of list.
*
* Sets the position of @item in the list of children of @group.
*
* Since: 2.20
*/
void
gtk_tool_item_group_set_item_position (GtkToolItemGroup *group,
GtkToolItem *item,
gint position)
{
gint old_position;
GList *link;
GtkToolItemGroupChild *child;
GtkToolItemGroupPrivate* priv;
g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group));
g_return_if_fail (GTK_IS_TOOL_ITEM (item));
g_return_if_fail (position >= -1);
child = gtk_tool_item_group_get_child (group, item, &old_position, &link);
priv = group->priv;
g_return_if_fail (child != NULL);
if (position == old_position)
return;
priv->children = g_list_delete_link (priv->children, link);
priv->children = g_list_insert (priv->children, child, position);
gtk_widget_child_notify (GTK_WIDGET (item), "position");
if (gtk_widget_get_visible (GTK_WIDGET (group)) &&
gtk_widget_get_visible (GTK_WIDGET (item)))
gtk_widget_queue_resize (GTK_WIDGET (group));
}
/**
* gtk_tool_item_group_get_item_position:
* @group: a #GtkToolItemGroup
* @item: a #GtkToolItem
*
* Gets the position of @item in @group as index.
*
* Returns: the index of @item in @group or -1 if @item is no child of @group
*
* Since: 2.20
*/
gint
gtk_tool_item_group_get_item_position (GtkToolItemGroup *group,
GtkToolItem *item)
{
gint position;
g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), -1);
g_return_val_if_fail (GTK_IS_TOOL_ITEM (item), -1);
if (gtk_tool_item_group_get_child (group, item, &position, NULL))
return position;
return -1;
}
/**
* gtk_tool_item_group_get_n_items:
* @group: a #GtkToolItemGroup
*
* Gets the number of tool items in @group.
*
* Returns: the number of tool items in @group
*
* Since: 2.20
*/
guint
gtk_tool_item_group_get_n_items (GtkToolItemGroup *group)
{
g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), 0);
return g_list_length (group->priv->children);
}
/**
* gtk_tool_item_group_get_nth_item:
* @group: a #GtkToolItemGroup
* @index: the index
*
* Gets the tool item at @index in group.
*
* Returns: (transfer none): the #GtkToolItem at index
*
* Since: 2.20
*/
GtkToolItem*
gtk_tool_item_group_get_nth_item (GtkToolItemGroup *group,
guint index)
{
GtkToolItemGroupChild *child;
g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), NULL);
child = g_list_nth_data (group->priv->children, index);
return child != NULL ? child->item : NULL;
}
/**
* gtk_tool_item_group_get_drop_item:
* @group: a #GtkToolItemGroup
* @x: the x position
* @y: the y position
*
* Gets the tool item at position (x, y).
*
* Returns: (transfer none): the #GtkToolItem at position (x, y)
*
* Since: 2.20
*/
GtkToolItem*
gtk_tool_item_group_get_drop_item (GtkToolItemGroup *group,
gint x,
gint y)
{
GtkAllocation allocation;
GList *it;
g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), NULL);
gtk_widget_get_allocation (GTK_WIDGET (group), &allocation);
g_return_val_if_fail (x >= 0 && x < allocation.width, NULL);
g_return_val_if_fail (y >= 0 && y < allocation.height, NULL);
for (it = group->priv->children; it != NULL; it = it->next)
{
GtkToolItemGroupChild *child = it->data;
GtkToolItem *item = child->item;
gint x0, y0;
if (!item || !gtk_tool_item_group_is_item_visible (group, child))
continue;
gtk_widget_get_allocation (GTK_WIDGET (item), &allocation);
x0 = x - allocation.x;
y0 = y - allocation.y;
if (x0 >= 0 && x0 < allocation.width &&
y0 >= 0 && y0 < allocation.height)
return item;
}
return NULL;
}
void
_gtk_tool_item_group_item_size_request (GtkToolItemGroup *group,
GtkRequisition *item_size,
gboolean homogeneous_only,
gint *requested_rows)
{
GtkRequisition child_requisition;
GList *it;
gint rows = 0;
gboolean new_row = TRUE;
g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group));
g_return_if_fail (NULL != item_size);
item_size->width = item_size->height = 0;
for (it = group->priv->children; it != NULL; it = it->next)
{
GtkToolItemGroupChild *child = it->data;
if (!gtk_tool_item_group_is_item_visible (group, child))
continue;
if (child->new_row || new_row)
{
rows++;
new_row = FALSE;
}
if (!child->homogeneous && child->expand)
new_row = TRUE;
gtk_widget_get_preferred_size (GTK_WIDGET (child->item),
&child_requisition, NULL);
if (!homogeneous_only || child->homogeneous)
item_size->width = MAX (item_size->width, child_requisition.width);
item_size->height = MAX (item_size->height, child_requisition.height);
}
if (requested_rows)
*requested_rows = rows;
}
gint
_gtk_tool_item_group_get_size_for_limit (GtkToolItemGroup *group,
gint limit,
gboolean vertical,
gboolean animation)
{
GtkRequisition requisition;
GtkToolItemGroupPrivate* priv = group->priv;
gtk_widget_get_preferred_size (GTK_WIDGET (group),
&requisition, NULL);
if (!priv->collapsed || priv->animation_timeout)
{
GtkAllocation allocation = { 0, 0, requisition.width, requisition.height };
GtkRequisition inquery;
if (vertical)
allocation.width = limit;
else
allocation.height = limit;
gtk_tool_item_group_real_size_query (GTK_WIDGET (group),
&allocation, &inquery);
if (vertical)
inquery.height -= requisition.height;
else
inquery.width -= requisition.width;
if (priv->animation_timeout && animation)
{
gint64 timestamp = gtk_tool_item_group_get_animation_timestamp (group);
timestamp = MIN (timestamp, ANIMATION_DURATION);
if (priv->collapsed)
timestamp = ANIMATION_DURATION - timestamp;
if (vertical)
{
inquery.height *= timestamp;
inquery.height /= ANIMATION_DURATION;
}
else
{
inquery.width *= timestamp;
inquery.width /= ANIMATION_DURATION;
}
}
if (vertical)
requisition.height += inquery.height;
else
requisition.width += inquery.width;
}
return (vertical ? requisition.height : requisition.width);
}
gint
_gtk_tool_item_group_get_height_for_width (GtkToolItemGroup *group,
gint width)
{
return _gtk_tool_item_group_get_size_for_limit (group, width, TRUE, group->priv->animation);
}
gint
_gtk_tool_item_group_get_width_for_height (GtkToolItemGroup *group,
gint height)
{
return _gtk_tool_item_group_get_size_for_limit (group, height, FALSE, TRUE);
}
static void
gtk_tool_palette_reconfigured_foreach_item (GtkWidget *child,
gpointer data)
{
if (GTK_IS_TOOL_ITEM (child))
gtk_tool_item_toolbar_reconfigured (GTK_TOOL_ITEM (child));
}
void
_gtk_tool_item_group_palette_reconfigured (GtkToolItemGroup *group)
{
gtk_container_foreach (GTK_CONTAINER (group),
gtk_tool_palette_reconfigured_foreach_item,
NULL);
gtk_tool_item_group_header_adjust_style (group);
}