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 
29 typedef struct
30 {
31   GList      link;
32   GtkWidget *widget;
33   int        x;
34   int        y;
35 } Overlay;
36 
37 struct _GtkTextViewChild
38 {
39   GtkWidget          parent_instance;
40   GtkTextWindowType  window_type;
41   GQueue             overlays;
42   int                xoffset;
43   int                yoffset;
44   GtkWidget         *child;
45 };
46 
47 enum {
48   PROP_0,
49   PROP_WINDOW_TYPE,
50   N_PROPS
51 };
52 
G_DEFINE_TYPE(GtkTextViewChild,gtk_text_view_child,GTK_TYPE_WIDGET)53 G_DEFINE_TYPE (GtkTextViewChild, gtk_text_view_child, GTK_TYPE_WIDGET)
54 
55 static GParamSpec *properties[N_PROPS];
56 
57 static Overlay *
58 overlay_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 
73 static void
overlay_free(Overlay * overlay)74 overlay_free (Overlay *overlay)
75 {
76   g_assert (overlay->link.prev == NULL);
77   g_assert (overlay->link.next == NULL);
78 
79   g_object_unref (overlay->widget);
80   g_slice_free (Overlay, overlay);
81 }
82 
83 static void
gtk_text_view_child_remove_overlay(GtkTextViewChild * self,Overlay * overlay)84 gtk_text_view_child_remove_overlay (GtkTextViewChild *self,
85                                     Overlay          *overlay)
86 {
87   g_queue_unlink (&self->overlays, &overlay->link);
88   gtk_widget_unparent (overlay->widget);
89   overlay_free (overlay);
90 }
91 
92 static Overlay *
gtk_text_view_child_get_overlay(GtkTextViewChild * self,GtkWidget * widget)93 gtk_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 
109 void
gtk_text_view_child_add(GtkTextViewChild * self,GtkWidget * widget)110 gtk_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 
125 void
gtk_text_view_child_remove(GtkTextViewChild * self,GtkWidget * widget)126 gtk_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 (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 
144 static void
gtk_text_view_child_measure(GtkWidget * widget,GtkOrientation orientation,int for_size,int * min_size,int * nat_size,int * min_baseline,int * nat_baseline)145 gtk_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 (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 (self->child,
160                         orientation,
161                         for_size,
162                         &real_min_size,
163                         &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 (overlay->widget,
174                           orientation,
175                           for_size,
176                           &child_min_size,
177                           &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 
201 static void
gtk_text_view_child_size_allocate(GtkWidget * widget,int width,int height,int baseline)202 gtk_text_view_child_size_allocate (GtkWidget *widget,
203                                    int        width,
204                                    int        height,
205                                    int        baseline)
206 {
207   GtkTextViewChild *self = GTK_TEXT_VIEW_CHILD (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 (self->child, &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 (overlay->widget, &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 (overlay->widget, &rect, -1);
248     }
249 }
250 
251 static void
gtk_text_view_child_snapshot(GtkWidget * widget,GtkSnapshot * snapshot)252 gtk_text_view_child_snapshot (GtkWidget   *widget,
253                               GtkSnapshot *snapshot)
254 {
255   GtkTextViewChild *self = GTK_TEXT_VIEW_CHILD (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, 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, overlay->widget, snapshot);
268     }
269 }
270 
271 static void
gtk_text_view_child_constructed(GObject * object)272 gtk_text_view_child_constructed (GObject *object)
273 {
274   GtkTextViewChild *self = GTK_TEXT_VIEW_CHILD (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 (css_node, g_quark_from_static_string ("border"));
285       gtk_css_node_add_class (css_node, g_quark_from_static_string ("left"));
286       break;
287 
288     case GTK_TEXT_WINDOW_RIGHT:
289       gtk_css_node_set_name (css_node, g_quark_from_static_string ("border"));
290       gtk_css_node_add_class (css_node, g_quark_from_static_string ("right"));
291       break;
292 
293     case GTK_TEXT_WINDOW_TOP:
294       gtk_css_node_set_name (css_node, g_quark_from_static_string ("border"));
295       gtk_css_node_add_class (css_node, g_quark_from_static_string ("top"));
296       break;
297 
298     case GTK_TEXT_WINDOW_BOTTOM:
299       gtk_css_node_set_name (css_node, g_quark_from_static_string ("border"));
300       gtk_css_node_add_class (css_node, g_quark_from_static_string ("bottom"));
301       break;
302 
303     case GTK_TEXT_WINDOW_TEXT:
304       gtk_css_node_set_name (css_node, g_quark_from_static_string ("child"));
305       break;
306 
307     case GTK_TEXT_WINDOW_WIDGET:
308     default:
309       break;
310     }
311 }
312 
313 static void
gtk_text_view_child_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)314 gtk_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 (object);
320 
321   switch (prop_id)
322     {
323     case PROP_WINDOW_TYPE:
324       g_value_set_enum (value, self->window_type);
325       break;
326 
327     default:
328       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
329     }
330 }
331 
332 static void
gtk_text_view_child_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)333 gtk_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 (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 
351 static void
gtk_text_view_child_dispose(GObject * object)352 gtk_text_view_child_dispose (GObject *object)
353 {
354   GtkTextViewChild *self = GTK_TEXT_VIEW_CHILD (object);
355   GtkWidget *child;
356 
357   while ((child = gtk_widget_get_first_child (GTK_WIDGET (self))))
358     gtk_text_view_child_remove (self, child);
359 
360   G_OBJECT_CLASS (gtk_text_view_child_parent_class)->dispose (object);
361 }
362 
363 static void
gtk_text_view_child_class_init(GtkTextViewChildClass * klass)364 gtk_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 ("window-type",
386                        P_("Window Type"),
387                        P_("The GtkTextWindowType"),
388                        GTK_TYPE_TEXT_WINDOW_TYPE,
389                        GTK_TEXT_WINDOW_TEXT,
390                        GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY|G_PARAM_EXPLICIT_NOTIFY);
391 
392   g_object_class_install_properties (object_class, N_PROPS, properties);
393 }
394 
395 static void
gtk_text_view_child_init(GtkTextViewChild * self)396 gtk_text_view_child_init (GtkTextViewChild *self)
397 {
398   self->window_type = GTK_TEXT_WINDOW_TEXT;
399 
400   gtk_widget_set_overflow (GTK_WIDGET (self), GTK_OVERFLOW_HIDDEN);
401 }
402 
403 GtkWidget *
gtk_text_view_child_new(GtkTextWindowType window_type)404 gtk_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                        "window-type", window_type,
415                        NULL);
416 }
417 
418 void
gtk_text_view_child_add_overlay(GtkTextViewChild * self,GtkWidget * widget,int xpos,int ypos)419 gtk_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, xpos, ypos);
430   g_queue_push_tail (&self->overlays, &overlay->link);
431   gtk_widget_set_parent (widget, GTK_WIDGET (self));
432 }
433 
434 void
gtk_text_view_child_move_overlay(GtkTextViewChild * self,GtkWidget * widget,int xpos,int ypos)435 gtk_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 
458 GtkTextWindowType
gtk_text_view_child_get_window_type(GtkTextViewChild * self)459 gtk_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 
466 void
gtk_text_view_child_set_offset(GtkTextViewChild * self,int xoffset,int yoffset)467 gtk_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