doc: update copyright line for 2021
[adg.git] / src / adg / adg-gtk-layout.c
blob2506f4fe13c8e5e05f79cb95ffe401423e0c1dec
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2021 Nicola Fontana <ntd at entidi.it>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
21 /**
22 * SECTION:adg-gtk-layout
23 * @short_description: A scrollable AdgGtkArea based widget
25 * This is an #AdgGtkArea derived object with scrolling capabilities.
26 * It means an #AdgGtkLayout object can be added directly to a
27 * #GtkScrolledWindow container without the need for an intermediate
28 * #GtkViewport container.
30 * Since: 1.0
31 **/
33 /**
34 * AdgGtkLayout:
36 * All fields are private and should not be used directly.
37 * Use its public methods instead.
39 * Since: 1.0
40 **/
43 #include "adg-internal.h"
44 #include <gtk/gtk.h>
46 #include "adg-container.h"
47 #include "adg-table.h"
48 #include "adg-title-block.h"
49 #include <adg-canvas.h>
50 #include "adg-gtk-area.h"
52 #include "adg-gtk-layout.h"
53 #include "adg-gtk-layout-private.h"
55 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_gtk_layout_parent_class)
56 #define _ADG_OLD_WIDGET_CLASS ((GtkWidgetClass *) adg_gtk_layout_parent_class)
57 #define _ADG_OLD_AREA_CLASS ((AdgGtkAreaClass *) adg_gtk_layout_parent_class)
59 #ifdef GTK2_ENABLED
60 enum {
61 PROP_0,
62 PROP_HADJUSTMENT,
63 PROP_VADJUSTMENT
66 G_DEFINE_TYPE_WITH_PRIVATE(AdgGtkLayout, adg_gtk_layout, ADG_GTK_TYPE_AREA)
68 static void
69 _adg_set_scroll_adjustments(GtkWidget *widget,
70 GtkAdjustment *hadjustment,
71 GtkAdjustment *vadjustment)
73 g_object_set(widget,
74 "hadjustment", hadjustment,
75 "vadjustment", vadjustment,
76 NULL);
78 #endif
80 #ifdef GTK3_ENABLED
81 enum {
82 PROP_0,
83 PROP_HADJUSTMENT,
84 PROP_VADJUSTMENT,
85 PROP_HSCROLL_POLICY,
86 PROP_VSCROLL_POLICY
89 G_DEFINE_TYPE_WITH_CODE(AdgGtkLayout, adg_gtk_layout, ADG_GTK_TYPE_AREA,
90 G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, NULL)
91 G_ADD_PRIVATE(AdgGtkLayout))
92 #endif
95 static void
96 _adg_set_parent_size(AdgGtkLayout *layout)
98 GtkWidget *widget;
99 AdgGtkLayoutPrivate *data;
100 GtkWidget *parent;
101 const CpmlExtents *sheet;
102 GtkScrolledWindow *scrolled_window;
104 widget = (GtkWidget *) layout;
106 /* When the widget is realized it is too late to suggest a size */
107 if (gtk_widget_get_realized(widget))
108 return;
110 data = adg_gtk_layout_get_instance_private(layout);
111 parent = gtk_widget_get_parent(widget);
112 if (!GTK_IS_WIDGET(parent))
113 return;
115 sheet = adg_gtk_area_get_extents((AdgGtkArea *) layout);
116 if (sheet == NULL || !sheet->is_defined)
117 return;
119 gtk_widget_set_size_request(parent, sheet->size.x + 2, sheet->size.y + 2);
121 if (GTK_IS_SCROLLED_WINDOW(parent) && !data->policy_stored) {
122 scrolled_window = (GtkScrolledWindow *) parent;
124 gtk_scrolled_window_get_policy(scrolled_window,
125 &data->hpolicy, &data->vpolicy);
126 gtk_scrolled_window_set_policy(scrolled_window,
127 GTK_POLICY_NEVER, GTK_POLICY_NEVER);
129 data->policy_stored = TRUE;
133 static void
134 _adg_parent_set(GtkWidget *widget, GtkWidget *old_parent)
136 AdgGtkLayout *layout = (AdgGtkLayout *) widget;
138 if (_ADG_OLD_WIDGET_CLASS->parent_set != NULL)
139 _ADG_OLD_WIDGET_CLASS->parent_set(widget, old_parent);
141 _adg_set_parent_size(layout);
145 * _adg_update_adjustments:
146 * @layout: an #AdgGtkLayout
148 * Updates the scrollbars according to the new extents of the canvas
149 * of @area and to the current viewport.
151 * The algorithm uses three local #CpmlExtents variables: the
152 * <var>viewport</var> (what physically shown by the graphic device),
153 * the <var>sheet</var> (the extents of the drawing, margins
154 * included) and <var>surface</var> (a helper variable that is the
155 * union of the previous two extents).
157 static void
158 _adg_update_adjustments(AdgGtkLayout *layout)
160 AdgGtkArea *area = (AdgGtkArea *) layout;
161 AdgCanvas *canvas = adg_gtk_area_get_canvas(area);
162 const CpmlExtents *sheet, *viewport;
163 AdgGtkLayoutPrivate *data;
164 GtkAdjustment *hadj, *vadj;
165 CpmlExtents surface;
167 if (canvas == NULL)
168 return;
170 sheet = adg_gtk_area_get_extents(area);
171 if (sheet == NULL || !sheet->is_defined)
172 return;
174 data = adg_gtk_layout_get_instance_private(layout);
175 hadj = data->hadjustment;
176 vadj = data->vadjustment;
177 viewport = &data->viewport;
178 surface = *sheet;
179 cpml_extents_add(&surface, viewport);
181 if (data->policy_stored) {
182 /* Restore the original policy for the scrollbars */
183 GtkWidget *parent;
184 GtkScrolledWindow *scrolled_window;
186 parent = gtk_widget_get_parent((GtkWidget *) layout);
187 scrolled_window = (GtkScrolledWindow *) parent;
189 gtk_scrolled_window_set_policy(scrolled_window,
190 data->hpolicy, data->vpolicy);
192 data->policy_stored = TRUE;
195 g_object_set(hadj,
196 "lower", surface.org.x,
197 "upper", surface.org.x + surface.size.x,
198 "page-size", viewport->size.x,
199 "value", viewport->org.x,
200 NULL);
201 g_object_set(vadj,
202 "lower", surface.org.y,
203 "upper", surface.org.y + surface.size.y,
204 "page-size", viewport->size.y,
205 "value", viewport->org.y,
206 NULL);
209 static void
210 _adg_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
212 AdgGtkLayout *layout = (AdgGtkLayout *) widget;
213 AdgGtkLayoutPrivate *data = adg_gtk_layout_get_instance_private(layout);
215 if (_ADG_OLD_WIDGET_CLASS->size_allocate != NULL)
216 _ADG_OLD_WIDGET_CLASS->size_allocate(widget, allocation);
218 /* Resize the viewport on a new allocation.
219 * TODO: plan other policies instead of forcibly set only the
220 * size field on the viewport struct, such as modifying the
221 * org to keep the sheet centered in the allocation space. */
222 data->viewport.size.x = allocation->width;
223 data->viewport.size.y = allocation->height;
224 data->viewport.is_defined = TRUE;
226 _adg_update_adjustments(layout);
229 static void
230 _adg_canvas_changed(AdgGtkArea *area, AdgCanvas *old_canvas)
232 AdgGtkLayout *layout = (AdgGtkLayout *) area;
233 AdgGtkLayoutPrivate *data = adg_gtk_layout_get_instance_private(layout);
235 if (_ADG_OLD_AREA_CLASS->canvas_changed != NULL)
236 _ADG_OLD_AREA_CLASS->canvas_changed(area, old_canvas);
238 /* By convention, expect the origin of a new canvas to be
239 * on the top/left corner of the allocation area */
240 data->viewport.org.x = 0;
241 data->viewport.org.y = 0;
243 _adg_set_parent_size(layout);
246 static void
247 _adg_extents_changed(AdgGtkArea *area, const CpmlExtents *old_extents)
249 if (_ADG_OLD_AREA_CLASS->extents_changed != NULL)
250 _ADG_OLD_AREA_CLASS->extents_changed(area, old_extents);
252 _adg_update_adjustments((AdgGtkLayout *) area);
255 static void
256 _adg_value_changed(AdgGtkLayout *layout)
258 GtkWidget *widget;
259 AdgGtkArea *area;
260 AdgGtkLayoutPrivate *data;
261 CpmlPair org;
262 cairo_matrix_t map;
264 widget = (GtkWidget *) layout;
266 if (!gtk_widget_get_realized(widget))
267 return;
269 area = (AdgGtkArea *) layout;
270 data = adg_gtk_layout_get_instance_private(layout);
271 org.x = gtk_adjustment_get_value(data->hadjustment);
272 org.y = gtk_adjustment_get_value(data->vadjustment);
274 cairo_matrix_init_translate(&map, data->viewport.org.x - org.x,
275 data->viewport.org.y - org.y);
276 adg_gtk_area_transform_render_map(area, &map, ADG_TRANSFORM_BEFORE);
278 gtk_widget_queue_draw(widget);
279 _adg_update_adjustments(layout);
282 static void
283 _adg_set_adjustment(AdgGtkLayout *layout,
284 GtkAdjustment **dst, GtkAdjustment *src)
286 GCallback value_changed;
288 if (*dst == src)
289 return;
291 value_changed = G_CALLBACK(_adg_value_changed);
293 if (*dst != NULL) {
294 /* Release the old adjustment */
295 g_signal_handlers_disconnect_by_func(*dst, (gpointer) value_changed, layout);
296 g_object_unref(*dst);
299 g_signal_connect_swapped(src, "value-changed", value_changed, layout);
300 g_object_ref_sink(src);
302 *dst = src;
305 static void
306 _adg_dispose(GObject *object)
308 AdgGtkLayoutPrivate *data = adg_gtk_layout_get_instance_private((AdgGtkLayout *) object);
310 if (data->hadjustment != NULL) {
311 g_object_unref(data->hadjustment);
312 data->hadjustment = NULL;
315 if (data->vadjustment != NULL) {
316 g_object_unref(data->vadjustment);
317 data->vadjustment = NULL;
320 if (_ADG_OLD_OBJECT_CLASS->dispose != NULL)
321 _ADG_OLD_OBJECT_CLASS->dispose(object);
324 static void
325 _adg_get_property(GObject *object, guint prop_id,
326 GValue *value, GParamSpec *pspec)
328 AdgGtkLayoutPrivate *data = adg_gtk_layout_get_instance_private((AdgGtkLayout *) object);
330 switch (prop_id) {
331 case PROP_HADJUSTMENT:
332 g_value_set_object(value, data->hadjustment);
333 break;
334 case PROP_VADJUSTMENT:
335 g_value_set_object(value, data->vadjustment);
336 break;
337 #ifdef GTK3_ENABLED
338 case PROP_HSCROLL_POLICY:
339 g_value_set_enum(value, data->hpolicy);
340 break;
341 case PROP_VSCROLL_POLICY:
342 g_value_set_enum(value, data->vpolicy);
343 break;
344 #endif
345 default:
346 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
347 break;
351 static void
352 _adg_set_property(GObject *object, guint prop_id,
353 const GValue *value, GParamSpec *pspec)
355 AdgGtkLayout *layout = (AdgGtkLayout *) object;
356 AdgGtkLayoutPrivate *data = adg_gtk_layout_get_instance_private(layout);
357 GtkAdjustment *adjustment;
359 switch (prop_id) {
360 case PROP_HADJUSTMENT:
361 adjustment = g_value_get_object(value);
362 if (adjustment == NULL)
363 adjustment = (GtkAdjustment *) gtk_adjustment_new(0, 0, 0, 0, 0, 0);
365 _adg_set_adjustment(layout, &data->hadjustment, adjustment);
366 break;
367 case PROP_VADJUSTMENT:
368 adjustment = g_value_get_object(value);
369 if (adjustment == NULL)
370 adjustment = (GtkAdjustment *) gtk_adjustment_new(0, 0, 0, 0, 0, 0);
372 _adg_set_adjustment(layout, &data->vadjustment, adjustment);
373 break;
374 #ifdef GTK3_ENABLED
375 case PROP_HSCROLL_POLICY:
376 data->hpolicy = g_value_get_enum(value);
377 break;
378 case PROP_VSCROLL_POLICY:
379 data->vpolicy = g_value_get_enum(value);
380 break;
381 #endif
382 default:
383 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
384 break;
388 static void
389 adg_gtk_layout_class_init(AdgGtkLayoutClass *klass)
391 GObjectClass *gobject_class;
392 GtkWidgetClass *widget_class;
393 AdgGtkAreaClass *area_class;
395 gobject_class = (GObjectClass *) klass;
396 widget_class = (GtkWidgetClass *) klass;
397 area_class = (AdgGtkAreaClass *) klass;
399 gobject_class->dispose = _adg_dispose;
400 gobject_class->get_property = _adg_get_property;
401 gobject_class->set_property = _adg_set_property;
403 widget_class->parent_set = _adg_parent_set;
404 widget_class->size_allocate = _adg_size_allocate;
406 area_class->canvas_changed = _adg_canvas_changed;
407 area_class->extents_changed = _adg_extents_changed;
409 #ifdef GTK2_ENABLED
410 g_object_class_install_property(gobject_class, PROP_HADJUSTMENT,
411 g_param_spec_object("hadjustment",
412 P_("Horizontal adjustment"),
413 P_("The GtkAdjustment that determines the values of the horizontal position for this viewport"),
414 GTK_TYPE_ADJUSTMENT,
415 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
417 g_object_class_install_property(gobject_class, PROP_VADJUSTMENT,
418 g_param_spec_object("vadjustment",
419 P_("Vertical adjustment"),
420 P_("The GtkAdjustment that determines the values of the vertical position for this viewport"),
421 GTK_TYPE_ADJUSTMENT,
422 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
425 * AdgGtkLayout::set-scroll-adjustments:
426 * @layout: an #AdgGtkLayout
427 * @hadjustment: the horizontal #GtkAdjustment
428 * @vadjustment: the vertical #GtkAdjustment
430 * Emitted when the adjustments of the scroll bars must be changed.
432 * Since: 1.0
434 widget_class->set_scroll_adjustments_signal =
435 g_signal_new_class_handler("set-scroll-adjustments", ADG_GTK_TYPE_LAYOUT,
436 G_SIGNAL_RUN_LAST,
437 G_CALLBACK(_adg_set_scroll_adjustments),
438 NULL, NULL,
439 adg_marshal_VOID__OBJECT_OBJECT,
440 G_TYPE_NONE, 2,
441 GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
442 #endif
444 #ifdef GTK3_ENABLED
445 g_object_class_override_property(gobject_class, PROP_HADJUSTMENT,
446 "hadjustment");
447 g_object_class_override_property(gobject_class, PROP_HSCROLL_POLICY,
448 "hscroll-policy");
449 g_object_class_override_property(gobject_class, PROP_VADJUSTMENT,
450 "vadjustment");
451 g_object_class_override_property(gobject_class, PROP_VSCROLL_POLICY,
452 "vscroll-policy");
453 #endif
456 static void
457 adg_gtk_layout_init(AdgGtkLayout *layout)
459 AdgGtkLayoutPrivate *data = adg_gtk_layout_get_instance_private(layout);
460 data->hadjustment = NULL;
461 data->vadjustment = NULL;
462 data->policy_stored = FALSE;
463 data->viewport.is_defined = FALSE;
468 * adg_gtk_layout_new:
470 * Creates a new empty #AdgGtkLayout. The widget is useful only after
471 * an #AdgCanvas has been added either using the #AdgGtkArea:canvas
472 * property or with adg_gtk_area_set_canvas().
474 * Returns: (transfer full): the newly created widget.
476 * Since: 1.0
478 GtkWidget *
479 adg_gtk_layout_new(void)
481 return g_object_new(ADG_GTK_TYPE_LAYOUT, NULL);
485 * adg_gtk_layout_new_with_canvas:
486 * @canvas: the #AdgCanvas shown by this widget
488 * Creates a new #AdgGtkLayout and sets the #AdgGtkArea:canvas
489 * property to @canvas.
491 * Returns: (transfer full): the newly created widget.
493 * Since: 1.0
495 GtkWidget *
496 adg_gtk_layout_new_with_canvas(AdgCanvas *canvas)
498 g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL);
500 return g_object_new(ADG_GTK_TYPE_LAYOUT, "canvas", canvas, NULL);
504 * adg_gtk_layout_set_hadjustment:
505 * @layout: an #AdgGtkLayout
506 * @hadjustment: the new adjustment
508 * Sets the new horizontal adjustment for @layout to @hadjustment.
509 * The old adjustment, if present, is unreferenced.
511 * This is basically the same as manually setting the
512 * #GtkScrollable:hadjustment property with g_object_set().
514 * Since: 1.0
516 void
517 adg_gtk_layout_set_hadjustment(AdgGtkLayout *layout,
518 GtkAdjustment *hadjustment)
520 g_return_if_fail(ADG_GTK_IS_LAYOUT(layout));
521 g_object_set(layout, "hadjustment", hadjustment, NULL);
525 * adg_gtk_layout_get_hadjustment:
526 * @layout: an #AdgGtkLayout
528 * Retrieves the current horizontal adjustment of @layout.
530 * The returned alignment is owned by @layout and should
531 * not be modified or freed.
533 * Returns: (transfer none): the alignment of @layout.
535 * Since: 1.0
537 GtkAdjustment *
538 adg_gtk_layout_get_hadjustment(AdgGtkLayout *layout)
540 AdgGtkLayoutPrivate *data;
542 g_return_val_if_fail(ADG_GTK_IS_LAYOUT(layout), NULL);
544 data = adg_gtk_layout_get_instance_private(layout);
545 return data->hadjustment;
549 * adg_gtk_layout_set_vadjustment:
550 * @layout: an #AdgGtkLayout
551 * @vadjustment: the new adjustment
553 * Sets the new vertical adjustment for @layout to @vadjustment.
554 * The old adjustment, if present, is unreferenced.
556 * This is basically the same as manually setting the
557 * #GtkScrollable:vadjustment property with g_object_set().
559 * Since: 1.0
561 void
562 adg_gtk_layout_set_vadjustment(AdgGtkLayout *layout,
563 GtkAdjustment *vadjustment)
565 g_return_if_fail(ADG_GTK_IS_LAYOUT(layout));
566 g_object_set(layout, "vadjustment", vadjustment, NULL);
570 * adg_gtk_layout_get_vadjustment:
571 * @layout: an #AdgGtkLayout
573 * Retrieves the current vertical adjustment of @layout.
575 * The returned alignment is owned by @layout and should
576 * not be modified or freed.
578 * Returns: (transfer none): the alignment of @layout.
580 * Since: 1.0
582 GtkAdjustment *
583 adg_gtk_layout_get_vadjustment(AdgGtkLayout *layout)
585 AdgGtkLayoutPrivate *data;
587 g_return_val_if_fail(ADG_GTK_IS_LAYOUT(layout), NULL);
589 data = adg_gtk_layout_get_instance_private(layout);
590 return data->vadjustment;