/* PSPPIRE - a graphical user interface for PSPP.
Copyright (C) 2011, 2012, 2014 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
#include
#include "pspp-widget-facade.h"
#include
#include
#include
#include
static void
inset_rectangle (const GdkRectangle *src,
const GtkBorder *inset,
GdkRectangle *dst)
{
dst->x = src->x + inset->left;
dst->y = src->y + inset->top;
dst->width = MAX (1, src->width - inset->left - inset->right);
dst->height = MAX (1, src->height - inset->top - inset->bottom);
}
static void
thicken_border (gint x, gint y, GtkBorder *border)
{
border->left += x;
border->right += x;
border->top += y;
border->bottom += y;
}
GtkStyle *
facade_get_style (GtkWidget *base,
GType type1,
...)
{
GString path, class_path;
GType final_type;
GtkStyle *style;
va_list args;
GType type;
gtk_widget_path (base, NULL, &path.str, NULL);
path.len = path.allocated_len = strlen (path.str);
gtk_widget_class_path (base, NULL, &class_path.str, NULL);
class_path.len = class_path.allocated_len = strlen (class_path.str);
va_start (args, type1);
for (type = final_type = type1; type != 0; type = va_arg (args, GType))
{
const gchar *type_name = g_type_name (type);
g_string_append_printf (&path, ".%s", type_name);
g_string_append_printf (&class_path, ".%s", type_name);
final_type = type;
}
va_end (args);
style = gtk_rc_get_style_by_paths (gtk_widget_get_settings (base),
path.str, class_path.str, final_type);
free (path.str);
free (class_path.str);
return style;
}
void
facade_hbox_get_base_size_request (gint border_width,
gint spacing,
gint n_children,
GtkRequisition *request)
{
request->width = border_width * 2;
if (n_children > 1)
request->width += spacing * (n_children - 1);
request->height = border_width * 2;
}
void
facade_hbox_add_child_size_request (gint hbox_border_width,
const GtkRequisition *child_request,
gint child_padding,
GtkRequisition *request)
{
request->width += child_request->width + child_padding * 2;
request->height = MAX (request->height,
hbox_border_width * 2 + child_request->height);
}
void
facade_arrow_get_size_request (gint xpad,
gint ypad,
GtkRequisition *request)
{
#define MIN_ARROW_SIZE 15
request->width = MIN_ARROW_SIZE + xpad * 2;
request->height = MIN_ARROW_SIZE + ypad * 2;
}
void
facade_alignment_get_size_request (gint border_width,
gint padding_left,
gint padding_right,
gint padding_top,
gint padding_bottom,
const GtkRequisition *child_request,
GtkRequisition *request)
{
request->width = (border_width * 2 + padding_left + padding_right
+ child_request->width);
request->height = (border_width * 2 + padding_top + padding_bottom
+ child_request->height);
}
void
facade_label_get_size_request (gint xpad,
gint ypad,
GtkWidget *base,
const char *text,
GtkRequisition *request)
{
PangoLayout *layout;
layout = facade_label_get_layout (base, text);
facade_label_get_size_request_from_layout (xpad, ypad, layout, request);
g_object_unref (layout);
}
void
facade_label_get_size_request_from_layout (gint xpad,
gint ypad,
PangoLayout *layout,
GtkRequisition *request)
{
PangoRectangle logical_rect;
pango_layout_get_extents (layout, NULL, &logical_rect);
request->width = xpad * 2 + PANGO_PIXELS (logical_rect.width);
request->height = ypad * 2 + PANGO_PIXELS (logical_rect.height);
}
PangoLayout *
facade_label_get_layout (GtkWidget *base,
const char *text)
{
PangoAlignment alignment;
PangoLayout *layout;
gboolean rtl;
rtl = gtk_widget_get_direction (base) == GTK_TEXT_DIR_RTL;
alignment = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
layout = gtk_widget_create_pango_layout (base, text);
pango_layout_set_alignment (layout, alignment);
return layout;
}
static void
facade_button_get_inner_border (GtkStyle *button_style,
GtkBorder *inner_border)
{
GtkBorder *tmp_border;
gtk_style_get (button_style, GTK_TYPE_BUTTON,
"inner-border", &tmp_border,
NULL);
if (tmp_border)
{
*inner_border = *tmp_border;
gtk_border_free (tmp_border);
}
else
{
static const GtkBorder default_inner_border = { 1, 1, 1, 1 };
*inner_border = default_inner_border;
}
}
void
facade_button_get_size_request (gint border_width,
GtkWidget *base,
GtkStyle *button_style,
const GtkRequisition *child_request,
GtkRequisition *request)
{
GtkBorder inner_border;
gint common_width;
gint focus_width;
gint focus_pad;
gtk_style_get (button_style, GTK_TYPE_BUTTON,
"focus-line-width", &focus_width,
"focus-padding", &focus_pad,
NULL);
facade_button_get_inner_border (button_style, &inner_border);
common_width = 2 * (border_width + focus_width + focus_pad);
request->width = (common_width
+ 2 * button_style->xthickness
+ inner_border.left + inner_border.right
+ child_request->width);
request->height = (common_width
+ 2 * button_style->ythickness
+ inner_border.top + inner_border.bottom
+ child_request->height);
}
void
facade_button_get_focus_inset (gint border_width,
GtkWidget *base,
GtkStyle *button_style,
GtkBorder *focus_inset)
{
facade_button_get_inner_border (button_style, focus_inset);
thicken_border (border_width + button_style->xthickness,
border_width + button_style->ythickness,
focus_inset);
}
static void
facade_button_get_label_inset (gint border_width,
GtkWidget *base,
GtkStyle *button_style,
GtkBorder *label_inset)
{
gint focus_width;
gint focus_pad;
facade_button_get_focus_inset (border_width, base, button_style,
label_inset);
gtk_style_get (button_style, GTK_TYPE_BUTTON,
"focus-line-width", &focus_width,
"focus-padding", &focus_pad,
NULL);
thicken_border (focus_width + focus_pad,
focus_width + focus_pad,
label_inset);
}
static void
get_layout_location (GtkWidget *base,
const GdkRectangle *label_area,
PangoLayout *layout,
gint xpad,
gint ypad,
gfloat xalign,
gfloat yalign,
gint *x,
gint *y)
{
PangoRectangle logical;
GtkRequisition req;
if (gtk_widget_get_direction (base) == GTK_TEXT_DIR_RTL)
xalign = 1.0 - xalign;
pango_layout_get_pixel_extents (layout, NULL, &logical);
facade_label_get_size_request_from_layout (xpad, ypad, layout, &req);
*x = floor (label_area->x + xpad + xalign * (label_area->width - req.width));
if (gtk_widget_get_direction (base) == GTK_TEXT_DIR_LTR)
*x = MAX (*x, label_area->x + xpad);
else
*x = MIN (*x, label_area->x + label_area->width - xpad);
*x -= logical.x;
/* bgo#315462 - For single-line labels, *do* align the requisition with
* respect to the allocation, even if we are under-allocated. For multi-line
* labels, always show the top of the text when they are under-allocated.
* The rationale is this:
*
* - Single-line labels appear in GtkButtons, and it is very easy to get them
* to be smaller than their requisition. The button may clip the label,
* but the label will still be able to show most of itself and the focus
* rectangle. Also, it is fairly easy to read a single line of clipped
* text.
*
* - Multi-line labels should not be clipped to showing "something in the
* middle". You want to read the first line, at least, to get some
* context.
*/
if (pango_layout_get_line_count (layout) == 1)
*y = floor (label_area->y + ypad
+ (label_area->height - req.height) * yalign);
else
*y = floor (label_area->y + ypad
+ MAX (((label_area->height - req.height) * yalign),
0));
}
void
facade_button_render (GtkWidget *base,
cairo_t *cr,
const GdkRectangle *button_area,
gint border_width,
GtkStyle *button_style,
GtkStateType state_type,
GtkStyle *label_style,
const gchar *label,
gint xpad,
gint ypad,
gfloat xalign,
gfloat yalign)
{
GdkRectangle label_area;
PangoLayout *layout;
GtkBorder inset;
gint x, y;
/* Paint the button. */
gtk_paint_box (button_style, cr,
state_type,
GTK_SHADOW_OUT, base, "button",
button_area->x + border_width,
button_area->y + border_width,
button_area->width - border_width * 2,
button_area->height - border_width * 2);
/* Figure out where the label should go. */
facade_button_get_label_inset (border_width, base, button_style, &inset);
inset_rectangle (button_area, &inset, &label_area);
/* Paint the label. */
layout = facade_label_get_layout (base, label);
get_layout_location (base, &label_area, layout, xpad, ypad, xalign, yalign,
&x, &y);
gtk_paint_layout (label_style, cr, state_type, FALSE,
base, "label", x, y, layout);
g_object_unref (layout);
}