2 * gnm-notebook.c: Implements a button-only notebook.
4 * Copyright (c) 2008 Morten Welinder <terra@gnome.org>
5 * Copyright notices for included gtknotebook.c, see below.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, see <https://www.gnu.org/licenses/>.
21 #include <gnumeric-config.h>
22 #include "gnm-notebook.h"
23 #include <goffice/goffice.h>
25 #include <gsf/gsf-impl-utils.h>
27 /* ------------------------------------------------------------------------- */
29 struct GnmNotebookButton_
{
31 * We need to derive from GtkLabel mostly for theming reasons,
32 * but GtkLabel is also special for clipping.
37 PangoLayout
*layout_active
;
39 PangoRectangle logical
, logical_active
;
40 int x_offset
, x_offset_active
;
46 GtkLabelClass parent_class
;
47 } GnmNotebookButtonClass
;
49 static GObjectClass
*gnm_notebook_button_parent_class
;
53 NBB_PROP_BACKGROUND_COLOR
,
58 gnm_notebook_button_finalize (GObject
*obj
)
60 GnmNotebookButton
*nbb
= GNM_NOTEBOOK_BUTTON (obj
);
61 g_clear_object (&nbb
->layout
);
62 g_clear_object (&nbb
->layout_active
);
63 gdk_rgba_free (nbb
->fg
);
64 gdk_rgba_free (nbb
->bg
);
65 gnm_notebook_button_parent_class
->finalize (obj
);
69 gnm_notebook_button_set_property (GObject
*obj
,
74 GnmNotebookButton
*nbb
= GNM_NOTEBOOK_BUTTON (obj
);
77 case NBB_PROP_BACKGROUND_COLOR
:
78 gdk_rgba_free (nbb
->bg
);
79 nbb
->bg
= g_value_dup_boxed (value
);
80 gtk_widget_queue_draw (GTK_WIDGET (obj
));
81 g_clear_object (&nbb
->layout
);
82 g_clear_object (&nbb
->layout_active
);
84 case NBB_PROP_TEXT_COLOR
:
85 gdk_rgba_free (nbb
->fg
);
86 nbb
->fg
= g_value_dup_boxed (value
);
87 gtk_widget_queue_draw (GTK_WIDGET (obj
));
88 gtk_widget_override_color (GTK_WIDGET (obj
),
89 GTK_STATE_FLAG_NORMAL
,
91 gtk_widget_override_color (GTK_WIDGET (obj
),
92 GTK_STATE_FLAG_ACTIVE
,
96 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj
, prop_id
, pspec
);
102 gnm_notebook_button_ensure_layout (GnmNotebookButton
*nbb
)
104 const char *text
= gtk_label_get_text (GTK_LABEL (nbb
));
107 if (strcmp (text
, pango_layout_get_text (nbb
->layout
)) == 0)
109 pango_layout_set_text (nbb
->layout
, text
, -1);
110 pango_layout_set_text (nbb
->layout_active
, text
, -1);
112 PangoAttrList
*attrs
, *attrs_active
;
113 PangoAttribute
*attr
;
114 PangoFontDescription
*desc
;
115 GtkWidget
*widget
= GTK_WIDGET (nbb
);
116 GtkStyleContext
*context
=
117 gtk_widget_get_style_context (widget
);
119 nbb
->layout
= gtk_widget_create_pango_layout (widget
, text
);
120 nbb
->layout_active
= gtk_widget_create_pango_layout (widget
, text
);
123 attrs
= pango_attr_list_new ();
125 attr
= go_color_to_pango
126 (go_color_from_gdk_rgba (nbb
->bg
, NULL
),
128 attr
->start_index
= 0;
129 attr
->end_index
= -1;
130 pango_attr_list_insert (attrs
, attr
);
132 attrs_active
= pango_attr_list_copy (attrs
);
134 // As-of gtk+ 3.20 we have to set the context state to the state
135 // we are querying for. This ought to work before gtk+ 3.20
137 gtk_style_context_save (context
);
140 gtk_style_context_set_state (context
, GTK_STATE_FLAG_NORMAL
);
141 gtk_style_context_get (context
, GTK_STATE_FLAG_NORMAL
,
142 "font", &desc
, NULL
);
143 attr
= pango_attr_font_desc_new (desc
);
144 attr
->start_index
= 0;
145 attr
->end_index
= -1;
146 pango_attr_list_insert (attrs
, attr
);
147 pango_font_description_free (desc
);
148 pango_layout_set_attributes (nbb
->layout
, attrs
);
149 pango_attr_list_unref (attrs
);
152 gtk_style_context_set_state (context
, GTK_STATE_FLAG_ACTIVE
);
153 gtk_style_context_get (context
, GTK_STATE_FLAG_ACTIVE
,
154 "font", &desc
, NULL
);
155 attr
= pango_attr_font_desc_new (desc
);
156 attr
->start_index
= 0;
157 attr
->end_index
= -1;
158 pango_attr_list_insert (attrs_active
, attr
);
159 pango_font_description_free (desc
);
160 pango_layout_set_attributes (nbb
->layout_active
, attrs_active
);
161 pango_attr_list_unref (attrs_active
);
163 gtk_style_context_restore (context
);
166 pango_layout_get_extents (nbb
->layout
, NULL
, &nbb
->logical
);
167 pango_layout_get_extents (nbb
->layout_active
, NULL
, &nbb
->logical_active
);
171 gnm_notebook_button_screen_changed (GtkWidget
*widget
, G_GNUC_UNUSED GdkScreen
*prev
)
173 GnmNotebookButton
*nbb
= GNM_NOTEBOOK_BUTTON (widget
);
174 g_clear_object (&nbb
->layout
);
175 g_clear_object (&nbb
->layout_active
);
179 gnm_notebook_button_draw (GtkWidget
*widget
, cairo_t
*cr
)
181 GnmNotebookButton
*nbb
= GNM_NOTEBOOK_BUTTON (widget
);
182 GnmNotebook
*nb
= GNM_NOTEBOOK (gtk_widget_get_parent (widget
));
183 GtkStyleContext
*context
= gtk_widget_get_style_context (widget
);
184 gboolean is_active
= (widget
== gnm_notebook_get_current_label (nb
));
185 GtkStateFlags state
=
186 is_active
? GTK_STATE_FLAG_ACTIVE
: GTK_STATE_FLAG_NORMAL
;
189 gtk_style_context_save (context
);
190 gtk_style_context_set_state (context
, state
);
192 gtk_style_context_get_padding (context
, state
, &padding
);
194 gnm_notebook_button_ensure_layout (nbb
);
196 gtk_render_layout (context
, cr
,
197 padding
.left
+ (is_active
? nbb
->x_offset_active
: nbb
->x_offset
),
199 is_active
? nbb
->layout_active
: nbb
->layout
);
201 gtk_style_context_restore (context
);
205 static GtkSizeRequestMode
206 gnm_notebook_button_get_request_mode (GtkWidget
*widget
)
208 return GTK_SIZE_REQUEST_CONSTANT_SIZE
;
212 gnm_notebook_button_get_preferred_height (GtkWidget
*widget
,
216 GnmNotebookButton
*nbb
= GNM_NOTEBOOK_BUTTON (widget
);
218 GtkStyleContext
*ctxt
= gtk_widget_get_style_context (widget
);
220 // As-of gtk+ 3.20 we have to set the context state to the state
221 // we are querying for. This ought to work before gtk+ 3.20 too.
222 gtk_style_context_save (ctxt
);
223 gtk_style_context_set_state (ctxt
, GTK_STATE_FLAG_NORMAL
);
224 gtk_style_context_get_padding (ctxt
,
225 GTK_STATE_FLAG_NORMAL
,
227 gtk_style_context_restore (ctxt
);
229 gnm_notebook_button_ensure_layout (nbb
);
231 *minimum
= *natural
=
233 PANGO_PIXELS_CEIL (MAX (nbb
->logical
.height
,
234 nbb
->logical_active
.height
)) +
239 gnm_notebook_button_get_preferred_width (GtkWidget
*widget
,
243 GnmNotebookButton
*nbb
= GNM_NOTEBOOK_BUTTON (widget
);
245 GtkStyleContext
*ctxt
= gtk_widget_get_style_context (widget
);
247 // As-of gtk+ 3.20 we have to set the context state to the state
248 // we are querying for. This ought to work before gtk+ 3.20 too.
249 gtk_style_context_save (ctxt
);
250 gtk_style_context_set_state (ctxt
, GTK_STATE_FLAG_NORMAL
);
251 gtk_style_context_get_padding (ctxt
,
252 GTK_STATE_FLAG_NORMAL
,
254 gtk_style_context_restore (ctxt
);
256 gnm_notebook_button_ensure_layout (nbb
);
258 *minimum
= *natural
=
260 PANGO_PIXELS_CEIL (MAX (nbb
->logical
.width
,
261 nbb
->logical_active
.width
)) +
266 gnm_notebook_button_size_allocate (GtkWidget
*widget
,
267 GtkAllocation
*allocation
)
269 GnmNotebookButton
*nbb
= GNM_NOTEBOOK_BUTTON (widget
);
271 gnm_notebook_button_ensure_layout (nbb
);
273 (allocation
->width
- PANGO_PIXELS (nbb
->logical
.width
)) / 2;
274 nbb
->x_offset_active
=
275 (allocation
->width
- PANGO_PIXELS (nbb
->logical_active
.width
)) / 2;
277 GTK_WIDGET_CLASS(gnm_notebook_button_parent_class
)
278 ->size_allocate (widget
, allocation
);
282 gnm_notebook_button_class_init (GObjectClass
*klass
)
284 GtkWidgetClass
*wclass
= (GtkWidgetClass
*)klass
;
286 gnm_notebook_button_parent_class
= g_type_class_peek_parent (klass
);
287 klass
->finalize
= gnm_notebook_button_finalize
;
288 klass
->set_property
= gnm_notebook_button_set_property
;
290 g_object_class_install_property
292 NBB_PROP_BACKGROUND_COLOR
,
293 g_param_spec_boxed ("background-color",
294 P_("Background Color"),
295 P_("Override color to use for background"),
298 g_object_class_install_property
301 g_param_spec_boxed ("text-color",
303 P_("Override color to use for label"),
307 wclass
->draw
= gnm_notebook_button_draw
;
308 wclass
->screen_changed
= gnm_notebook_button_screen_changed
;
309 wclass
->get_request_mode
= gnm_notebook_button_get_request_mode
;
310 wclass
->get_preferred_width
= gnm_notebook_button_get_preferred_width
;
311 wclass
->get_preferred_height
= gnm_notebook_button_get_preferred_height
;
312 wclass
->size_allocate
= gnm_notebook_button_size_allocate
;
316 gnm_notebook_button_init (GnmNotebookButton
*nbb
)
320 GSF_CLASS (GnmNotebookButton
, gnm_notebook_button
,
321 gnm_notebook_button_class_init
,
322 gnm_notebook_button_init
, GTK_TYPE_LABEL
)
327 /* ------------------------------------------------------------------------- */
329 struct _GnmNotebook
{
333 * This is the number of pixels from a regular notebook that
334 * we are not drawing. It is caused by the empty widgets
335 * that we have to use.
341 GtkNotebookClass parent_class
;
344 static GtkNotebookClass
*gnm_notebook_parent_class
;
346 #define DUMMY_KEY "GNM-NOTEBOOK-DUMMY-WIDGET"
349 gnm_notebook_size_allocate (GtkWidget
*widget
,
350 GtkAllocation
*allocation
)
353 GnmNotebook
*gnb
= (GnmNotebook
*)widget
;
354 GtkAllocation alc
= *allocation
;
356 for (i
= 0; TRUE
; i
++) {
357 GtkWidget
*page
= gtk_notebook_get_nth_page (GTK_NOTEBOOK (widget
), i
);
361 if (!gtk_widget_get_visible (page
))
363 gtk_widget_get_allocation (page
, &a
);
364 h
= MAX (h
, a
.height
);
367 gnb
->dummy_height
= h
;
370 ((GtkWidgetClass
*)gnm_notebook_parent_class
)->size_allocate
375 gnm_notebook_button_press (GtkWidget
*widget
,
376 GdkEventButton
*event
)
378 GnmNotebook
*nb
= GNM_NOTEBOOK (widget
);
381 for (ui
= 0; /* Nothing */; ui
++) {
382 GtkWidget
*child
= gnm_notebook_get_nth_label (nb
, ui
);
383 GtkAllocation child_allocation
;
388 if (!gtk_widget_get_child_visible (child
))
391 gtk_widget_get_allocation (child
, &child_allocation
);
393 if (event
->x
>= child_allocation
.x
&&
394 event
->x
< child_allocation
.x
+ child_allocation
.width
&&
395 event
->y
>= child_allocation
.y
&&
396 event
->y
< child_allocation
.y
+ child_allocation
.height
) {
398 g_printerr ("Button %d pressed\n", ui
);
399 if (gtk_widget_event (child
, (GdkEvent
*)event
))
406 return GTK_WIDGET_CLASS(gnm_notebook_parent_class
)
407 ->button_press_event (widget
, event
);
411 gnm_notebook_child_type (G_GNUC_UNUSED GtkContainer
*container
)
413 return GNM_NOTEBOOK_BUTTON_TYPE
;
417 gnm_notebook_class_init (GtkWidgetClass
*klass
)
419 GtkWidgetClass
*wclass
= (GtkWidgetClass
*)klass
;
420 GtkContainerClass
*cclass
= (GtkContainerClass
*)klass
;
422 gnm_notebook_parent_class
= g_type_class_peek (GTK_TYPE_NOTEBOOK
);
423 klass
->size_allocate
= gnm_notebook_size_allocate
;
425 cclass
->child_type
= gnm_notebook_child_type
;
427 wclass
->button_press_event
= gnm_notebook_button_press
;
431 gnm_notebook_init (GnmNotebook
*notebook
)
433 gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook
), TRUE
);
434 gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook
), GTK_POS_BOTTOM
);
435 gtk_notebook_set_show_border (GTK_NOTEBOOK (notebook
), FALSE
);
436 gtk_notebook_set_group_name (GTK_NOTEBOOK (notebook
), "Gnumeric");
439 GSF_CLASS (GnmNotebook
, gnm_notebook
,
440 gnm_notebook_class_init
, gnm_notebook_init
, GTK_TYPE_NOTEBOOK
)
443 gnm_notebook_get_n_visible (GnmNotebook
*nb
)
446 GList
*l
, *children
= gtk_container_get_children (GTK_CONTAINER (nb
));
448 for (l
= children
; l
; l
= l
->next
) {
449 GtkWidget
*child
= l
->data
;
450 if (gtk_widget_get_visible (child
))
454 g_list_free (children
);
460 gnm_notebook_get_nth_label (GnmNotebook
*nb
, int n
)
464 g_return_val_if_fail (GNM_IS_NOTEBOOK (nb
), NULL
);
466 page
= gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb
), n
);
470 return gtk_notebook_get_tab_label (GTK_NOTEBOOK (nb
), page
);
474 gnm_notebook_get_current_label (GnmNotebook
*nb
)
478 g_return_val_if_fail (GNM_IS_NOTEBOOK (nb
), NULL
);
480 i
= gtk_notebook_get_current_page (GTK_NOTEBOOK (nb
));
481 return i
== -1 ? NULL
: gnm_notebook_get_nth_label (nb
, i
);
485 cb_label_destroyed (G_GNUC_UNUSED GtkWidget
*label
, GtkWidget
*dummy
)
487 gtk_widget_destroy (dummy
);
491 cb_label_visibility (GtkWidget
*label
,
492 G_GNUC_UNUSED GParamSpec
*pspec
,
495 gtk_widget_set_visible (dummy
, gtk_widget_get_visible (label
));
499 gnm_notebook_insert_tab (GnmNotebook
*nb
, GtkWidget
*label
, int pos
)
501 GtkWidget
*dummy_page
= gtk_box_new (GTK_ORIENTATION_HORIZONTAL
, 0);
502 gtk_widget_set_size_request (dummy_page
, 1, 1);
504 g_object_set_data (G_OBJECT (label
), DUMMY_KEY
, dummy_page
);
506 g_signal_connect_object (G_OBJECT (label
), "destroy",
507 G_CALLBACK (cb_label_destroyed
), dummy_page
,
510 cb_label_visibility (label
, NULL
, dummy_page
);
511 g_signal_connect_object (G_OBJECT (label
), "notify::visible",
512 G_CALLBACK (cb_label_visibility
), dummy_page
,
515 gtk_notebook_insert_page (GTK_NOTEBOOK (nb
), dummy_page
, label
, pos
);
517 gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (nb
), dummy_page
,
522 gnm_notebook_move_tab (GnmNotebook
*nb
, GtkWidget
*label
, int newpos
)
524 GtkWidget
*child
= g_object_get_data (G_OBJECT (label
), DUMMY_KEY
);
525 gtk_notebook_reorder_child (GTK_NOTEBOOK (nb
), child
, newpos
);
529 gnm_notebook_set_current_page (GnmNotebook
*nb
, int page
)
531 gtk_notebook_set_current_page (GTK_NOTEBOOK (nb
), page
);
535 gnm_notebook_prev_page (GnmNotebook
*nb
)
537 gtk_notebook_prev_page (GTK_NOTEBOOK (nb
));
541 gnm_notebook_next_page (GnmNotebook
*nb
)
543 gtk_notebook_next_page (GTK_NOTEBOOK (nb
));