1/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
2/* GTK - The GIMP Toolkit
3 * gtk_text_view_child.c Copyright (C) 2019 Red Hat, Inc.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "config.h"
20
21#include "gtkcssnodeprivate.h"
22#include "gtkintl.h"
23#include "gtkprivate.h"
24#include "gtktextview.h"
25#include "gtktextviewchildprivate.h"
26#include "gtktypebuiltins.h"
27#include "gtkwidgetprivate.h"
28
29typedef struct
30{
31 GList link;
32 GtkWidget *widget;
33 int x;
34 int y;
35} Overlay;
36
37struct _GtkTextViewChild
38{
39 GtkWidget parent_instance;
40 GtkTextWindowType window_type;
41 GQueue overlays;
42 int xoffset;
43 int yoffset;
44 GtkWidget *child;
45};
46
47enum {
48 PROP_0,
49 PROP_WINDOW_TYPE,
50 N_PROPS
51};
52
53G_DEFINE_TYPE (GtkTextViewChild, gtk_text_view_child, GTK_TYPE_WIDGET)
54
55static GParamSpec *properties[N_PROPS];
56
57static Overlay *
58overlay_new (GtkWidget *widget,
59 int x,
60 int y)
61{
62 Overlay *overlay;
63
64 overlay = g_slice_new0 (Overlay);
65 overlay->link.data = overlay;
66 overlay->widget = g_object_ref (widget);
67 overlay->x = x;
68 overlay->y = y;
69
70 return overlay;
71}
72
73static void
74overlay_free (Overlay *overlay)
75{
76 g_assert (overlay->link.prev == NULL);
77 g_assert (overlay->link.next == NULL);
78
79 g_object_unref (object: overlay->widget);
80 g_slice_free (Overlay, overlay);
81}
82
83static void
84gtk_text_view_child_remove_overlay (GtkTextViewChild *self,
85 Overlay *overlay)
86{
87 g_queue_unlink (queue: &self->overlays, link_: &overlay->link);
88 gtk_widget_unparent (widget: overlay->widget);
89 overlay_free (overlay);
90}
91
92static Overlay *
93gtk_text_view_child_get_overlay (GtkTextViewChild *self,
94 GtkWidget *widget)
95{
96 GList *iter;
97
98 for (iter = self->overlays.head; iter; iter = iter->next)
99 {
100 Overlay *overlay = iter->data;
101
102 if (overlay->widget == widget)
103 return overlay;
104 }
105
106 return NULL;
107}
108
109void
110gtk_text_view_child_add (GtkTextViewChild *self,
111 GtkWidget *widget)
112{
113 if (self->child != NULL)
114 {
115 g_warning ("%s allows a single child and already contains a %s",
116 G_OBJECT_TYPE_NAME (self),
117 G_OBJECT_TYPE_NAME (widget));
118 return;
119 }
120
121 self->child = g_object_ref (widget);
122 gtk_widget_set_parent (widget, GTK_WIDGET (self));
123}
124
125void
126gtk_text_view_child_remove (GtkTextViewChild *self,
127 GtkWidget *widget)
128{
129 if (widget == self->child)
130 {
131 self->child = NULL;
132 gtk_widget_unparent (widget);
133 g_object_unref (object: widget);
134 }
135 else
136 {
137 Overlay *overlay = gtk_text_view_child_get_overlay (self, widget);
138
139 if (overlay != NULL)
140 gtk_text_view_child_remove_overlay (self, overlay);
141 }
142}
143
144static void
145gtk_text_view_child_measure (GtkWidget *widget,
146 GtkOrientation orientation,
147 int for_size,
148 int *min_size,
149 int *nat_size,
150 int *min_baseline,
151 int *nat_baseline)
152{
153 GtkTextViewChild *self = GTK_TEXT_VIEW_CHILD (ptr: widget);
154 const GList *iter;
155 int real_min_size = 0;
156 int real_nat_size = 0;
157
158 if (self->child != NULL)
159 gtk_widget_measure (widget: self->child,
160 orientation,
161 for_size,
162 minimum: &real_min_size,
163 natural: &real_nat_size,
164 NULL,
165 NULL);
166
167 for (iter = self->overlays.head; iter; iter = iter->next)
168 {
169 Overlay *overlay = iter->data;
170 int child_min_size = 0;
171 int child_nat_size = 0;
172
173 gtk_widget_measure (widget: overlay->widget,
174 orientation,
175 for_size,
176 minimum: &child_min_size,
177 natural: &child_nat_size,
178 NULL,
179 NULL);
180
181 if (child_min_size > real_min_size)
182 real_min_size = child_min_size;
183
184 if (child_nat_size > real_nat_size)
185 real_nat_size = child_nat_size;
186 }
187
188 if (min_size)
189 *min_size = real_min_size;
190
191 if (nat_size)
192 *nat_size = real_nat_size;
193
194 if (min_baseline)
195 *min_baseline = -1;
196
197 if (nat_baseline)
198 *nat_baseline = -1;
199}
200
201static void
202gtk_text_view_child_size_allocate (GtkWidget *widget,
203 int width,
204 int height,
205 int baseline)
206{
207 GtkTextViewChild *self = GTK_TEXT_VIEW_CHILD (ptr: widget);
208 GtkRequisition min_req;
209 GdkRectangle rect;
210 const GList *iter;
211
212 GTK_WIDGET_CLASS (gtk_text_view_child_parent_class)->size_allocate (widget, width, height, baseline);
213
214 if (self->child != NULL)
215 {
216 rect.x = 0;
217 rect.y = 0;
218 rect.width = width;
219 rect.height = height;
220
221 gtk_widget_size_allocate (widget: self->child, allocation: &rect, baseline);
222 }
223
224 for (iter = self->overlays.head; iter; iter = iter->next)
225 {
226 Overlay *overlay = iter->data;
227
228 gtk_widget_get_preferred_size (widget: overlay->widget, minimum_size: &min_req, NULL);
229
230 rect.width = min_req.width;
231 rect.height = min_req.height;
232
233 if (self->window_type == GTK_TEXT_WINDOW_TEXT ||
234 self->window_type == GTK_TEXT_WINDOW_TOP ||
235 self->window_type == GTK_TEXT_WINDOW_BOTTOM)
236 rect.x = overlay->x - self->xoffset;
237 else
238 rect.x = overlay->x;
239
240 if (self->window_type == GTK_TEXT_WINDOW_TEXT ||
241 self->window_type == GTK_TEXT_WINDOW_RIGHT ||
242 self->window_type == GTK_TEXT_WINDOW_LEFT)
243 rect.y = overlay->y - self->yoffset;
244 else
245 rect.y = overlay->y;
246
247 gtk_widget_size_allocate (widget: overlay->widget, allocation: &rect, baseline: -1);
248 }
249}
250
251static void
252gtk_text_view_child_snapshot (GtkWidget *widget,
253 GtkSnapshot *snapshot)
254{
255 GtkTextViewChild *self = GTK_TEXT_VIEW_CHILD (ptr: widget);
256 const GList *iter;
257
258 GTK_WIDGET_CLASS (gtk_text_view_child_parent_class)->snapshot (widget, snapshot);
259
260 if (self->child)
261 gtk_widget_snapshot_child (widget, child: self->child, snapshot);
262
263 for (iter = self->overlays.head; iter; iter = iter->next)
264 {
265 Overlay *overlay = iter->data;
266
267 gtk_widget_snapshot_child (widget, child: overlay->widget, snapshot);
268 }
269}
270
271static void
272gtk_text_view_child_constructed (GObject *object)
273{
274 GtkTextViewChild *self = GTK_TEXT_VIEW_CHILD (ptr: object);
275 GtkCssNode *css_node;
276
277 G_OBJECT_CLASS (gtk_text_view_child_parent_class)->constructed (object);
278
279 css_node = gtk_widget_get_css_node (GTK_WIDGET (self));
280
281 switch (self->window_type)
282 {
283 case GTK_TEXT_WINDOW_LEFT:
284 gtk_css_node_set_name (cssnode: css_node, name: g_quark_from_static_string (string: "border"));
285 gtk_css_node_add_class (cssnode: css_node, style_class: g_quark_from_static_string (string: "left"));
286 break;
287
288 case GTK_TEXT_WINDOW_RIGHT:
289 gtk_css_node_set_name (cssnode: css_node, name: g_quark_from_static_string (string: "border"));
290 gtk_css_node_add_class (cssnode: css_node, style_class: g_quark_from_static_string (string: "right"));
291 break;
292
293 case GTK_TEXT_WINDOW_TOP:
294 gtk_css_node_set_name (cssnode: css_node, name: g_quark_from_static_string (string: "border"));
295 gtk_css_node_add_class (cssnode: css_node, style_class: g_quark_from_static_string (string: "top"));
296 break;
297
298 case GTK_TEXT_WINDOW_BOTTOM:
299 gtk_css_node_set_name (cssnode: css_node, name: g_quark_from_static_string (string: "border"));
300 gtk_css_node_add_class (cssnode: css_node, style_class: g_quark_from_static_string (string: "bottom"));
301 break;
302
303 case GTK_TEXT_WINDOW_TEXT:
304 gtk_css_node_set_name (cssnode: css_node, name: g_quark_from_static_string (string: "child"));
305 break;
306
307 case GTK_TEXT_WINDOW_WIDGET:
308 default:
309 break;
310 }
311}
312
313static void
314gtk_text_view_child_get_property (GObject *object,
315 guint prop_id,
316 GValue *value,
317 GParamSpec *pspec)
318{
319 GtkTextViewChild *self = GTK_TEXT_VIEW_CHILD (ptr: object);
320
321 switch (prop_id)
322 {
323 case PROP_WINDOW_TYPE:
324 g_value_set_enum (value, v_enum: self->window_type);
325 break;
326
327 default:
328 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
329 }
330}
331
332static void
333gtk_text_view_child_set_property (GObject *object,
334 guint prop_id,
335 const GValue *value,
336 GParamSpec *pspec)
337{
338 GtkTextViewChild *self = GTK_TEXT_VIEW_CHILD (ptr: object);
339
340 switch (prop_id)
341 {
342 case PROP_WINDOW_TYPE:
343 self->window_type = g_value_get_enum (value);
344 break;
345
346 default:
347 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
348 }
349}
350
351static void
352gtk_text_view_child_dispose (GObject *object)
353{
354 GtkTextViewChild *self = GTK_TEXT_VIEW_CHILD (ptr: object);
355 GtkWidget *child;
356
357 while ((child = gtk_widget_get_first_child (GTK_WIDGET (self))))
358 gtk_text_view_child_remove (self, widget: child);
359
360 G_OBJECT_CLASS (gtk_text_view_child_parent_class)->dispose (object);
361}
362
363static void
364gtk_text_view_child_class_init (GtkTextViewChildClass *klass)
365{
366 GObjectClass *object_class = G_OBJECT_CLASS (klass);
367 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
368
369 object_class->dispose = gtk_text_view_child_dispose;
370 object_class->constructed = gtk_text_view_child_constructed;
371 object_class->get_property = gtk_text_view_child_get_property;
372 object_class->set_property = gtk_text_view_child_set_property;
373
374 widget_class->measure = gtk_text_view_child_measure;
375 widget_class->size_allocate = gtk_text_view_child_size_allocate;
376 widget_class->snapshot = gtk_text_view_child_snapshot;
377
378 /**
379 * GtkTextViewChild:window-type:
380 *
381 * The "window-type" property is the `GtkTextWindowType` of the
382 * `GtkTextView` that the child is attached.
383 */
384 properties[PROP_WINDOW_TYPE] =
385 g_param_spec_enum (name: "window-type",
386 P_("Window Type"),
387 P_("The GtkTextWindowType"),
388 enum_type: GTK_TYPE_TEXT_WINDOW_TYPE,
389 default_value: GTK_TEXT_WINDOW_TEXT,
390 GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY|G_PARAM_EXPLICIT_NOTIFY);
391
392 g_object_class_install_properties (oclass: object_class, n_pspecs: N_PROPS, pspecs: properties);
393}
394
395static void
396gtk_text_view_child_init (GtkTextViewChild *self)
397{
398 self->window_type = GTK_TEXT_WINDOW_TEXT;
399
400 gtk_widget_set_overflow (GTK_WIDGET (self), overflow: GTK_OVERFLOW_HIDDEN);
401}
402
403GtkWidget *
404gtk_text_view_child_new (GtkTextWindowType window_type)
405{
406 g_return_val_if_fail (window_type == GTK_TEXT_WINDOW_LEFT ||
407 window_type == GTK_TEXT_WINDOW_RIGHT ||
408 window_type == GTK_TEXT_WINDOW_TOP ||
409 window_type == GTK_TEXT_WINDOW_BOTTOM ||
410 window_type == GTK_TEXT_WINDOW_TEXT,
411 NULL);
412
413 return g_object_new (GTK_TYPE_TEXT_VIEW_CHILD,
414 first_property_name: "window-type", window_type,
415 NULL);
416}
417
418void
419gtk_text_view_child_add_overlay (GtkTextViewChild *self,
420 GtkWidget *widget,
421 int xpos,
422 int ypos)
423{
424 Overlay *overlay;
425
426 g_return_if_fail (GTK_IS_TEXT_VIEW_CHILD (self));
427 g_return_if_fail (GTK_IS_WIDGET (widget));
428
429 overlay = overlay_new (widget, x: xpos, y: ypos);
430 g_queue_push_tail (queue: &self->overlays, data: &overlay->link);
431 gtk_widget_set_parent (widget, GTK_WIDGET (self));
432}
433
434void
435gtk_text_view_child_move_overlay (GtkTextViewChild *self,
436 GtkWidget *widget,
437 int xpos,
438 int ypos)
439{
440 Overlay *overlay;
441
442 g_return_if_fail (GTK_IS_TEXT_VIEW_CHILD (self));
443 g_return_if_fail (GTK_IS_WIDGET (widget));
444
445 overlay = gtk_text_view_child_get_overlay (self, widget);
446
447 if (overlay != NULL)
448 {
449 overlay->x = xpos;
450 overlay->y = ypos;
451
452 if (gtk_widget_get_visible (GTK_WIDGET (self)) &&
453 gtk_widget_get_visible (widget))
454 gtk_widget_queue_allocate (GTK_WIDGET (self));
455 }
456}
457
458GtkTextWindowType
459gtk_text_view_child_get_window_type (GtkTextViewChild *self)
460{
461 g_return_val_if_fail (GTK_IS_TEXT_VIEW_CHILD (self), 0);
462
463 return self->window_type;
464}
465
466void
467gtk_text_view_child_set_offset (GtkTextViewChild *self,
468 int xoffset,
469 int yoffset)
470{
471 gboolean changed = FALSE;
472
473 g_return_if_fail (GTK_IS_TEXT_VIEW_CHILD (self));
474
475 if (self->window_type == GTK_TEXT_WINDOW_TEXT ||
476 self->window_type == GTK_TEXT_WINDOW_TOP ||
477 self->window_type == GTK_TEXT_WINDOW_BOTTOM)
478 {
479 if (self->xoffset != xoffset)
480 {
481 self->xoffset = xoffset;
482 changed = TRUE;
483 }
484 }
485
486 if (self->window_type == GTK_TEXT_WINDOW_TEXT ||
487 self->window_type == GTK_TEXT_WINDOW_LEFT ||
488 self->window_type == GTK_TEXT_WINDOW_RIGHT)
489 {
490 if (self->yoffset != yoffset)
491 {
492 self->yoffset = yoffset;
493 changed = TRUE;
494 }
495 }
496
497 if (changed)
498 gtk_widget_queue_draw (GTK_WIDGET (self));
499}
500

source code of gtk/gtk/gtktextviewchild.c