Commit 3f945530 authored by Matthias Clasen's avatar Matthias Clasen

Add GtkHeaderBar

This widget has been developed as GdHeaderBar in libgd.
The copy here has been renamed and changed to use GTK+
internals.
parent 55a98da4
......@@ -235,6 +235,7 @@
<xi:include href="xml/gtknotebook.xml" />
<xi:include href="xml/gtkexpander.xml" />
<xi:include href="xml/gtkoverlay.xml" />
<xi:include href="xml/gtkheaderbar.xml" />
<xi:include href="xml/gtkorientable.xml" />
</chapter>
......
......@@ -7494,3 +7494,27 @@ GTK_COLOR_CHOOSER_DIALOG_GET_CLASS
GtkColorChooserDialogPrivate
gtk_color_chooser_dialog_get_type
</SECTION>
<SECTION>
<FILE>gtkheaderbar</FILE>
<TITLE>GtkHeaderBar</TITLE>
GtkHeaderBar
gtk_header_bar_new
gtk_header_bar_set_title
gtk_header_bar_get_title
gtk_header_bar_set_custom_title
gtk_header_bar_get_custom_title
gtk_header_bar_pack_start
gtk_header_bar_pack_end
<SUBSECTION Standard>
GTK_TYPE_HEADER_BAR
GTK_HEADER_BAR
GTK_HEADER_BAR_CLASS
GTK_IS_HEADER_BAR
GTK_IS_HEADER_BAR_CLASS
GTK_HEADER_BAR_GET_CLASS
<SUBSECTION Private>
gtk_header_bar_get_type
</SECTION>
......@@ -257,6 +257,7 @@ gtk_public_h_sources = \
gtkfontchooserwidget.h \
gtkframe.h \
gtkgrid.h \
gtkheaderbar.h \
gtkiconfactory.h \
gtkicontheme.h \
gtkiconview.h \
......@@ -731,6 +732,7 @@ gtk_base_c_sources = \
gtkfontchooserwidget.c \
gtkframe.c \
gtkgrid.c \
gtkheaderbar.c \
gtkhsla.c \
gtkiconcache.c \
gtkiconcachevalidator.c \
......
......@@ -108,6 +108,7 @@
#include <gtk/gtkfontchooserwidget.h>
#include <gtk/gtkframe.h>
#include <gtk/gtkgrid.h>
#include <gtk/gtkheaderbar.h>
#include <gtk/gtkiconfactory.h>
#include <gtk/gtkicontheme.h>
#include <gtk/gtkiconview.h>
......
......@@ -1222,6 +1222,13 @@ gtk_hbox_get_type
gtk_hbox_new
gtk_hbutton_box_get_type
gtk_hbutton_box_new
gtk_header_bar_new
gtk_header_bar_pack_start
gtk_header_bar_pack_end
gtk_header_bar_set_title
gtk_header_bar_get_title
gtk_header_bar_set_custom_title
gtk_header_bar_get_custom_title
gtk_hpaned_get_type
gtk_hpaned_new
gtk_hscale_get_type
......
/*
* Copyright (c) 2013 Red Hat, Inc.
*
* This program 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 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "config.h"
#include "gtkheaderbar.h"
#include "gtkintl.h"
#include "gtkprivate.h"
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
#include "gtkcontainerprivate.h"
#include "a11y/gtkcontaineraccessible.h"
#include <string.h>
/**
* SECTION:gtkheaderbar
* @Short_description: A box with a centered child
* @Title: GtkHeaderBar
* @See_also: #GtkBox
*
* GtkHeaderBar is similar to a horizontal #GtkBox, it allows
* to place children at the start or the end. In addition,
* it allows a title to be displayed. The title will be
* centered wrt to the widget of the box, even if the children
* at either side take up different amounts of space.
*/
#define DEFAULT_SPACING 6
#define DEFAULT_HPADDING 6
#define DEFAULT_VPADDING 6
struct _GtkHeaderBarPrivate
{
gchar *title;
GtkWidget *label;
GtkWidget *custom_title;
gint spacing;
gint hpadding;
gint vpadding;
GList *children;
};
typedef struct _Child Child;
struct _Child
{
GtkWidget *widget;
GtkPackType pack_type;
};
enum {
PROP_0,
PROP_TITLE,
PROP_CUSTOM_TITLE,
PROP_SPACING,
PROP_HPADDING,
PROP_VPADDING
};
enum {
CHILD_PROP_0,
CHILD_PROP_PACK_TYPE,
CHILD_PROP_POSITION
};
static void gtk_header_buildable_init (GtkBuildableIface *iface);
G_DEFINE_TYPE_WITH_CODE (GtkHeaderBar, gtk_header_bar, GTK_TYPE_CONTAINER,
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
gtk_header_buildable_init));
static void
boldify_label (GtkWidget *label)
{
PangoAttrList *attrs;
attrs = pango_attr_list_new ();
pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
gtk_label_set_attributes (GTK_LABEL (label), attrs);
pango_attr_list_unref (attrs);
}
static void
get_css_padding_and_border (GtkWidget *widget,
GtkBorder *border)
{
GtkStyleContext *context;
GtkStateFlags state;
GtkBorder tmp;
context = gtk_widget_get_style_context (widget);
state = gtk_widget_get_state_flags (widget);
gtk_style_context_get_padding (context, state, border);
gtk_style_context_get_border (context, state, &tmp);
border->top += tmp.top;
border->right += tmp.right;
border->bottom += tmp.bottom;
border->left += tmp.left;
}
static void
gtk_header_bar_init (GtkHeaderBar *bar)
{
GtkStyleContext *context;
GtkHeaderBarPrivate *priv;
priv = G_TYPE_INSTANCE_GET_PRIVATE (bar, GTK_TYPE_HEADER_BAR, GtkHeaderBarPrivate);
bar->priv = priv;
gtk_widget_set_has_window (GTK_WIDGET (bar), FALSE);
gtk_widget_set_redraw_on_allocate (GTK_WIDGET (bar), FALSE);
priv->label = gtk_label_new ("");
boldify_label (priv->label);
gtk_widget_set_parent (priv->label, GTK_WIDGET (bar));
gtk_widget_set_valign (priv->label, GTK_ALIGN_CENTER);
gtk_label_set_line_wrap (GTK_LABEL (priv->label), FALSE);
gtk_label_set_single_line_mode (GTK_LABEL (priv->label), TRUE);
gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_END);
gtk_widget_show (priv->label);
priv->title = NULL;
priv->custom_title = NULL;
priv->children = NULL;
priv->spacing = DEFAULT_SPACING;
priv->hpadding = DEFAULT_HPADDING;
priv->vpadding = DEFAULT_VPADDING;
context = gtk_widget_get_style_context (GTK_WIDGET (bar));
gtk_style_context_add_class (context, "header-bar");
gtk_style_context_add_class (context, GTK_STYLE_CLASS_HORIZONTAL);
}
static gint
count_visible_children (GtkHeaderBar *bar)
{
GList *l;
Child *child;
gint n;
n = 0;
for (l = bar->priv->children; l; l = l->next)
{
child = l->data;
if (gtk_widget_get_visible (child->widget))
n++;
}
return n;
}
static gboolean
add_child_size (GtkWidget *child,
GtkOrientation orientation,
gint *minimum,
gint *natural)
{
gint child_minimum, child_natural;
if (!gtk_widget_get_visible (child))
return FALSE;
if (orientation == GTK_ORIENTATION_HORIZONTAL)
gtk_widget_get_preferred_width (child, &child_minimum, &child_natural);
else
gtk_widget_get_preferred_height (child, &child_minimum, &child_natural);
if (GTK_ORIENTATION_HORIZONTAL == orientation)
{
*minimum += child_minimum;
*natural += child_natural;
}
else
{
*minimum = MAX (*minimum, child_minimum);
*natural = MAX (*natural, child_natural);
}
return TRUE;
}
static void
gtk_header_bar_get_size (GtkWidget *widget,
GtkOrientation orientation,
gint *minimum_size,
gint *natural_size)
{
GtkHeaderBar *bar = GTK_HEADER_BAR (widget);
GtkHeaderBarPrivate *priv = bar->priv;
GList *l;
gint nvis_children;
gint minimum, natural;
GtkBorder css_borders;
minimum = natural = 0;
nvis_children = 0;
for (l = priv->children; l; l = l->next)
{
Child *child = l->data;
if (add_child_size (child->widget, orientation, &minimum, &natural))
nvis_children += 1;
}
if (add_child_size (priv->label, orientation, &minimum, &natural))
nvis_children += 1;
if (priv->custom_title)
{
if (add_child_size (priv->custom_title, orientation, &minimum, &natural))
nvis_children += 1;
}
if (nvis_children > 0 && orientation == GTK_ORIENTATION_HORIZONTAL)
{
minimum += nvis_children * priv->spacing;
natural += nvis_children * priv->spacing;
}
get_css_padding_and_border (widget, &css_borders);
if (GTK_ORIENTATION_HORIZONTAL == orientation)
{
minimum += 2 * priv->hpadding + css_borders.left + css_borders.right;
natural += 2 * priv->hpadding + css_borders.left + css_borders.right;
}
else
{
minimum += 2 * priv->vpadding + css_borders.top + css_borders.bottom;
natural += 2 * priv->vpadding + css_borders.top + css_borders.bottom;
}
if (minimum_size)
*minimum_size = minimum;
if (natural_size)
*natural_size = natural;
}
static void
gtk_header_bar_compute_size_for_orientation (GtkWidget *widget,
gint avail_size,
gint *minimum_size,
gint *natural_size)
{
GtkHeaderBar *bar = GTK_HEADER_BAR (widget);
GtkHeaderBarPrivate *priv = bar->priv;
GList *children;
gint required_size = 0;
gint required_natural = 0;
gint child_size;
gint child_natural;
gint nvis_children;
GtkBorder css_borders;
avail_size -= 2 * priv->vpadding;
nvis_children = 0;
for (children = priv->children; children != NULL; children = children->next)
{
Child *child = children->data;
if (gtk_widget_get_visible (child->widget))
{
gtk_widget_get_preferred_width_for_height (child->widget,
avail_size, &child_size, &child_natural);
required_size += child_size;
required_natural += child_natural;
nvis_children += 1;
}
}
/* FIXME label size */
if (nvis_children > 0)
{
required_size += nvis_children * priv->spacing;
required_natural += nvis_children * priv->spacing;
}
get_css_padding_and_border (widget, &css_borders);
required_size += 2 * priv->hpadding + css_borders.left + css_borders.right;
required_natural += 2 * priv->hpadding + css_borders.left + css_borders.right;
if (minimum_size)
*minimum_size = required_size;
if (natural_size)
*natural_size = required_natural;
}
static void
gtk_header_bar_compute_size_for_opposing_orientation (GtkWidget *widget,
gint avail_size,
gint *minimum_size,
gint *natural_size)
{
GtkHeaderBar *bar = GTK_HEADER_BAR (widget);
GtkHeaderBarPrivate *priv = bar->priv;
Child *child;
GList *children;
gint nvis_children;
gint computed_minimum = 0;
gint computed_natural = 0;
GtkRequestedSize *sizes;
GtkPackType packing;
gint size;
gint i;
gint child_size;
gint child_minimum;
gint child_natural;
GtkBorder css_borders;
nvis_children = count_visible_children (bar);
if (nvis_children <= 0)
return;
sizes = g_newa (GtkRequestedSize, nvis_children);
size = avail_size - 2 * priv->hpadding;
/* Retrieve desired size for visible children */
for (i = 0, children = priv->children; children; children = children->next)
{
child = children->data;
if (gtk_widget_get_visible (child->widget))
{
gtk_widget_get_preferred_width (child->widget,
&sizes[i].minimum_size,
&sizes[i].natural_size);
size -= sizes[i].minimum_size;
sizes[i].data = child;
i += 1;
}
}
/* Bring children up to size first */
size = gtk_distribute_natural_allocation (MAX (0, size), nvis_children, sizes);
/* Allocate child positions. */
for (packing = GTK_PACK_START; packing <= GTK_PACK_END; ++packing)
{
for (i = 0, children = priv->children; children; children = children->next)
{
child = children->data;
/* If widget is not visible, skip it. */
if (!gtk_widget_get_visible (child->widget))
continue;
/* If widget is packed differently skip it, but still increment i,
* since widget is visible and will be handled in next loop
* iteration.
*/
if (child->pack_type != packing)
{
i++;
continue;
}
child_size = sizes[i].minimum_size;
gtk_widget_get_preferred_height_for_width (child->widget,
child_size, &child_minimum, &child_natural);
computed_minimum = MAX (computed_minimum, child_minimum);
computed_natural = MAX (computed_natural, child_natural);
}
i += 1;
}
get_css_padding_and_border (widget, &css_borders);
computed_minimum += 2 * priv->vpadding + css_borders.top + css_borders.bottom;
computed_natural += 2 * priv->vpadding + css_borders.top + css_borders.bottom;
if (minimum_size)
*minimum_size = computed_minimum;
if (natural_size)
*natural_size = computed_natural;
}
static void
gtk_header_bar_get_preferred_width (GtkWidget *widget,
gint *minimum_size,
gint *natural_size)
{
gtk_header_bar_get_size (widget, GTK_ORIENTATION_HORIZONTAL, minimum_size, natural_size);
}
static void
gtk_header_bar_get_preferred_height (GtkWidget *widget,
gint *minimum_size,
gint *natural_size)
{
gtk_header_bar_get_size (widget, GTK_ORIENTATION_VERTICAL, minimum_size, natural_size);
}
static void
gtk_header_bar_get_preferred_width_for_height (GtkWidget *widget,
gint height,
gint *minimum_width,
gint *natural_width)
{
gtk_header_bar_compute_size_for_orientation (widget, height, minimum_width, natural_width);
}
static void
gtk_header_bar_get_preferred_height_for_width (GtkWidget *widget,
gint width,
gint *minimum_height,
gint *natural_height)
{
gtk_header_bar_compute_size_for_opposing_orientation (widget, width, minimum_height, natural_height);
}
static void
gtk_header_bar_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkHeaderBar *bar = GTK_HEADER_BAR (widget);
GtkHeaderBarPrivate *priv = bar->priv;
GtkRequestedSize *sizes;
gint width, height;
gint nvis_children;
gint title_minimum_size;
gint title_natural_size;
gint side[2];
GList *l;
gint i;
Child *child;
GtkPackType packing;
GtkAllocation child_allocation;
gint x;
gint child_size;
GtkTextDirection direction;
GtkBorder css_borders;
gtk_widget_set_allocation (widget, allocation);
direction = gtk_widget_get_direction (widget);
nvis_children = count_visible_children (bar);
sizes = g_newa (GtkRequestedSize, nvis_children);
get_css_padding_and_border (widget, &css_borders);
width = allocation->width - nvis_children * priv->spacing -
2 * priv->hpadding - css_borders.left - css_borders.right;
height = allocation->height - 2 * priv->vpadding - css_borders.top - css_borders.bottom;
i = 0;
for (l = priv->children; l; l = l->next)
{
child = l->data;
if (!gtk_widget_get_visible (child->widget))
continue;
gtk_widget_get_preferred_width_for_height (child->widget,
height,
&sizes[i].minimum_size,
&sizes[i].natural_size);
width -= sizes[i].minimum_size;
i++;
}
if (priv->custom_title)
{
gtk_widget_get_preferred_width_for_height (priv->custom_title,
height,
&title_minimum_size,
&title_natural_size);
}
else
{
gtk_widget_get_preferred_width_for_height (priv->label,
height,
&title_minimum_size,
&title_natural_size);
}
width -= title_natural_size;
width = gtk_distribute_natural_allocation (MAX (0, width), nvis_children, sizes);
side[0] = side[1] = 0;
for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++)
{
child_allocation.y = allocation->y + priv->vpadding;
child_allocation.height = height;
if (packing == GTK_PACK_START)
x = allocation->x + priv->hpadding;
else
x = allocation->x + allocation->width - priv->hpadding;
if (packing == GTK_PACK_START)
{
l = priv->children;
i = 0;
}
else
{
l = g_list_last (priv->children);
i = nvis_children - 1;
}
for (; l != NULL; (packing == GTK_PACK_START) ? (l = l->next) : (l = l->prev))
{
child = l->data;
if (!gtk_widget_get_visible (child->widget))
continue;
if (child->pack_type != packing)
goto next;
child_size = sizes[i].minimum_size;
child_allocation.width = child_size;
if (packing == GTK_PACK_START)
{
child_allocation.x = x;
x += child_size;
x += priv->spacing;
}
else
{
x -= child_size;
child_allocation.x = x;
x -= priv->spacing;
}
side[packing] += child_size + priv->spacing;
if (direction == GTK_TEXT_DIR_RTL)
child_allocation.x = allocation->x + allocation->width - (child_allocation.x - allocation->x) - child_allocation.width;
gtk_widget_size_allocate (child->widget, &child_allocation);
next:
if (packing == GTK_PACK_START)
i++;
else
i--;
}
}
child_allocation.y = allocation->y + priv->vpadding;
child_allocation.height = height;
width = MAX (side[0], side[1]);
if (allocation->width - 2 * width >= title_natural_size)
child_size = MIN (title_natural_size, allocation->width - 2 * width);
else if (allocation->width - side[0] - side[1] >= title_natural_size)
child_size = MIN (title_natural_size, allocation->width - side[0] - side[1]);
else
child_size = allocation->width - side[0] - side[1];
child_allocation.x = allocation->x + (allocation->width - child_size) / 2;
child_allocation.width = child_size;
if (allocation->x + side[0] > child_allocation.x)
child_allocation.x = allocation->x + side[0];
else if (allocation->x + allocation->width - side[1] < child_allocation.x + child_allocation.width)
child_allocation.x = allocation->x + allocation->width - side[1] - child_allocation.width;
if (direction == GTK_TEXT_DIR_RTL)
child_allocation.x = allocation->x + allocation->width - (child_allocation.x - allocation->x) - child_allocation.width;
if (priv->custom_title)
gtk_widget_size_allocate (priv->custom_title, &child_allocation);
else
gtk_widget_size_allocate (priv->label, &child_allocation);
}
/**
* gtk_header_bar_set_title:
* @bar: a #GtkHeaderBar
* @title: (allow-none): a title
*
* Sets the title of the #GtkHeaderBar. The title should help a user
* identify the current view. A good title should not include the
* application name.
*
* Since: 3.10
*/
void
gtk_header_bar_set_title (GtkHeaderBar *bar,
const gchar *title)
{
GtkHeaderBarPrivate *priv;
char *new_title;
g_return_if_fail (GTK_IS_HEADER_BAR (bar));
priv = bar->priv;
new_title = g_strdup (title);
g_free (priv->title);
priv->title = new_title;
gtk_label_set_label (GTK_LABEL (priv->label), priv->title);
gtk_widget_queue_resize (GTK_WIDGET (bar));
g_object_notify (G_OBJECT (bar), "title");
}
/**
* gtk_header_bar_get_title:
* @bar: a #GtkHeaderBar
*
* Retrieves the title of the header. See gtk_header_bar_set_title().
*
* Return value: the title of the header, or %NULL if none has
* been set explicitely. The returned string is owned by the widget
* and must not be modified or freed.
*
* Since: 3.10
*/
const gchar *
gtk_header_bar_get_title (GtkHeaderBar *bar)
{
g_return_val_if_fail (GTK_IS_HEADER_BAR (bar), NULL);
return bar->priv->title;
}
/**
* gtk_header_bar_set_custom_title:
* @bar: a #GtkHeaderBar
* @title_widget: (allow-none): a custom widget to use for a title
*
* Sets a custom title for the #GtkHeaderBar. The title should help a
* user identify the current view. This supercedes any title set by
* gtk_header_bar_set_title(). You should set the custom title to %NULL,
* for the header title label to be visible again.
*
* Since: 3.10
*/
void
gtk_header_bar_set_custom_title (GtkHeaderBar *bar,
GtkWidget *title_widget)
{
GtkHeaderBarPrivate *priv;
g_return_if_fail (GTK_IS_HEADER_BAR (bar));
if (title_widget)
g_return_if_fail (GTK_IS_WIDGET (title_widget));
priv = bar->priv;