diff --git a/gtk/gtktoolpaletteprivate.h b/gtk/gtktoolpaletteprivate.h new file mode 100644 index 0000000000..c1477f9e73 --- /dev/null +++ b/gtk/gtktoolpaletteprivate.h @@ -0,0 +1,59 @@ +/* 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Mathias Hasselmann + */ + +#ifndef __GTK_TOOL_PALETTE_PRIVATE_H__ +#define __GTK_TOOL_PALETTE_PRIVATE_H__ + +#include + +void _gtk_tool_palette_get_item_size (GtkToolPalette *palette, + GtkRequisition *item_size, + gboolean homogeneous_only, + gint *requested_rows); +void _gtk_tool_palette_child_set_drag_source (GtkWidget *widget, + gpointer data); +void _gtk_tool_palette_set_expanding_child (GtkToolPalette *palette, + GtkWidget *widget); + +void _gtk_tool_item_group_palette_reconfigured (GtkToolItemGroup *group); +void _gtk_tool_item_group_item_size_request (GtkToolItemGroup *group, + GtkRequisition *item_size, + gboolean homogeneous_only, + gint *requested_rows); +gint _gtk_tool_item_group_get_height_for_width (GtkToolItemGroup *group, + gint width); +gint _gtk_tool_item_group_get_width_for_height (GtkToolItemGroup *group, + gint height); +void _gtk_tool_item_group_paint (GtkToolItemGroup *group, + cairo_t *cr); +gint _gtk_tool_item_group_get_size_for_limit (GtkToolItemGroup *group, + gint limit, + gboolean vertical, + gboolean animation); + +#undef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 +/* #define HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 */ + +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 +GtkSizeGroup *_gtk_tool_palette_get_size_group (GtkToolPalette *palette); +#endif + +#endif /* __GTK_TOOL_PALETTE_PRIVATE_H__ */ diff --git a/gtktoolitemgroup.c b/gtktoolitemgroup.c new file mode 100644 index 0000000000..aac2e77ec4 --- /dev/null +++ b/gtktoolitemgroup.c @@ -0,0 +1,6972 @@ +/* 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Mathias Hasselmann + * Jan Arne Petersen + */ + +#include "gtktoolpaletteprivate.h" + +#include +#include +#include + +#define P_(msgid) (msgid) + +#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_NAME NULL +#define DEFAULT_COLLAPSED FALSE +#define DEFAULT_ELLIPSIZE PANGO_ELLIPSIZE_NONE + +/** + * SECTION:GtkToolItemGroup + * @short_description: A sub container used in a tool palette + * @include: gtktoolitemgroup.h + * + * An #GtkToolItemGroup is used together with #GtkToolPalette to add #GtkToolItems to a palette like container + * with different categories and drag and drop support. + * + * Since: 2.18 + */ + +enum +{ + PROP_NONE, + PROP_NAME, + 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; + + GList *children; + + gboolean animation; + gint64 animation_start; + GSource *animation_timeout; + GtkExpanderStyle expander_style; + gint expander_size; + gint header_spacing; + PangoEllipsizeMode ellipsize; + + gulong focus_set_id; + GtkWidget *toplevel; + + GtkSettings *settings; + gulong settings_connection; + + 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_IMPLEMENT_INTERFACE (GTK_TYPE_TOOL_SHELL, gtk_tool_item_group_tool_shell_init)); + +static GtkWidget* +gtk_tool_item_group_get_alignment (GtkToolItemGroup *group) +{ + return gtk_bin_get_child (GTK_BIN (group->priv->header)); +} + +static GtkWidget* +gtk_tool_item_group_get_label (GtkToolItemGroup *group) +{ + GtkWidget *alignment = gtk_tool_item_group_get_alignment (group); + return gtk_bin_get_child (GTK_BIN (alignment)); +} + +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_tool_palette_get_orientation (GTK_TOOL_PALETTE (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; +} + +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + +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) +{ + GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (shell)); + + if (GTK_IS_TOOL_PALETTE (parent)) + { + GtkOrientation orientation = gtk_tool_palette_get_orientation (GTK_TOOL_PALETTE (parent)); + if (GTK_ORIENTATION_HORIZONTAL == orientation && + (GTK_TOOLBAR_TEXT == gtk_tool_item_group_get_style (shell)/* || + GTK_TOOLBAR_BOTH_HORIZ == gtk_tool_item_group_get_style (shell)*/)) + return GTK_ORIENTATION_VERTICAL; + } + + 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; +} + +#endif + +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")) + { + 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); + 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; +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + 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; +#endif +} + +static gboolean +gtk_tool_item_group_header_expose_event_cb (GtkWidget *widget, + GdkEventExpose *event, + gpointer data) +{ + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (data); + GtkExpanderStyle expander_style; + GtkOrientation orientation; + gint x, y; + GtkTextDirection direction; + + orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group)); + expander_style = group->priv->expander_style; + direction = gtk_widget_get_direction (widget); + + if (GTK_ORIENTATION_VERTICAL == orientation) + { + if (GTK_TEXT_DIR_RTL == direction) + x = widget->allocation.x + widget->allocation.width - group->priv->expander_size / 2; + else + x = widget->allocation.x + group->priv->expander_size / 2; + y = widget->allocation.y + widget->allocation.height / 2; + } + else + { + x = widget->allocation.x + widget->allocation.width / 2; + y = widget->allocation.y + group->priv->expander_size / 2; + + /* Unfortunatly gtk_paint_expander() doesn't support rotated drawing + * modes. Luckily the following shady arithmetics produce the desired + * result. */ + expander_style = GTK_EXPANDER_EXPANDED - expander_style; /* XXX */ + } + + gtk_paint_expander (widget->style, widget->window, + group->priv->header->state, + &event->area, GTK_WIDGET (group), + "tool-palette-header", x, y, + expander_style); + + return FALSE; +} + +static void +gtk_tool_item_group_header_size_request_cb (GtkWidget *widget G_GNUC_UNUSED, + GtkRequisition *requisition, + gpointer data) +{ + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (data); + requisition->height = MAX (requisition->height, group->priv->expander_size); +} + +static void +gtk_tool_item_group_header_clicked_cb (GtkButton *button G_GNUC_UNUSED, + gpointer data) +{ + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (data); + GtkWidget *parent = gtk_widget_get_parent (data); + + if (group->priv->collapsed || + !GTK_IS_TOOL_PALETTE (parent) || + !gtk_tool_palette_get_exclusive (GTK_TOOL_PALETTE (parent), data)) + gtk_tool_item_group_set_collapsed (group, !group->priv->collapsed); +} + +static void +gtk_tool_item_group_header_adjust_style (GtkToolItemGroup *group) +{ + GtkWidget *alignment = gtk_tool_item_group_get_alignment (group); + GtkWidget *label = gtk_bin_get_child (GTK_BIN (alignment)); + GtkWidget *widget = GTK_WIDGET (group); + gint dx = 0, dy = 0; + GtkTextDirection direction = gtk_widget_get_direction (widget); + + gtk_widget_style_get (widget, + "header-spacing", &group->priv->header_spacing, + "expander-size", &group->priv->expander_size, + NULL); + + switch (gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group))) + { + case GTK_ORIENTATION_HORIZONTAL: + dy = group->priv->header_spacing + group->priv->expander_size; + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_NONE); + if (GTK_TEXT_DIR_RTL == direction) + gtk_label_set_angle (GTK_LABEL (label), -90); + else + gtk_label_set_angle (GTK_LABEL (label), 90); + break; + + case GTK_ORIENTATION_VERTICAL: + dx = group->priv->header_spacing + group->priv->expander_size; + gtk_label_set_ellipsize (GTK_LABEL (label), group->priv->ellipsize); + gtk_label_set_angle (GTK_LABEL (label), 0); + break; + } + + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), dy, 0, dx, 0); +} + +static void +gtk_tool_item_group_init (GtkToolItemGroup *group) +{ + GtkWidget *alignment; + GtkWidget *label; + + gtk_widget_set_redraw_on_allocate (GTK_WIDGET (group), FALSE); + + group->priv = G_TYPE_INSTANCE_GET_PRIVATE (group, + GTK_TYPE_TOOL_ITEM_GROUP, + GtkToolItemGroupPrivate); + + group->priv->children = NULL; + group->priv->header_spacing = DEFAULT_HEADER_SPACING; + group->priv->expander_size = DEFAULT_EXPANDER_SIZE; + group->priv->expander_style = GTK_EXPANDER_EXPANDED; + + label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0); + gtk_container_add (GTK_CONTAINER (alignment), label); + gtk_widget_show_all (alignment); + + gtk_widget_push_composite_child (); + group->priv->header = gtk_button_new (); + gtk_widget_set_composite_name (group->priv->header, "header"); + gtk_widget_pop_composite_child (); + + g_object_ref_sink (group->priv->header); + gtk_button_set_focus_on_click (GTK_BUTTON (group->priv->header), FALSE); + gtk_container_add (GTK_CONTAINER (group->priv->header), alignment); + gtk_widget_set_parent (group->priv->header, GTK_WIDGET (group)); + + gtk_tool_item_group_header_adjust_style (group); + + g_signal_connect_after (alignment, "expose-event", + G_CALLBACK (gtk_tool_item_group_header_expose_event_cb), + group); + g_signal_connect_after (alignment, "size-request", + G_CALLBACK (gtk_tool_item_group_header_size_request_cb), + group); + + g_signal_connect (group->priv->header, "clicked", + G_CALLBACK (gtk_tool_item_group_header_clicked_cb), + 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_NAME: + gtk_tool_item_group_set_name (group, g_value_get_string (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_NAME: + g_value_set_string (value, gtk_tool_item_group_get_name (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); + + if (group->priv->children) + { + g_list_free (group->priv->children); + group->priv->children = NULL; + } + + 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); + + if (group->priv->toplevel) + { + /* disconnect focus tracking handler */ + g_signal_handler_disconnect (group->priv->toplevel, + group->priv->focus_set_id); + + group->priv->focus_set_id = 0; + group->priv->toplevel = NULL; + } + + 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) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (widget); + GtkOrientation orientation; + GtkRequisition item_size; + gint requested_rows; + + if (group->priv->children && gtk_tool_item_group_get_name (group)) + { + gtk_widget_size_request (group->priv->header, requisition); + gtk_widget_show (group->priv->header); + } + else + { + requisition->width = requisition->height = 0; + gtk_widget_hide (group->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); + + requisition->width += border_width * 2; + requisition->height += border_width * 2; +} + +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_VISIBLE (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) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (widget); + + GtkRequisition item_size; + GtkAllocation item_area; + + GtkOrientation orientation; + GtkToolbarStyle style; + + gint min_rows; + + orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group)); + style = gtk_tool_shell_get_style (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_area.width = 0; + item_area.height = 0; + + /* figure out the required columns (n_columns) and rows (n_rows) to place all items */ + if (!group->priv->collapsed || !group->priv->animation || group->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 - 2 * border_width; + n_columns = MAX (item_area.width / item_size.width, 1); + + /* calculate required rows for n_columns columns */ + 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 (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_size_request (GTK_WIDGET (child->item), &req); + + 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 - 2 * border_width; + 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 = group->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_size_request (GTK_WIDGET (child->item), &req); + + 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 = group->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_size_request (GTK_WIDGET (child->item), &req); + + 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_VISIBLE (group->priv->header)) + { + GtkRequisition child_requisition; + + gtk_widget_size_request (group->priv->header, &child_requisition); + + 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 + 2 * border_width; + inquery->height += item_area.height + 2 * border_width; +} + +static void +gtk_tool_item_group_real_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (widget); + GtkRequisition child_requisition; + GtkAllocation child_allocation; + + GtkRequisition item_size; + GtkAllocation item_area; + + GtkOrientation orientation; + GtkToolbarStyle style; + + GList *it; + + gint n_columns, n_rows = 1; + gint min_rows; + + GtkTextDirection direction = gtk_widget_get_direction (widget); + + orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group)); + style = gtk_tool_shell_get_style (GTK_TOOL_SHELL (group)); + + /* chain up */ + GTK_WIDGET_CLASS (gtk_tool_item_group_parent_class)->size_allocate (widget, allocation); + + child_allocation.x = border_width; + child_allocation.y = border_width; + + /* place the header widget */ + if (GTK_WIDGET_VISIBLE (group->priv->header)) + { + gtk_widget_size_request (group->priv->header, &child_requisition); + + 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 - border_width - child_allocation.width; + } + + gtk_widget_size_allocate (group->priv->header, &child_allocation); + + 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 = border_width; + } + 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); + + /* 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 - 2 * border_width; + item_area.height = allocation->height - 2 * border_width - 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 - 2 * border_width - child_requisition.width; + item_area.height = allocation->height - 2 * border_width; + + 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 (!group->priv->collapsed || !group->priv->animation || group->priv->animation_timeout) + { + gint col = 0, row = 0; + + for (it = group->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_size_request (GTK_WIDGET (child->item), &child_requisition); + 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); + 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 = group->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, + GtkAllocation *allocation) +{ + gtk_tool_item_group_real_size_allocate (widget, allocation); + + if (GTK_WIDGET_MAPPED (widget)) + gdk_window_invalidate_rect (widget->window, NULL, FALSE); +} + +static void +gtk_tool_item_group_set_focus_cb (GtkWidget *window G_GNUC_UNUSED, + GtkWidget *widget, + gpointer user_data) +{ + GtkAdjustment *adjustment; + 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_tool_palette_get_hadjustment (GTK_TOOL_PALETTE (p)); + adjustment = gtk_tool_palette_get_vadjustment (GTK_TOOL_PALETTE (p)); + + if (adjustment) + { + int y; + + /* Handle vertical adjustment. */ + if (gtk_widget_translate_coordinates + (widget, p, 0, 0, NULL, &y) && y < 0) + { + y += adjustment->value; + gtk_adjustment_clamp_page (adjustment, y, y + widget->allocation.height); + } + else if (gtk_widget_translate_coordinates + (widget, p, 0, widget->allocation.height, NULL, &y) && + y > p->allocation.height) + { + y += adjustment->value; + gtk_adjustment_clamp_page (adjustment, y - widget->allocation.height, y); + } + } + + adjustment = gtk_tool_palette_get_hadjustment (GTK_TOOL_PALETTE (p)); + + if (adjustment) + { + int x; + + /* Handle horizontal adjustment. */ + if (gtk_widget_translate_coordinates + (widget, p, 0, 0, &x, NULL) && x < 0) + { + x += adjustment->value; + gtk_adjustment_clamp_page (adjustment, x, x + widget->allocation.width); + } + else if (gtk_widget_translate_coordinates + (widget, p, widget->allocation.width, 0, &x, NULL) && + x > p->allocation.width) + { + x += adjustment->value; + gtk_adjustment_clamp_page (adjustment, x - widget->allocation.width, x); + } + + return; + } + } +} + +static void +gtk_tool_item_group_set_toplevel_window (GtkToolItemGroup *group, + GtkWidget *toplevel) +{ + if (toplevel != group->priv->toplevel) + { + if (group->priv->toplevel) + { + /* Disconnect focus tracking handler. */ + g_signal_handler_disconnect (group->priv->toplevel, + group->priv->focus_set_id); + + group->priv->focus_set_id = 0; + group->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. + */ + group->priv->focus_set_id = + g_signal_connect (toplevel, "set-focus", + G_CALLBACK (gtk_tool_item_group_set_focus_cb), + group); + + group->priv->toplevel = toplevel; + } + } +} + +static void +gtk_tool_item_group_realize (GtkWidget *widget) +{ + GtkWidget *toplevel_window; + const gint border_width = GTK_CONTAINER (widget)->border_width; + gint attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + GdkWindowAttr attributes; + GdkDisplay *display; + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = widget->allocation.x + border_width; + attributes.y = widget->allocation.y + border_width; + attributes.width = widget->allocation.width - border_width * 2; + attributes.height = widget->allocation.height - border_width * 2; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK + | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + | GDK_BUTTON_MOTION_MASK; + + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + + display = gdk_drawable_get_display (widget->window); + + if (gdk_display_supports_composite (display)) + gdk_window_set_composited (widget->window, TRUE); + + gdk_window_set_user_data (widget->window, widget); + widget->style = gtk_style_attach (widget->style, widget->window); + gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + + gtk_container_forall (GTK_CONTAINER (widget), + (GtkCallback) gtk_widget_set_parent_window, + widget->window); + + gtk_widget_queue_resize_no_redraw (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_set (GtkWidget *widget, + GtkStyle *previous_style) +{ + gtk_tool_item_group_header_adjust_style (GTK_TOOL_ITEM_GROUP (widget)); + GTK_WIDGET_CLASS (gtk_tool_item_group_parent_class)->style_set (widget, previous_style); +} + +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; + GList *it; + + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (container)); + group = GTK_TOOL_ITEM_GROUP (container); + + for (it = group->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); + group->priv->children = g_list_delete_link (group->priv->children, it); + + gtk_widget_queue_resize (GTK_WIDGET (container)); + break; + } + } +} + +static void +gtk_tool_item_group_forall (GtkContainer *container, + gboolean internals, + GtkCallback callback, + gpointer callback_data) +{ + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (container); + GList *children; + + if (internals && group->priv->header) + callback (group->priv->header, callback_data); + + children = group->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 G_GNUC_UNUSED) +{ + 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_VISIBLE (group) && GTK_WIDGET_VISIBLE (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->size_request = gtk_tool_item_group_size_request; + 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_set = gtk_tool_item_group_style_set; + wclass->screen_changed = gtk_tool_item_group_screen_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_NAME, + g_param_spec_string ("name", + P_("Name"), + P_("The name of this item group"), + DEFAULT_NAME, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (oclass, PROP_COLLAPSED, + g_param_spec_boolean ("collapsed", + P_("Collapsed"), + P_("Wether the group has been collapsed and items are hidden"), + DEFAULT_COLLAPSED, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + 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, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (oclass, PROP_ELLIPSIZE, + g_param_spec_enum ("header-relief", + P_("header-relif"), + P_("Relief of the group header button"), + GTK_TYPE_RELIEF_STYLE, GTK_RELIEF_NORMAL, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + gtk_widget_class_install_style_property (wclass, + g_param_spec_int ("expander-size", + P_("Expander Size"), + P_("Size of the expander arrow"), + 0, + G_MAXINT, + DEFAULT_EXPANDER_SIZE, + G_PARAM_READABLE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + gtk_widget_class_install_style_property (wclass, + g_param_spec_int ("header-spacing", + P_("Header Spacing"), + P_("Spacing between expander arrow and caption"), + 0, + G_MAXINT, + DEFAULT_HEADER_SPACING, + G_PARAM_READABLE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + 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, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + 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 toolbar grows"), + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + gtk_container_class_install_child_property (cclass, CHILD_PROP_FILL, + g_param_spec_boolean ("fill", + P_("Fill"), + P_("Whether the item should fill the avaiable space"), + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + 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, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + 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, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_type_class_add_private (cls, sizeof (GtkToolItemGroupPrivate)); +} + +/** + * gtk_tool_item_group_new: + * @name: the name of the new group. + * + * Creates a new tool item group with name @name. + * + * Returns: a new #GtkToolItemGroup. + * + * Since: 2.18 + */ +GtkWidget* +gtk_tool_item_group_new (const gchar *name) +{ + return g_object_new (GTK_TYPE_TOOL_ITEM_GROUP, "name", name, NULL); +} + +/** + * gtk_tool_item_group_set_name: + * @group: an #GtkToolItemGroup. + * @name: the new name of of the group. + * + * Sets the name of the tool item group. The name is displayed in the header + * of the group. + * + * Since: 2.18 + */ +void +gtk_tool_item_group_set_name (GtkToolItemGroup *group, + const gchar *name) +{ + const gchar *current_name; + GtkWidget *label; + + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + current_name = gtk_tool_item_group_get_name (group); + + if (current_name != name && (!current_name || !name || strcmp (current_name, name))) + { + label = gtk_tool_item_group_get_label (group); + gtk_label_set_text (GTK_LABEL (label), name); + + if (name && group->priv->children) + gtk_widget_show (group->priv->header); + else + gtk_widget_hide (group->priv->header); + + g_object_notify (G_OBJECT (group), "name"); + } +} + +/** + * gtk_tool_item_group_header_relief: + * @group: an #GtkToolItemGroup. + * @style: The GtkReliefStyle + * + * Set the button relief of the group header. See #gtk_button_set_relief for + * details + * + * Since: 2.18 + */ +void +gtk_tool_item_group_set_header_relief (GtkToolItemGroup *group, + GtkReliefStyle style) +{ + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + gtk_button_set_relief (GTK_BUTTON(group->priv->header), style); +} + +static gint64 +gtk_tool_item_group_get_animation_timestamp (GtkToolItemGroup *group) +{ + GTimeVal now; + g_source_get_current_time (group->priv->animation_timeout, &now); + return (now.tv_sec * G_USEC_PER_SEC + now.tv_usec - group->priv->animation_start) / 1000; +} + +static void +gtk_tool_item_group_force_expose (GtkToolItemGroup *group) +{ + if (GTK_WIDGET_REALIZED (group->priv->header)) + { + GtkWidget *alignment = gtk_tool_item_group_get_alignment (group); + GdkRectangle area; + + /* Find the header button's arrow area... */ + area.x = alignment->allocation.x; + area.y = alignment->allocation.y + (alignment->allocation.height - group->priv->expander_size) / 2; + area.height = group->priv->expander_size; + area.width = group->priv->expander_size; + + /* ... and invalidated it to get it animated. */ + gdk_window_invalidate_rect (group->priv->header->window, &area, TRUE); + } + + if (GTK_WIDGET_REALIZED (group)) + { + GtkWidget *widget = GTK_WIDGET (group); + GtkWidget *parent = gtk_widget_get_parent (widget); + int x, y, width, height; + + /* Find the tool item area button's arrow area... */ + width = widget->allocation.width; + height = widget->allocation.height; + + gtk_widget_translate_coordinates (widget, parent, 0, 0, &x, &y); + + if (GTK_WIDGET_VISIBLE (group->priv->header)) + { + height -= group->priv->header->allocation.height; + y += group->priv->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); + gint64 timestamp = gtk_tool_item_group_get_animation_timestamp (group); + + /* Enque this early to reduce number of expose events. */ + gtk_widget_queue_resize_no_redraw (GTK_WIDGET (group)); + + /* Figure out current style of the expander arrow. */ + if (group->priv->collapsed) + { + if (group->priv->expander_style == GTK_EXPANDER_EXPANDED) + group->priv->expander_style = GTK_EXPANDER_SEMI_COLLAPSED; + else + group->priv->expander_style = GTK_EXPANDER_COLLAPSED; + } + else + { + if (group->priv->expander_style == GTK_EXPANDER_COLLAPSED) + group->priv->expander_style = GTK_EXPANDER_SEMI_EXPANDED; + else + group->priv->expander_style = GTK_EXPANDER_EXPANDED; + } + + gtk_tool_item_group_force_expose (group); + + /* Finish animation when done. */ + if (timestamp >= ANIMATION_DURATION) + group->priv->animation_timeout = NULL; + + /* Ensure that all composited windows and child windows are repainted, before + * the parent widget gets its expose-event. This is needed to avoid heavy + * rendering artifacts. GTK+ should take care about this issue by itself I + * guess, but currently it doesn't. Also I don't understand the parameters + * of this issue well enough yet, to file a bug report. + */ + gdk_window_process_updates (GTK_WIDGET (group)->window, TRUE); + + return (group->priv->animation_timeout != NULL); +} + +/** + * gtk_tool_item_group_set_collapsed: + * @group: an #GtkToolItemGroup. + * @collapsed: whether the @group should be collapsed or expanded. + * + * Sets whether the @group should be collapsed or expanded. + * + * Since: 2.18 + */ +void +gtk_tool_item_group_set_collapsed (GtkToolItemGroup *group, + gboolean collapsed) +{ + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + GtkWidget *parent; + + 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 != group->priv->collapsed) + { + if (group->priv->animation) + { + GTimeVal now; + + g_get_current_time (&now); + + if (group->priv->animation_timeout) + g_source_destroy (group->priv->animation_timeout); + + group->priv->animation_start = (now.tv_sec * G_USEC_PER_SEC + now.tv_usec); + group->priv->animation_timeout = g_timeout_source_new (ANIMATION_TIMEOUT); + + g_source_set_callback (group->priv->animation_timeout, + gtk_tool_item_group_animation_cb, + group, NULL); + + g_source_attach (group->priv->animation_timeout, NULL); + } + else + { + group->priv->expander_style = GTK_EXPANDER_COLLAPSED; + gtk_tool_item_group_force_expose (group); + } + group->priv->collapsed = collapsed; + g_object_notify (G_OBJECT (group), "collapsed"); + } +} + +/** + * gtk_tool_item_group_set_ellipsize: + * @group: an #GtkToolItemGroup. + * @ellipsize: the #PangoEllipsizeMode labels in @group should use. + * + * Sets the ellipsization mode which should be used by labels in @group. + * + * Since: 2.18 + */ +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"); +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + _gtk_tool_item_group_palette_reconfigured (group); +#endif + } +} + +/** + * gtk_tool_item_group_get_name: + * @group: an #GtkToolItemGroup. + * + * Gets the name of @group. + * + * Returns: the name of @group. The name is an internal string of @group and must not be modified. + * + * Since: 2.18 + */ +G_CONST_RETURN gchar* +gtk_tool_item_group_get_name (GtkToolItemGroup *group) +{ + GtkWidget *label; + + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), DEFAULT_NAME); + + label = gtk_tool_item_group_get_label (group); + return gtk_label_get_text (GTK_LABEL (label)); +} + +/** + * gtk_tool_item_group_get_collapsed: + * @group: an GtkToolItemGroup. + * + * Gets whether @group is collapsed or expanded. + * + * Returns: %TRUE if @group is collapsed, %FALSE if it is expanded. + * + * Since: 2.18 + */ +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: an #GtkToolItemGroup. + * + * Gets the ellipsization mode of @group. + * + * Returns: the #PangoEllipsizeMode of @group. + * + * Since: 2.18 + */ +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: an #GtkToolItemGroup. + * + * Gets the relief mode of the header button of @group. + * + * Returns: the #GtkReliefStyle + * + * Since: 2.18 + */ +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: an #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.18 + */ +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)); + + if (GTK_IS_BUTTON (child_widget)) + gtk_button_set_focus_on_click (GTK_BUTTON (child_widget), TRUE); + + gtk_widget_set_parent (GTK_WIDGET (item), GTK_WIDGET (group)); +} + +/** + * gtk_tool_item_group_set_item_position: + * @group: an #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.18 + */ +void +gtk_tool_item_group_set_item_position (GtkToolItemGroup *group, + GtkToolItem *item, + gint position) +{ + gint old_position; + GList *link; + 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); + + child = gtk_tool_item_group_get_child (group, item, &old_position, &link); + + g_return_if_fail (child != NULL); + + if (position == old_position) + return; + + group->priv->children = g_list_delete_link (group->priv->children, link); + group->priv->children = g_list_insert (group->priv->children, child, position); + + gtk_widget_child_notify (GTK_WIDGET (item), "position"); + if (GTK_WIDGET_VISIBLE (group) && GTK_WIDGET_VISIBLE (item)) + gtk_widget_queue_resize (GTK_WIDGET (group)); +} + +/** + * gtk_tool_item_group_get_item_position: + * @group: an #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.18 + */ +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: an #GtkToolItemGroup. + * + * Gets the number of tool items in group. + * + * Returns: the number of tool items in group. + * + * Since: 2.18 + */ +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: an #GtkToolItemGroup. + * @index: the index. + * + * Gets the tool item at index in group. + * + * Returns: the #GtkToolItem at index. + * + * Since: 2.18 + */ +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: an #GtkToolItemGroup. + * @x: the x position. + * @y: the y position. + * + * Gets the tool item at position (x, y). + * + * Returns: the #GtkToolItem at position (x, y). + * + * Since: 2.18 + */ +GtkToolItem* +gtk_tool_item_group_get_drop_item (GtkToolItemGroup *group, + gint x, + gint y) +{ + GtkAllocation *allocation; + GtkOrientation orientation; + GList *it; + + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), NULL); + + allocation = >K_WIDGET (group)->allocation; + orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group)); + + 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; + + allocation = >K_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; + GtkOrientation orientation; + GtkToolbarStyle style; + + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + g_return_if_fail (NULL != item_size); + + orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group)); + style = gtk_tool_shell_get_style (GTK_TOOL_SHELL (group)); + + 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_size_request (GTK_WIDGET (child->item), &child_requisition); + + 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; +} + +void +_gtk_tool_item_group_paint (GtkToolItemGroup *group, + cairo_t *cr) +{ + GtkWidget *widget = GTK_WIDGET (group); + + gdk_cairo_set_source_pixmap (cr, widget->window, + widget->allocation.x, + widget->allocation.y); + + if (group->priv->animation_timeout) + { + GtkOrientation orientation = gtk_tool_item_group_get_orientation (GTK_TOOL_SHELL (group)); + cairo_pattern_t *mask; + gdouble v0, v1; + + if (GTK_ORIENTATION_VERTICAL == orientation) + v1 = widget->allocation.height; + else + v1 = widget->allocation.width; + + v0 = v1 - 256; + + if (!GTK_WIDGET_VISIBLE (group->priv->header)) + v0 = MAX (v0, 0); + else if (GTK_ORIENTATION_VERTICAL == orientation) + v0 = MAX (v0, group->priv->header->allocation.height); + else + v0 = MAX (v0, group->priv->header->allocation.width); + + v1 = MIN (v0 + 256, v1); + + if (GTK_ORIENTATION_VERTICAL == orientation) + { + v0 += widget->allocation.y; + v1 += widget->allocation.y; + + mask = cairo_pattern_create_linear (0.0, v0, 0.0, v1); + } + else + { + v0 += widget->allocation.x; + v1 += widget->allocation.x; + + mask = cairo_pattern_create_linear (v0, 0.0, v1, 0.0); + } + + cairo_pattern_add_color_stop_rgba (mask, 0.00, 0.0, 0.0, 0.0, 1.00); + cairo_pattern_add_color_stop_rgba (mask, 0.25, 0.0, 0.0, 0.0, 0.25); + cairo_pattern_add_color_stop_rgba (mask, 0.50, 0.0, 0.0, 0.0, 0.10); + cairo_pattern_add_color_stop_rgba (mask, 0.75, 0.0, 0.0, 0.0, 0.01); + cairo_pattern_add_color_stop_rgba (mask, 1.00, 0.0, 0.0, 0.0, 0.00); + + cairo_mask (cr, mask); + cairo_pattern_destroy (mask); + } + else + cairo_paint (cr); +} + +gint +_gtk_tool_item_group_get_size_for_limit (GtkToolItemGroup *group, + gint limit, + gboolean vertical, + gboolean animation) +{ + GtkRequisition requisition; + + gtk_widget_size_request (GTK_WIDGET (group), &requisition); + + if (!group->priv->collapsed || group->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 (group->priv->animation_timeout && animation) + { + gint64 timestamp = gtk_tool_item_group_get_animation_timestamp (group); + + timestamp = MIN (timestamp, ANIMATION_DURATION); + + if (group->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 G_GNUC_UNUSED) +{ + 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); +} +/* 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Mathias Hasselmann + * Jan Arne Petersen + */ + +#include "gtktoolpaletteprivate.h" + +#include +#include +#include + +#define P_(msgid) (msgid) + +#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_NAME NULL +#define DEFAULT_COLLAPSED FALSE +#define DEFAULT_ELLIPSIZE PANGO_ELLIPSIZE_NONE + +/** + * SECTION:GtkToolItemGroup + * @short_description: A sub container used in a tool palette + * @include: gtktoolitemgroup.h + * + * An #GtkToolItemGroup is used together with #GtkToolPalette to add #GtkToolItems to a palette like container + * with different categories and drag and drop support. + * + * Since: 2.18 + */ + +enum +{ + PROP_NONE, + PROP_NAME, + 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; + + GList *children; + + gboolean animation; + gint64 animation_start; + GSource *animation_timeout; + GtkExpanderStyle expander_style; + gint expander_size; + gint header_spacing; + PangoEllipsizeMode ellipsize; + + gulong focus_set_id; + GtkWidget *toplevel; + + GtkSettings *settings; + gulong settings_connection; + + 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_IMPLEMENT_INTERFACE (GTK_TYPE_TOOL_SHELL, gtk_tool_item_group_tool_shell_init)); + +static GtkWidget* +gtk_tool_item_group_get_alignment (GtkToolItemGroup *group) +{ + return gtk_bin_get_child (GTK_BIN (group->priv->header)); +} + +static GtkWidget* +gtk_tool_item_group_get_label (GtkToolItemGroup *group) +{ + GtkWidget *alignment = gtk_tool_item_group_get_alignment (group); + return gtk_bin_get_child (GTK_BIN (alignment)); +} + +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_tool_palette_get_orientation (GTK_TOOL_PALETTE (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; +} + +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + +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) +{ + GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (shell)); + + if (GTK_IS_TOOL_PALETTE (parent)) + { + GtkOrientation orientation = gtk_tool_palette_get_orientation (GTK_TOOL_PALETTE (parent)); + if (GTK_ORIENTATION_HORIZONTAL == orientation && + (GTK_TOOLBAR_TEXT == gtk_tool_item_group_get_style (shell)/* || + GTK_TOOLBAR_BOTH_HORIZ == gtk_tool_item_group_get_style (shell)*/)) + return GTK_ORIENTATION_VERTICAL; + } + + 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; +} + +#endif + +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")) + { + 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); + 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; +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + 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; +#endif +} + +static gboolean +gtk_tool_item_group_header_expose_event_cb (GtkWidget *widget, + GdkEventExpose *event, + gpointer data) +{ + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (data); + GtkExpanderStyle expander_style; + GtkOrientation orientation; + gint x, y; + GtkTextDirection direction; + + orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group)); + expander_style = group->priv->expander_style; + direction = gtk_widget_get_direction (widget); + + if (GTK_ORIENTATION_VERTICAL == orientation) + { + if (GTK_TEXT_DIR_RTL == direction) + x = widget->allocation.x + widget->allocation.width - group->priv->expander_size / 2; + else + x = widget->allocation.x + group->priv->expander_size / 2; + y = widget->allocation.y + widget->allocation.height / 2; + } + else + { + x = widget->allocation.x + widget->allocation.width / 2; + y = widget->allocation.y + group->priv->expander_size / 2; + + /* Unfortunatly gtk_paint_expander() doesn't support rotated drawing + * modes. Luckily the following shady arithmetics produce the desired + * result. */ + expander_style = GTK_EXPANDER_EXPANDED - expander_style; /* XXX */ + } + + gtk_paint_expander (widget->style, widget->window, + group->priv->header->state, + &event->area, GTK_WIDGET (group), + "tool-palette-header", x, y, + expander_style); + + return FALSE; +} + +static void +gtk_tool_item_group_header_size_request_cb (GtkWidget *widget G_GNUC_UNUSED, + GtkRequisition *requisition, + gpointer data) +{ + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (data); + requisition->height = MAX (requisition->height, group->priv->expander_size); +} + +static void +gtk_tool_item_group_header_clicked_cb (GtkButton *button G_GNUC_UNUSED, + gpointer data) +{ + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (data); + GtkWidget *parent = gtk_widget_get_parent (data); + + if (group->priv->collapsed || + !GTK_IS_TOOL_PALETTE (parent) || + !gtk_tool_palette_get_exclusive (GTK_TOOL_PALETTE (parent), data)) + gtk_tool_item_group_set_collapsed (group, !group->priv->collapsed); +} + +static void +gtk_tool_item_group_header_adjust_style (GtkToolItemGroup *group) +{ + GtkWidget *alignment = gtk_tool_item_group_get_alignment (group); + GtkWidget *label = gtk_bin_get_child (GTK_BIN (alignment)); + GtkWidget *widget = GTK_WIDGET (group); + gint dx = 0, dy = 0; + GtkTextDirection direction = gtk_widget_get_direction (widget); + + gtk_widget_style_get (widget, + "header-spacing", &group->priv->header_spacing, + "expander-size", &group->priv->expander_size, + NULL); + + switch (gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group))) + { + case GTK_ORIENTATION_HORIZONTAL: + dy = group->priv->header_spacing + group->priv->expander_size; + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_NONE); + if (GTK_TEXT_DIR_RTL == direction) + gtk_label_set_angle (GTK_LABEL (label), -90); + else + gtk_label_set_angle (GTK_LABEL (label), 90); + break; + + case GTK_ORIENTATION_VERTICAL: + dx = group->priv->header_spacing + group->priv->expander_size; + gtk_label_set_ellipsize (GTK_LABEL (label), group->priv->ellipsize); + gtk_label_set_angle (GTK_LABEL (label), 0); + break; + } + + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), dy, 0, dx, 0); +} + +static void +gtk_tool_item_group_init (GtkToolItemGroup *group) +{ + GtkWidget *alignment; + GtkWidget *label; + + gtk_widget_set_redraw_on_allocate (GTK_WIDGET (group), FALSE); + + group->priv = G_TYPE_INSTANCE_GET_PRIVATE (group, + GTK_TYPE_TOOL_ITEM_GROUP, + GtkToolItemGroupPrivate); + + group->priv->children = NULL; + group->priv->header_spacing = DEFAULT_HEADER_SPACING; + group->priv->expander_size = DEFAULT_EXPANDER_SIZE; + group->priv->expander_style = GTK_EXPANDER_EXPANDED; + + label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0); + gtk_container_add (GTK_CONTAINER (alignment), label); + gtk_widget_show_all (alignment); + + gtk_widget_push_composite_child (); + group->priv->header = gtk_button_new (); + gtk_widget_set_composite_name (group->priv->header, "header"); + gtk_widget_pop_composite_child (); + + g_object_ref_sink (group->priv->header); + gtk_button_set_focus_on_click (GTK_BUTTON (group->priv->header), FALSE); + gtk_container_add (GTK_CONTAINER (group->priv->header), alignment); + gtk_widget_set_parent (group->priv->header, GTK_WIDGET (group)); + + gtk_tool_item_group_header_adjust_style (group); + + g_signal_connect_after (alignment, "expose-event", + G_CALLBACK (gtk_tool_item_group_header_expose_event_cb), + group); + g_signal_connect_after (alignment, "size-request", + G_CALLBACK (gtk_tool_item_group_header_size_request_cb), + group); + + g_signal_connect (group->priv->header, "clicked", + G_CALLBACK (gtk_tool_item_group_header_clicked_cb), + 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_NAME: + gtk_tool_item_group_set_name (group, g_value_get_string (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_NAME: + g_value_set_string (value, gtk_tool_item_group_get_name (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); + + if (group->priv->children) + { + g_list_free (group->priv->children); + group->priv->children = NULL; + } + + 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); + + if (group->priv->toplevel) + { + /* disconnect focus tracking handler */ + g_signal_handler_disconnect (group->priv->toplevel, + group->priv->focus_set_id); + + group->priv->focus_set_id = 0; + group->priv->toplevel = NULL; + } + + 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) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (widget); + GtkOrientation orientation; + GtkRequisition item_size; + gint requested_rows; + + if (group->priv->children && gtk_tool_item_group_get_name (group)) + { + gtk_widget_size_request (group->priv->header, requisition); + gtk_widget_show (group->priv->header); + } + else + { + requisition->width = requisition->height = 0; + gtk_widget_hide (group->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); + + requisition->width += border_width * 2; + requisition->height += border_width * 2; +} + +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_VISIBLE (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) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (widget); + + GtkRequisition item_size; + GtkAllocation item_area; + + GtkOrientation orientation; + GtkToolbarStyle style; + + gint min_rows; + + orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group)); + style = gtk_tool_shell_get_style (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_area.width = 0; + item_area.height = 0; + + /* figure out the required columns (n_columns) and rows (n_rows) to place all items */ + if (!group->priv->collapsed || !group->priv->animation || group->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 - 2 * border_width; + n_columns = MAX (item_area.width / item_size.width, 1); + + /* calculate required rows for n_columns columns */ + 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 (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_size_request (GTK_WIDGET (child->item), &req); + + 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 - 2 * border_width; + 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 = group->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_size_request (GTK_WIDGET (child->item), &req); + + 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 = group->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_size_request (GTK_WIDGET (child->item), &req); + + 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_VISIBLE (group->priv->header)) + { + GtkRequisition child_requisition; + + gtk_widget_size_request (group->priv->header, &child_requisition); + + 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 + 2 * border_width; + inquery->height += item_area.height + 2 * border_width; +} + +static void +gtk_tool_item_group_real_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (widget); + GtkRequisition child_requisition; + GtkAllocation child_allocation; + + GtkRequisition item_size; + GtkAllocation item_area; + + GtkOrientation orientation; + GtkToolbarStyle style; + + GList *it; + + gint n_columns, n_rows = 1; + gint min_rows; + + GtkTextDirection direction = gtk_widget_get_direction (widget); + + orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group)); + style = gtk_tool_shell_get_style (GTK_TOOL_SHELL (group)); + + /* chain up */ + GTK_WIDGET_CLASS (gtk_tool_item_group_parent_class)->size_allocate (widget, allocation); + + child_allocation.x = border_width; + child_allocation.y = border_width; + + /* place the header widget */ + if (GTK_WIDGET_VISIBLE (group->priv->header)) + { + gtk_widget_size_request (group->priv->header, &child_requisition); + + 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 - border_width - child_allocation.width; + } + + gtk_widget_size_allocate (group->priv->header, &child_allocation); + + 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 = border_width; + } + 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); + + /* 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 - 2 * border_width; + item_area.height = allocation->height - 2 * border_width - 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 - 2 * border_width - child_requisition.width; + item_area.height = allocation->height - 2 * border_width; + + 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 (!group->priv->collapsed || !group->priv->animation || group->priv->animation_timeout) + { + gint col = 0, row = 0; + + for (it = group->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_size_request (GTK_WIDGET (child->item), &child_requisition); + 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); + 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 = group->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, + GtkAllocation *allocation) +{ + gtk_tool_item_group_real_size_allocate (widget, allocation); + + if (GTK_WIDGET_MAPPED (widget)) + gdk_window_invalidate_rect (widget->window, NULL, FALSE); +} + +static void +gtk_tool_item_group_set_focus_cb (GtkWidget *window G_GNUC_UNUSED, + GtkWidget *widget, + gpointer user_data) +{ + GtkAdjustment *adjustment; + 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_tool_palette_get_hadjustment (GTK_TOOL_PALETTE (p)); + adjustment = gtk_tool_palette_get_vadjustment (GTK_TOOL_PALETTE (p)); + + if (adjustment) + { + int y; + + /* Handle vertical adjustment. */ + if (gtk_widget_translate_coordinates + (widget, p, 0, 0, NULL, &y) && y < 0) + { + y += adjustment->value; + gtk_adjustment_clamp_page (adjustment, y, y + widget->allocation.height); + } + else if (gtk_widget_translate_coordinates + (widget, p, 0, widget->allocation.height, NULL, &y) && + y > p->allocation.height) + { + y += adjustment->value; + gtk_adjustment_clamp_page (adjustment, y - widget->allocation.height, y); + } + } + + adjustment = gtk_tool_palette_get_hadjustment (GTK_TOOL_PALETTE (p)); + + if (adjustment) + { + int x; + + /* Handle horizontal adjustment. */ + if (gtk_widget_translate_coordinates + (widget, p, 0, 0, &x, NULL) && x < 0) + { + x += adjustment->value; + gtk_adjustment_clamp_page (adjustment, x, x + widget->allocation.width); + } + else if (gtk_widget_translate_coordinates + (widget, p, widget->allocation.width, 0, &x, NULL) && + x > p->allocation.width) + { + x += adjustment->value; + gtk_adjustment_clamp_page (adjustment, x - widget->allocation.width, x); + } + + return; + } + } +} + +static void +gtk_tool_item_group_set_toplevel_window (GtkToolItemGroup *group, + GtkWidget *toplevel) +{ + if (toplevel != group->priv->toplevel) + { + if (group->priv->toplevel) + { + /* Disconnect focus tracking handler. */ + g_signal_handler_disconnect (group->priv->toplevel, + group->priv->focus_set_id); + + group->priv->focus_set_id = 0; + group->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. + */ + group->priv->focus_set_id = + g_signal_connect (toplevel, "set-focus", + G_CALLBACK (gtk_tool_item_group_set_focus_cb), + group); + + group->priv->toplevel = toplevel; + } + } +} + +static void +gtk_tool_item_group_realize (GtkWidget *widget) +{ + GtkWidget *toplevel_window; + const gint border_width = GTK_CONTAINER (widget)->border_width; + gint attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + GdkWindowAttr attributes; + GdkDisplay *display; + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = widget->allocation.x + border_width; + attributes.y = widget->allocation.y + border_width; + attributes.width = widget->allocation.width - border_width * 2; + attributes.height = widget->allocation.height - border_width * 2; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK + | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + | GDK_BUTTON_MOTION_MASK; + + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + + display = gdk_drawable_get_display (widget->window); + + if (gdk_display_supports_composite (display)) + gdk_window_set_composited (widget->window, TRUE); + + gdk_window_set_user_data (widget->window, widget); + widget->style = gtk_style_attach (widget->style, widget->window); + gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + + gtk_container_forall (GTK_CONTAINER (widget), + (GtkCallback) gtk_widget_set_parent_window, + widget->window); + + gtk_widget_queue_resize_no_redraw (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_set (GtkWidget *widget, + GtkStyle *previous_style) +{ + gtk_tool_item_group_header_adjust_style (GTK_TOOL_ITEM_GROUP (widget)); + GTK_WIDGET_CLASS (gtk_tool_item_group_parent_class)->style_set (widget, previous_style); +} + +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; + GList *it; + + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (container)); + group = GTK_TOOL_ITEM_GROUP (container); + + for (it = group->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); + group->priv->children = g_list_delete_link (group->priv->children, it); + + gtk_widget_queue_resize (GTK_WIDGET (container)); + break; + } + } +} + +static void +gtk_tool_item_group_forall (GtkContainer *container, + gboolean internals, + GtkCallback callback, + gpointer callback_data) +{ + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (container); + GList *children; + + if (internals && group->priv->header) + callback (group->priv->header, callback_data); + + children = group->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 G_GNUC_UNUSED) +{ + 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_VISIBLE (group) && GTK_WIDGET_VISIBLE (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->size_request = gtk_tool_item_group_size_request; + 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_set = gtk_tool_item_group_style_set; + wclass->screen_changed = gtk_tool_item_group_screen_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_NAME, + g_param_spec_string ("name", + P_("Name"), + P_("The name of this item group"), + DEFAULT_NAME, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (oclass, PROP_COLLAPSED, + g_param_spec_boolean ("collapsed", + P_("Collapsed"), + P_("Wether the group has been collapsed and items are hidden"), + DEFAULT_COLLAPSED, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + 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, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (oclass, PROP_ELLIPSIZE, + g_param_spec_enum ("header-relief", + P_("header-relif"), + P_("Relief of the group header button"), + GTK_TYPE_RELIEF_STYLE, GTK_RELIEF_NORMAL, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + gtk_widget_class_install_style_property (wclass, + g_param_spec_int ("expander-size", + P_("Expander Size"), + P_("Size of the expander arrow"), + 0, + G_MAXINT, + DEFAULT_EXPANDER_SIZE, + G_PARAM_READABLE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + gtk_widget_class_install_style_property (wclass, + g_param_spec_int ("header-spacing", + P_("Header Spacing"), + P_("Spacing between expander arrow and caption"), + 0, + G_MAXINT, + DEFAULT_HEADER_SPACING, + G_PARAM_READABLE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + 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, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + 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 toolbar grows"), + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + gtk_container_class_install_child_property (cclass, CHILD_PROP_FILL, + g_param_spec_boolean ("fill", + P_("Fill"), + P_("Whether the item should fill the avaiable space"), + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + 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, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + 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, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_type_class_add_private (cls, sizeof (GtkToolItemGroupPrivate)); +} + +/** + * gtk_tool_item_group_new: + * @name: the name of the new group. + * + * Creates a new tool item group with name @name. + * + * Returns: a new #GtkToolItemGroup. + * + * Since: 2.18 + */ +GtkWidget* +gtk_tool_item_group_new (const gchar *name) +{ + return g_object_new (GTK_TYPE_TOOL_ITEM_GROUP, "name", name, NULL); +} + +/** + * gtk_tool_item_group_set_name: + * @group: an #GtkToolItemGroup. + * @name: the new name of of the group. + * + * Sets the name of the tool item group. The name is displayed in the header + * of the group. + * + * Since: 2.18 + */ +void +gtk_tool_item_group_set_name (GtkToolItemGroup *group, + const gchar *name) +{ + const gchar *current_name; + GtkWidget *label; + + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + current_name = gtk_tool_item_group_get_name (group); + + if (current_name != name && (!current_name || !name || strcmp (current_name, name))) + { + label = gtk_tool_item_group_get_label (group); + gtk_label_set_text (GTK_LABEL (label), name); + + if (name && group->priv->children) + gtk_widget_show (group->priv->header); + else + gtk_widget_hide (group->priv->header); + + g_object_notify (G_OBJECT (group), "name"); + } +} + +/** + * gtk_tool_item_group_header_relief: + * @group: an #GtkToolItemGroup. + * @style: The GtkReliefStyle + * + * Set the button relief of the group header. See #gtk_button_set_relief for + * details + * + * Since: 2.18 + */ +void +gtk_tool_item_group_set_header_relief (GtkToolItemGroup *group, + GtkReliefStyle style) +{ + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + gtk_button_set_relief (GTK_BUTTON(group->priv->header), style); +} + +static gint64 +gtk_tool_item_group_get_animation_timestamp (GtkToolItemGroup *group) +{ + GTimeVal now; + g_source_get_current_time (group->priv->animation_timeout, &now); + return (now.tv_sec * G_USEC_PER_SEC + now.tv_usec - group->priv->animation_start) / 1000; +} + +static void +gtk_tool_item_group_force_expose (GtkToolItemGroup *group) +{ + if (GTK_WIDGET_REALIZED (group->priv->header)) + { + GtkWidget *alignment = gtk_tool_item_group_get_alignment (group); + GdkRectangle area; + + /* Find the header button's arrow area... */ + area.x = alignment->allocation.x; + area.y = alignment->allocation.y + (alignment->allocation.height - group->priv->expander_size) / 2; + area.height = group->priv->expander_size; + area.width = group->priv->expander_size; + + /* ... and invalidated it to get it animated. */ + gdk_window_invalidate_rect (group->priv->header->window, &area, TRUE); + } + + if (GTK_WIDGET_REALIZED (group)) + { + GtkWidget *widget = GTK_WIDGET (group); + GtkWidget *parent = gtk_widget_get_parent (widget); + int x, y, width, height; + + /* Find the tool item area button's arrow area... */ + width = widget->allocation.width; + height = widget->allocation.height; + + gtk_widget_translate_coordinates (widget, parent, 0, 0, &x, &y); + + if (GTK_WIDGET_VISIBLE (group->priv->header)) + { + height -= group->priv->header->allocation.height; + y += group->priv->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); + gint64 timestamp = gtk_tool_item_group_get_animation_timestamp (group); + + /* Enque this early to reduce number of expose events. */ + gtk_widget_queue_resize_no_redraw (GTK_WIDGET (group)); + + /* Figure out current style of the expander arrow. */ + if (group->priv->collapsed) + { + if (group->priv->expander_style == GTK_EXPANDER_EXPANDED) + group->priv->expander_style = GTK_EXPANDER_SEMI_COLLAPSED; + else + group->priv->expander_style = GTK_EXPANDER_COLLAPSED; + } + else + { + if (group->priv->expander_style == GTK_EXPANDER_COLLAPSED) + group->priv->expander_style = GTK_EXPANDER_SEMI_EXPANDED; + else + group->priv->expander_style = GTK_EXPANDER_EXPANDED; + } + + gtk_tool_item_group_force_expose (group); + + /* Finish animation when done. */ + if (timestamp >= ANIMATION_DURATION) + group->priv->animation_timeout = NULL; + + /* Ensure that all composited windows and child windows are repainted, before + * the parent widget gets its expose-event. This is needed to avoid heavy + * rendering artifacts. GTK+ should take care about this issue by itself I + * guess, but currently it doesn't. Also I don't understand the parameters + * of this issue well enough yet, to file a bug report. + */ + gdk_window_process_updates (GTK_WIDGET (group)->window, TRUE); + + return (group->priv->animation_timeout != NULL); +} + +/** + * gtk_tool_item_group_set_collapsed: + * @group: an #GtkToolItemGroup. + * @collapsed: whether the @group should be collapsed or expanded. + * + * Sets whether the @group should be collapsed or expanded. + * + * Since: 2.18 + */ +void +gtk_tool_item_group_set_collapsed (GtkToolItemGroup *group, + gboolean collapsed) +{ + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + GtkWidget *parent; + + 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 != group->priv->collapsed) + { + if (group->priv->animation) + { + GTimeVal now; + + g_get_current_time (&now); + + if (group->priv->animation_timeout) + g_source_destroy (group->priv->animation_timeout); + + group->priv->animation_start = (now.tv_sec * G_USEC_PER_SEC + now.tv_usec); + group->priv->animation_timeout = g_timeout_source_new (ANIMATION_TIMEOUT); + + g_source_set_callback (group->priv->animation_timeout, + gtk_tool_item_group_animation_cb, + group, NULL); + + g_source_attach (group->priv->animation_timeout, NULL); + } + else + { + group->priv->expander_style = GTK_EXPANDER_COLLAPSED; + gtk_tool_item_group_force_expose (group); + } + group->priv->collapsed = collapsed; + g_object_notify (G_OBJECT (group), "collapsed"); + } +} + +/** + * gtk_tool_item_group_set_ellipsize: + * @group: an #GtkToolItemGroup. + * @ellipsize: the #PangoEllipsizeMode labels in @group should use. + * + * Sets the ellipsization mode which should be used by labels in @group. + * + * Since: 2.18 + */ +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"); +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + _gtk_tool_item_group_palette_reconfigured (group); +#endif + } +} + +/** + * gtk_tool_item_group_get_name: + * @group: an #GtkToolItemGroup. + * + * Gets the name of @group. + * + * Returns: the name of @group. The name is an internal string of @group and must not be modified. + * + * Since: 2.18 + */ +G_CONST_RETURN gchar* +gtk_tool_item_group_get_name (GtkToolItemGroup *group) +{ + GtkWidget *label; + + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), DEFAULT_NAME); + + label = gtk_tool_item_group_get_label (group); + return gtk_label_get_text (GTK_LABEL (label)); +} + +/** + * gtk_tool_item_group_get_collapsed: + * @group: an GtkToolItemGroup. + * + * Gets whether @group is collapsed or expanded. + * + * Returns: %TRUE if @group is collapsed, %FALSE if it is expanded. + * + * Since: 2.18 + */ +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: an #GtkToolItemGroup. + * + * Gets the ellipsization mode of @group. + * + * Returns: the #PangoEllipsizeMode of @group. + * + * Since: 2.18 + */ +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: an #GtkToolItemGroup. + * + * Gets the relief mode of the header button of @group. + * + * Returns: the #GtkReliefStyle + * + * Since: 2.18 + */ +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: an #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.18 + */ +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)); + + if (GTK_IS_BUTTON (child_widget)) + gtk_button_set_focus_on_click (GTK_BUTTON (child_widget), TRUE); + + gtk_widget_set_parent (GTK_WIDGET (item), GTK_WIDGET (group)); +} + +/** + * gtk_tool_item_group_set_item_position: + * @group: an #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.18 + */ +void +gtk_tool_item_group_set_item_position (GtkToolItemGroup *group, + GtkToolItem *item, + gint position) +{ + gint old_position; + GList *link; + 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); + + child = gtk_tool_item_group_get_child (group, item, &old_position, &link); + + g_return_if_fail (child != NULL); + + if (position == old_position) + return; + + group->priv->children = g_list_delete_link (group->priv->children, link); + group->priv->children = g_list_insert (group->priv->children, child, position); + + gtk_widget_child_notify (GTK_WIDGET (item), "position"); + if (GTK_WIDGET_VISIBLE (group) && GTK_WIDGET_VISIBLE (item)) + gtk_widget_queue_resize (GTK_WIDGET (group)); +} + +/** + * gtk_tool_item_group_get_item_position: + * @group: an #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.18 + */ +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: an #GtkToolItemGroup. + * + * Gets the number of tool items in group. + * + * Returns: the number of tool items in group. + * + * Since: 2.18 + */ +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: an #GtkToolItemGroup. + * @index: the index. + * + * Gets the tool item at index in group. + * + * Returns: the #GtkToolItem at index. + * + * Since: 2.18 + */ +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: an #GtkToolItemGroup. + * @x: the x position. + * @y: the y position. + * + * Gets the tool item at position (x, y). + * + * Returns: the #GtkToolItem at position (x, y). + * + * Since: 2.18 + */ +GtkToolItem* +gtk_tool_item_group_get_drop_item (GtkToolItemGroup *group, + gint x, + gint y) +{ + GtkAllocation *allocation; + GtkOrientation orientation; + GList *it; + + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), NULL); + + allocation = >K_WIDGET (group)->allocation; + orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group)); + + 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; + + allocation = >K_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; + GtkOrientation orientation; + GtkToolbarStyle style; + + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + g_return_if_fail (NULL != item_size); + + orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group)); + style = gtk_tool_shell_get_style (GTK_TOOL_SHELL (group)); + + 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_size_request (GTK_WIDGET (child->item), &child_requisition); + + 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; +} + +void +_gtk_tool_item_group_paint (GtkToolItemGroup *group, + cairo_t *cr) +{ + GtkWidget *widget = GTK_WIDGET (group); + + gdk_cairo_set_source_pixmap (cr, widget->window, + widget->allocation.x, + widget->allocation.y); + + if (group->priv->animation_timeout) + { + GtkOrientation orientation = gtk_tool_item_group_get_orientation (GTK_TOOL_SHELL (group)); + cairo_pattern_t *mask; + gdouble v0, v1; + + if (GTK_ORIENTATION_VERTICAL == orientation) + v1 = widget->allocation.height; + else + v1 = widget->allocation.width; + + v0 = v1 - 256; + + if (!GTK_WIDGET_VISIBLE (group->priv->header)) + v0 = MAX (v0, 0); + else if (GTK_ORIENTATION_VERTICAL == orientation) + v0 = MAX (v0, group->priv->header->allocation.height); + else + v0 = MAX (v0, group->priv->header->allocation.width); + + v1 = MIN (v0 + 256, v1); + + if (GTK_ORIENTATION_VERTICAL == orientation) + { + v0 += widget->allocation.y; + v1 += widget->allocation.y; + + mask = cairo_pattern_create_linear (0.0, v0, 0.0, v1); + } + else + { + v0 += widget->allocation.x; + v1 += widget->allocation.x; + + mask = cairo_pattern_create_linear (v0, 0.0, v1, 0.0); + } + + cairo_pattern_add_color_stop_rgba (mask, 0.00, 0.0, 0.0, 0.0, 1.00); + cairo_pattern_add_color_stop_rgba (mask, 0.25, 0.0, 0.0, 0.0, 0.25); + cairo_pattern_add_color_stop_rgba (mask, 0.50, 0.0, 0.0, 0.0, 0.10); + cairo_pattern_add_color_stop_rgba (mask, 0.75, 0.0, 0.0, 0.0, 0.01); + cairo_pattern_add_color_stop_rgba (mask, 1.00, 0.0, 0.0, 0.0, 0.00); + + cairo_mask (cr, mask); + cairo_pattern_destroy (mask); + } + else + cairo_paint (cr); +} + +gint +_gtk_tool_item_group_get_size_for_limit (GtkToolItemGroup *group, + gint limit, + gboolean vertical, + gboolean animation) +{ + GtkRequisition requisition; + + gtk_widget_size_request (GTK_WIDGET (group), &requisition); + + if (!group->priv->collapsed || group->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 (group->priv->animation_timeout && animation) + { + gint64 timestamp = gtk_tool_item_group_get_animation_timestamp (group); + + timestamp = MIN (timestamp, ANIMATION_DURATION); + + if (group->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 G_GNUC_UNUSED) +{ + 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); +} +/* 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Mathias Hasselmann + * Jan Arne Petersen + */ + +#include "gtktoolpaletteprivate.h" + +#include +#include +#include + +#define P_(msgid) (msgid) + +#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_NAME NULL +#define DEFAULT_COLLAPSED FALSE +#define DEFAULT_ELLIPSIZE PANGO_ELLIPSIZE_NONE + +/** + * SECTION:GtkToolItemGroup + * @short_description: A sub container used in a tool palette + * @include: gtktoolitemgroup.h + * + * An #GtkToolItemGroup is used together with #GtkToolPalette to add #GtkToolItems to a palette like container + * with different categories and drag and drop support. + * + * Since: 2.18 + */ + +enum +{ + PROP_NONE, + PROP_NAME, + 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; + + GList *children; + + gboolean animation; + gint64 animation_start; + GSource *animation_timeout; + GtkExpanderStyle expander_style; + gint expander_size; + gint header_spacing; + PangoEllipsizeMode ellipsize; + + gulong focus_set_id; + GtkWidget *toplevel; + + GtkSettings *settings; + gulong settings_connection; + + 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_IMPLEMENT_INTERFACE (GTK_TYPE_TOOL_SHELL, gtk_tool_item_group_tool_shell_init)); + +static GtkWidget* +gtk_tool_item_group_get_alignment (GtkToolItemGroup *group) +{ + return gtk_bin_get_child (GTK_BIN (group->priv->header)); +} + +static GtkWidget* +gtk_tool_item_group_get_label (GtkToolItemGroup *group) +{ + GtkWidget *alignment = gtk_tool_item_group_get_alignment (group); + return gtk_bin_get_child (GTK_BIN (alignment)); +} + +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_tool_palette_get_orientation (GTK_TOOL_PALETTE (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; +} + +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + +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) +{ + GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (shell)); + + if (GTK_IS_TOOL_PALETTE (parent)) + { + GtkOrientation orientation = gtk_tool_palette_get_orientation (GTK_TOOL_PALETTE (parent)); + if (GTK_ORIENTATION_HORIZONTAL == orientation && + (GTK_TOOLBAR_TEXT == gtk_tool_item_group_get_style (shell)/* || + GTK_TOOLBAR_BOTH_HORIZ == gtk_tool_item_group_get_style (shell)*/)) + return GTK_ORIENTATION_VERTICAL; + } + + 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; +} + +#endif + +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")) + { + 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); + 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; +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + 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; +#endif +} + +static gboolean +gtk_tool_item_group_header_expose_event_cb (GtkWidget *widget, + GdkEventExpose *event, + gpointer data) +{ + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (data); + GtkExpanderStyle expander_style; + GtkOrientation orientation; + gint x, y; + GtkTextDirection direction; + + orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group)); + expander_style = group->priv->expander_style; + direction = gtk_widget_get_direction (widget); + + if (GTK_ORIENTATION_VERTICAL == orientation) + { + if (GTK_TEXT_DIR_RTL == direction) + x = widget->allocation.x + widget->allocation.width - group->priv->expander_size / 2; + else + x = widget->allocation.x + group->priv->expander_size / 2; + y = widget->allocation.y + widget->allocation.height / 2; + } + else + { + x = widget->allocation.x + widget->allocation.width / 2; + y = widget->allocation.y + group->priv->expander_size / 2; + + /* Unfortunatly gtk_paint_expander() doesn't support rotated drawing + * modes. Luckily the following shady arithmetics produce the desired + * result. */ + expander_style = GTK_EXPANDER_EXPANDED - expander_style; /* XXX */ + } + + gtk_paint_expander (widget->style, widget->window, + group->priv->header->state, + &event->area, GTK_WIDGET (group), + "tool-palette-header", x, y, + expander_style); + + return FALSE; +} + +static void +gtk_tool_item_group_header_size_request_cb (GtkWidget *widget G_GNUC_UNUSED, + GtkRequisition *requisition, + gpointer data) +{ + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (data); + requisition->height = MAX (requisition->height, group->priv->expander_size); +} + +static void +gtk_tool_item_group_header_clicked_cb (GtkButton *button G_GNUC_UNUSED, + gpointer data) +{ + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (data); + GtkWidget *parent = gtk_widget_get_parent (data); + + if (group->priv->collapsed || + !GTK_IS_TOOL_PALETTE (parent) || + !gtk_tool_palette_get_exclusive (GTK_TOOL_PALETTE (parent), data)) + gtk_tool_item_group_set_collapsed (group, !group->priv->collapsed); +} + +static void +gtk_tool_item_group_header_adjust_style (GtkToolItemGroup *group) +{ + GtkWidget *alignment = gtk_tool_item_group_get_alignment (group); + GtkWidget *label = gtk_bin_get_child (GTK_BIN (alignment)); + GtkWidget *widget = GTK_WIDGET (group); + gint dx = 0, dy = 0; + GtkTextDirection direction = gtk_widget_get_direction (widget); + + gtk_widget_style_get (widget, + "header-spacing", &group->priv->header_spacing, + "expander-size", &group->priv->expander_size, + NULL); + + switch (gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group))) + { + case GTK_ORIENTATION_HORIZONTAL: + dy = group->priv->header_spacing + group->priv->expander_size; + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_NONE); + if (GTK_TEXT_DIR_RTL == direction) + gtk_label_set_angle (GTK_LABEL (label), -90); + else + gtk_label_set_angle (GTK_LABEL (label), 90); + break; + + case GTK_ORIENTATION_VERTICAL: + dx = group->priv->header_spacing + group->priv->expander_size; + gtk_label_set_ellipsize (GTK_LABEL (label), group->priv->ellipsize); + gtk_label_set_angle (GTK_LABEL (label), 0); + break; + } + + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), dy, 0, dx, 0); +} + +static void +gtk_tool_item_group_init (GtkToolItemGroup *group) +{ + GtkWidget *alignment; + GtkWidget *label; + + gtk_widget_set_redraw_on_allocate (GTK_WIDGET (group), FALSE); + + group->priv = G_TYPE_INSTANCE_GET_PRIVATE (group, + GTK_TYPE_TOOL_ITEM_GROUP, + GtkToolItemGroupPrivate); + + group->priv->children = NULL; + group->priv->header_spacing = DEFAULT_HEADER_SPACING; + group->priv->expander_size = DEFAULT_EXPANDER_SIZE; + group->priv->expander_style = GTK_EXPANDER_EXPANDED; + + label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0); + gtk_container_add (GTK_CONTAINER (alignment), label); + gtk_widget_show_all (alignment); + + gtk_widget_push_composite_child (); + group->priv->header = gtk_button_new (); + gtk_widget_set_composite_name (group->priv->header, "header"); + gtk_widget_pop_composite_child (); + + g_object_ref_sink (group->priv->header); + gtk_button_set_focus_on_click (GTK_BUTTON (group->priv->header), FALSE); + gtk_container_add (GTK_CONTAINER (group->priv->header), alignment); + gtk_widget_set_parent (group->priv->header, GTK_WIDGET (group)); + + gtk_tool_item_group_header_adjust_style (group); + + g_signal_connect_after (alignment, "expose-event", + G_CALLBACK (gtk_tool_item_group_header_expose_event_cb), + group); + g_signal_connect_after (alignment, "size-request", + G_CALLBACK (gtk_tool_item_group_header_size_request_cb), + group); + + g_signal_connect (group->priv->header, "clicked", + G_CALLBACK (gtk_tool_item_group_header_clicked_cb), + 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_NAME: + gtk_tool_item_group_set_name (group, g_value_get_string (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_NAME: + g_value_set_string (value, gtk_tool_item_group_get_name (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); + + if (group->priv->children) + { + g_list_free (group->priv->children); + group->priv->children = NULL; + } + + 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); + + if (group->priv->toplevel) + { + /* disconnect focus tracking handler */ + g_signal_handler_disconnect (group->priv->toplevel, + group->priv->focus_set_id); + + group->priv->focus_set_id = 0; + group->priv->toplevel = NULL; + } + + 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) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (widget); + GtkOrientation orientation; + GtkRequisition item_size; + gint requested_rows; + + if (group->priv->children && gtk_tool_item_group_get_name (group)) + { + gtk_widget_size_request (group->priv->header, requisition); + gtk_widget_show (group->priv->header); + } + else + { + requisition->width = requisition->height = 0; + gtk_widget_hide (group->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); + + requisition->width += border_width * 2; + requisition->height += border_width * 2; +} + +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_VISIBLE (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) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (widget); + + GtkRequisition item_size; + GtkAllocation item_area; + + GtkOrientation orientation; + GtkToolbarStyle style; + + gint min_rows; + + orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group)); + style = gtk_tool_shell_get_style (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_area.width = 0; + item_area.height = 0; + + /* figure out the required columns (n_columns) and rows (n_rows) to place all items */ + if (!group->priv->collapsed || !group->priv->animation || group->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 - 2 * border_width; + n_columns = MAX (item_area.width / item_size.width, 1); + + /* calculate required rows for n_columns columns */ + 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 (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_size_request (GTK_WIDGET (child->item), &req); + + 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 - 2 * border_width; + 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 = group->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_size_request (GTK_WIDGET (child->item), &req); + + 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 = group->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_size_request (GTK_WIDGET (child->item), &req); + + 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_VISIBLE (group->priv->header)) + { + GtkRequisition child_requisition; + + gtk_widget_size_request (group->priv->header, &child_requisition); + + 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 + 2 * border_width; + inquery->height += item_area.height + 2 * border_width; +} + +static void +gtk_tool_item_group_real_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (widget); + GtkRequisition child_requisition; + GtkAllocation child_allocation; + + GtkRequisition item_size; + GtkAllocation item_area; + + GtkOrientation orientation; + GtkToolbarStyle style; + + GList *it; + + gint n_columns, n_rows = 1; + gint min_rows; + + GtkTextDirection direction = gtk_widget_get_direction (widget); + + orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group)); + style = gtk_tool_shell_get_style (GTK_TOOL_SHELL (group)); + + /* chain up */ + GTK_WIDGET_CLASS (gtk_tool_item_group_parent_class)->size_allocate (widget, allocation); + + child_allocation.x = border_width; + child_allocation.y = border_width; + + /* place the header widget */ + if (GTK_WIDGET_VISIBLE (group->priv->header)) + { + gtk_widget_size_request (group->priv->header, &child_requisition); + + 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 - border_width - child_allocation.width; + } + + gtk_widget_size_allocate (group->priv->header, &child_allocation); + + 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 = border_width; + } + 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); + + /* 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 - 2 * border_width; + item_area.height = allocation->height - 2 * border_width - 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 - 2 * border_width - child_requisition.width; + item_area.height = allocation->height - 2 * border_width; + + 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 (!group->priv->collapsed || !group->priv->animation || group->priv->animation_timeout) + { + gint col = 0, row = 0; + + for (it = group->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_size_request (GTK_WIDGET (child->item), &child_requisition); + 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); + 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 = group->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, + GtkAllocation *allocation) +{ + gtk_tool_item_group_real_size_allocate (widget, allocation); + + if (GTK_WIDGET_MAPPED (widget)) + gdk_window_invalidate_rect (widget->window, NULL, FALSE); +} + +static void +gtk_tool_item_group_set_focus_cb (GtkWidget *window G_GNUC_UNUSED, + GtkWidget *widget, + gpointer user_data) +{ + GtkAdjustment *adjustment; + 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_tool_palette_get_hadjustment (GTK_TOOL_PALETTE (p)); + adjustment = gtk_tool_palette_get_vadjustment (GTK_TOOL_PALETTE (p)); + + if (adjustment) + { + int y; + + /* Handle vertical adjustment. */ + if (gtk_widget_translate_coordinates + (widget, p, 0, 0, NULL, &y) && y < 0) + { + y += adjustment->value; + gtk_adjustment_clamp_page (adjustment, y, y + widget->allocation.height); + } + else if (gtk_widget_translate_coordinates + (widget, p, 0, widget->allocation.height, NULL, &y) && + y > p->allocation.height) + { + y += adjustment->value; + gtk_adjustment_clamp_page (adjustment, y - widget->allocation.height, y); + } + } + + adjustment = gtk_tool_palette_get_hadjustment (GTK_TOOL_PALETTE (p)); + + if (adjustment) + { + int x; + + /* Handle horizontal adjustment. */ + if (gtk_widget_translate_coordinates + (widget, p, 0, 0, &x, NULL) && x < 0) + { + x += adjustment->value; + gtk_adjustment_clamp_page (adjustment, x, x + widget->allocation.width); + } + else if (gtk_widget_translate_coordinates + (widget, p, widget->allocation.width, 0, &x, NULL) && + x > p->allocation.width) + { + x += adjustment->value; + gtk_adjustment_clamp_page (adjustment, x - widget->allocation.width, x); + } + + return; + } + } +} + +static void +gtk_tool_item_group_set_toplevel_window (GtkToolItemGroup *group, + GtkWidget *toplevel) +{ + if (toplevel != group->priv->toplevel) + { + if (group->priv->toplevel) + { + /* Disconnect focus tracking handler. */ + g_signal_handler_disconnect (group->priv->toplevel, + group->priv->focus_set_id); + + group->priv->focus_set_id = 0; + group->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. + */ + group->priv->focus_set_id = + g_signal_connect (toplevel, "set-focus", + G_CALLBACK (gtk_tool_item_group_set_focus_cb), + group); + + group->priv->toplevel = toplevel; + } + } +} + +static void +gtk_tool_item_group_realize (GtkWidget *widget) +{ + GtkWidget *toplevel_window; + const gint border_width = GTK_CONTAINER (widget)->border_width; + gint attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + GdkWindowAttr attributes; + GdkDisplay *display; + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = widget->allocation.x + border_width; + attributes.y = widget->allocation.y + border_width; + attributes.width = widget->allocation.width - border_width * 2; + attributes.height = widget->allocation.height - border_width * 2; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK + | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + | GDK_BUTTON_MOTION_MASK; + + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + + display = gdk_drawable_get_display (widget->window); + + if (gdk_display_supports_composite (display)) + gdk_window_set_composited (widget->window, TRUE); + + gdk_window_set_user_data (widget->window, widget); + widget->style = gtk_style_attach (widget->style, widget->window); + gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + + gtk_container_forall (GTK_CONTAINER (widget), + (GtkCallback) gtk_widget_set_parent_window, + widget->window); + + gtk_widget_queue_resize_no_redraw (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_set (GtkWidget *widget, + GtkStyle *previous_style) +{ + gtk_tool_item_group_header_adjust_style (GTK_TOOL_ITEM_GROUP (widget)); + GTK_WIDGET_CLASS (gtk_tool_item_group_parent_class)->style_set (widget, previous_style); +} + +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; + GList *it; + + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (container)); + group = GTK_TOOL_ITEM_GROUP (container); + + for (it = group->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); + group->priv->children = g_list_delete_link (group->priv->children, it); + + gtk_widget_queue_resize (GTK_WIDGET (container)); + break; + } + } +} + +static void +gtk_tool_item_group_forall (GtkContainer *container, + gboolean internals, + GtkCallback callback, + gpointer callback_data) +{ + GtkToolItemGroup *group = GTK_TOOL_ITEM_GROUP (container); + GList *children; + + if (internals && group->priv->header) + callback (group->priv->header, callback_data); + + children = group->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 G_GNUC_UNUSED) +{ + 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_VISIBLE (group) && GTK_WIDGET_VISIBLE (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->size_request = gtk_tool_item_group_size_request; + 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_set = gtk_tool_item_group_style_set; + wclass->screen_changed = gtk_tool_item_group_screen_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_NAME, + g_param_spec_string ("name", + P_("Name"), + P_("The name of this item group"), + DEFAULT_NAME, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (oclass, PROP_COLLAPSED, + g_param_spec_boolean ("collapsed", + P_("Collapsed"), + P_("Wether the group has been collapsed and items are hidden"), + DEFAULT_COLLAPSED, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + 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, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (oclass, PROP_ELLIPSIZE, + g_param_spec_enum ("header-relief", + P_("header-relif"), + P_("Relief of the group header button"), + GTK_TYPE_RELIEF_STYLE, GTK_RELIEF_NORMAL, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + gtk_widget_class_install_style_property (wclass, + g_param_spec_int ("expander-size", + P_("Expander Size"), + P_("Size of the expander arrow"), + 0, + G_MAXINT, + DEFAULT_EXPANDER_SIZE, + G_PARAM_READABLE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + gtk_widget_class_install_style_property (wclass, + g_param_spec_int ("header-spacing", + P_("Header Spacing"), + P_("Spacing between expander arrow and caption"), + 0, + G_MAXINT, + DEFAULT_HEADER_SPACING, + G_PARAM_READABLE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + 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, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + 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 toolbar grows"), + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + gtk_container_class_install_child_property (cclass, CHILD_PROP_FILL, + g_param_spec_boolean ("fill", + P_("Fill"), + P_("Whether the item should fill the avaiable space"), + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + 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, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + 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, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_type_class_add_private (cls, sizeof (GtkToolItemGroupPrivate)); +} + +/** + * gtk_tool_item_group_new: + * @name: the name of the new group. + * + * Creates a new tool item group with name @name. + * + * Returns: a new #GtkToolItemGroup. + * + * Since: 2.18 + */ +GtkWidget* +gtk_tool_item_group_new (const gchar *name) +{ + return g_object_new (GTK_TYPE_TOOL_ITEM_GROUP, "name", name, NULL); +} + +/** + * gtk_tool_item_group_set_name: + * @group: an #GtkToolItemGroup. + * @name: the new name of of the group. + * + * Sets the name of the tool item group. The name is displayed in the header + * of the group. + * + * Since: 2.18 + */ +void +gtk_tool_item_group_set_name (GtkToolItemGroup *group, + const gchar *name) +{ + const gchar *current_name; + GtkWidget *label; + + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + current_name = gtk_tool_item_group_get_name (group); + + if (current_name != name && (!current_name || !name || strcmp (current_name, name))) + { + label = gtk_tool_item_group_get_label (group); + gtk_label_set_text (GTK_LABEL (label), name); + + if (name && group->priv->children) + gtk_widget_show (group->priv->header); + else + gtk_widget_hide (group->priv->header); + + g_object_notify (G_OBJECT (group), "name"); + } +} + +/** + * gtk_tool_item_group_header_relief: + * @group: an #GtkToolItemGroup. + * @style: The GtkReliefStyle + * + * Set the button relief of the group header. See #gtk_button_set_relief for + * details + * + * Since: 2.18 + */ +void +gtk_tool_item_group_set_header_relief (GtkToolItemGroup *group, + GtkReliefStyle style) +{ + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + gtk_button_set_relief (GTK_BUTTON(group->priv->header), style); +} + +static gint64 +gtk_tool_item_group_get_animation_timestamp (GtkToolItemGroup *group) +{ + GTimeVal now; + g_source_get_current_time (group->priv->animation_timeout, &now); + return (now.tv_sec * G_USEC_PER_SEC + now.tv_usec - group->priv->animation_start) / 1000; +} + +static void +gtk_tool_item_group_force_expose (GtkToolItemGroup *group) +{ + if (GTK_WIDGET_REALIZED (group->priv->header)) + { + GtkWidget *alignment = gtk_tool_item_group_get_alignment (group); + GdkRectangle area; + + /* Find the header button's arrow area... */ + area.x = alignment->allocation.x; + area.y = alignment->allocation.y + (alignment->allocation.height - group->priv->expander_size) / 2; + area.height = group->priv->expander_size; + area.width = group->priv->expander_size; + + /* ... and invalidated it to get it animated. */ + gdk_window_invalidate_rect (group->priv->header->window, &area, TRUE); + } + + if (GTK_WIDGET_REALIZED (group)) + { + GtkWidget *widget = GTK_WIDGET (group); + GtkWidget *parent = gtk_widget_get_parent (widget); + int x, y, width, height; + + /* Find the tool item area button's arrow area... */ + width = widget->allocation.width; + height = widget->allocation.height; + + gtk_widget_translate_coordinates (widget, parent, 0, 0, &x, &y); + + if (GTK_WIDGET_VISIBLE (group->priv->header)) + { + height -= group->priv->header->allocation.height; + y += group->priv->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); + gint64 timestamp = gtk_tool_item_group_get_animation_timestamp (group); + + /* Enque this early to reduce number of expose events. */ + gtk_widget_queue_resize_no_redraw (GTK_WIDGET (group)); + + /* Figure out current style of the expander arrow. */ + if (group->priv->collapsed) + { + if (group->priv->expander_style == GTK_EXPANDER_EXPANDED) + group->priv->expander_style = GTK_EXPANDER_SEMI_COLLAPSED; + else + group->priv->expander_style = GTK_EXPANDER_COLLAPSED; + } + else + { + if (group->priv->expander_style == GTK_EXPANDER_COLLAPSED) + group->priv->expander_style = GTK_EXPANDER_SEMI_EXPANDED; + else + group->priv->expander_style = GTK_EXPANDER_EXPANDED; + } + + gtk_tool_item_group_force_expose (group); + + /* Finish animation when done. */ + if (timestamp >= ANIMATION_DURATION) + group->priv->animation_timeout = NULL; + + /* Ensure that all composited windows and child windows are repainted, before + * the parent widget gets its expose-event. This is needed to avoid heavy + * rendering artifacts. GTK+ should take care about this issue by itself I + * guess, but currently it doesn't. Also I don't understand the parameters + * of this issue well enough yet, to file a bug report. + */ + gdk_window_process_updates (GTK_WIDGET (group)->window, TRUE); + + return (group->priv->animation_timeout != NULL); +} + +/** + * gtk_tool_item_group_set_collapsed: + * @group: an #GtkToolItemGroup. + * @collapsed: whether the @group should be collapsed or expanded. + * + * Sets whether the @group should be collapsed or expanded. + * + * Since: 2.18 + */ +void +gtk_tool_item_group_set_collapsed (GtkToolItemGroup *group, + gboolean collapsed) +{ + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + GtkWidget *parent; + + 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 != group->priv->collapsed) + { + if (group->priv->animation) + { + GTimeVal now; + + g_get_current_time (&now); + + if (group->priv->animation_timeout) + g_source_destroy (group->priv->animation_timeout); + + group->priv->animation_start = (now.tv_sec * G_USEC_PER_SEC + now.tv_usec); + group->priv->animation_timeout = g_timeout_source_new (ANIMATION_TIMEOUT); + + g_source_set_callback (group->priv->animation_timeout, + gtk_tool_item_group_animation_cb, + group, NULL); + + g_source_attach (group->priv->animation_timeout, NULL); + } + else + { + group->priv->expander_style = GTK_EXPANDER_COLLAPSED; + gtk_tool_item_group_force_expose (group); + } + group->priv->collapsed = collapsed; + g_object_notify (G_OBJECT (group), "collapsed"); + } +} + +/** + * gtk_tool_item_group_set_ellipsize: + * @group: an #GtkToolItemGroup. + * @ellipsize: the #PangoEllipsizeMode labels in @group should use. + * + * Sets the ellipsization mode which should be used by labels in @group. + * + * Since: 2.18 + */ +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"); +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + _gtk_tool_item_group_palette_reconfigured (group); +#endif + } +} + +/** + * gtk_tool_item_group_get_name: + * @group: an #GtkToolItemGroup. + * + * Gets the name of @group. + * + * Returns: the name of @group. The name is an internal string of @group and must not be modified. + * + * Since: 2.18 + */ +G_CONST_RETURN gchar* +gtk_tool_item_group_get_name (GtkToolItemGroup *group) +{ + GtkWidget *label; + + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), DEFAULT_NAME); + + label = gtk_tool_item_group_get_label (group); + return gtk_label_get_text (GTK_LABEL (label)); +} + +/** + * gtk_tool_item_group_get_collapsed: + * @group: an GtkToolItemGroup. + * + * Gets whether @group is collapsed or expanded. + * + * Returns: %TRUE if @group is collapsed, %FALSE if it is expanded. + * + * Since: 2.18 + */ +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: an #GtkToolItemGroup. + * + * Gets the ellipsization mode of @group. + * + * Returns: the #PangoEllipsizeMode of @group. + * + * Since: 2.18 + */ +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: an #GtkToolItemGroup. + * + * Gets the relief mode of the header button of @group. + * + * Returns: the #GtkReliefStyle + * + * Since: 2.18 + */ +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: an #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.18 + */ +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)); + + if (GTK_IS_BUTTON (child_widget)) + gtk_button_set_focus_on_click (GTK_BUTTON (child_widget), TRUE); + + gtk_widget_set_parent (GTK_WIDGET (item), GTK_WIDGET (group)); +} + +/** + * gtk_tool_item_group_set_item_position: + * @group: an #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.18 + */ +void +gtk_tool_item_group_set_item_position (GtkToolItemGroup *group, + GtkToolItem *item, + gint position) +{ + gint old_position; + GList *link; + 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); + + child = gtk_tool_item_group_get_child (group, item, &old_position, &link); + + g_return_if_fail (child != NULL); + + if (position == old_position) + return; + + group->priv->children = g_list_delete_link (group->priv->children, link); + group->priv->children = g_list_insert (group->priv->children, child, position); + + gtk_widget_child_notify (GTK_WIDGET (item), "position"); + if (GTK_WIDGET_VISIBLE (group) && GTK_WIDGET_VISIBLE (item)) + gtk_widget_queue_resize (GTK_WIDGET (group)); +} + +/** + * gtk_tool_item_group_get_item_position: + * @group: an #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.18 + */ +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: an #GtkToolItemGroup. + * + * Gets the number of tool items in group. + * + * Returns: the number of tool items in group. + * + * Since: 2.18 + */ +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: an #GtkToolItemGroup. + * @index: the index. + * + * Gets the tool item at index in group. + * + * Returns: the #GtkToolItem at index. + * + * Since: 2.18 + */ +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: an #GtkToolItemGroup. + * @x: the x position. + * @y: the y position. + * + * Gets the tool item at position (x, y). + * + * Returns: the #GtkToolItem at position (x, y). + * + * Since: 2.18 + */ +GtkToolItem* +gtk_tool_item_group_get_drop_item (GtkToolItemGroup *group, + gint x, + gint y) +{ + GtkAllocation *allocation; + GtkOrientation orientation; + GList *it; + + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), NULL); + + allocation = >K_WIDGET (group)->allocation; + orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group)); + + 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; + + allocation = >K_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; + GtkOrientation orientation; + GtkToolbarStyle style; + + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + g_return_if_fail (NULL != item_size); + + orientation = gtk_tool_shell_get_orientation (GTK_TOOL_SHELL (group)); + style = gtk_tool_shell_get_style (GTK_TOOL_SHELL (group)); + + 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_size_request (GTK_WIDGET (child->item), &child_requisition); + + 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; +} + +void +_gtk_tool_item_group_paint (GtkToolItemGroup *group, + cairo_t *cr) +{ + GtkWidget *widget = GTK_WIDGET (group); + + gdk_cairo_set_source_pixmap (cr, widget->window, + widget->allocation.x, + widget->allocation.y); + + if (group->priv->animation_timeout) + { + GtkOrientation orientation = gtk_tool_item_group_get_orientation (GTK_TOOL_SHELL (group)); + cairo_pattern_t *mask; + gdouble v0, v1; + + if (GTK_ORIENTATION_VERTICAL == orientation) + v1 = widget->allocation.height; + else + v1 = widget->allocation.width; + + v0 = v1 - 256; + + if (!GTK_WIDGET_VISIBLE (group->priv->header)) + v0 = MAX (v0, 0); + else if (GTK_ORIENTATION_VERTICAL == orientation) + v0 = MAX (v0, group->priv->header->allocation.height); + else + v0 = MAX (v0, group->priv->header->allocation.width); + + v1 = MIN (v0 + 256, v1); + + if (GTK_ORIENTATION_VERTICAL == orientation) + { + v0 += widget->allocation.y; + v1 += widget->allocation.y; + + mask = cairo_pattern_create_linear (0.0, v0, 0.0, v1); + } + else + { + v0 += widget->allocation.x; + v1 += widget->allocation.x; + + mask = cairo_pattern_create_linear (v0, 0.0, v1, 0.0); + } + + cairo_pattern_add_color_stop_rgba (mask, 0.00, 0.0, 0.0, 0.0, 1.00); + cairo_pattern_add_color_stop_rgba (mask, 0.25, 0.0, 0.0, 0.0, 0.25); + cairo_pattern_add_color_stop_rgba (mask, 0.50, 0.0, 0.0, 0.0, 0.10); + cairo_pattern_add_color_stop_rgba (mask, 0.75, 0.0, 0.0, 0.0, 0.01); + cairo_pattern_add_color_stop_rgba (mask, 1.00, 0.0, 0.0, 0.0, 0.00); + + cairo_mask (cr, mask); + cairo_pattern_destroy (mask); + } + else + cairo_paint (cr); +} + +gint +_gtk_tool_item_group_get_size_for_limit (GtkToolItemGroup *group, + gint limit, + gboolean vertical, + gboolean animation) +{ + GtkRequisition requisition; + + gtk_widget_size_request (GTK_WIDGET (group), &requisition); + + if (!group->priv->collapsed || group->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 (group->priv->animation_timeout && animation) + { + gint64 timestamp = gtk_tool_item_group_get_animation_timestamp (group); + + timestamp = MIN (timestamp, ANIMATION_DURATION); + + if (group->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 G_GNUC_UNUSED) +{ + 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); +} diff --git a/gtktoolitemgroup.h b/gtktoolitemgroup.h new file mode 100644 index 0000000000..c91f504a97 --- /dev/null +++ b/gtktoolitemgroup.h @@ -0,0 +1,276 @@ +/* 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Mathias Hasselmann + */ + +#ifndef __GTK_TOOL_ITEM_GROUP_H__ +#define __GTK_TOOL_ITEM_GROUP_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_TOOL_ITEM_GROUP (gtk_tool_item_group_get_type()) +#define GTK_TOOL_ITEM_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, GTK_TYPE_TOOL_ITEM_GROUP, GtkToolItemGroup)) +#define GTK_TOOL_ITEM_GROUP_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST(cls, GTK_TYPE_TOOL_ITEM_GROUP, GtkToolItemGroupClass)) +#define GTK_IS_TOOL_ITEM_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE(obj, GTK_TYPE_TOOL_ITEM_GROUP)) +#define GTK_IS_TOOL_ITEM_GROUP_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE(obj, GTK_TYPE_TOOL_ITEM_GROUP)) +#define GTK_TOOL_ITEM_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_TOOL_ITEM_GROUP, GtkToolItemGroupClass)) + +typedef struct _GtkToolItemGroup GtkToolItemGroup; +typedef struct _GtkToolItemGroupClass GtkToolItemGroupClass; +typedef struct _GtkToolItemGroupPrivate GtkToolItemGroupPrivate; + +/** + * GtkToolItemGroup: + * + * This should not be accessed directly. Use the accessor functions below. + */ +struct _GtkToolItemGroup +{ + GtkContainer parent_instance; + GtkToolItemGroupPrivate *priv; +}; + +struct _GtkToolItemGroupClass +{ + GtkContainerClass parent_class; +}; + +GType gtk_tool_item_group_get_type (void) G_GNUC_CONST; +GtkWidget* gtk_tool_item_group_new (const gchar *name); + +void gtk_tool_item_group_set_name (GtkToolItemGroup *group, + const gchar *name); +void gtk_tool_item_group_set_collapsed (GtkToolItemGroup *group, + gboolean collapsed); +void gtk_tool_item_group_set_ellipsize (GtkToolItemGroup *group, + PangoEllipsizeMode ellipsize); +void gtk_tool_item_group_set_header_relief (GtkToolItemGroup *group, + GtkReliefStyle style); + +G_CONST_RETURN gchar* gtk_tool_item_group_get_name (GtkToolItemGroup *group); +gboolean gtk_tool_item_group_get_collapsed (GtkToolItemGroup *group); +PangoEllipsizeMode gtk_tool_item_group_get_ellipsize (GtkToolItemGroup *group); +GtkReliefStyle gtk_tool_item_group_get_header_relief (GtkToolItemGroup *group); + +void gtk_tool_item_group_insert (GtkToolItemGroup *group, + GtkToolItem *item, + gint position); +void gtk_tool_item_group_set_item_position (GtkToolItemGroup *group, + GtkToolItem *item, + gint position); +gint gtk_tool_item_group_get_item_position (GtkToolItemGroup *group, + GtkToolItem *item); + +guint gtk_tool_item_group_get_n_items (GtkToolItemGroup *group); +GtkToolItem* gtk_tool_item_group_get_nth_item (GtkToolItemGroup *group, + guint index); +GtkToolItem* gtk_tool_item_group_get_drop_item (GtkToolItemGroup *group, + gint x, + gint y); + +G_END_DECLS + +#endif /* __GTK_TOOL_ITEM_GROUP_H__ */ +/* 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Mathias Hasselmann + */ + +#ifndef __GTK_TOOL_ITEM_GROUP_H__ +#define __GTK_TOOL_ITEM_GROUP_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_TOOL_ITEM_GROUP (gtk_tool_item_group_get_type()) +#define GTK_TOOL_ITEM_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, GTK_TYPE_TOOL_ITEM_GROUP, GtkToolItemGroup)) +#define GTK_TOOL_ITEM_GROUP_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST(cls, GTK_TYPE_TOOL_ITEM_GROUP, GtkToolItemGroupClass)) +#define GTK_IS_TOOL_ITEM_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE(obj, GTK_TYPE_TOOL_ITEM_GROUP)) +#define GTK_IS_TOOL_ITEM_GROUP_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE(obj, GTK_TYPE_TOOL_ITEM_GROUP)) +#define GTK_TOOL_ITEM_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_TOOL_ITEM_GROUP, GtkToolItemGroupClass)) + +typedef struct _GtkToolItemGroup GtkToolItemGroup; +typedef struct _GtkToolItemGroupClass GtkToolItemGroupClass; +typedef struct _GtkToolItemGroupPrivate GtkToolItemGroupPrivate; + +/** + * GtkToolItemGroup: + * + * This should not be accessed directly. Use the accessor functions below. + */ +struct _GtkToolItemGroup +{ + GtkContainer parent_instance; + GtkToolItemGroupPrivate *priv; +}; + +struct _GtkToolItemGroupClass +{ + GtkContainerClass parent_class; +}; + +GType gtk_tool_item_group_get_type (void) G_GNUC_CONST; +GtkWidget* gtk_tool_item_group_new (const gchar *name); + +void gtk_tool_item_group_set_name (GtkToolItemGroup *group, + const gchar *name); +void gtk_tool_item_group_set_collapsed (GtkToolItemGroup *group, + gboolean collapsed); +void gtk_tool_item_group_set_ellipsize (GtkToolItemGroup *group, + PangoEllipsizeMode ellipsize); +void gtk_tool_item_group_set_header_relief (GtkToolItemGroup *group, + GtkReliefStyle style); + +G_CONST_RETURN gchar* gtk_tool_item_group_get_name (GtkToolItemGroup *group); +gboolean gtk_tool_item_group_get_collapsed (GtkToolItemGroup *group); +PangoEllipsizeMode gtk_tool_item_group_get_ellipsize (GtkToolItemGroup *group); +GtkReliefStyle gtk_tool_item_group_get_header_relief (GtkToolItemGroup *group); + +void gtk_tool_item_group_insert (GtkToolItemGroup *group, + GtkToolItem *item, + gint position); +void gtk_tool_item_group_set_item_position (GtkToolItemGroup *group, + GtkToolItem *item, + gint position); +gint gtk_tool_item_group_get_item_position (GtkToolItemGroup *group, + GtkToolItem *item); + +guint gtk_tool_item_group_get_n_items (GtkToolItemGroup *group); +GtkToolItem* gtk_tool_item_group_get_nth_item (GtkToolItemGroup *group, + guint index); +GtkToolItem* gtk_tool_item_group_get_drop_item (GtkToolItemGroup *group, + gint x, + gint y); + +G_END_DECLS + +#endif /* __GTK_TOOL_ITEM_GROUP_H__ */ +/* 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Mathias Hasselmann + */ + +#ifndef __GTK_TOOL_ITEM_GROUP_H__ +#define __GTK_TOOL_ITEM_GROUP_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_TOOL_ITEM_GROUP (gtk_tool_item_group_get_type()) +#define GTK_TOOL_ITEM_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, GTK_TYPE_TOOL_ITEM_GROUP, GtkToolItemGroup)) +#define GTK_TOOL_ITEM_GROUP_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST(cls, GTK_TYPE_TOOL_ITEM_GROUP, GtkToolItemGroupClass)) +#define GTK_IS_TOOL_ITEM_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE(obj, GTK_TYPE_TOOL_ITEM_GROUP)) +#define GTK_IS_TOOL_ITEM_GROUP_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE(obj, GTK_TYPE_TOOL_ITEM_GROUP)) +#define GTK_TOOL_ITEM_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_TOOL_ITEM_GROUP, GtkToolItemGroupClass)) + +typedef struct _GtkToolItemGroup GtkToolItemGroup; +typedef struct _GtkToolItemGroupClass GtkToolItemGroupClass; +typedef struct _GtkToolItemGroupPrivate GtkToolItemGroupPrivate; + +/** + * GtkToolItemGroup: + * + * This should not be accessed directly. Use the accessor functions below. + */ +struct _GtkToolItemGroup +{ + GtkContainer parent_instance; + GtkToolItemGroupPrivate *priv; +}; + +struct _GtkToolItemGroupClass +{ + GtkContainerClass parent_class; +}; + +GType gtk_tool_item_group_get_type (void) G_GNUC_CONST; +GtkWidget* gtk_tool_item_group_new (const gchar *name); + +void gtk_tool_item_group_set_name (GtkToolItemGroup *group, + const gchar *name); +void gtk_tool_item_group_set_collapsed (GtkToolItemGroup *group, + gboolean collapsed); +void gtk_tool_item_group_set_ellipsize (GtkToolItemGroup *group, + PangoEllipsizeMode ellipsize); +void gtk_tool_item_group_set_header_relief (GtkToolItemGroup *group, + GtkReliefStyle style); + +G_CONST_RETURN gchar* gtk_tool_item_group_get_name (GtkToolItemGroup *group); +gboolean gtk_tool_item_group_get_collapsed (GtkToolItemGroup *group); +PangoEllipsizeMode gtk_tool_item_group_get_ellipsize (GtkToolItemGroup *group); +GtkReliefStyle gtk_tool_item_group_get_header_relief (GtkToolItemGroup *group); + +void gtk_tool_item_group_insert (GtkToolItemGroup *group, + GtkToolItem *item, + gint position); +void gtk_tool_item_group_set_item_position (GtkToolItemGroup *group, + GtkToolItem *item, + gint position); +gint gtk_tool_item_group_get_item_position (GtkToolItemGroup *group, + GtkToolItem *item); + +guint gtk_tool_item_group_get_n_items (GtkToolItemGroup *group); +GtkToolItem* gtk_tool_item_group_get_nth_item (GtkToolItemGroup *group, + guint index); +GtkToolItem* gtk_tool_item_group_get_drop_item (GtkToolItemGroup *group, + gint x, + gint y); + +G_END_DECLS + +#endif /* __GTK_TOOL_ITEM_GROUP_H__ */ diff --git a/gtktoolpalette.c b/gtktoolpalette.c new file mode 100644 index 0000000000..4574b6f03d --- /dev/null +++ b/gtktoolpalette.c @@ -0,0 +1,5055 @@ +/* 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Mathias Hasselmann + */ + +#include "gtktoolpaletteprivate.h" +#include "gtkmarshalers.h" + +#include +#include + +#define DEFAULT_ICON_SIZE GTK_ICON_SIZE_SMALL_TOOLBAR +#define DEFAULT_ORIENTATION GTK_ORIENTATION_VERTICAL +#define DEFAULT_TOOLBAR_STYLE GTK_TOOLBAR_ICONS + +#define DEFAULT_CHILD_EXCLUSIVE FALSE +#define DEFAULT_CHILD_EXPAND FALSE + +#define P_(msgid) (msgid) + +/** + * SECTION:GtkToolPalette + * @short_description: A tool palette with categories + * @include: gtktoolpalette.h + * + * An #GtkToolPalette allows it to add #GtkToolItems to a palette like container + * with different categories and drag and drop support. + * + * An #GtkToolPalette is created with a call to gtk_tool_palette_new(). + * + * #GtkToolItems cannot be added directly to an #GtkToolPalette, instead they + * are added to an #GtkToolItemGroup which can than be added to an #GtkToolPalette. To add + * an #GtkToolItemGroup to an #GtkToolPalette use gtk_container_add(). + * + * |[ + * GtkWidget *palette, *group; + * GtkToolItem *item; + * + * palette = gtk_tool_palette_new (); + * group = gtk_tool_item_group_new (_("Test Category")); + * gtk_container_add (GTK_CONTAINER (palette), group); + * + * item = gtk_tool_button_new_from_stock (GTK_STOCK_OK); + * gtk_tool_item_group_insert (GTK_TOOL_ITEM_GROUP (group), item, -1); + * ]| + * + * The easiest way to use drag and drop with GtkToolPalette is to call gtk_tool_palette_add_drag_dest() + * with the desired drag source @palette and the desired drag target @widget. Than gtk_tool_palette_get_drag_item() + * can be used to get the dragged item in the #GtkWidget::drag-data-received signal handler of the drag target. + * + * |[ + * static void + * passive_canvas_drag_data_received (GtkWidget *widget, + * GdkDragContext *context, + * gint x, + * gint y, + * GtkSelectionData *selection, + * guint info, + * guint time, + * gpointer data) + * { + * GtkWidget *palette; + * GtkWidget *item; + * + * /* Get the dragged item */ + * palette = gtk_widget_get_ancestor (gtk_drag_get_source_widget (context), GTK_TYPE_TOOL_PALETTE); + * if (palette != NULL) + * item = gtk_tool_palette_get_drag_item (GTK_TOOL_PALETTE (palette), selection); + * + * /* Do something with item */ + * } + * + * GtkWidget *target, palette; + * + * palette = gtk_tool_palette_new (); + * target = gtk_drawing_area_new (); + * + * g_signal_connect (G_OBJECT (target), "drag-data-received", + * G_CALLBACK (passive_canvas_drag_data_received), NULL); + * gtk_tool_palette_add_drag_dest (GTK_TOOL_PALETTE (palette), target, + * GTK_DEST_DEFAULT_ALL, + * GTK_TOOL_PALETTE_DRAG_ITEMS, + * GDK_ACTION_COPY); + * ]| + * + * Since: 2.18 + */ + +typedef struct _GtkToolItemGroupInfo GtkToolItemGroupInfo; +typedef struct _GtkToolPaletteDragData GtkToolPaletteDragData; + +enum +{ + PROP_NONE, + PROP_ICON_SIZE, + PROP_ORIENTATION, + PROP_TOOLBAR_STYLE, +}; + +enum +{ + CHILD_PROP_NONE, + CHILD_PROP_EXCLUSIVE, + CHILD_PROP_EXPAND, +}; + +struct _GtkToolItemGroupInfo +{ + GtkToolItemGroup *widget; + + guint notify_collapsed; + guint exclusive : 1; + guint expand : 1; +}; + +struct _GtkToolPalettePrivate +{ + GtkToolItemGroupInfo *groups; + gsize groups_size; + gsize groups_length; + + GtkAdjustment *hadjustment; + GtkAdjustment *vadjustment; + + GtkIconSize icon_size; + GtkOrientation orientation; + GtkToolbarStyle style; + + GtkWidget *expanding_child; + +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + GtkSizeGroup *text_size_group; +#endif + + guint sparse_groups : 1; + guint drag_source : 2; +}; + +struct _GtkToolPaletteDragData +{ + GtkToolPalette *palette; + GtkWidget *item; +}; + +static GdkAtom dnd_target_atom_item = GDK_NONE; +static GdkAtom dnd_target_atom_group = GDK_NONE; + +static const GtkTargetEntry dnd_targets[] = +{ + { "application/x-GTK-tool-palette-item", GTK_TARGET_SAME_APP, 0 }, + { "application/x-GTK-tool-palette-group", GTK_TARGET_SAME_APP, 0 }, +}; + +G_DEFINE_TYPE (GtkToolPalette, + gtk_tool_palette, + GTK_TYPE_CONTAINER); + +static void +gtk_tool_palette_init (GtkToolPalette *palette) +{ + palette->priv = G_TYPE_INSTANCE_GET_PRIVATE (palette, + GTK_TYPE_TOOL_PALETTE, + GtkToolPalettePrivate); + + palette->priv->groups_size = 4; + palette->priv->groups_length = 0; + palette->priv->groups = g_new0 (GtkToolItemGroupInfo, palette->priv->groups_size); + + palette->priv->icon_size = DEFAULT_ICON_SIZE; + palette->priv->orientation = DEFAULT_ORIENTATION; + palette->priv->style = DEFAULT_TOOLBAR_STYLE; + +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + palette->priv->text_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); +#endif +} + +static void +gtk_tool_palette_reconfigured (GtkToolPalette *palette) +{ + guint i; + + for (i = 0; i < palette->priv->groups_length; ++i) + { + if (palette->priv->groups[i].widget) + _gtk_tool_item_group_palette_reconfigured (palette->priv->groups[i].widget); + } + + gtk_widget_queue_resize_no_redraw (GTK_WIDGET (palette)); +} + +static void +gtk_tool_palette_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (object); + + switch (prop_id) + { + case PROP_ICON_SIZE: + if ((guint) g_value_get_enum (value) != palette->priv->icon_size) + { + palette->priv->icon_size = g_value_get_enum (value); + gtk_tool_palette_reconfigured (palette); + } + break; + + case PROP_ORIENTATION: + if ((guint) g_value_get_enum (value) != palette->priv->orientation) + { + palette->priv->orientation = g_value_get_enum (value); + gtk_tool_palette_reconfigured (palette); + } + break; + + case PROP_TOOLBAR_STYLE: + if ((guint) g_value_get_enum (value) != palette->priv->style) + { + palette->priv->style = g_value_get_enum (value); + gtk_tool_palette_reconfigured (palette); + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_tool_palette_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (object); + + switch (prop_id) + { + case PROP_ICON_SIZE: + g_value_set_enum (value, gtk_tool_palette_get_icon_size (palette)); + break; + + case PROP_ORIENTATION: + g_value_set_enum (value, gtk_tool_palette_get_orientation (palette)); + break; + + case PROP_TOOLBAR_STYLE: + g_value_set_enum (value, gtk_tool_palette_get_style (palette)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_tool_palette_dispose (GObject *object) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (object); + guint i; + + if (palette->priv->hadjustment) + { + g_object_unref (palette->priv->hadjustment); + palette->priv->hadjustment = NULL; + } + + if (palette->priv->vadjustment) + { + g_object_unref (palette->priv->vadjustment); + palette->priv->vadjustment = NULL; + } + + for (i = 0; i < palette->priv->groups_size; ++i) + { + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + + if (group->notify_collapsed) + { + g_signal_handler_disconnect (group->widget, group->notify_collapsed); + group->notify_collapsed = 0; + } + } + +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + if (palette->priv->text_size_group) + { + g_object_unref (palette->priv->text_size_group); + palette->priv->text_size_group = NULL; + } +#endif + + G_OBJECT_CLASS (gtk_tool_palette_parent_class)->dispose (object); +} + +static void +gtk_tool_palette_finalize (GObject *object) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (object); + + if (palette->priv->groups) + { + palette->priv->groups_length = 0; + g_free (palette->priv->groups); + palette->priv->groups = NULL; + } + + G_OBJECT_CLASS (gtk_tool_palette_parent_class)->finalize (object); +} + +static void +gtk_tool_palette_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + GtkToolPalette *palette = GTK_TOOL_PALETTE (widget); + GtkRequisition child_requisition; + guint i; + + requisition->width = 0; + requisition->height = 0; + + for (i = 0; i < palette->priv->groups_length; ++i) + { + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + + if (!group->widget) + continue; + + gtk_widget_size_request (GTK_WIDGET (group->widget), &child_requisition); + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + { + requisition->width = MAX (requisition->width, child_requisition.width); + requisition->height += child_requisition.height; + } + else + { + requisition->width += child_requisition.width; + requisition->height = MAX (requisition->height, child_requisition.height); + } + } + + requisition->width += border_width * 2; + requisition->height += border_width * 2; +} + +static void +gtk_tool_palette_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + GtkToolPalette *palette = GTK_TOOL_PALETTE (widget); + GtkAdjustment *adjustment = NULL; + GtkAllocation child_allocation; + + gint n_expand_groups = 0; + gint remaining_space = 0; + gint expand_space = 0; + + gint page_start, page_size = 0; + gint offset = 0; + guint i; + + gint min_offset = -1, max_offset = -1; + + gint x; + + gint *group_sizes = g_newa(gint, palette->priv->groups_length); + + GtkTextDirection direction = gtk_widget_get_direction (widget); + + GTK_WIDGET_CLASS (gtk_tool_palette_parent_class)->size_allocate (widget, allocation); + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + { + adjustment = palette->priv->vadjustment; + page_size = allocation->height; + } + else + { + adjustment = palette->priv->hadjustment; + page_size = allocation->width; + } + + if (adjustment) + offset = gtk_adjustment_get_value (adjustment); + if (GTK_ORIENTATION_HORIZONTAL == palette->priv->orientation && + GTK_TEXT_DIR_RTL == direction) + offset = -offset; + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + child_allocation.width = allocation->width - border_width * 2; + else + child_allocation.height = allocation->height - border_width * 2; + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + remaining_space = allocation->height; + else + remaining_space = allocation->width; + + /* figure out the required size of all groups to be able to distribute the + * remaining space on allocation */ + for (i = 0; i < palette->priv->groups_length; ++i) + { + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + gint size; + + if (!group->widget) + continue; + + widget = GTK_WIDGET (group->widget); + + if (gtk_tool_item_group_get_n_items (group->widget)) + { + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + size = _gtk_tool_item_group_get_height_for_width (group->widget, child_allocation.width); + else + size = _gtk_tool_item_group_get_width_for_height (group->widget, child_allocation.height); + + if (group->expand && !gtk_tool_item_group_get_collapsed (group->widget)) + n_expand_groups += 1; + } + else + size = 0; + + remaining_space -= size; + group_sizes[i] = size; + + /* if the widget is currently expanding an offset which allows to display as much of the + * widget as possible is calculated */ + if (widget == palette->priv->expanding_child) + { + gint limit = + GTK_ORIENTATION_VERTICAL == palette->priv->orientation ? + child_allocation.width : child_allocation.height; + + gint real_size; + guint j; + + min_offset = 0; + + for (j = 0; j < i; ++j) + min_offset += group_sizes[j]; + + max_offset = min_offset + group_sizes[i]; + + real_size = _gtk_tool_item_group_get_size_for_limit + (GTK_TOOL_ITEM_GROUP (widget), limit, + GTK_ORIENTATION_VERTICAL == palette->priv->orientation, + FALSE); + + if (size == real_size) + palette->priv->expanding_child = NULL; + } + } + + if (n_expand_groups > 0) + { + remaining_space = MAX (0, remaining_space); + expand_space = remaining_space / n_expand_groups; + } + + if (max_offset != -1) + { + gint limit = + GTK_ORIENTATION_VERTICAL == palette->priv->orientation ? + allocation->height : allocation->width; + + offset = MIN (MAX (offset, max_offset - limit), min_offset); + } + + if (remaining_space > 0) + offset = 0; + + x = border_width; + child_allocation.y = border_width; + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + child_allocation.y -= offset; + else + x -= offset; + + /* allocate all groups at the calculated positions */ + for (i = 0; i < palette->priv->groups_length; ++i) + { + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + GtkWidget *widget; + + if (!group->widget) + continue; + + widget = GTK_WIDGET (group->widget); + + if (gtk_tool_item_group_get_n_items (group->widget)) + { + gint size = group_sizes[i]; + + if (group->expand && !gtk_tool_item_group_get_collapsed (group->widget)) + { + size += MIN (expand_space, remaining_space); + remaining_space -= expand_space; + } + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + child_allocation.height = size; + else + child_allocation.width = size; + + if (GTK_ORIENTATION_HORIZONTAL == palette->priv->orientation && + GTK_TEXT_DIR_RTL == direction) + child_allocation.x = allocation->width - x - child_allocation.width; + else + child_allocation.x = x; + + gtk_widget_size_allocate (widget, &child_allocation); + gtk_widget_show (widget); + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + child_allocation.y += child_allocation.height; + else + x += child_allocation.width; + } + else + gtk_widget_hide (widget); + } + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + { + child_allocation.y += border_width; + child_allocation.y += offset; + + page_start = child_allocation.y; + } + else + { + x += border_width; + x += offset; + + page_start = x; + } + + /* update the scrollbar to match the displayed adjustment */ + if (adjustment) + { + gdouble value; + + adjustment->page_increment = page_size * 0.9; + adjustment->step_increment = page_size * 0.1; + adjustment->page_size = page_size; + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation || + GTK_TEXT_DIR_LTR == direction) + { + adjustment->lower = 0; + adjustment->upper = MAX (0, page_start); + + value = MIN (offset, adjustment->upper - adjustment->page_size); + gtk_adjustment_clamp_page (adjustment, value, offset + page_size); + } + else + { + adjustment->lower = page_size - MAX (0, page_start); + adjustment->upper = page_size; + + offset = -offset; + + value = MAX (offset, adjustment->lower); + gtk_adjustment_clamp_page (adjustment, offset, value + page_size); + } + + gtk_adjustment_changed (adjustment); + } +} + +static gboolean +gtk_tool_palette_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (widget); + GdkDisplay *display; + cairo_t *cr; + guint i; + + display = gdk_drawable_get_display (widget->window); + + if (!gdk_display_supports_composite (display)) + return FALSE; + + cr = gdk_cairo_create (widget->window); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + cairo_push_group (cr); + + for (i = 0; i < palette->priv->groups_length; ++i) + if (palette->priv->groups[i].widget) + _gtk_tool_item_group_paint (palette->priv->groups[i].widget, cr); + + cairo_pop_group_to_source (cr); + + cairo_paint (cr); + cairo_destroy (cr); + + return FALSE; +} + +static void +gtk_tool_palette_realize (GtkWidget *widget) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + gint attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + GdkWindowAttr attributes; + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = widget->allocation.x + border_width; + attributes.y = widget->allocation.y + border_width; + attributes.width = widget->allocation.width - border_width * 2; + attributes.height = widget->allocation.height - border_width * 2; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK + | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + | GDK_BUTTON_MOTION_MASK; + + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + + gdk_window_set_user_data (widget->window, widget); + widget->style = gtk_style_attach (widget->style, widget->window); + gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + + gtk_container_forall (GTK_CONTAINER (widget), + (GtkCallback) gtk_widget_set_parent_window, + widget->window); + + gtk_widget_queue_resize_no_redraw (widget); +} + +static void +gtk_tool_palette_adjustment_value_changed (GtkAdjustment *adjustment G_GNUC_UNUSED, + gpointer data) +{ + GtkWidget *widget = GTK_WIDGET (data); + gtk_tool_palette_size_allocate (widget, &widget->allocation); +} + +static void +gtk_tool_palette_set_scroll_adjustments (GtkWidget *widget, + GtkAdjustment *hadjustment, + GtkAdjustment *vadjustment) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (widget); + + if (palette->priv->hadjustment) + g_object_unref (palette->priv->hadjustment); + if (palette->priv->vadjustment) + g_object_unref (palette->priv->vadjustment); + + if (hadjustment) + g_object_ref_sink (hadjustment); + if (vadjustment) + g_object_ref_sink (vadjustment); + + palette->priv->hadjustment = hadjustment; + palette->priv->vadjustment = vadjustment; + + if (palette->priv->hadjustment) + g_signal_connect (palette->priv->hadjustment, "value-changed", + G_CALLBACK (gtk_tool_palette_adjustment_value_changed), + palette); + if (palette->priv->vadjustment) + g_signal_connect (palette->priv->vadjustment, "value-changed", + G_CALLBACK (gtk_tool_palette_adjustment_value_changed), + palette); +} + +static void +gtk_tool_palette_repack (GtkToolPalette *palette) +{ + guint si, di; + + for (si = di = 0; di < palette->priv->groups_length; ++si) + { + if (palette->priv->groups[si].widget) + { + palette->priv->groups[di] = palette->priv->groups[si]; + ++di; + } + else + --palette->priv->groups_length; + } + + palette->priv->sparse_groups = FALSE; +} + +static void +gtk_tool_palette_add (GtkContainer *container, + GtkWidget *child) +{ + GtkToolPalette *palette; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (container)); + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (child)); + + palette = GTK_TOOL_PALETTE (container); + + if (palette->priv->groups_length == palette->priv->groups_size) + gtk_tool_palette_repack (palette); + + if (palette->priv->groups_length == palette->priv->groups_size) + { + gsize old_size = palette->priv->groups_size; + gsize new_size = old_size * 2; + + palette->priv->groups = g_renew (GtkToolItemGroupInfo, + palette->priv->groups, + new_size); + + memset (palette->priv->groups + old_size, 0, + sizeof (GtkToolItemGroupInfo) * old_size); + + palette->priv->groups_size = new_size; + } + + palette->priv->groups[palette->priv->groups_length].widget = g_object_ref_sink (child); + palette->priv->groups_length += 1; + + gtk_widget_set_parent (child, GTK_WIDGET (palette)); +} + +static void +gtk_tool_palette_remove (GtkContainer *container, + GtkWidget *child) +{ + GtkToolPalette *palette; + guint i; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (container)); + palette = GTK_TOOL_PALETTE (container); + + for (i = 0; i < palette->priv->groups_length; ++i) + if ((GtkWidget*) palette->priv->groups[i].widget == child) + { + g_object_unref (child); + gtk_widget_unparent (child); + + memset (&palette->priv->groups[i], 0, sizeof (GtkToolItemGroupInfo)); + palette->priv->sparse_groups = TRUE; + } +} + +static void +gtk_tool_palette_forall (GtkContainer *container, + gboolean internals G_GNUC_UNUSED, + GtkCallback callback, + gpointer callback_data) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (container); + guint i; + + if (palette->priv->groups) + { + for (i = 0; i < palette->priv->groups_length; ++i) + if (palette->priv->groups[i].widget) + callback (GTK_WIDGET (palette->priv->groups[i].widget), + callback_data); + } +} + +static GType +gtk_tool_palette_child_type (GtkContainer *container G_GNUC_UNUSED) +{ + return GTK_TYPE_TOOL_ITEM_GROUP; +} + +static void +gtk_tool_palette_set_child_property (GtkContainer *container, + GtkWidget *child, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (container); + + switch (prop_id) + { + case CHILD_PROP_EXCLUSIVE: + gtk_tool_palette_set_exclusive (palette, child, g_value_get_boolean (value)); + break; + + case CHILD_PROP_EXPAND: + gtk_tool_palette_set_expand (palette, child, g_value_get_boolean (value)); + break; + + default: + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec); + break; + } +} + +static void +gtk_tool_palette_get_child_property (GtkContainer *container, + GtkWidget *child, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (container); + + switch (prop_id) + { + case CHILD_PROP_EXCLUSIVE: + g_value_set_boolean (value, gtk_tool_palette_get_exclusive (palette, child)); + break; + + case CHILD_PROP_EXPAND: + g_value_set_boolean (value, gtk_tool_palette_get_expand (palette, child)); + break; + + default: + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec); + break; + } +} + +static void +gtk_tool_palette_class_init (GtkToolPaletteClass *cls) +{ + GObjectClass *oclass = G_OBJECT_CLASS (cls); + GtkWidgetClass *wclass = GTK_WIDGET_CLASS (cls); + GtkContainerClass *cclass = GTK_CONTAINER_CLASS (cls); + + oclass->set_property = gtk_tool_palette_set_property; + oclass->get_property = gtk_tool_palette_get_property; + oclass->dispose = gtk_tool_palette_dispose; + oclass->finalize = gtk_tool_palette_finalize; + + wclass->size_request = gtk_tool_palette_size_request; + wclass->size_allocate = gtk_tool_palette_size_allocate; + wclass->expose_event = gtk_tool_palette_expose_event; + wclass->realize = gtk_tool_palette_realize; + + cclass->add = gtk_tool_palette_add; + cclass->remove = gtk_tool_palette_remove; + cclass->forall = gtk_tool_palette_forall; + cclass->child_type = gtk_tool_palette_child_type; + cclass->set_child_property = gtk_tool_palette_set_child_property; + cclass->get_child_property = gtk_tool_palette_get_child_property; + + cls->set_scroll_adjustments = gtk_tool_palette_set_scroll_adjustments; + /** + * GtkToolPalette::set-scroll-adjustments: + * @widget: the GtkToolPalette that received the signal + * @hadjustment: The horizontal adjustment + * @vadjustment: The vertical adjustment + * + * The ::set-scroll-adjustments when FIXME + * + * Since: 2.18 + */ + wclass->set_scroll_adjustments_signal = + g_signal_new ("set-scroll-adjustments", + G_TYPE_FROM_CLASS (oclass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkToolPaletteClass, set_scroll_adjustments), + NULL, NULL, + _gtk_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, + GTK_TYPE_ADJUSTMENT, + GTK_TYPE_ADJUSTMENT); + + g_object_class_install_property (oclass, PROP_ICON_SIZE, + g_param_spec_enum ("icon-size", + P_("Icon Size"), + P_("The size of palette icons"), + GTK_TYPE_ICON_SIZE, + DEFAULT_ICON_SIZE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (oclass, PROP_ORIENTATION, + g_param_spec_enum ("orientation", + P_("Orientation"), + P_("Orientation of the tool palette"), + GTK_TYPE_ORIENTATION, + DEFAULT_ORIENTATION, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (oclass, PROP_TOOLBAR_STYLE, + g_param_spec_enum ("toolbar-style", + P_("Toolbar Style"), + P_("Style of items in the tool palette"), + GTK_TYPE_TOOLBAR_STYLE, + DEFAULT_TOOLBAR_STYLE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + gtk_container_class_install_child_property (cclass, CHILD_PROP_EXCLUSIVE, + g_param_spec_boolean ("exclusive", + P_("Exclusive"), + P_("Whether the item group should be the only expanded at a given time"), + DEFAULT_CHILD_EXCLUSIVE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + gtk_container_class_install_child_property (cclass, CHILD_PROP_EXPAND, + g_param_spec_boolean ("expand", + P_("Expand"), + P_("Whether the item group should receive extra space when the palette grows"), + DEFAULT_CHILD_EXPAND, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_type_class_add_private (cls, sizeof (GtkToolPalettePrivate)); + + dnd_target_atom_item = gdk_atom_intern_static_string (dnd_targets[0].target); + dnd_target_atom_group = gdk_atom_intern_static_string (dnd_targets[1].target); +} + +/** + * gtk_tool_palette_new: + * + * Creates a new tool palette. + * + * Returns: a new #GtkToolPalette. + * + * Since: 2.18 + */ +GtkWidget* +gtk_tool_palette_new (void) +{ + return g_object_new (GTK_TYPE_TOOL_PALETTE, NULL); +} + +/** + * gtk_tool_palette_set_icon_size: + * @palette: an #GtkToolPalette. + * @icon_size: the #GtkIconSize that icons in the tool palette shall have. + * + * Sets the size of icons in the tool palette. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_icon_size (GtkToolPalette *palette, + GtkIconSize icon_size) +{ + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + + if (icon_size != palette->priv->icon_size) + g_object_set (palette, "icon-size", icon_size, NULL); +} + +/** + * gtk_tool_palette_set_orientation: + * @palette: an #GtkToolPalette. + * @orientation: the #GtkOrientation that the tool palette shall have. + * + * Sets the orientation (horizontal or vertical) of the tool palette. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_orientation (GtkToolPalette *palette, + GtkOrientation orientation) +{ + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + + if (orientation != palette->priv->orientation) + g_object_set (palette, "orientation", orientation, NULL); +} + +/** + * gtk_tool_palette_set_style: + * @palette: an #GtkToolPalette. + * @style: the #GtkToolbarStyle that items in the tool palette shall have. + * + * Sets the style (text, icons or both) of items in the tool palette. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_style (GtkToolPalette *palette, + GtkToolbarStyle style) +{ + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + + if (style != palette->priv->style) + g_object_set (palette, "toolbar-style", style, NULL); +} + +/** + * gtk_tool_palette_get_icon_size: + * @palette: an #GtkToolPalette. + * + * Gets the size of icons in the tool palette. See gtk_tool_palette_set_icon_size(). + * + * Returns: the #GtkIconSize of icons in the tool palette. + * + * Since: 2.18 + */ +GtkIconSize +gtk_tool_palette_get_icon_size (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), DEFAULT_ICON_SIZE); + return palette->priv->icon_size; +} + +/** + * gtk_tool_palette_get_orientation: + * @palette: an #GtkToolPalette. + * + * Gets the orientation (horizontal or vertical) of the tool palette. See gtk_tool_palette_set_orientation(). + * + * Returns the #GtkOrientation of the tool palette. + */ +GtkOrientation +gtk_tool_palette_get_orientation (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), DEFAULT_ORIENTATION); + return palette->priv->orientation; +} + +/** + * gtk_tool_palette_get_style: + * @palette: an #GtkToolPalette. + * + * Gets the style (icons, text or both) of items in the tool palette. + * + * Returns: the #GtkToolbarStyle of items in the tool palette. + * + * Since: 2.18 + */ +GtkToolbarStyle +gtk_tool_palette_get_style (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), DEFAULT_TOOLBAR_STYLE); + return palette->priv->style; +} + +/** + * gtk_tool_palette_set_group_position: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup which is a child of palette. + * @position: a new index for group. + * + * Sets the position of the group as an index of the tool palette. + * If position is 0 the group will become the first child, if position is + * -1 it will become the last child. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_group_position (GtkToolPalette *palette, + GtkWidget *group, + gint position) +{ + GtkToolItemGroupInfo group_info; + gint old_position; + gpointer src, dst; + gsize len; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + + gtk_tool_palette_repack (palette); + + g_return_if_fail (position >= -1); + + if (-1 == position) + position = palette->priv->groups_length - 1; + + g_return_if_fail ((guint) position < palette->priv->groups_length); + + if (GTK_TOOL_ITEM_GROUP (group) == palette->priv->groups[position].widget) + return; + + old_position = gtk_tool_palette_get_group_position (palette, group); + g_return_if_fail (old_position >= 0); + + group_info = palette->priv->groups[old_position]; + + if (position < old_position) + { + dst = palette->priv->groups + position + 1; + src = palette->priv->groups + position; + len = old_position - position; + } + else + { + dst = palette->priv->groups + old_position; + src = palette->priv->groups + old_position + 1; + len = position - old_position; + } + + memmove (dst, src, len * sizeof (*palette->priv->groups)); + palette->priv->groups[position] = group_info; + + gtk_widget_queue_resize (GTK_WIDGET (palette)); +} + +static void +gtk_tool_palette_group_notify_collapsed (GtkToolItemGroup *group, + GParamSpec *pspec G_GNUC_UNUSED, + gpointer data) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (data); + guint i; + + if (gtk_tool_item_group_get_collapsed (group)) + return; + + for (i = 0; i < palette->priv->groups_size; ++i) + { + GtkToolItemGroup *current_group = palette->priv->groups[i].widget; + + if (current_group && current_group != group) + gtk_tool_item_group_set_collapsed (palette->priv->groups[i].widget, TRUE); + } +} + +/** + * gtk_tool_palette_set_exclusive: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup which is a child of palette. + * @exclusive: whether the group should be exclusive or not. + * + * Sets whether the group should be exclusive or not. If an exclusive group is expanded + * all other groups are collapsed. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_exclusive (GtkToolPalette *palette, + GtkWidget *group, + gboolean exclusive) +{ + GtkToolItemGroupInfo *group_info; + gint position; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + + position = gtk_tool_palette_get_group_position (palette, group); + g_return_if_fail (position >= 0); + + group_info = &palette->priv->groups[position]; + + if (exclusive == group_info->exclusive) + return; + + group_info->exclusive = exclusive; + + if (group_info->exclusive != (0 != group_info->notify_collapsed)) + { + if (group_info->exclusive) + { + group_info->notify_collapsed = + g_signal_connect (group, "notify::collapsed", + G_CALLBACK (gtk_tool_palette_group_notify_collapsed), + palette); + } + else + { + g_signal_handler_disconnect (group, group_info->notify_collapsed); + group_info->notify_collapsed = 0; + } + } + + gtk_tool_palette_group_notify_collapsed (group_info->widget, NULL, palette); + gtk_widget_child_notify (group, "exclusive"); +} + +/** + * gtk_tool_palette_set_expand: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup which is a child of palette. + * @expand: whether the group should be given extra space. + * + * Sets whether the group should be given extra space. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_expand (GtkToolPalette *palette, + GtkWidget *group, + gboolean expand) +{ + GtkToolItemGroupInfo *group_info; + gint position; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + + position = gtk_tool_palette_get_group_position (palette, group); + g_return_if_fail (position >= 0); + + group_info = &palette->priv->groups[position]; + + if (expand != group_info->expand) + { + group_info->expand = expand; + gtk_widget_queue_resize (GTK_WIDGET (palette)); + gtk_widget_child_notify (group, "expand"); + } +} + +/** + * gtk_tool_palette_get_group_position: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup. + * + * Gets the position of @group in @palette as index. see gtk_tool_palette_set_group_position(). + * + * Returns: the index of group or -1 if @group is not a child of @palette. + * + * Since: 2.18 + */ +gint +gtk_tool_palette_get_group_position (GtkToolPalette *palette, + GtkWidget *group) +{ + guint i; + + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), -1); + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), -1); + + for (i = 0; i < palette->priv->groups_length; ++i) + if ((gpointer) group == palette->priv->groups[i].widget) + return i; + + return -1; +} + +/** + * gtk_tool_palette_get_exclusive: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup which is a child of palette. + * + * Gets whether group is exclusive or not. See gtk_tool_palette_set_exclusive(). + * + * Returns: %TRUE if group is exclusive. + * + * Since: 2.18 + */ +gboolean +gtk_tool_palette_get_exclusive (GtkToolPalette *palette, + GtkWidget *group) +{ + gint position; + + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), DEFAULT_CHILD_EXCLUSIVE); + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), DEFAULT_CHILD_EXCLUSIVE); + + position = gtk_tool_palette_get_group_position (palette, group); + g_return_val_if_fail (position >= 0, DEFAULT_CHILD_EXCLUSIVE); + + return palette->priv->groups[position].exclusive; +} + +/** + * gtk_tool_palette_get_expand: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup which is a child of palette. + * + * Gets whether group should be given extra space. See gtk_tool_palette_set_expand(). + * + * Returns: %TRUE if group should be given extra space, %FALSE otherwise. + * + * Since: 2.18 + */ +gboolean +gtk_tool_palette_get_expand (GtkToolPalette *palette, + GtkWidget *group) +{ + gint position; + + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), DEFAULT_CHILD_EXPAND); + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), DEFAULT_CHILD_EXPAND); + + position = gtk_tool_palette_get_group_position (palette, group); + g_return_val_if_fail (position >= 0, DEFAULT_CHILD_EXPAND); + + return palette->priv->groups[position].expand; +} + +/** + * gtk_tool_palette_get_drop_item: + * @palette: an #GtkToolPalette. + * @x: the x position. + * @y: the y position. + * + * Gets the item at position (x, y). See gtk_tool_palette_get_drop_group(). + * + * Returns: the #GtkToolItem at position or %NULL if there is no such item. + * + * Since: 2.18 + */ +GtkToolItem* +gtk_tool_palette_get_drop_item (GtkToolPalette *palette, + gint x, + gint y) +{ + GtkWidget *group = gtk_tool_palette_get_drop_group (palette, x, y); + + if (group) + return gtk_tool_item_group_get_drop_item (GTK_TOOL_ITEM_GROUP (group), + x - group->allocation.x, + y - group->allocation.y); + + return NULL; +} + +/** + * gtk_tool_palette_get_drop_group: + * @palette: an #GtkToolPalette. + * @x: the x position. + * @y: the y position. + * + * Gets the group at position (x, y). + * + * Returns: the #GtkToolItemGroup at position or %NULL if there is no such group. + * + * Since: 2.18 + */ +GtkWidget* +gtk_tool_palette_get_drop_group (GtkToolPalette *palette, + gint x, + gint y) +{ + GtkAllocation *allocation; + guint i; + + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), NULL); + + allocation = >K_WIDGET (palette)->allocation; + + g_return_val_if_fail (x >= 0 && x < allocation->width, NULL); + g_return_val_if_fail (y >= 0 && y < allocation->height, NULL); + + for (i = 0; i < palette->priv->groups_length; ++i) + { + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + GtkWidget *widget; + gint x0, y0; + + if (!group->widget) + continue; + + widget = GTK_WIDGET (group->widget); + + x0 = x - widget->allocation.x; + y0 = y - widget->allocation.y; + + if (x0 >= 0 && x0 < widget->allocation.width && + y0 >= 0 && y0 < widget->allocation.height) + return widget; + } + + return NULL; +} + +/** + * gtk_tool_palette_get_drag_item: + * @palette: an #GtkToolPalette. + * @selection: a #GtkSelectionData. + * + * Get the dragged item from the selection. This could be a #GtkToolItem or + * an #GtkToolItemGroup. + * + * Returns: the dragged item in selection. + * + * Since: 2.18 + */ +GtkWidget* +gtk_tool_palette_get_drag_item (GtkToolPalette *palette, + const GtkSelectionData *selection) +{ + GtkToolPaletteDragData *data; + + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), NULL); + g_return_val_if_fail (NULL != selection, NULL); + + g_return_val_if_fail (selection->format == 8, NULL); + g_return_val_if_fail (selection->length == sizeof (GtkToolPaletteDragData), NULL); + g_return_val_if_fail (selection->target == dnd_target_atom_item || + selection->target == dnd_target_atom_group, + NULL); + + data = (GtkToolPaletteDragData*) selection->data; + + g_return_val_if_fail (data->palette == palette, NULL); + + if (dnd_target_atom_item == selection->target) + g_return_val_if_fail (GTK_IS_TOOL_ITEM (data->item), NULL); + else if (dnd_target_atom_group == selection->target) + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (data->item), NULL); + + return data->item; +} + +/** + * gtk_tool_palette_set_drag_source: + * @palette: an #GtkToolPalette. + * @targets: the #GtkToolPaletteDragTargets which the widget should support. + * + * Sets the tool palette as a drag source. Enables all groups and items in + * the tool palette as drag sources on button 1 and button 3 press with copy + * and move actions. + * + * See gtk_drag_source_set(). + * + * Since: 2.18 + * + */ +void +gtk_tool_palette_set_drag_source (GtkToolPalette *palette, + GtkToolPaletteDragTargets targets) +{ + guint i; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + + if ((palette->priv->drag_source & targets) == targets) + return; + + palette->priv->drag_source |= targets; + + for (i = 0; i < palette->priv->groups_length; ++i) + { + if (palette->priv->groups[i].widget) + gtk_container_forall (GTK_CONTAINER (palette->priv->groups[i].widget), + _gtk_tool_palette_child_set_drag_source, + palette); + } +} + +/** + * gtk_tool_palette_add_drag_dest: + * @palette: an #GtkToolPalette. + * @widget: a #GtkWidget which should be a drag destination for palette. + * @flags: the flags that specify what actions GTK+ should take for drops on that widget. + * @targets: the #GtkToolPaletteDragTargets which the widget should support. + * @actions: the #GdkDragActions which the widget should suppport. + * + * Sets the tool palette as drag source (see gtk_tool_palette_set_drag_source) and + * sets widget as a drag destination for drags from palette. With flags the actions + * (like highlighting and target checking) which should be performed by GTK+ for + * drops on widget can be specified. With targets the supported drag targets + * (groups and/or items) can be specified. With actions the supported drag actions + * (copy and move) can be specified. + * + * See gtk_drag_dest_set(). + * + * Since: 2.18 + */ +void +gtk_tool_palette_add_drag_dest (GtkToolPalette *palette, + GtkWidget *widget, + GtkDestDefaults flags, + GtkToolPaletteDragTargets targets, + GdkDragAction actions) +{ + GtkTargetEntry entries[G_N_ELEMENTS (dnd_targets)]; + gint n_entries = 0; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gtk_tool_palette_set_drag_source (palette, + targets); + + if (targets & GTK_TOOL_PALETTE_DRAG_ITEMS) + entries[n_entries++] = dnd_targets[0]; + if (targets & GTK_TOOL_PALETTE_DRAG_GROUPS) + entries[n_entries++] = dnd_targets[1]; + + gtk_drag_dest_set (widget, flags, entries, n_entries, actions); +} + +void +_gtk_tool_palette_get_item_size (GtkToolPalette *palette, + GtkRequisition *item_size, + gboolean homogeneous_only, + gint *requested_rows) +{ + GtkRequisition max_requisition; + gint max_rows; + guint i; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + g_return_if_fail (NULL != item_size); + + max_requisition.width = 0; + max_requisition.height = 0; + max_rows = 0; + + /* iterate over all groups and calculate the max item_size and max row request */ + for (i = 0; i < palette->priv->groups_length; ++i) + { + GtkRequisition requisition; + gint rows; + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + + if (!group->widget) + continue; + + _gtk_tool_item_group_item_size_request (group->widget, &requisition, homogeneous_only, &rows); + + max_requisition.width = MAX (max_requisition.width, requisition.width); + max_requisition.height = MAX (max_requisition.height, requisition.height); + max_rows = MAX (max_rows, rows); + } + + *item_size = max_requisition; + if (requested_rows) + *requested_rows = max_rows; +} + +static void +gtk_tool_palette_item_drag_data_get (GtkWidget *widget, + GdkDragContext *context G_GNUC_UNUSED, + GtkSelectionData *selection, + guint info G_GNUC_UNUSED, + guint time G_GNUC_UNUSED, + gpointer data) +{ + GtkToolPaletteDragData drag_data = { GTK_TOOL_PALETTE (data), NULL }; + + if (selection->target == dnd_target_atom_item) + drag_data.item = gtk_widget_get_ancestor (widget, GTK_TYPE_TOOL_ITEM); + + if (drag_data.item) + gtk_selection_data_set (selection, selection->target, 8, + (guchar*) &drag_data, sizeof (drag_data)); +} + +static void +gtk_tool_palette_child_drag_data_get (GtkWidget *widget, + GdkDragContext *context G_GNUC_UNUSED, + GtkSelectionData *selection, + guint info G_GNUC_UNUSED, + guint time G_GNUC_UNUSED, + gpointer data) +{ + GtkToolPaletteDragData drag_data = { GTK_TOOL_PALETTE (data), NULL }; + + if (selection->target == dnd_target_atom_group) + drag_data.item = gtk_widget_get_ancestor (widget, GTK_TYPE_TOOL_ITEM_GROUP); + + if (drag_data.item) + gtk_selection_data_set (selection, selection->target, 8, + (guchar*) &drag_data, sizeof (drag_data)); +} + +void +_gtk_tool_palette_child_set_drag_source (GtkWidget *child, + gpointer data) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (data); + + /* Check drag_source, + * to work properly when called from gtk_tool_item_group_insert(). + */ + if (!palette->priv->drag_source) + return; + + if (GTK_IS_TOOL_ITEM (child) && + (palette->priv->drag_source & GTK_TOOL_PALETTE_DRAG_ITEMS)) + { + /* Connect to child instead of the item itself, + * to work arround bug 510377. + */ + if (GTK_IS_TOOL_BUTTON (child)) + child = gtk_bin_get_child (GTK_BIN (child)); + + if (!child) + return; + + gtk_drag_source_set (child, GDK_BUTTON1_MASK | GDK_BUTTON3_MASK, + &dnd_targets[0], 1, GDK_ACTION_COPY | GDK_ACTION_MOVE); + + g_signal_connect (child, "drag-data-get", + G_CALLBACK (gtk_tool_palette_item_drag_data_get), + palette); + } + else if (GTK_IS_BUTTON (child) && + (palette->priv->drag_source & GTK_TOOL_PALETTE_DRAG_GROUPS)) + { + gtk_drag_source_set (child, GDK_BUTTON1_MASK | GDK_BUTTON3_MASK, + &dnd_targets[1], 1, GDK_ACTION_COPY | GDK_ACTION_MOVE); + + g_signal_connect (child, "drag-data-get", + G_CALLBACK (gtk_tool_palette_child_drag_data_get), + palette); + } +} + +/** + * gtk_tool_palette_get_drag_target_item: + * + * Get the target entry for a dragged #GtkToolItem. + * + * Returns: the #GtkTargetEntry for a dragged item. + * + * Since: 2.18 + */ +G_CONST_RETURN GtkTargetEntry* +gtk_tool_palette_get_drag_target_item (void) +{ + return &dnd_targets[0]; +} + +/** + * gtk_tool_palette_get_drag_target_group: + * + * Get the target entry for a dragged #GtkToolItemGroup. + * + * Returns: the #GtkTargetEntry for a dragged group. + * + * Since: 2.18 + */ +G_CONST_RETURN GtkTargetEntry* +gtk_tool_palette_get_drag_target_group (void) +{ + return &dnd_targets[1]; +} + +void +_gtk_tool_palette_set_expanding_child (GtkToolPalette *palette, + GtkWidget *widget) +{ + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + palette->priv->expanding_child = widget; +} + +GtkAdjustment* +gtk_tool_palette_get_hadjustment (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), NULL); + return palette->priv->hadjustment; +} + +GtkAdjustment* +gtk_tool_palette_get_vadjustment (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), NULL); + return palette->priv->vadjustment; +} + +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + +GtkSizeGroup * +_gtk_tool_palette_get_size_group (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), NULL); + + return palette->priv->text_size_group; +} + +#endif +/* 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Mathias Hasselmann + */ + +#include "gtktoolpaletteprivate.h" +#include "gtkmarshalers.h" + +#include +#include + +#define DEFAULT_ICON_SIZE GTK_ICON_SIZE_SMALL_TOOLBAR +#define DEFAULT_ORIENTATION GTK_ORIENTATION_VERTICAL +#define DEFAULT_TOOLBAR_STYLE GTK_TOOLBAR_ICONS + +#define DEFAULT_CHILD_EXCLUSIVE FALSE +#define DEFAULT_CHILD_EXPAND FALSE + +#define P_(msgid) (msgid) + +/** + * SECTION:GtkToolPalette + * @short_description: A tool palette with categories + * @include: gtktoolpalette.h + * + * An #GtkToolPalette allows it to add #GtkToolItems to a palette like container + * with different categories and drag and drop support. + * + * An #GtkToolPalette is created with a call to gtk_tool_palette_new(). + * + * #GtkToolItems cannot be added directly to an #GtkToolPalette, instead they + * are added to an #GtkToolItemGroup which can than be added to an #GtkToolPalette. To add + * an #GtkToolItemGroup to an #GtkToolPalette use gtk_container_add(). + * + * |[ + * GtkWidget *palette, *group; + * GtkToolItem *item; + * + * palette = gtk_tool_palette_new (); + * group = gtk_tool_item_group_new (_("Test Category")); + * gtk_container_add (GTK_CONTAINER (palette), group); + * + * item = gtk_tool_button_new_from_stock (GTK_STOCK_OK); + * gtk_tool_item_group_insert (GTK_TOOL_ITEM_GROUP (group), item, -1); + * ]| + * + * The easiest way to use drag and drop with GtkToolPalette is to call gtk_tool_palette_add_drag_dest() + * with the desired drag source @palette and the desired drag target @widget. Than gtk_tool_palette_get_drag_item() + * can be used to get the dragged item in the #GtkWidget::drag-data-received signal handler of the drag target. + * + * |[ + * static void + * passive_canvas_drag_data_received (GtkWidget *widget, + * GdkDragContext *context, + * gint x, + * gint y, + * GtkSelectionData *selection, + * guint info, + * guint time, + * gpointer data) + * { + * GtkWidget *palette; + * GtkWidget *item; + * + * /* Get the dragged item */ + * palette = gtk_widget_get_ancestor (gtk_drag_get_source_widget (context), GTK_TYPE_TOOL_PALETTE); + * if (palette != NULL) + * item = gtk_tool_palette_get_drag_item (GTK_TOOL_PALETTE (palette), selection); + * + * /* Do something with item */ + * } + * + * GtkWidget *target, palette; + * + * palette = gtk_tool_palette_new (); + * target = gtk_drawing_area_new (); + * + * g_signal_connect (G_OBJECT (target), "drag-data-received", + * G_CALLBACK (passive_canvas_drag_data_received), NULL); + * gtk_tool_palette_add_drag_dest (GTK_TOOL_PALETTE (palette), target, + * GTK_DEST_DEFAULT_ALL, + * GTK_TOOL_PALETTE_DRAG_ITEMS, + * GDK_ACTION_COPY); + * ]| + * + * Since: 2.18 + */ + +typedef struct _GtkToolItemGroupInfo GtkToolItemGroupInfo; +typedef struct _GtkToolPaletteDragData GtkToolPaletteDragData; + +enum +{ + PROP_NONE, + PROP_ICON_SIZE, + PROP_ORIENTATION, + PROP_TOOLBAR_STYLE, +}; + +enum +{ + CHILD_PROP_NONE, + CHILD_PROP_EXCLUSIVE, + CHILD_PROP_EXPAND, +}; + +struct _GtkToolItemGroupInfo +{ + GtkToolItemGroup *widget; + + guint notify_collapsed; + guint exclusive : 1; + guint expand : 1; +}; + +struct _GtkToolPalettePrivate +{ + GtkToolItemGroupInfo *groups; + gsize groups_size; + gsize groups_length; + + GtkAdjustment *hadjustment; + GtkAdjustment *vadjustment; + + GtkIconSize icon_size; + GtkOrientation orientation; + GtkToolbarStyle style; + + GtkWidget *expanding_child; + +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + GtkSizeGroup *text_size_group; +#endif + + guint sparse_groups : 1; + guint drag_source : 2; +}; + +struct _GtkToolPaletteDragData +{ + GtkToolPalette *palette; + GtkWidget *item; +}; + +static GdkAtom dnd_target_atom_item = GDK_NONE; +static GdkAtom dnd_target_atom_group = GDK_NONE; + +static const GtkTargetEntry dnd_targets[] = +{ + { "application/x-GTK-tool-palette-item", GTK_TARGET_SAME_APP, 0 }, + { "application/x-GTK-tool-palette-group", GTK_TARGET_SAME_APP, 0 }, +}; + +G_DEFINE_TYPE (GtkToolPalette, + gtk_tool_palette, + GTK_TYPE_CONTAINER); + +static void +gtk_tool_palette_init (GtkToolPalette *palette) +{ + palette->priv = G_TYPE_INSTANCE_GET_PRIVATE (palette, + GTK_TYPE_TOOL_PALETTE, + GtkToolPalettePrivate); + + palette->priv->groups_size = 4; + palette->priv->groups_length = 0; + palette->priv->groups = g_new0 (GtkToolItemGroupInfo, palette->priv->groups_size); + + palette->priv->icon_size = DEFAULT_ICON_SIZE; + palette->priv->orientation = DEFAULT_ORIENTATION; + palette->priv->style = DEFAULT_TOOLBAR_STYLE; + +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + palette->priv->text_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); +#endif +} + +static void +gtk_tool_palette_reconfigured (GtkToolPalette *palette) +{ + guint i; + + for (i = 0; i < palette->priv->groups_length; ++i) + { + if (palette->priv->groups[i].widget) + _gtk_tool_item_group_palette_reconfigured (palette->priv->groups[i].widget); + } + + gtk_widget_queue_resize_no_redraw (GTK_WIDGET (palette)); +} + +static void +gtk_tool_palette_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (object); + + switch (prop_id) + { + case PROP_ICON_SIZE: + if ((guint) g_value_get_enum (value) != palette->priv->icon_size) + { + palette->priv->icon_size = g_value_get_enum (value); + gtk_tool_palette_reconfigured (palette); + } + break; + + case PROP_ORIENTATION: + if ((guint) g_value_get_enum (value) != palette->priv->orientation) + { + palette->priv->orientation = g_value_get_enum (value); + gtk_tool_palette_reconfigured (palette); + } + break; + + case PROP_TOOLBAR_STYLE: + if ((guint) g_value_get_enum (value) != palette->priv->style) + { + palette->priv->style = g_value_get_enum (value); + gtk_tool_palette_reconfigured (palette); + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_tool_palette_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (object); + + switch (prop_id) + { + case PROP_ICON_SIZE: + g_value_set_enum (value, gtk_tool_palette_get_icon_size (palette)); + break; + + case PROP_ORIENTATION: + g_value_set_enum (value, gtk_tool_palette_get_orientation (palette)); + break; + + case PROP_TOOLBAR_STYLE: + g_value_set_enum (value, gtk_tool_palette_get_style (palette)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_tool_palette_dispose (GObject *object) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (object); + guint i; + + if (palette->priv->hadjustment) + { + g_object_unref (palette->priv->hadjustment); + palette->priv->hadjustment = NULL; + } + + if (palette->priv->vadjustment) + { + g_object_unref (palette->priv->vadjustment); + palette->priv->vadjustment = NULL; + } + + for (i = 0; i < palette->priv->groups_size; ++i) + { + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + + if (group->notify_collapsed) + { + g_signal_handler_disconnect (group->widget, group->notify_collapsed); + group->notify_collapsed = 0; + } + } + +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + if (palette->priv->text_size_group) + { + g_object_unref (palette->priv->text_size_group); + palette->priv->text_size_group = NULL; + } +#endif + + G_OBJECT_CLASS (gtk_tool_palette_parent_class)->dispose (object); +} + +static void +gtk_tool_palette_finalize (GObject *object) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (object); + + if (palette->priv->groups) + { + palette->priv->groups_length = 0; + g_free (palette->priv->groups); + palette->priv->groups = NULL; + } + + G_OBJECT_CLASS (gtk_tool_palette_parent_class)->finalize (object); +} + +static void +gtk_tool_palette_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + GtkToolPalette *palette = GTK_TOOL_PALETTE (widget); + GtkRequisition child_requisition; + guint i; + + requisition->width = 0; + requisition->height = 0; + + for (i = 0; i < palette->priv->groups_length; ++i) + { + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + + if (!group->widget) + continue; + + gtk_widget_size_request (GTK_WIDGET (group->widget), &child_requisition); + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + { + requisition->width = MAX (requisition->width, child_requisition.width); + requisition->height += child_requisition.height; + } + else + { + requisition->width += child_requisition.width; + requisition->height = MAX (requisition->height, child_requisition.height); + } + } + + requisition->width += border_width * 2; + requisition->height += border_width * 2; +} + +static void +gtk_tool_palette_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + GtkToolPalette *palette = GTK_TOOL_PALETTE (widget); + GtkAdjustment *adjustment = NULL; + GtkAllocation child_allocation; + + gint n_expand_groups = 0; + gint remaining_space = 0; + gint expand_space = 0; + + gint page_start, page_size = 0; + gint offset = 0; + guint i; + + gint min_offset = -1, max_offset = -1; + + gint x; + + gint *group_sizes = g_newa(gint, palette->priv->groups_length); + + GtkTextDirection direction = gtk_widget_get_direction (widget); + + GTK_WIDGET_CLASS (gtk_tool_palette_parent_class)->size_allocate (widget, allocation); + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + { + adjustment = palette->priv->vadjustment; + page_size = allocation->height; + } + else + { + adjustment = palette->priv->hadjustment; + page_size = allocation->width; + } + + if (adjustment) + offset = gtk_adjustment_get_value (adjustment); + if (GTK_ORIENTATION_HORIZONTAL == palette->priv->orientation && + GTK_TEXT_DIR_RTL == direction) + offset = -offset; + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + child_allocation.width = allocation->width - border_width * 2; + else + child_allocation.height = allocation->height - border_width * 2; + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + remaining_space = allocation->height; + else + remaining_space = allocation->width; + + /* figure out the required size of all groups to be able to distribute the + * remaining space on allocation */ + for (i = 0; i < palette->priv->groups_length; ++i) + { + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + gint size; + + if (!group->widget) + continue; + + widget = GTK_WIDGET (group->widget); + + if (gtk_tool_item_group_get_n_items (group->widget)) + { + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + size = _gtk_tool_item_group_get_height_for_width (group->widget, child_allocation.width); + else + size = _gtk_tool_item_group_get_width_for_height (group->widget, child_allocation.height); + + if (group->expand && !gtk_tool_item_group_get_collapsed (group->widget)) + n_expand_groups += 1; + } + else + size = 0; + + remaining_space -= size; + group_sizes[i] = size; + + /* if the widget is currently expanding an offset which allows to display as much of the + * widget as possible is calculated */ + if (widget == palette->priv->expanding_child) + { + gint limit = + GTK_ORIENTATION_VERTICAL == palette->priv->orientation ? + child_allocation.width : child_allocation.height; + + gint real_size; + guint j; + + min_offset = 0; + + for (j = 0; j < i; ++j) + min_offset += group_sizes[j]; + + max_offset = min_offset + group_sizes[i]; + + real_size = _gtk_tool_item_group_get_size_for_limit + (GTK_TOOL_ITEM_GROUP (widget), limit, + GTK_ORIENTATION_VERTICAL == palette->priv->orientation, + FALSE); + + if (size == real_size) + palette->priv->expanding_child = NULL; + } + } + + if (n_expand_groups > 0) + { + remaining_space = MAX (0, remaining_space); + expand_space = remaining_space / n_expand_groups; + } + + if (max_offset != -1) + { + gint limit = + GTK_ORIENTATION_VERTICAL == palette->priv->orientation ? + allocation->height : allocation->width; + + offset = MIN (MAX (offset, max_offset - limit), min_offset); + } + + if (remaining_space > 0) + offset = 0; + + x = border_width; + child_allocation.y = border_width; + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + child_allocation.y -= offset; + else + x -= offset; + + /* allocate all groups at the calculated positions */ + for (i = 0; i < palette->priv->groups_length; ++i) + { + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + GtkWidget *widget; + + if (!group->widget) + continue; + + widget = GTK_WIDGET (group->widget); + + if (gtk_tool_item_group_get_n_items (group->widget)) + { + gint size = group_sizes[i]; + + if (group->expand && !gtk_tool_item_group_get_collapsed (group->widget)) + { + size += MIN (expand_space, remaining_space); + remaining_space -= expand_space; + } + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + child_allocation.height = size; + else + child_allocation.width = size; + + if (GTK_ORIENTATION_HORIZONTAL == palette->priv->orientation && + GTK_TEXT_DIR_RTL == direction) + child_allocation.x = allocation->width - x - child_allocation.width; + else + child_allocation.x = x; + + gtk_widget_size_allocate (widget, &child_allocation); + gtk_widget_show (widget); + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + child_allocation.y += child_allocation.height; + else + x += child_allocation.width; + } + else + gtk_widget_hide (widget); + } + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + { + child_allocation.y += border_width; + child_allocation.y += offset; + + page_start = child_allocation.y; + } + else + { + x += border_width; + x += offset; + + page_start = x; + } + + /* update the scrollbar to match the displayed adjustment */ + if (adjustment) + { + gdouble value; + + adjustment->page_increment = page_size * 0.9; + adjustment->step_increment = page_size * 0.1; + adjustment->page_size = page_size; + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation || + GTK_TEXT_DIR_LTR == direction) + { + adjustment->lower = 0; + adjustment->upper = MAX (0, page_start); + + value = MIN (offset, adjustment->upper - adjustment->page_size); + gtk_adjustment_clamp_page (adjustment, value, offset + page_size); + } + else + { + adjustment->lower = page_size - MAX (0, page_start); + adjustment->upper = page_size; + + offset = -offset; + + value = MAX (offset, adjustment->lower); + gtk_adjustment_clamp_page (adjustment, offset, value + page_size); + } + + gtk_adjustment_changed (adjustment); + } +} + +static gboolean +gtk_tool_palette_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (widget); + GdkDisplay *display; + cairo_t *cr; + guint i; + + display = gdk_drawable_get_display (widget->window); + + if (!gdk_display_supports_composite (display)) + return FALSE; + + cr = gdk_cairo_create (widget->window); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + cairo_push_group (cr); + + for (i = 0; i < palette->priv->groups_length; ++i) + if (palette->priv->groups[i].widget) + _gtk_tool_item_group_paint (palette->priv->groups[i].widget, cr); + + cairo_pop_group_to_source (cr); + + cairo_paint (cr); + cairo_destroy (cr); + + return FALSE; +} + +static void +gtk_tool_palette_realize (GtkWidget *widget) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + gint attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + GdkWindowAttr attributes; + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = widget->allocation.x + border_width; + attributes.y = widget->allocation.y + border_width; + attributes.width = widget->allocation.width - border_width * 2; + attributes.height = widget->allocation.height - border_width * 2; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK + | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + | GDK_BUTTON_MOTION_MASK; + + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + + gdk_window_set_user_data (widget->window, widget); + widget->style = gtk_style_attach (widget->style, widget->window); + gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + + gtk_container_forall (GTK_CONTAINER (widget), + (GtkCallback) gtk_widget_set_parent_window, + widget->window); + + gtk_widget_queue_resize_no_redraw (widget); +} + +static void +gtk_tool_palette_adjustment_value_changed (GtkAdjustment *adjustment G_GNUC_UNUSED, + gpointer data) +{ + GtkWidget *widget = GTK_WIDGET (data); + gtk_tool_palette_size_allocate (widget, &widget->allocation); +} + +static void +gtk_tool_palette_set_scroll_adjustments (GtkWidget *widget, + GtkAdjustment *hadjustment, + GtkAdjustment *vadjustment) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (widget); + + if (palette->priv->hadjustment) + g_object_unref (palette->priv->hadjustment); + if (palette->priv->vadjustment) + g_object_unref (palette->priv->vadjustment); + + if (hadjustment) + g_object_ref_sink (hadjustment); + if (vadjustment) + g_object_ref_sink (vadjustment); + + palette->priv->hadjustment = hadjustment; + palette->priv->vadjustment = vadjustment; + + if (palette->priv->hadjustment) + g_signal_connect (palette->priv->hadjustment, "value-changed", + G_CALLBACK (gtk_tool_palette_adjustment_value_changed), + palette); + if (palette->priv->vadjustment) + g_signal_connect (palette->priv->vadjustment, "value-changed", + G_CALLBACK (gtk_tool_palette_adjustment_value_changed), + palette); +} + +static void +gtk_tool_palette_repack (GtkToolPalette *palette) +{ + guint si, di; + + for (si = di = 0; di < palette->priv->groups_length; ++si) + { + if (palette->priv->groups[si].widget) + { + palette->priv->groups[di] = palette->priv->groups[si]; + ++di; + } + else + --palette->priv->groups_length; + } + + palette->priv->sparse_groups = FALSE; +} + +static void +gtk_tool_palette_add (GtkContainer *container, + GtkWidget *child) +{ + GtkToolPalette *palette; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (container)); + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (child)); + + palette = GTK_TOOL_PALETTE (container); + + if (palette->priv->groups_length == palette->priv->groups_size) + gtk_tool_palette_repack (palette); + + if (palette->priv->groups_length == palette->priv->groups_size) + { + gsize old_size = palette->priv->groups_size; + gsize new_size = old_size * 2; + + palette->priv->groups = g_renew (GtkToolItemGroupInfo, + palette->priv->groups, + new_size); + + memset (palette->priv->groups + old_size, 0, + sizeof (GtkToolItemGroupInfo) * old_size); + + palette->priv->groups_size = new_size; + } + + palette->priv->groups[palette->priv->groups_length].widget = g_object_ref_sink (child); + palette->priv->groups_length += 1; + + gtk_widget_set_parent (child, GTK_WIDGET (palette)); +} + +static void +gtk_tool_palette_remove (GtkContainer *container, + GtkWidget *child) +{ + GtkToolPalette *palette; + guint i; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (container)); + palette = GTK_TOOL_PALETTE (container); + + for (i = 0; i < palette->priv->groups_length; ++i) + if ((GtkWidget*) palette->priv->groups[i].widget == child) + { + g_object_unref (child); + gtk_widget_unparent (child); + + memset (&palette->priv->groups[i], 0, sizeof (GtkToolItemGroupInfo)); + palette->priv->sparse_groups = TRUE; + } +} + +static void +gtk_tool_palette_forall (GtkContainer *container, + gboolean internals G_GNUC_UNUSED, + GtkCallback callback, + gpointer callback_data) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (container); + guint i; + + if (palette->priv->groups) + { + for (i = 0; i < palette->priv->groups_length; ++i) + if (palette->priv->groups[i].widget) + callback (GTK_WIDGET (palette->priv->groups[i].widget), + callback_data); + } +} + +static GType +gtk_tool_palette_child_type (GtkContainer *container G_GNUC_UNUSED) +{ + return GTK_TYPE_TOOL_ITEM_GROUP; +} + +static void +gtk_tool_palette_set_child_property (GtkContainer *container, + GtkWidget *child, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (container); + + switch (prop_id) + { + case CHILD_PROP_EXCLUSIVE: + gtk_tool_palette_set_exclusive (palette, child, g_value_get_boolean (value)); + break; + + case CHILD_PROP_EXPAND: + gtk_tool_palette_set_expand (palette, child, g_value_get_boolean (value)); + break; + + default: + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec); + break; + } +} + +static void +gtk_tool_palette_get_child_property (GtkContainer *container, + GtkWidget *child, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (container); + + switch (prop_id) + { + case CHILD_PROP_EXCLUSIVE: + g_value_set_boolean (value, gtk_tool_palette_get_exclusive (palette, child)); + break; + + case CHILD_PROP_EXPAND: + g_value_set_boolean (value, gtk_tool_palette_get_expand (palette, child)); + break; + + default: + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec); + break; + } +} + +static void +gtk_tool_palette_class_init (GtkToolPaletteClass *cls) +{ + GObjectClass *oclass = G_OBJECT_CLASS (cls); + GtkWidgetClass *wclass = GTK_WIDGET_CLASS (cls); + GtkContainerClass *cclass = GTK_CONTAINER_CLASS (cls); + + oclass->set_property = gtk_tool_palette_set_property; + oclass->get_property = gtk_tool_palette_get_property; + oclass->dispose = gtk_tool_palette_dispose; + oclass->finalize = gtk_tool_palette_finalize; + + wclass->size_request = gtk_tool_palette_size_request; + wclass->size_allocate = gtk_tool_palette_size_allocate; + wclass->expose_event = gtk_tool_palette_expose_event; + wclass->realize = gtk_tool_palette_realize; + + cclass->add = gtk_tool_palette_add; + cclass->remove = gtk_tool_palette_remove; + cclass->forall = gtk_tool_palette_forall; + cclass->child_type = gtk_tool_palette_child_type; + cclass->set_child_property = gtk_tool_palette_set_child_property; + cclass->get_child_property = gtk_tool_palette_get_child_property; + + cls->set_scroll_adjustments = gtk_tool_palette_set_scroll_adjustments; + /** + * GtkToolPalette::set-scroll-adjustments: + * @widget: the GtkToolPalette that received the signal + * @hadjustment: The horizontal adjustment + * @vadjustment: The vertical adjustment + * + * The ::set-scroll-adjustments when FIXME + * + * Since: 2.18 + */ + wclass->set_scroll_adjustments_signal = + g_signal_new ("set-scroll-adjustments", + G_TYPE_FROM_CLASS (oclass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkToolPaletteClass, set_scroll_adjustments), + NULL, NULL, + _gtk_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, + GTK_TYPE_ADJUSTMENT, + GTK_TYPE_ADJUSTMENT); + + g_object_class_install_property (oclass, PROP_ICON_SIZE, + g_param_spec_enum ("icon-size", + P_("Icon Size"), + P_("The size of palette icons"), + GTK_TYPE_ICON_SIZE, + DEFAULT_ICON_SIZE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (oclass, PROP_ORIENTATION, + g_param_spec_enum ("orientation", + P_("Orientation"), + P_("Orientation of the tool palette"), + GTK_TYPE_ORIENTATION, + DEFAULT_ORIENTATION, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (oclass, PROP_TOOLBAR_STYLE, + g_param_spec_enum ("toolbar-style", + P_("Toolbar Style"), + P_("Style of items in the tool palette"), + GTK_TYPE_TOOLBAR_STYLE, + DEFAULT_TOOLBAR_STYLE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + gtk_container_class_install_child_property (cclass, CHILD_PROP_EXCLUSIVE, + g_param_spec_boolean ("exclusive", + P_("Exclusive"), + P_("Whether the item group should be the only expanded at a given time"), + DEFAULT_CHILD_EXCLUSIVE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + gtk_container_class_install_child_property (cclass, CHILD_PROP_EXPAND, + g_param_spec_boolean ("expand", + P_("Expand"), + P_("Whether the item group should receive extra space when the palette grows"), + DEFAULT_CHILD_EXPAND, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_type_class_add_private (cls, sizeof (GtkToolPalettePrivate)); + + dnd_target_atom_item = gdk_atom_intern_static_string (dnd_targets[0].target); + dnd_target_atom_group = gdk_atom_intern_static_string (dnd_targets[1].target); +} + +/** + * gtk_tool_palette_new: + * + * Creates a new tool palette. + * + * Returns: a new #GtkToolPalette. + * + * Since: 2.18 + */ +GtkWidget* +gtk_tool_palette_new (void) +{ + return g_object_new (GTK_TYPE_TOOL_PALETTE, NULL); +} + +/** + * gtk_tool_palette_set_icon_size: + * @palette: an #GtkToolPalette. + * @icon_size: the #GtkIconSize that icons in the tool palette shall have. + * + * Sets the size of icons in the tool palette. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_icon_size (GtkToolPalette *palette, + GtkIconSize icon_size) +{ + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + + if (icon_size != palette->priv->icon_size) + g_object_set (palette, "icon-size", icon_size, NULL); +} + +/** + * gtk_tool_palette_set_orientation: + * @palette: an #GtkToolPalette. + * @orientation: the #GtkOrientation that the tool palette shall have. + * + * Sets the orientation (horizontal or vertical) of the tool palette. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_orientation (GtkToolPalette *palette, + GtkOrientation orientation) +{ + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + + if (orientation != palette->priv->orientation) + g_object_set (palette, "orientation", orientation, NULL); +} + +/** + * gtk_tool_palette_set_style: + * @palette: an #GtkToolPalette. + * @style: the #GtkToolbarStyle that items in the tool palette shall have. + * + * Sets the style (text, icons or both) of items in the tool palette. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_style (GtkToolPalette *palette, + GtkToolbarStyle style) +{ + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + + if (style != palette->priv->style) + g_object_set (palette, "toolbar-style", style, NULL); +} + +/** + * gtk_tool_palette_get_icon_size: + * @palette: an #GtkToolPalette. + * + * Gets the size of icons in the tool palette. See gtk_tool_palette_set_icon_size(). + * + * Returns: the #GtkIconSize of icons in the tool palette. + * + * Since: 2.18 + */ +GtkIconSize +gtk_tool_palette_get_icon_size (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), DEFAULT_ICON_SIZE); + return palette->priv->icon_size; +} + +/** + * gtk_tool_palette_get_orientation: + * @palette: an #GtkToolPalette. + * + * Gets the orientation (horizontal or vertical) of the tool palette. See gtk_tool_palette_set_orientation(). + * + * Returns the #GtkOrientation of the tool palette. + */ +GtkOrientation +gtk_tool_palette_get_orientation (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), DEFAULT_ORIENTATION); + return palette->priv->orientation; +} + +/** + * gtk_tool_palette_get_style: + * @palette: an #GtkToolPalette. + * + * Gets the style (icons, text or both) of items in the tool palette. + * + * Returns: the #GtkToolbarStyle of items in the tool palette. + * + * Since: 2.18 + */ +GtkToolbarStyle +gtk_tool_palette_get_style (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), DEFAULT_TOOLBAR_STYLE); + return palette->priv->style; +} + +/** + * gtk_tool_palette_set_group_position: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup which is a child of palette. + * @position: a new index for group. + * + * Sets the position of the group as an index of the tool palette. + * If position is 0 the group will become the first child, if position is + * -1 it will become the last child. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_group_position (GtkToolPalette *palette, + GtkWidget *group, + gint position) +{ + GtkToolItemGroupInfo group_info; + gint old_position; + gpointer src, dst; + gsize len; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + + gtk_tool_palette_repack (palette); + + g_return_if_fail (position >= -1); + + if (-1 == position) + position = palette->priv->groups_length - 1; + + g_return_if_fail ((guint) position < palette->priv->groups_length); + + if (GTK_TOOL_ITEM_GROUP (group) == palette->priv->groups[position].widget) + return; + + old_position = gtk_tool_palette_get_group_position (palette, group); + g_return_if_fail (old_position >= 0); + + group_info = palette->priv->groups[old_position]; + + if (position < old_position) + { + dst = palette->priv->groups + position + 1; + src = palette->priv->groups + position; + len = old_position - position; + } + else + { + dst = palette->priv->groups + old_position; + src = palette->priv->groups + old_position + 1; + len = position - old_position; + } + + memmove (dst, src, len * sizeof (*palette->priv->groups)); + palette->priv->groups[position] = group_info; + + gtk_widget_queue_resize (GTK_WIDGET (palette)); +} + +static void +gtk_tool_palette_group_notify_collapsed (GtkToolItemGroup *group, + GParamSpec *pspec G_GNUC_UNUSED, + gpointer data) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (data); + guint i; + + if (gtk_tool_item_group_get_collapsed (group)) + return; + + for (i = 0; i < palette->priv->groups_size; ++i) + { + GtkToolItemGroup *current_group = palette->priv->groups[i].widget; + + if (current_group && current_group != group) + gtk_tool_item_group_set_collapsed (palette->priv->groups[i].widget, TRUE); + } +} + +/** + * gtk_tool_palette_set_exclusive: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup which is a child of palette. + * @exclusive: whether the group should be exclusive or not. + * + * Sets whether the group should be exclusive or not. If an exclusive group is expanded + * all other groups are collapsed. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_exclusive (GtkToolPalette *palette, + GtkWidget *group, + gboolean exclusive) +{ + GtkToolItemGroupInfo *group_info; + gint position; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + + position = gtk_tool_palette_get_group_position (palette, group); + g_return_if_fail (position >= 0); + + group_info = &palette->priv->groups[position]; + + if (exclusive == group_info->exclusive) + return; + + group_info->exclusive = exclusive; + + if (group_info->exclusive != (0 != group_info->notify_collapsed)) + { + if (group_info->exclusive) + { + group_info->notify_collapsed = + g_signal_connect (group, "notify::collapsed", + G_CALLBACK (gtk_tool_palette_group_notify_collapsed), + palette); + } + else + { + g_signal_handler_disconnect (group, group_info->notify_collapsed); + group_info->notify_collapsed = 0; + } + } + + gtk_tool_palette_group_notify_collapsed (group_info->widget, NULL, palette); + gtk_widget_child_notify (group, "exclusive"); +} + +/** + * gtk_tool_palette_set_expand: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup which is a child of palette. + * @expand: whether the group should be given extra space. + * + * Sets whether the group should be given extra space. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_expand (GtkToolPalette *palette, + GtkWidget *group, + gboolean expand) +{ + GtkToolItemGroupInfo *group_info; + gint position; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + + position = gtk_tool_palette_get_group_position (palette, group); + g_return_if_fail (position >= 0); + + group_info = &palette->priv->groups[position]; + + if (expand != group_info->expand) + { + group_info->expand = expand; + gtk_widget_queue_resize (GTK_WIDGET (palette)); + gtk_widget_child_notify (group, "expand"); + } +} + +/** + * gtk_tool_palette_get_group_position: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup. + * + * Gets the position of @group in @palette as index. see gtk_tool_palette_set_group_position(). + * + * Returns: the index of group or -1 if @group is not a child of @palette. + * + * Since: 2.18 + */ +gint +gtk_tool_palette_get_group_position (GtkToolPalette *palette, + GtkWidget *group) +{ + guint i; + + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), -1); + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), -1); + + for (i = 0; i < palette->priv->groups_length; ++i) + if ((gpointer) group == palette->priv->groups[i].widget) + return i; + + return -1; +} + +/** + * gtk_tool_palette_get_exclusive: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup which is a child of palette. + * + * Gets whether group is exclusive or not. See gtk_tool_palette_set_exclusive(). + * + * Returns: %TRUE if group is exclusive. + * + * Since: 2.18 + */ +gboolean +gtk_tool_palette_get_exclusive (GtkToolPalette *palette, + GtkWidget *group) +{ + gint position; + + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), DEFAULT_CHILD_EXCLUSIVE); + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), DEFAULT_CHILD_EXCLUSIVE); + + position = gtk_tool_palette_get_group_position (palette, group); + g_return_val_if_fail (position >= 0, DEFAULT_CHILD_EXCLUSIVE); + + return palette->priv->groups[position].exclusive; +} + +/** + * gtk_tool_palette_get_expand: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup which is a child of palette. + * + * Gets whether group should be given extra space. See gtk_tool_palette_set_expand(). + * + * Returns: %TRUE if group should be given extra space, %FALSE otherwise. + * + * Since: 2.18 + */ +gboolean +gtk_tool_palette_get_expand (GtkToolPalette *palette, + GtkWidget *group) +{ + gint position; + + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), DEFAULT_CHILD_EXPAND); + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), DEFAULT_CHILD_EXPAND); + + position = gtk_tool_palette_get_group_position (palette, group); + g_return_val_if_fail (position >= 0, DEFAULT_CHILD_EXPAND); + + return palette->priv->groups[position].expand; +} + +/** + * gtk_tool_palette_get_drop_item: + * @palette: an #GtkToolPalette. + * @x: the x position. + * @y: the y position. + * + * Gets the item at position (x, y). See gtk_tool_palette_get_drop_group(). + * + * Returns: the #GtkToolItem at position or %NULL if there is no such item. + * + * Since: 2.18 + */ +GtkToolItem* +gtk_tool_palette_get_drop_item (GtkToolPalette *palette, + gint x, + gint y) +{ + GtkWidget *group = gtk_tool_palette_get_drop_group (palette, x, y); + + if (group) + return gtk_tool_item_group_get_drop_item (GTK_TOOL_ITEM_GROUP (group), + x - group->allocation.x, + y - group->allocation.y); + + return NULL; +} + +/** + * gtk_tool_palette_get_drop_group: + * @palette: an #GtkToolPalette. + * @x: the x position. + * @y: the y position. + * + * Gets the group at position (x, y). + * + * Returns: the #GtkToolItemGroup at position or %NULL if there is no such group. + * + * Since: 2.18 + */ +GtkWidget* +gtk_tool_palette_get_drop_group (GtkToolPalette *palette, + gint x, + gint y) +{ + GtkAllocation *allocation; + guint i; + + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), NULL); + + allocation = >K_WIDGET (palette)->allocation; + + g_return_val_if_fail (x >= 0 && x < allocation->width, NULL); + g_return_val_if_fail (y >= 0 && y < allocation->height, NULL); + + for (i = 0; i < palette->priv->groups_length; ++i) + { + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + GtkWidget *widget; + gint x0, y0; + + if (!group->widget) + continue; + + widget = GTK_WIDGET (group->widget); + + x0 = x - widget->allocation.x; + y0 = y - widget->allocation.y; + + if (x0 >= 0 && x0 < widget->allocation.width && + y0 >= 0 && y0 < widget->allocation.height) + return widget; + } + + return NULL; +} + +/** + * gtk_tool_palette_get_drag_item: + * @palette: an #GtkToolPalette. + * @selection: a #GtkSelectionData. + * + * Get the dragged item from the selection. This could be a #GtkToolItem or + * an #GtkToolItemGroup. + * + * Returns: the dragged item in selection. + * + * Since: 2.18 + */ +GtkWidget* +gtk_tool_palette_get_drag_item (GtkToolPalette *palette, + const GtkSelectionData *selection) +{ + GtkToolPaletteDragData *data; + + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), NULL); + g_return_val_if_fail (NULL != selection, NULL); + + g_return_val_if_fail (selection->format == 8, NULL); + g_return_val_if_fail (selection->length == sizeof (GtkToolPaletteDragData), NULL); + g_return_val_if_fail (selection->target == dnd_target_atom_item || + selection->target == dnd_target_atom_group, + NULL); + + data = (GtkToolPaletteDragData*) selection->data; + + g_return_val_if_fail (data->palette == palette, NULL); + + if (dnd_target_atom_item == selection->target) + g_return_val_if_fail (GTK_IS_TOOL_ITEM (data->item), NULL); + else if (dnd_target_atom_group == selection->target) + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (data->item), NULL); + + return data->item; +} + +/** + * gtk_tool_palette_set_drag_source: + * @palette: an #GtkToolPalette. + * @targets: the #GtkToolPaletteDragTargets which the widget should support. + * + * Sets the tool palette as a drag source. Enables all groups and items in + * the tool palette as drag sources on button 1 and button 3 press with copy + * and move actions. + * + * See gtk_drag_source_set(). + * + * Since: 2.18 + * + */ +void +gtk_tool_palette_set_drag_source (GtkToolPalette *palette, + GtkToolPaletteDragTargets targets) +{ + guint i; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + + if ((palette->priv->drag_source & targets) == targets) + return; + + palette->priv->drag_source |= targets; + + for (i = 0; i < palette->priv->groups_length; ++i) + { + if (palette->priv->groups[i].widget) + gtk_container_forall (GTK_CONTAINER (palette->priv->groups[i].widget), + _gtk_tool_palette_child_set_drag_source, + palette); + } +} + +/** + * gtk_tool_palette_add_drag_dest: + * @palette: an #GtkToolPalette. + * @widget: a #GtkWidget which should be a drag destination for palette. + * @flags: the flags that specify what actions GTK+ should take for drops on that widget. + * @targets: the #GtkToolPaletteDragTargets which the widget should support. + * @actions: the #GdkDragActions which the widget should suppport. + * + * Sets the tool palette as drag source (see gtk_tool_palette_set_drag_source) and + * sets widget as a drag destination for drags from palette. With flags the actions + * (like highlighting and target checking) which should be performed by GTK+ for + * drops on widget can be specified. With targets the supported drag targets + * (groups and/or items) can be specified. With actions the supported drag actions + * (copy and move) can be specified. + * + * See gtk_drag_dest_set(). + * + * Since: 2.18 + */ +void +gtk_tool_palette_add_drag_dest (GtkToolPalette *palette, + GtkWidget *widget, + GtkDestDefaults flags, + GtkToolPaletteDragTargets targets, + GdkDragAction actions) +{ + GtkTargetEntry entries[G_N_ELEMENTS (dnd_targets)]; + gint n_entries = 0; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gtk_tool_palette_set_drag_source (palette, + targets); + + if (targets & GTK_TOOL_PALETTE_DRAG_ITEMS) + entries[n_entries++] = dnd_targets[0]; + if (targets & GTK_TOOL_PALETTE_DRAG_GROUPS) + entries[n_entries++] = dnd_targets[1]; + + gtk_drag_dest_set (widget, flags, entries, n_entries, actions); +} + +void +_gtk_tool_palette_get_item_size (GtkToolPalette *palette, + GtkRequisition *item_size, + gboolean homogeneous_only, + gint *requested_rows) +{ + GtkRequisition max_requisition; + gint max_rows; + guint i; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + g_return_if_fail (NULL != item_size); + + max_requisition.width = 0; + max_requisition.height = 0; + max_rows = 0; + + /* iterate over all groups and calculate the max item_size and max row request */ + for (i = 0; i < palette->priv->groups_length; ++i) + { + GtkRequisition requisition; + gint rows; + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + + if (!group->widget) + continue; + + _gtk_tool_item_group_item_size_request (group->widget, &requisition, homogeneous_only, &rows); + + max_requisition.width = MAX (max_requisition.width, requisition.width); + max_requisition.height = MAX (max_requisition.height, requisition.height); + max_rows = MAX (max_rows, rows); + } + + *item_size = max_requisition; + if (requested_rows) + *requested_rows = max_rows; +} + +static void +gtk_tool_palette_item_drag_data_get (GtkWidget *widget, + GdkDragContext *context G_GNUC_UNUSED, + GtkSelectionData *selection, + guint info G_GNUC_UNUSED, + guint time G_GNUC_UNUSED, + gpointer data) +{ + GtkToolPaletteDragData drag_data = { GTK_TOOL_PALETTE (data), NULL }; + + if (selection->target == dnd_target_atom_item) + drag_data.item = gtk_widget_get_ancestor (widget, GTK_TYPE_TOOL_ITEM); + + if (drag_data.item) + gtk_selection_data_set (selection, selection->target, 8, + (guchar*) &drag_data, sizeof (drag_data)); +} + +static void +gtk_tool_palette_child_drag_data_get (GtkWidget *widget, + GdkDragContext *context G_GNUC_UNUSED, + GtkSelectionData *selection, + guint info G_GNUC_UNUSED, + guint time G_GNUC_UNUSED, + gpointer data) +{ + GtkToolPaletteDragData drag_data = { GTK_TOOL_PALETTE (data), NULL }; + + if (selection->target == dnd_target_atom_group) + drag_data.item = gtk_widget_get_ancestor (widget, GTK_TYPE_TOOL_ITEM_GROUP); + + if (drag_data.item) + gtk_selection_data_set (selection, selection->target, 8, + (guchar*) &drag_data, sizeof (drag_data)); +} + +void +_gtk_tool_palette_child_set_drag_source (GtkWidget *child, + gpointer data) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (data); + + /* Check drag_source, + * to work properly when called from gtk_tool_item_group_insert(). + */ + if (!palette->priv->drag_source) + return; + + if (GTK_IS_TOOL_ITEM (child) && + (palette->priv->drag_source & GTK_TOOL_PALETTE_DRAG_ITEMS)) + { + /* Connect to child instead of the item itself, + * to work arround bug 510377. + */ + if (GTK_IS_TOOL_BUTTON (child)) + child = gtk_bin_get_child (GTK_BIN (child)); + + if (!child) + return; + + gtk_drag_source_set (child, GDK_BUTTON1_MASK | GDK_BUTTON3_MASK, + &dnd_targets[0], 1, GDK_ACTION_COPY | GDK_ACTION_MOVE); + + g_signal_connect (child, "drag-data-get", + G_CALLBACK (gtk_tool_palette_item_drag_data_get), + palette); + } + else if (GTK_IS_BUTTON (child) && + (palette->priv->drag_source & GTK_TOOL_PALETTE_DRAG_GROUPS)) + { + gtk_drag_source_set (child, GDK_BUTTON1_MASK | GDK_BUTTON3_MASK, + &dnd_targets[1], 1, GDK_ACTION_COPY | GDK_ACTION_MOVE); + + g_signal_connect (child, "drag-data-get", + G_CALLBACK (gtk_tool_palette_child_drag_data_get), + palette); + } +} + +/** + * gtk_tool_palette_get_drag_target_item: + * + * Get the target entry for a dragged #GtkToolItem. + * + * Returns: the #GtkTargetEntry for a dragged item. + * + * Since: 2.18 + */ +G_CONST_RETURN GtkTargetEntry* +gtk_tool_palette_get_drag_target_item (void) +{ + return &dnd_targets[0]; +} + +/** + * gtk_tool_palette_get_drag_target_group: + * + * Get the target entry for a dragged #GtkToolItemGroup. + * + * Returns: the #GtkTargetEntry for a dragged group. + * + * Since: 2.18 + */ +G_CONST_RETURN GtkTargetEntry* +gtk_tool_palette_get_drag_target_group (void) +{ + return &dnd_targets[1]; +} + +void +_gtk_tool_palette_set_expanding_child (GtkToolPalette *palette, + GtkWidget *widget) +{ + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + palette->priv->expanding_child = widget; +} + +GtkAdjustment* +gtk_tool_palette_get_hadjustment (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), NULL); + return palette->priv->hadjustment; +} + +GtkAdjustment* +gtk_tool_palette_get_vadjustment (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), NULL); + return palette->priv->vadjustment; +} + +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + +GtkSizeGroup * +_gtk_tool_palette_get_size_group (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), NULL); + + return palette->priv->text_size_group; +} + +#endif +/* 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Mathias Hasselmann + */ + +#include "gtktoolpaletteprivate.h" +#include "gtkmarshalers.h" + +#include +#include + +#define DEFAULT_ICON_SIZE GTK_ICON_SIZE_SMALL_TOOLBAR +#define DEFAULT_ORIENTATION GTK_ORIENTATION_VERTICAL +#define DEFAULT_TOOLBAR_STYLE GTK_TOOLBAR_ICONS + +#define DEFAULT_CHILD_EXCLUSIVE FALSE +#define DEFAULT_CHILD_EXPAND FALSE + +#define P_(msgid) (msgid) + +/** + * SECTION:GtkToolPalette + * @short_description: A tool palette with categories + * @include: gtktoolpalette.h + * + * An #GtkToolPalette allows it to add #GtkToolItems to a palette like container + * with different categories and drag and drop support. + * + * An #GtkToolPalette is created with a call to gtk_tool_palette_new(). + * + * #GtkToolItems cannot be added directly to an #GtkToolPalette, instead they + * are added to an #GtkToolItemGroup which can than be added to an #GtkToolPalette. To add + * an #GtkToolItemGroup to an #GtkToolPalette use gtk_container_add(). + * + * |[ + * GtkWidget *palette, *group; + * GtkToolItem *item; + * + * palette = gtk_tool_palette_new (); + * group = gtk_tool_item_group_new (_("Test Category")); + * gtk_container_add (GTK_CONTAINER (palette), group); + * + * item = gtk_tool_button_new_from_stock (GTK_STOCK_OK); + * gtk_tool_item_group_insert (GTK_TOOL_ITEM_GROUP (group), item, -1); + * ]| + * + * The easiest way to use drag and drop with GtkToolPalette is to call gtk_tool_palette_add_drag_dest() + * with the desired drag source @palette and the desired drag target @widget. Than gtk_tool_palette_get_drag_item() + * can be used to get the dragged item in the #GtkWidget::drag-data-received signal handler of the drag target. + * + * |[ + * static void + * passive_canvas_drag_data_received (GtkWidget *widget, + * GdkDragContext *context, + * gint x, + * gint y, + * GtkSelectionData *selection, + * guint info, + * guint time, + * gpointer data) + * { + * GtkWidget *palette; + * GtkWidget *item; + * + * /* Get the dragged item */ + * palette = gtk_widget_get_ancestor (gtk_drag_get_source_widget (context), GTK_TYPE_TOOL_PALETTE); + * if (palette != NULL) + * item = gtk_tool_palette_get_drag_item (GTK_TOOL_PALETTE (palette), selection); + * + * /* Do something with item */ + * } + * + * GtkWidget *target, palette; + * + * palette = gtk_tool_palette_new (); + * target = gtk_drawing_area_new (); + * + * g_signal_connect (G_OBJECT (target), "drag-data-received", + * G_CALLBACK (passive_canvas_drag_data_received), NULL); + * gtk_tool_palette_add_drag_dest (GTK_TOOL_PALETTE (palette), target, + * GTK_DEST_DEFAULT_ALL, + * GTK_TOOL_PALETTE_DRAG_ITEMS, + * GDK_ACTION_COPY); + * ]| + * + * Since: 2.18 + */ + +typedef struct _GtkToolItemGroupInfo GtkToolItemGroupInfo; +typedef struct _GtkToolPaletteDragData GtkToolPaletteDragData; + +enum +{ + PROP_NONE, + PROP_ICON_SIZE, + PROP_ORIENTATION, + PROP_TOOLBAR_STYLE, +}; + +enum +{ + CHILD_PROP_NONE, + CHILD_PROP_EXCLUSIVE, + CHILD_PROP_EXPAND, +}; + +struct _GtkToolItemGroupInfo +{ + GtkToolItemGroup *widget; + + guint notify_collapsed; + guint exclusive : 1; + guint expand : 1; +}; + +struct _GtkToolPalettePrivate +{ + GtkToolItemGroupInfo *groups; + gsize groups_size; + gsize groups_length; + + GtkAdjustment *hadjustment; + GtkAdjustment *vadjustment; + + GtkIconSize icon_size; + GtkOrientation orientation; + GtkToolbarStyle style; + + GtkWidget *expanding_child; + +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + GtkSizeGroup *text_size_group; +#endif + + guint sparse_groups : 1; + guint drag_source : 2; +}; + +struct _GtkToolPaletteDragData +{ + GtkToolPalette *palette; + GtkWidget *item; +}; + +static GdkAtom dnd_target_atom_item = GDK_NONE; +static GdkAtom dnd_target_atom_group = GDK_NONE; + +static const GtkTargetEntry dnd_targets[] = +{ + { "application/x-GTK-tool-palette-item", GTK_TARGET_SAME_APP, 0 }, + { "application/x-GTK-tool-palette-group", GTK_TARGET_SAME_APP, 0 }, +}; + +G_DEFINE_TYPE (GtkToolPalette, + gtk_tool_palette, + GTK_TYPE_CONTAINER); + +static void +gtk_tool_palette_init (GtkToolPalette *palette) +{ + palette->priv = G_TYPE_INSTANCE_GET_PRIVATE (palette, + GTK_TYPE_TOOL_PALETTE, + GtkToolPalettePrivate); + + palette->priv->groups_size = 4; + palette->priv->groups_length = 0; + palette->priv->groups = g_new0 (GtkToolItemGroupInfo, palette->priv->groups_size); + + palette->priv->icon_size = DEFAULT_ICON_SIZE; + palette->priv->orientation = DEFAULT_ORIENTATION; + palette->priv->style = DEFAULT_TOOLBAR_STYLE; + +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + palette->priv->text_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); +#endif +} + +static void +gtk_tool_palette_reconfigured (GtkToolPalette *palette) +{ + guint i; + + for (i = 0; i < palette->priv->groups_length; ++i) + { + if (palette->priv->groups[i].widget) + _gtk_tool_item_group_palette_reconfigured (palette->priv->groups[i].widget); + } + + gtk_widget_queue_resize_no_redraw (GTK_WIDGET (palette)); +} + +static void +gtk_tool_palette_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (object); + + switch (prop_id) + { + case PROP_ICON_SIZE: + if ((guint) g_value_get_enum (value) != palette->priv->icon_size) + { + palette->priv->icon_size = g_value_get_enum (value); + gtk_tool_palette_reconfigured (palette); + } + break; + + case PROP_ORIENTATION: + if ((guint) g_value_get_enum (value) != palette->priv->orientation) + { + palette->priv->orientation = g_value_get_enum (value); + gtk_tool_palette_reconfigured (palette); + } + break; + + case PROP_TOOLBAR_STYLE: + if ((guint) g_value_get_enum (value) != palette->priv->style) + { + palette->priv->style = g_value_get_enum (value); + gtk_tool_palette_reconfigured (palette); + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_tool_palette_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (object); + + switch (prop_id) + { + case PROP_ICON_SIZE: + g_value_set_enum (value, gtk_tool_palette_get_icon_size (palette)); + break; + + case PROP_ORIENTATION: + g_value_set_enum (value, gtk_tool_palette_get_orientation (palette)); + break; + + case PROP_TOOLBAR_STYLE: + g_value_set_enum (value, gtk_tool_palette_get_style (palette)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_tool_palette_dispose (GObject *object) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (object); + guint i; + + if (palette->priv->hadjustment) + { + g_object_unref (palette->priv->hadjustment); + palette->priv->hadjustment = NULL; + } + + if (palette->priv->vadjustment) + { + g_object_unref (palette->priv->vadjustment); + palette->priv->vadjustment = NULL; + } + + for (i = 0; i < palette->priv->groups_size; ++i) + { + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + + if (group->notify_collapsed) + { + g_signal_handler_disconnect (group->widget, group->notify_collapsed); + group->notify_collapsed = 0; + } + } + +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + if (palette->priv->text_size_group) + { + g_object_unref (palette->priv->text_size_group); + palette->priv->text_size_group = NULL; + } +#endif + + G_OBJECT_CLASS (gtk_tool_palette_parent_class)->dispose (object); +} + +static void +gtk_tool_palette_finalize (GObject *object) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (object); + + if (palette->priv->groups) + { + palette->priv->groups_length = 0; + g_free (palette->priv->groups); + palette->priv->groups = NULL; + } + + G_OBJECT_CLASS (gtk_tool_palette_parent_class)->finalize (object); +} + +static void +gtk_tool_palette_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + GtkToolPalette *palette = GTK_TOOL_PALETTE (widget); + GtkRequisition child_requisition; + guint i; + + requisition->width = 0; + requisition->height = 0; + + for (i = 0; i < palette->priv->groups_length; ++i) + { + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + + if (!group->widget) + continue; + + gtk_widget_size_request (GTK_WIDGET (group->widget), &child_requisition); + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + { + requisition->width = MAX (requisition->width, child_requisition.width); + requisition->height += child_requisition.height; + } + else + { + requisition->width += child_requisition.width; + requisition->height = MAX (requisition->height, child_requisition.height); + } + } + + requisition->width += border_width * 2; + requisition->height += border_width * 2; +} + +static void +gtk_tool_palette_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + GtkToolPalette *palette = GTK_TOOL_PALETTE (widget); + GtkAdjustment *adjustment = NULL; + GtkAllocation child_allocation; + + gint n_expand_groups = 0; + gint remaining_space = 0; + gint expand_space = 0; + + gint page_start, page_size = 0; + gint offset = 0; + guint i; + + gint min_offset = -1, max_offset = -1; + + gint x; + + gint *group_sizes = g_newa(gint, palette->priv->groups_length); + + GtkTextDirection direction = gtk_widget_get_direction (widget); + + GTK_WIDGET_CLASS (gtk_tool_palette_parent_class)->size_allocate (widget, allocation); + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + { + adjustment = palette->priv->vadjustment; + page_size = allocation->height; + } + else + { + adjustment = palette->priv->hadjustment; + page_size = allocation->width; + } + + if (adjustment) + offset = gtk_adjustment_get_value (adjustment); + if (GTK_ORIENTATION_HORIZONTAL == palette->priv->orientation && + GTK_TEXT_DIR_RTL == direction) + offset = -offset; + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + child_allocation.width = allocation->width - border_width * 2; + else + child_allocation.height = allocation->height - border_width * 2; + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + remaining_space = allocation->height; + else + remaining_space = allocation->width; + + /* figure out the required size of all groups to be able to distribute the + * remaining space on allocation */ + for (i = 0; i < palette->priv->groups_length; ++i) + { + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + gint size; + + if (!group->widget) + continue; + + widget = GTK_WIDGET (group->widget); + + if (gtk_tool_item_group_get_n_items (group->widget)) + { + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + size = _gtk_tool_item_group_get_height_for_width (group->widget, child_allocation.width); + else + size = _gtk_tool_item_group_get_width_for_height (group->widget, child_allocation.height); + + if (group->expand && !gtk_tool_item_group_get_collapsed (group->widget)) + n_expand_groups += 1; + } + else + size = 0; + + remaining_space -= size; + group_sizes[i] = size; + + /* if the widget is currently expanding an offset which allows to display as much of the + * widget as possible is calculated */ + if (widget == palette->priv->expanding_child) + { + gint limit = + GTK_ORIENTATION_VERTICAL == palette->priv->orientation ? + child_allocation.width : child_allocation.height; + + gint real_size; + guint j; + + min_offset = 0; + + for (j = 0; j < i; ++j) + min_offset += group_sizes[j]; + + max_offset = min_offset + group_sizes[i]; + + real_size = _gtk_tool_item_group_get_size_for_limit + (GTK_TOOL_ITEM_GROUP (widget), limit, + GTK_ORIENTATION_VERTICAL == palette->priv->orientation, + FALSE); + + if (size == real_size) + palette->priv->expanding_child = NULL; + } + } + + if (n_expand_groups > 0) + { + remaining_space = MAX (0, remaining_space); + expand_space = remaining_space / n_expand_groups; + } + + if (max_offset != -1) + { + gint limit = + GTK_ORIENTATION_VERTICAL == palette->priv->orientation ? + allocation->height : allocation->width; + + offset = MIN (MAX (offset, max_offset - limit), min_offset); + } + + if (remaining_space > 0) + offset = 0; + + x = border_width; + child_allocation.y = border_width; + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + child_allocation.y -= offset; + else + x -= offset; + + /* allocate all groups at the calculated positions */ + for (i = 0; i < palette->priv->groups_length; ++i) + { + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + GtkWidget *widget; + + if (!group->widget) + continue; + + widget = GTK_WIDGET (group->widget); + + if (gtk_tool_item_group_get_n_items (group->widget)) + { + gint size = group_sizes[i]; + + if (group->expand && !gtk_tool_item_group_get_collapsed (group->widget)) + { + size += MIN (expand_space, remaining_space); + remaining_space -= expand_space; + } + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + child_allocation.height = size; + else + child_allocation.width = size; + + if (GTK_ORIENTATION_HORIZONTAL == palette->priv->orientation && + GTK_TEXT_DIR_RTL == direction) + child_allocation.x = allocation->width - x - child_allocation.width; + else + child_allocation.x = x; + + gtk_widget_size_allocate (widget, &child_allocation); + gtk_widget_show (widget); + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + child_allocation.y += child_allocation.height; + else + x += child_allocation.width; + } + else + gtk_widget_hide (widget); + } + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation) + { + child_allocation.y += border_width; + child_allocation.y += offset; + + page_start = child_allocation.y; + } + else + { + x += border_width; + x += offset; + + page_start = x; + } + + /* update the scrollbar to match the displayed adjustment */ + if (adjustment) + { + gdouble value; + + adjustment->page_increment = page_size * 0.9; + adjustment->step_increment = page_size * 0.1; + adjustment->page_size = page_size; + + if (GTK_ORIENTATION_VERTICAL == palette->priv->orientation || + GTK_TEXT_DIR_LTR == direction) + { + adjustment->lower = 0; + adjustment->upper = MAX (0, page_start); + + value = MIN (offset, adjustment->upper - adjustment->page_size); + gtk_adjustment_clamp_page (adjustment, value, offset + page_size); + } + else + { + adjustment->lower = page_size - MAX (0, page_start); + adjustment->upper = page_size; + + offset = -offset; + + value = MAX (offset, adjustment->lower); + gtk_adjustment_clamp_page (adjustment, offset, value + page_size); + } + + gtk_adjustment_changed (adjustment); + } +} + +static gboolean +gtk_tool_palette_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (widget); + GdkDisplay *display; + cairo_t *cr; + guint i; + + display = gdk_drawable_get_display (widget->window); + + if (!gdk_display_supports_composite (display)) + return FALSE; + + cr = gdk_cairo_create (widget->window); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + cairo_push_group (cr); + + for (i = 0; i < palette->priv->groups_length; ++i) + if (palette->priv->groups[i].widget) + _gtk_tool_item_group_paint (palette->priv->groups[i].widget, cr); + + cairo_pop_group_to_source (cr); + + cairo_paint (cr); + cairo_destroy (cr); + + return FALSE; +} + +static void +gtk_tool_palette_realize (GtkWidget *widget) +{ + const gint border_width = GTK_CONTAINER (widget)->border_width; + gint attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + GdkWindowAttr attributes; + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = widget->allocation.x + border_width; + attributes.y = widget->allocation.y + border_width; + attributes.width = widget->allocation.width - border_width * 2; + attributes.height = widget->allocation.height - border_width * 2; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK + | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + | GDK_BUTTON_MOTION_MASK; + + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + + gdk_window_set_user_data (widget->window, widget); + widget->style = gtk_style_attach (widget->style, widget->window); + gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + + gtk_container_forall (GTK_CONTAINER (widget), + (GtkCallback) gtk_widget_set_parent_window, + widget->window); + + gtk_widget_queue_resize_no_redraw (widget); +} + +static void +gtk_tool_palette_adjustment_value_changed (GtkAdjustment *adjustment G_GNUC_UNUSED, + gpointer data) +{ + GtkWidget *widget = GTK_WIDGET (data); + gtk_tool_palette_size_allocate (widget, &widget->allocation); +} + +static void +gtk_tool_palette_set_scroll_adjustments (GtkWidget *widget, + GtkAdjustment *hadjustment, + GtkAdjustment *vadjustment) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (widget); + + if (palette->priv->hadjustment) + g_object_unref (palette->priv->hadjustment); + if (palette->priv->vadjustment) + g_object_unref (palette->priv->vadjustment); + + if (hadjustment) + g_object_ref_sink (hadjustment); + if (vadjustment) + g_object_ref_sink (vadjustment); + + palette->priv->hadjustment = hadjustment; + palette->priv->vadjustment = vadjustment; + + if (palette->priv->hadjustment) + g_signal_connect (palette->priv->hadjustment, "value-changed", + G_CALLBACK (gtk_tool_palette_adjustment_value_changed), + palette); + if (palette->priv->vadjustment) + g_signal_connect (palette->priv->vadjustment, "value-changed", + G_CALLBACK (gtk_tool_palette_adjustment_value_changed), + palette); +} + +static void +gtk_tool_palette_repack (GtkToolPalette *palette) +{ + guint si, di; + + for (si = di = 0; di < palette->priv->groups_length; ++si) + { + if (palette->priv->groups[si].widget) + { + palette->priv->groups[di] = palette->priv->groups[si]; + ++di; + } + else + --palette->priv->groups_length; + } + + palette->priv->sparse_groups = FALSE; +} + +static void +gtk_tool_palette_add (GtkContainer *container, + GtkWidget *child) +{ + GtkToolPalette *palette; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (container)); + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (child)); + + palette = GTK_TOOL_PALETTE (container); + + if (palette->priv->groups_length == palette->priv->groups_size) + gtk_tool_palette_repack (palette); + + if (palette->priv->groups_length == palette->priv->groups_size) + { + gsize old_size = palette->priv->groups_size; + gsize new_size = old_size * 2; + + palette->priv->groups = g_renew (GtkToolItemGroupInfo, + palette->priv->groups, + new_size); + + memset (palette->priv->groups + old_size, 0, + sizeof (GtkToolItemGroupInfo) * old_size); + + palette->priv->groups_size = new_size; + } + + palette->priv->groups[palette->priv->groups_length].widget = g_object_ref_sink (child); + palette->priv->groups_length += 1; + + gtk_widget_set_parent (child, GTK_WIDGET (palette)); +} + +static void +gtk_tool_palette_remove (GtkContainer *container, + GtkWidget *child) +{ + GtkToolPalette *palette; + guint i; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (container)); + palette = GTK_TOOL_PALETTE (container); + + for (i = 0; i < palette->priv->groups_length; ++i) + if ((GtkWidget*) palette->priv->groups[i].widget == child) + { + g_object_unref (child); + gtk_widget_unparent (child); + + memset (&palette->priv->groups[i], 0, sizeof (GtkToolItemGroupInfo)); + palette->priv->sparse_groups = TRUE; + } +} + +static void +gtk_tool_palette_forall (GtkContainer *container, + gboolean internals G_GNUC_UNUSED, + GtkCallback callback, + gpointer callback_data) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (container); + guint i; + + if (palette->priv->groups) + { + for (i = 0; i < palette->priv->groups_length; ++i) + if (palette->priv->groups[i].widget) + callback (GTK_WIDGET (palette->priv->groups[i].widget), + callback_data); + } +} + +static GType +gtk_tool_palette_child_type (GtkContainer *container G_GNUC_UNUSED) +{ + return GTK_TYPE_TOOL_ITEM_GROUP; +} + +static void +gtk_tool_palette_set_child_property (GtkContainer *container, + GtkWidget *child, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (container); + + switch (prop_id) + { + case CHILD_PROP_EXCLUSIVE: + gtk_tool_palette_set_exclusive (palette, child, g_value_get_boolean (value)); + break; + + case CHILD_PROP_EXPAND: + gtk_tool_palette_set_expand (palette, child, g_value_get_boolean (value)); + break; + + default: + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec); + break; + } +} + +static void +gtk_tool_palette_get_child_property (GtkContainer *container, + GtkWidget *child, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (container); + + switch (prop_id) + { + case CHILD_PROP_EXCLUSIVE: + g_value_set_boolean (value, gtk_tool_palette_get_exclusive (palette, child)); + break; + + case CHILD_PROP_EXPAND: + g_value_set_boolean (value, gtk_tool_palette_get_expand (palette, child)); + break; + + default: + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec); + break; + } +} + +static void +gtk_tool_palette_class_init (GtkToolPaletteClass *cls) +{ + GObjectClass *oclass = G_OBJECT_CLASS (cls); + GtkWidgetClass *wclass = GTK_WIDGET_CLASS (cls); + GtkContainerClass *cclass = GTK_CONTAINER_CLASS (cls); + + oclass->set_property = gtk_tool_palette_set_property; + oclass->get_property = gtk_tool_palette_get_property; + oclass->dispose = gtk_tool_palette_dispose; + oclass->finalize = gtk_tool_palette_finalize; + + wclass->size_request = gtk_tool_palette_size_request; + wclass->size_allocate = gtk_tool_palette_size_allocate; + wclass->expose_event = gtk_tool_palette_expose_event; + wclass->realize = gtk_tool_palette_realize; + + cclass->add = gtk_tool_palette_add; + cclass->remove = gtk_tool_palette_remove; + cclass->forall = gtk_tool_palette_forall; + cclass->child_type = gtk_tool_palette_child_type; + cclass->set_child_property = gtk_tool_palette_set_child_property; + cclass->get_child_property = gtk_tool_palette_get_child_property; + + cls->set_scroll_adjustments = gtk_tool_palette_set_scroll_adjustments; + /** + * GtkToolPalette::set-scroll-adjustments: + * @widget: the GtkToolPalette that received the signal + * @hadjustment: The horizontal adjustment + * @vadjustment: The vertical adjustment + * + * The ::set-scroll-adjustments when FIXME + * + * Since: 2.18 + */ + wclass->set_scroll_adjustments_signal = + g_signal_new ("set-scroll-adjustments", + G_TYPE_FROM_CLASS (oclass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkToolPaletteClass, set_scroll_adjustments), + NULL, NULL, + _gtk_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, 2, + GTK_TYPE_ADJUSTMENT, + GTK_TYPE_ADJUSTMENT); + + g_object_class_install_property (oclass, PROP_ICON_SIZE, + g_param_spec_enum ("icon-size", + P_("Icon Size"), + P_("The size of palette icons"), + GTK_TYPE_ICON_SIZE, + DEFAULT_ICON_SIZE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (oclass, PROP_ORIENTATION, + g_param_spec_enum ("orientation", + P_("Orientation"), + P_("Orientation of the tool palette"), + GTK_TYPE_ORIENTATION, + DEFAULT_ORIENTATION, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (oclass, PROP_TOOLBAR_STYLE, + g_param_spec_enum ("toolbar-style", + P_("Toolbar Style"), + P_("Style of items in the tool palette"), + GTK_TYPE_TOOLBAR_STYLE, + DEFAULT_TOOLBAR_STYLE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + gtk_container_class_install_child_property (cclass, CHILD_PROP_EXCLUSIVE, + g_param_spec_boolean ("exclusive", + P_("Exclusive"), + P_("Whether the item group should be the only expanded at a given time"), + DEFAULT_CHILD_EXCLUSIVE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + gtk_container_class_install_child_property (cclass, CHILD_PROP_EXPAND, + g_param_spec_boolean ("expand", + P_("Expand"), + P_("Whether the item group should receive extra space when the palette grows"), + DEFAULT_CHILD_EXPAND, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_type_class_add_private (cls, sizeof (GtkToolPalettePrivate)); + + dnd_target_atom_item = gdk_atom_intern_static_string (dnd_targets[0].target); + dnd_target_atom_group = gdk_atom_intern_static_string (dnd_targets[1].target); +} + +/** + * gtk_tool_palette_new: + * + * Creates a new tool palette. + * + * Returns: a new #GtkToolPalette. + * + * Since: 2.18 + */ +GtkWidget* +gtk_tool_palette_new (void) +{ + return g_object_new (GTK_TYPE_TOOL_PALETTE, NULL); +} + +/** + * gtk_tool_palette_set_icon_size: + * @palette: an #GtkToolPalette. + * @icon_size: the #GtkIconSize that icons in the tool palette shall have. + * + * Sets the size of icons in the tool palette. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_icon_size (GtkToolPalette *palette, + GtkIconSize icon_size) +{ + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + + if (icon_size != palette->priv->icon_size) + g_object_set (palette, "icon-size", icon_size, NULL); +} + +/** + * gtk_tool_palette_set_orientation: + * @palette: an #GtkToolPalette. + * @orientation: the #GtkOrientation that the tool palette shall have. + * + * Sets the orientation (horizontal or vertical) of the tool palette. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_orientation (GtkToolPalette *palette, + GtkOrientation orientation) +{ + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + + if (orientation != palette->priv->orientation) + g_object_set (palette, "orientation", orientation, NULL); +} + +/** + * gtk_tool_palette_set_style: + * @palette: an #GtkToolPalette. + * @style: the #GtkToolbarStyle that items in the tool palette shall have. + * + * Sets the style (text, icons or both) of items in the tool palette. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_style (GtkToolPalette *palette, + GtkToolbarStyle style) +{ + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + + if (style != palette->priv->style) + g_object_set (palette, "toolbar-style", style, NULL); +} + +/** + * gtk_tool_palette_get_icon_size: + * @palette: an #GtkToolPalette. + * + * Gets the size of icons in the tool palette. See gtk_tool_palette_set_icon_size(). + * + * Returns: the #GtkIconSize of icons in the tool palette. + * + * Since: 2.18 + */ +GtkIconSize +gtk_tool_palette_get_icon_size (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), DEFAULT_ICON_SIZE); + return palette->priv->icon_size; +} + +/** + * gtk_tool_palette_get_orientation: + * @palette: an #GtkToolPalette. + * + * Gets the orientation (horizontal or vertical) of the tool palette. See gtk_tool_palette_set_orientation(). + * + * Returns the #GtkOrientation of the tool palette. + */ +GtkOrientation +gtk_tool_palette_get_orientation (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), DEFAULT_ORIENTATION); + return palette->priv->orientation; +} + +/** + * gtk_tool_palette_get_style: + * @palette: an #GtkToolPalette. + * + * Gets the style (icons, text or both) of items in the tool palette. + * + * Returns: the #GtkToolbarStyle of items in the tool palette. + * + * Since: 2.18 + */ +GtkToolbarStyle +gtk_tool_palette_get_style (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), DEFAULT_TOOLBAR_STYLE); + return palette->priv->style; +} + +/** + * gtk_tool_palette_set_group_position: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup which is a child of palette. + * @position: a new index for group. + * + * Sets the position of the group as an index of the tool palette. + * If position is 0 the group will become the first child, if position is + * -1 it will become the last child. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_group_position (GtkToolPalette *palette, + GtkWidget *group, + gint position) +{ + GtkToolItemGroupInfo group_info; + gint old_position; + gpointer src, dst; + gsize len; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + + gtk_tool_palette_repack (palette); + + g_return_if_fail (position >= -1); + + if (-1 == position) + position = palette->priv->groups_length - 1; + + g_return_if_fail ((guint) position < palette->priv->groups_length); + + if (GTK_TOOL_ITEM_GROUP (group) == palette->priv->groups[position].widget) + return; + + old_position = gtk_tool_palette_get_group_position (palette, group); + g_return_if_fail (old_position >= 0); + + group_info = palette->priv->groups[old_position]; + + if (position < old_position) + { + dst = palette->priv->groups + position + 1; + src = palette->priv->groups + position; + len = old_position - position; + } + else + { + dst = palette->priv->groups + old_position; + src = palette->priv->groups + old_position + 1; + len = position - old_position; + } + + memmove (dst, src, len * sizeof (*palette->priv->groups)); + palette->priv->groups[position] = group_info; + + gtk_widget_queue_resize (GTK_WIDGET (palette)); +} + +static void +gtk_tool_palette_group_notify_collapsed (GtkToolItemGroup *group, + GParamSpec *pspec G_GNUC_UNUSED, + gpointer data) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (data); + guint i; + + if (gtk_tool_item_group_get_collapsed (group)) + return; + + for (i = 0; i < palette->priv->groups_size; ++i) + { + GtkToolItemGroup *current_group = palette->priv->groups[i].widget; + + if (current_group && current_group != group) + gtk_tool_item_group_set_collapsed (palette->priv->groups[i].widget, TRUE); + } +} + +/** + * gtk_tool_palette_set_exclusive: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup which is a child of palette. + * @exclusive: whether the group should be exclusive or not. + * + * Sets whether the group should be exclusive or not. If an exclusive group is expanded + * all other groups are collapsed. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_exclusive (GtkToolPalette *palette, + GtkWidget *group, + gboolean exclusive) +{ + GtkToolItemGroupInfo *group_info; + gint position; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + + position = gtk_tool_palette_get_group_position (palette, group); + g_return_if_fail (position >= 0); + + group_info = &palette->priv->groups[position]; + + if (exclusive == group_info->exclusive) + return; + + group_info->exclusive = exclusive; + + if (group_info->exclusive != (0 != group_info->notify_collapsed)) + { + if (group_info->exclusive) + { + group_info->notify_collapsed = + g_signal_connect (group, "notify::collapsed", + G_CALLBACK (gtk_tool_palette_group_notify_collapsed), + palette); + } + else + { + g_signal_handler_disconnect (group, group_info->notify_collapsed); + group_info->notify_collapsed = 0; + } + } + + gtk_tool_palette_group_notify_collapsed (group_info->widget, NULL, palette); + gtk_widget_child_notify (group, "exclusive"); +} + +/** + * gtk_tool_palette_set_expand: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup which is a child of palette. + * @expand: whether the group should be given extra space. + * + * Sets whether the group should be given extra space. + * + * Since: 2.18 + */ +void +gtk_tool_palette_set_expand (GtkToolPalette *palette, + GtkWidget *group, + gboolean expand) +{ + GtkToolItemGroupInfo *group_info; + gint position; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + g_return_if_fail (GTK_IS_TOOL_ITEM_GROUP (group)); + + position = gtk_tool_palette_get_group_position (palette, group); + g_return_if_fail (position >= 0); + + group_info = &palette->priv->groups[position]; + + if (expand != group_info->expand) + { + group_info->expand = expand; + gtk_widget_queue_resize (GTK_WIDGET (palette)); + gtk_widget_child_notify (group, "expand"); + } +} + +/** + * gtk_tool_palette_get_group_position: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup. + * + * Gets the position of @group in @palette as index. see gtk_tool_palette_set_group_position(). + * + * Returns: the index of group or -1 if @group is not a child of @palette. + * + * Since: 2.18 + */ +gint +gtk_tool_palette_get_group_position (GtkToolPalette *palette, + GtkWidget *group) +{ + guint i; + + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), -1); + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), -1); + + for (i = 0; i < palette->priv->groups_length; ++i) + if ((gpointer) group == palette->priv->groups[i].widget) + return i; + + return -1; +} + +/** + * gtk_tool_palette_get_exclusive: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup which is a child of palette. + * + * Gets whether group is exclusive or not. See gtk_tool_palette_set_exclusive(). + * + * Returns: %TRUE if group is exclusive. + * + * Since: 2.18 + */ +gboolean +gtk_tool_palette_get_exclusive (GtkToolPalette *palette, + GtkWidget *group) +{ + gint position; + + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), DEFAULT_CHILD_EXCLUSIVE); + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), DEFAULT_CHILD_EXCLUSIVE); + + position = gtk_tool_palette_get_group_position (palette, group); + g_return_val_if_fail (position >= 0, DEFAULT_CHILD_EXCLUSIVE); + + return palette->priv->groups[position].exclusive; +} + +/** + * gtk_tool_palette_get_expand: + * @palette: an #GtkToolPalette. + * @group: an #GtkToolItemGroup which is a child of palette. + * + * Gets whether group should be given extra space. See gtk_tool_palette_set_expand(). + * + * Returns: %TRUE if group should be given extra space, %FALSE otherwise. + * + * Since: 2.18 + */ +gboolean +gtk_tool_palette_get_expand (GtkToolPalette *palette, + GtkWidget *group) +{ + gint position; + + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), DEFAULT_CHILD_EXPAND); + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (group), DEFAULT_CHILD_EXPAND); + + position = gtk_tool_palette_get_group_position (palette, group); + g_return_val_if_fail (position >= 0, DEFAULT_CHILD_EXPAND); + + return palette->priv->groups[position].expand; +} + +/** + * gtk_tool_palette_get_drop_item: + * @palette: an #GtkToolPalette. + * @x: the x position. + * @y: the y position. + * + * Gets the item at position (x, y). See gtk_tool_palette_get_drop_group(). + * + * Returns: the #GtkToolItem at position or %NULL if there is no such item. + * + * Since: 2.18 + */ +GtkToolItem* +gtk_tool_palette_get_drop_item (GtkToolPalette *palette, + gint x, + gint y) +{ + GtkWidget *group = gtk_tool_palette_get_drop_group (palette, x, y); + + if (group) + return gtk_tool_item_group_get_drop_item (GTK_TOOL_ITEM_GROUP (group), + x - group->allocation.x, + y - group->allocation.y); + + return NULL; +} + +/** + * gtk_tool_palette_get_drop_group: + * @palette: an #GtkToolPalette. + * @x: the x position. + * @y: the y position. + * + * Gets the group at position (x, y). + * + * Returns: the #GtkToolItemGroup at position or %NULL if there is no such group. + * + * Since: 2.18 + */ +GtkWidget* +gtk_tool_palette_get_drop_group (GtkToolPalette *palette, + gint x, + gint y) +{ + GtkAllocation *allocation; + guint i; + + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), NULL); + + allocation = >K_WIDGET (palette)->allocation; + + g_return_val_if_fail (x >= 0 && x < allocation->width, NULL); + g_return_val_if_fail (y >= 0 && y < allocation->height, NULL); + + for (i = 0; i < palette->priv->groups_length; ++i) + { + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + GtkWidget *widget; + gint x0, y0; + + if (!group->widget) + continue; + + widget = GTK_WIDGET (group->widget); + + x0 = x - widget->allocation.x; + y0 = y - widget->allocation.y; + + if (x0 >= 0 && x0 < widget->allocation.width && + y0 >= 0 && y0 < widget->allocation.height) + return widget; + } + + return NULL; +} + +/** + * gtk_tool_palette_get_drag_item: + * @palette: an #GtkToolPalette. + * @selection: a #GtkSelectionData. + * + * Get the dragged item from the selection. This could be a #GtkToolItem or + * an #GtkToolItemGroup. + * + * Returns: the dragged item in selection. + * + * Since: 2.18 + */ +GtkWidget* +gtk_tool_palette_get_drag_item (GtkToolPalette *palette, + const GtkSelectionData *selection) +{ + GtkToolPaletteDragData *data; + + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), NULL); + g_return_val_if_fail (NULL != selection, NULL); + + g_return_val_if_fail (selection->format == 8, NULL); + g_return_val_if_fail (selection->length == sizeof (GtkToolPaletteDragData), NULL); + g_return_val_if_fail (selection->target == dnd_target_atom_item || + selection->target == dnd_target_atom_group, + NULL); + + data = (GtkToolPaletteDragData*) selection->data; + + g_return_val_if_fail (data->palette == palette, NULL); + + if (dnd_target_atom_item == selection->target) + g_return_val_if_fail (GTK_IS_TOOL_ITEM (data->item), NULL); + else if (dnd_target_atom_group == selection->target) + g_return_val_if_fail (GTK_IS_TOOL_ITEM_GROUP (data->item), NULL); + + return data->item; +} + +/** + * gtk_tool_palette_set_drag_source: + * @palette: an #GtkToolPalette. + * @targets: the #GtkToolPaletteDragTargets which the widget should support. + * + * Sets the tool palette as a drag source. Enables all groups and items in + * the tool palette as drag sources on button 1 and button 3 press with copy + * and move actions. + * + * See gtk_drag_source_set(). + * + * Since: 2.18 + * + */ +void +gtk_tool_palette_set_drag_source (GtkToolPalette *palette, + GtkToolPaletteDragTargets targets) +{ + guint i; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + + if ((palette->priv->drag_source & targets) == targets) + return; + + palette->priv->drag_source |= targets; + + for (i = 0; i < palette->priv->groups_length; ++i) + { + if (palette->priv->groups[i].widget) + gtk_container_forall (GTK_CONTAINER (palette->priv->groups[i].widget), + _gtk_tool_palette_child_set_drag_source, + palette); + } +} + +/** + * gtk_tool_palette_add_drag_dest: + * @palette: an #GtkToolPalette. + * @widget: a #GtkWidget which should be a drag destination for palette. + * @flags: the flags that specify what actions GTK+ should take for drops on that widget. + * @targets: the #GtkToolPaletteDragTargets which the widget should support. + * @actions: the #GdkDragActions which the widget should suppport. + * + * Sets the tool palette as drag source (see gtk_tool_palette_set_drag_source) and + * sets widget as a drag destination for drags from palette. With flags the actions + * (like highlighting and target checking) which should be performed by GTK+ for + * drops on widget can be specified. With targets the supported drag targets + * (groups and/or items) can be specified. With actions the supported drag actions + * (copy and move) can be specified. + * + * See gtk_drag_dest_set(). + * + * Since: 2.18 + */ +void +gtk_tool_palette_add_drag_dest (GtkToolPalette *palette, + GtkWidget *widget, + GtkDestDefaults flags, + GtkToolPaletteDragTargets targets, + GdkDragAction actions) +{ + GtkTargetEntry entries[G_N_ELEMENTS (dnd_targets)]; + gint n_entries = 0; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + gtk_tool_palette_set_drag_source (palette, + targets); + + if (targets & GTK_TOOL_PALETTE_DRAG_ITEMS) + entries[n_entries++] = dnd_targets[0]; + if (targets & GTK_TOOL_PALETTE_DRAG_GROUPS) + entries[n_entries++] = dnd_targets[1]; + + gtk_drag_dest_set (widget, flags, entries, n_entries, actions); +} + +void +_gtk_tool_palette_get_item_size (GtkToolPalette *palette, + GtkRequisition *item_size, + gboolean homogeneous_only, + gint *requested_rows) +{ + GtkRequisition max_requisition; + gint max_rows; + guint i; + + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + g_return_if_fail (NULL != item_size); + + max_requisition.width = 0; + max_requisition.height = 0; + max_rows = 0; + + /* iterate over all groups and calculate the max item_size and max row request */ + for (i = 0; i < palette->priv->groups_length; ++i) + { + GtkRequisition requisition; + gint rows; + GtkToolItemGroupInfo *group = &palette->priv->groups[i]; + + if (!group->widget) + continue; + + _gtk_tool_item_group_item_size_request (group->widget, &requisition, homogeneous_only, &rows); + + max_requisition.width = MAX (max_requisition.width, requisition.width); + max_requisition.height = MAX (max_requisition.height, requisition.height); + max_rows = MAX (max_rows, rows); + } + + *item_size = max_requisition; + if (requested_rows) + *requested_rows = max_rows; +} + +static void +gtk_tool_palette_item_drag_data_get (GtkWidget *widget, + GdkDragContext *context G_GNUC_UNUSED, + GtkSelectionData *selection, + guint info G_GNUC_UNUSED, + guint time G_GNUC_UNUSED, + gpointer data) +{ + GtkToolPaletteDragData drag_data = { GTK_TOOL_PALETTE (data), NULL }; + + if (selection->target == dnd_target_atom_item) + drag_data.item = gtk_widget_get_ancestor (widget, GTK_TYPE_TOOL_ITEM); + + if (drag_data.item) + gtk_selection_data_set (selection, selection->target, 8, + (guchar*) &drag_data, sizeof (drag_data)); +} + +static void +gtk_tool_palette_child_drag_data_get (GtkWidget *widget, + GdkDragContext *context G_GNUC_UNUSED, + GtkSelectionData *selection, + guint info G_GNUC_UNUSED, + guint time G_GNUC_UNUSED, + gpointer data) +{ + GtkToolPaletteDragData drag_data = { GTK_TOOL_PALETTE (data), NULL }; + + if (selection->target == dnd_target_atom_group) + drag_data.item = gtk_widget_get_ancestor (widget, GTK_TYPE_TOOL_ITEM_GROUP); + + if (drag_data.item) + gtk_selection_data_set (selection, selection->target, 8, + (guchar*) &drag_data, sizeof (drag_data)); +} + +void +_gtk_tool_palette_child_set_drag_source (GtkWidget *child, + gpointer data) +{ + GtkToolPalette *palette = GTK_TOOL_PALETTE (data); + + /* Check drag_source, + * to work properly when called from gtk_tool_item_group_insert(). + */ + if (!palette->priv->drag_source) + return; + + if (GTK_IS_TOOL_ITEM (child) && + (palette->priv->drag_source & GTK_TOOL_PALETTE_DRAG_ITEMS)) + { + /* Connect to child instead of the item itself, + * to work arround bug 510377. + */ + if (GTK_IS_TOOL_BUTTON (child)) + child = gtk_bin_get_child (GTK_BIN (child)); + + if (!child) + return; + + gtk_drag_source_set (child, GDK_BUTTON1_MASK | GDK_BUTTON3_MASK, + &dnd_targets[0], 1, GDK_ACTION_COPY | GDK_ACTION_MOVE); + + g_signal_connect (child, "drag-data-get", + G_CALLBACK (gtk_tool_palette_item_drag_data_get), + palette); + } + else if (GTK_IS_BUTTON (child) && + (palette->priv->drag_source & GTK_TOOL_PALETTE_DRAG_GROUPS)) + { + gtk_drag_source_set (child, GDK_BUTTON1_MASK | GDK_BUTTON3_MASK, + &dnd_targets[1], 1, GDK_ACTION_COPY | GDK_ACTION_MOVE); + + g_signal_connect (child, "drag-data-get", + G_CALLBACK (gtk_tool_palette_child_drag_data_get), + palette); + } +} + +/** + * gtk_tool_palette_get_drag_target_item: + * + * Get the target entry for a dragged #GtkToolItem. + * + * Returns: the #GtkTargetEntry for a dragged item. + * + * Since: 2.18 + */ +G_CONST_RETURN GtkTargetEntry* +gtk_tool_palette_get_drag_target_item (void) +{ + return &dnd_targets[0]; +} + +/** + * gtk_tool_palette_get_drag_target_group: + * + * Get the target entry for a dragged #GtkToolItemGroup. + * + * Returns: the #GtkTargetEntry for a dragged group. + * + * Since: 2.18 + */ +G_CONST_RETURN GtkTargetEntry* +gtk_tool_palette_get_drag_target_group (void) +{ + return &dnd_targets[1]; +} + +void +_gtk_tool_palette_set_expanding_child (GtkToolPalette *palette, + GtkWidget *widget) +{ + g_return_if_fail (GTK_IS_TOOL_PALETTE (palette)); + palette->priv->expanding_child = widget; +} + +GtkAdjustment* +gtk_tool_palette_get_hadjustment (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), NULL); + return palette->priv->hadjustment; +} + +GtkAdjustment* +gtk_tool_palette_get_vadjustment (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), NULL); + return palette->priv->vadjustment; +} + +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 + +GtkSizeGroup * +_gtk_tool_palette_get_size_group (GtkToolPalette *palette) +{ + g_return_val_if_fail (GTK_IS_TOOL_PALETTE (palette), NULL); + + return palette->priv->text_size_group; +} + +#endif diff --git a/gtktoolpalette.h b/gtktoolpalette.h new file mode 100644 index 0000000000..13dfafbb6b --- /dev/null +++ b/gtktoolpalette.h @@ -0,0 +1,399 @@ +/* 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Mathias Hasselmann + */ + +#ifndef __GTK_TOOL_PALETTE_H__ +#define __GTK_TOOL_PALETTE_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_TOOL_PALETTE (gtk_tool_palette_get_type()) +#define GTK_TOOL_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, GTK_TYPE_TOOL_PALETTE, GtkToolPalette)) +#define GTK_TOOL_PALETTE_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST(cls, GTK_TYPE_TOOL_PALETTE, GtkToolPaletteClass)) +#define GTK_IS_TOOL_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE(obj, GTK_TYPE_TOOL_PALETTE)) +#define GTK_IS_TOOL_PALETTE_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE(obj, GTK_TYPE_TOOL_PALETTE)) +#define GTK_TOOL_PALETTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_TOOL_PALETTE, GtkToolPaletteClass)) + +typedef struct _GtkToolPalette GtkToolPalette; +typedef struct _GtkToolPaletteClass GtkToolPaletteClass; +typedef struct _GtkToolPalettePrivate GtkToolPalettePrivate; + +/** + * GtkToolPaletteDragTargets: + * @GTK_TOOL_PALETTE_DRAG_ITEMS: Support drag of items. + * @GTK_TOOL_PALETTE_DRAG_GROUPS: Support drag of groups. + * + * Flags used to specify the supported drag targets. + */ +typedef enum /*< flags >*/ +{ + GTK_TOOL_PALETTE_DRAG_ITEMS = (1 << 0), + GTK_TOOL_PALETTE_DRAG_GROUPS = (1 << 1) +} +GtkToolPaletteDragTargets; + +/** + * GtkToolPalette: + * + * This should not be accessed directly. Use the accessor functions below. + */ +struct _GtkToolPalette +{ + GtkContainer parent_instance; + GtkToolPalettePrivate *priv; +}; + +struct _GtkToolPaletteClass +{ + GtkContainerClass parent_class; + + void (*set_scroll_adjustments) (GtkWidget *widget, + GtkAdjustment *hadjustment, + GtkAdjustment *vadjustment); +}; + +GType gtk_tool_palette_get_type (void) G_GNUC_CONST; +GtkWidget* gtk_tool_palette_new (void); + +void gtk_tool_palette_set_group_position (GtkToolPalette *palette, + GtkWidget *group, + gint position); +void gtk_tool_palette_set_exclusive (GtkToolPalette *palette, + GtkWidget *group, + gboolean exclusive); +void gtk_tool_palette_set_expand (GtkToolPalette *palette, + GtkWidget *group, + gboolean expand); + +gint gtk_tool_palette_get_group_position (GtkToolPalette *palette, + GtkWidget *group); +gboolean gtk_tool_palette_get_exclusive (GtkToolPalette *palette, + GtkWidget *group); +gboolean gtk_tool_palette_get_expand (GtkToolPalette *palette, + GtkWidget *group); + +void gtk_tool_palette_set_icon_size (GtkToolPalette *palette, + GtkIconSize icon_size); +void gtk_tool_palette_set_orientation (GtkToolPalette *palette, + GtkOrientation orientation); +void gtk_tool_palette_set_style (GtkToolPalette *palette, + GtkToolbarStyle style); + +GtkIconSize gtk_tool_palette_get_icon_size (GtkToolPalette *palette); +GtkOrientation gtk_tool_palette_get_orientation (GtkToolPalette *palette); +GtkToolbarStyle gtk_tool_palette_get_style (GtkToolPalette *palette); + +GtkToolItem* gtk_tool_palette_get_drop_item (GtkToolPalette *palette, + gint x, + gint y); +GtkWidget* gtk_tool_palette_get_drop_group (GtkToolPalette *palette, + gint x, + gint y); +GtkWidget* gtk_tool_palette_get_drag_item (GtkToolPalette *palette, + const GtkSelectionData *selection); + +void gtk_tool_palette_set_drag_source (GtkToolPalette *palette, + GtkToolPaletteDragTargets targets); +void gtk_tool_palette_add_drag_dest (GtkToolPalette *palette, + GtkWidget *widget, + GtkDestDefaults flags, + GtkToolPaletteDragTargets targets, + GdkDragAction actions); + +GtkAdjustment* gtk_tool_palette_get_hadjustment (GtkToolPalette *palette); +GtkAdjustment* gtk_tool_palette_get_vadjustment (GtkToolPalette *palette); + +G_CONST_RETURN GtkTargetEntry* gtk_tool_palette_get_drag_target_item (void) G_GNUC_CONST; +G_CONST_RETURN GtkTargetEntry* gtk_tool_palette_get_drag_target_group (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __GTK_TOOL_PALETTE_H__ */ +/* 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Mathias Hasselmann + */ + +#ifndef __GTK_TOOL_PALETTE_H__ +#define __GTK_TOOL_PALETTE_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_TOOL_PALETTE (gtk_tool_palette_get_type()) +#define GTK_TOOL_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, GTK_TYPE_TOOL_PALETTE, GtkToolPalette)) +#define GTK_TOOL_PALETTE_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST(cls, GTK_TYPE_TOOL_PALETTE, GtkToolPaletteClass)) +#define GTK_IS_TOOL_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE(obj, GTK_TYPE_TOOL_PALETTE)) +#define GTK_IS_TOOL_PALETTE_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE(obj, GTK_TYPE_TOOL_PALETTE)) +#define GTK_TOOL_PALETTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_TOOL_PALETTE, GtkToolPaletteClass)) + +typedef struct _GtkToolPalette GtkToolPalette; +typedef struct _GtkToolPaletteClass GtkToolPaletteClass; +typedef struct _GtkToolPalettePrivate GtkToolPalettePrivate; + +/** + * GtkToolPaletteDragTargets: + * @GTK_TOOL_PALETTE_DRAG_ITEMS: Support drag of items. + * @GTK_TOOL_PALETTE_DRAG_GROUPS: Support drag of groups. + * + * Flags used to specify the supported drag targets. + */ +typedef enum /*< flags >*/ +{ + GTK_TOOL_PALETTE_DRAG_ITEMS = (1 << 0), + GTK_TOOL_PALETTE_DRAG_GROUPS = (1 << 1) +} +GtkToolPaletteDragTargets; + +/** + * GtkToolPalette: + * + * This should not be accessed directly. Use the accessor functions below. + */ +struct _GtkToolPalette +{ + GtkContainer parent_instance; + GtkToolPalettePrivate *priv; +}; + +struct _GtkToolPaletteClass +{ + GtkContainerClass parent_class; + + void (*set_scroll_adjustments) (GtkWidget *widget, + GtkAdjustment *hadjustment, + GtkAdjustment *vadjustment); +}; + +GType gtk_tool_palette_get_type (void) G_GNUC_CONST; +GtkWidget* gtk_tool_palette_new (void); + +void gtk_tool_palette_set_group_position (GtkToolPalette *palette, + GtkWidget *group, + gint position); +void gtk_tool_palette_set_exclusive (GtkToolPalette *palette, + GtkWidget *group, + gboolean exclusive); +void gtk_tool_palette_set_expand (GtkToolPalette *palette, + GtkWidget *group, + gboolean expand); + +gint gtk_tool_palette_get_group_position (GtkToolPalette *palette, + GtkWidget *group); +gboolean gtk_tool_palette_get_exclusive (GtkToolPalette *palette, + GtkWidget *group); +gboolean gtk_tool_palette_get_expand (GtkToolPalette *palette, + GtkWidget *group); + +void gtk_tool_palette_set_icon_size (GtkToolPalette *palette, + GtkIconSize icon_size); +void gtk_tool_palette_set_orientation (GtkToolPalette *palette, + GtkOrientation orientation); +void gtk_tool_palette_set_style (GtkToolPalette *palette, + GtkToolbarStyle style); + +GtkIconSize gtk_tool_palette_get_icon_size (GtkToolPalette *palette); +GtkOrientation gtk_tool_palette_get_orientation (GtkToolPalette *palette); +GtkToolbarStyle gtk_tool_palette_get_style (GtkToolPalette *palette); + +GtkToolItem* gtk_tool_palette_get_drop_item (GtkToolPalette *palette, + gint x, + gint y); +GtkWidget* gtk_tool_palette_get_drop_group (GtkToolPalette *palette, + gint x, + gint y); +GtkWidget* gtk_tool_palette_get_drag_item (GtkToolPalette *palette, + const GtkSelectionData *selection); + +void gtk_tool_palette_set_drag_source (GtkToolPalette *palette, + GtkToolPaletteDragTargets targets); +void gtk_tool_palette_add_drag_dest (GtkToolPalette *palette, + GtkWidget *widget, + GtkDestDefaults flags, + GtkToolPaletteDragTargets targets, + GdkDragAction actions); + +GtkAdjustment* gtk_tool_palette_get_hadjustment (GtkToolPalette *palette); +GtkAdjustment* gtk_tool_palette_get_vadjustment (GtkToolPalette *palette); + +G_CONST_RETURN GtkTargetEntry* gtk_tool_palette_get_drag_target_item (void) G_GNUC_CONST; +G_CONST_RETURN GtkTargetEntry* gtk_tool_palette_get_drag_target_group (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __GTK_TOOL_PALETTE_H__ */ +/* 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Mathias Hasselmann + */ + +#ifndef __GTK_TOOL_PALETTE_H__ +#define __GTK_TOOL_PALETTE_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_TOOL_PALETTE (gtk_tool_palette_get_type()) +#define GTK_TOOL_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, GTK_TYPE_TOOL_PALETTE, GtkToolPalette)) +#define GTK_TOOL_PALETTE_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST(cls, GTK_TYPE_TOOL_PALETTE, GtkToolPaletteClass)) +#define GTK_IS_TOOL_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE(obj, GTK_TYPE_TOOL_PALETTE)) +#define GTK_IS_TOOL_PALETTE_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE(obj, GTK_TYPE_TOOL_PALETTE)) +#define GTK_TOOL_PALETTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_TOOL_PALETTE, GtkToolPaletteClass)) + +typedef struct _GtkToolPalette GtkToolPalette; +typedef struct _GtkToolPaletteClass GtkToolPaletteClass; +typedef struct _GtkToolPalettePrivate GtkToolPalettePrivate; + +/** + * GtkToolPaletteDragTargets: + * @GTK_TOOL_PALETTE_DRAG_ITEMS: Support drag of items. + * @GTK_TOOL_PALETTE_DRAG_GROUPS: Support drag of groups. + * + * Flags used to specify the supported drag targets. + */ +typedef enum /*< flags >*/ +{ + GTK_TOOL_PALETTE_DRAG_ITEMS = (1 << 0), + GTK_TOOL_PALETTE_DRAG_GROUPS = (1 << 1) +} +GtkToolPaletteDragTargets; + +/** + * GtkToolPalette: + * + * This should not be accessed directly. Use the accessor functions below. + */ +struct _GtkToolPalette +{ + GtkContainer parent_instance; + GtkToolPalettePrivate *priv; +}; + +struct _GtkToolPaletteClass +{ + GtkContainerClass parent_class; + + void (*set_scroll_adjustments) (GtkWidget *widget, + GtkAdjustment *hadjustment, + GtkAdjustment *vadjustment); +}; + +GType gtk_tool_palette_get_type (void) G_GNUC_CONST; +GtkWidget* gtk_tool_palette_new (void); + +void gtk_tool_palette_set_group_position (GtkToolPalette *palette, + GtkWidget *group, + gint position); +void gtk_tool_palette_set_exclusive (GtkToolPalette *palette, + GtkWidget *group, + gboolean exclusive); +void gtk_tool_palette_set_expand (GtkToolPalette *palette, + GtkWidget *group, + gboolean expand); + +gint gtk_tool_palette_get_group_position (GtkToolPalette *palette, + GtkWidget *group); +gboolean gtk_tool_palette_get_exclusive (GtkToolPalette *palette, + GtkWidget *group); +gboolean gtk_tool_palette_get_expand (GtkToolPalette *palette, + GtkWidget *group); + +void gtk_tool_palette_set_icon_size (GtkToolPalette *palette, + GtkIconSize icon_size); +void gtk_tool_palette_set_orientation (GtkToolPalette *palette, + GtkOrientation orientation); +void gtk_tool_palette_set_style (GtkToolPalette *palette, + GtkToolbarStyle style); + +GtkIconSize gtk_tool_palette_get_icon_size (GtkToolPalette *palette); +GtkOrientation gtk_tool_palette_get_orientation (GtkToolPalette *palette); +GtkToolbarStyle gtk_tool_palette_get_style (GtkToolPalette *palette); + +GtkToolItem* gtk_tool_palette_get_drop_item (GtkToolPalette *palette, + gint x, + gint y); +GtkWidget* gtk_tool_palette_get_drop_group (GtkToolPalette *palette, + gint x, + gint y); +GtkWidget* gtk_tool_palette_get_drag_item (GtkToolPalette *palette, + const GtkSelectionData *selection); + +void gtk_tool_palette_set_drag_source (GtkToolPalette *palette, + GtkToolPaletteDragTargets targets); +void gtk_tool_palette_add_drag_dest (GtkToolPalette *palette, + GtkWidget *widget, + GtkDestDefaults flags, + GtkToolPaletteDragTargets targets, + GdkDragAction actions); + +GtkAdjustment* gtk_tool_palette_get_hadjustment (GtkToolPalette *palette); +GtkAdjustment* gtk_tool_palette_get_vadjustment (GtkToolPalette *palette); + +G_CONST_RETURN GtkTargetEntry* gtk_tool_palette_get_drag_target_item (void) G_GNUC_CONST; +G_CONST_RETURN GtkTargetEntry* gtk_tool_palette_get_drag_target_group (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __GTK_TOOL_PALETTE_H__ */ diff --git a/gtktoolpaletteprivate.h b/gtktoolpaletteprivate.h new file mode 100644 index 0000000000..c1477f9e73 --- /dev/null +++ b/gtktoolpaletteprivate.h @@ -0,0 +1,59 @@ +/* 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Mathias Hasselmann + */ + +#ifndef __GTK_TOOL_PALETTE_PRIVATE_H__ +#define __GTK_TOOL_PALETTE_PRIVATE_H__ + +#include + +void _gtk_tool_palette_get_item_size (GtkToolPalette *palette, + GtkRequisition *item_size, + gboolean homogeneous_only, + gint *requested_rows); +void _gtk_tool_palette_child_set_drag_source (GtkWidget *widget, + gpointer data); +void _gtk_tool_palette_set_expanding_child (GtkToolPalette *palette, + GtkWidget *widget); + +void _gtk_tool_item_group_palette_reconfigured (GtkToolItemGroup *group); +void _gtk_tool_item_group_item_size_request (GtkToolItemGroup *group, + GtkRequisition *item_size, + gboolean homogeneous_only, + gint *requested_rows); +gint _gtk_tool_item_group_get_height_for_width (GtkToolItemGroup *group, + gint width); +gint _gtk_tool_item_group_get_width_for_height (GtkToolItemGroup *group, + gint height); +void _gtk_tool_item_group_paint (GtkToolItemGroup *group, + cairo_t *cr); +gint _gtk_tool_item_group_get_size_for_limit (GtkToolItemGroup *group, + gint limit, + gboolean vertical, + gboolean animation); + +#undef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 +/* #define HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 */ + +#ifdef HAVE_EXTENDED_TOOL_SHELL_SUPPORT_BUG_535090 +GtkSizeGroup *_gtk_tool_palette_get_size_group (GtkToolPalette *palette); +#endif + +#endif /* __GTK_TOOL_PALETTE_PRIVATE_H__ */