From f9035ac5308961fa6957894b5660bdb3adad60d0 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Mon, 23 Apr 2012 22:43:13 -0700 Subject: [PATCH] pspp-widget-facade: New code to measure and render some GTK+ widgets. There are two upcoming uses for this in the tree, both related to rendering fake buttons. First, creating, maintaining, and rendering the column header buttons in a PsppSheetView is a huge cost when there are hundreds or thousands of columns. Using this facade code to "fake" buttons makes the sheet much faster. Second, this code will be used to implement a cell renderer for GtkButton, so that buttons can be put into PsppSheetView rows. --- src/ui/gui/automake.mk | 2 + src/ui/gui/pspp-widget-facade.c | 357 ++++++++++++++++++++++++++++++++ src/ui/gui/pspp-widget-facade.h | 87 ++++++++ 3 files changed, 446 insertions(+) create mode 100644 src/ui/gui/pspp-widget-facade.c create mode 100644 src/ui/gui/pspp-widget-facade.h diff --git a/src/ui/gui/automake.mk b/src/ui/gui/automake.mk index 778e0720fa..e184975e5c 100644 --- a/src/ui/gui/automake.mk +++ b/src/ui/gui/automake.mk @@ -140,6 +140,8 @@ src_ui_gui_psppire_SOURCES = \ src/ui/gui/pspp-sheet-view-column.h \ src/ui/gui/pspp-sheet-view.c \ src/ui/gui/pspp-sheet-view.h \ + src/ui/gui/pspp-widget-facade.c \ + src/ui/gui/pspp-widget-facade.h \ src/ui/gui/psppire-dialog.c \ src/ui/gui/psppire-keypad.c \ src/ui/gui/psppire-selector.c \ diff --git a/src/ui/gui/pspp-widget-facade.c b/src/ui/gui/pspp-widget-facade.c new file mode 100644 index 0000000000..ee065919ce --- /dev/null +++ b/src/ui/gui/pspp-widget-facade.c @@ -0,0 +1,357 @@ +/* PSPPIRE - a graphical user interface for PSPP. + Copyright (C) 2011, 2012 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_LTR) + xalign = xalign; + else + 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, + GdkDrawable *window, + GdkRectangle *expose_area, + + 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, window, + state_type, + GTK_SHADOW_OUT, expose_area, 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, window, state_type, FALSE, expose_area, + base, "label", x, y, layout); + g_object_unref (layout); +} + diff --git a/src/ui/gui/pspp-widget-facade.h b/src/ui/gui/pspp-widget-facade.h new file mode 100644 index 0000000000..6583d329ca --- /dev/null +++ b/src/ui/gui/pspp-widget-facade.h @@ -0,0 +1,87 @@ +/* PSPPIRE - a graphical user interface for PSPP. + Copyright (C) 2011, 2012 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 . */ + +#ifndef FACADE_WIDGET_FACADE_H +#define FACADE_WIDGET_FACADE_H 1 + +#include + +G_BEGIN_DECLS + +GtkStyle *facade_get_style (GtkWidget *base, GType, ...); + +void facade_hbox_get_base_size_request (gint border_width, + gint spacing, + gint n_children, + GtkRequisition *); +void facade_hbox_add_child_size_request (gint hbox_border_width, + const GtkRequisition *child_request, + gint child_padding, + GtkRequisition *); + +void facade_arrow_get_size_request (gint xpad, + gint ypad, + GtkRequisition *); + + +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 *); + +void facade_label_get_size_request (gint xpad, + gint ypad, + GtkWidget *base, + const char *text, + GtkRequisition *); +void facade_label_get_size_request_from_layout (gint xpad, + gint ypad, + PangoLayout *, + GtkRequisition *); +PangoLayout *facade_label_get_layout (GtkWidget *base, + const char *text); + +void facade_button_get_size_request (gint border_width, + GtkWidget *base, + GtkStyle *button_style, + const GtkRequisition *child_request, + GtkRequisition *); +void facade_button_render (GtkWidget *base, + GdkDrawable *window, + GdkRectangle *expose_area, + + 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); +void facade_button_get_focus_inset (gint border_width, + GtkWidget *base, + GtkStyle *button_style, + GtkBorder *focus_inset); + +G_END_DECLS + +#endif /* FACADE_WIDGET_FACADE_H */ -- 2.30.2