/* 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); }