1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * gimpcanvasgroup.c
5  * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
6  *
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 3 of the License, or
10  * (at your option) any later version.
11  *
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.
16  *
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/>.
19  */
20 
21 #include "config.h"
22 
23 #include <gegl.h>
24 #include <gtk/gtk.h>
25 
26 #include "libgimpbase/gimpbase.h"
27 #include "libgimpmath/gimpmath.h"
28 
29 #include "display-types.h"
30 
31 #include "gimpcanvasgroup.h"
32 #include "gimpdisplayshell.h"
33 
34 
35 enum
36 {
37   PROP_0,
38   PROP_GROUP_STROKING,
39   PROP_GROUP_FILLING
40 };
41 
42 
43 struct _GimpCanvasGroupPrivate
44 {
45   GQueue   *items;
46   gboolean  group_stroking;
47   gboolean  group_filling;
48 };
49 
50 
51 /*  local function prototypes  */
52 
53 static void             gimp_canvas_group_finalize     (GObject         *object);
54 static void             gimp_canvas_group_set_property (GObject         *object,
55                                                         guint            property_id,
56                                                         const GValue    *value,
57                                                         GParamSpec      *pspec);
58 static void             gimp_canvas_group_get_property (GObject         *object,
59                                                         guint            property_id,
60                                                         GValue          *value,
61                                                         GParamSpec      *pspec);
62 static void             gimp_canvas_group_draw         (GimpCanvasItem  *item,
63                                                         cairo_t         *cr);
64 static cairo_region_t * gimp_canvas_group_get_extents  (GimpCanvasItem  *item);
65 static gboolean         gimp_canvas_group_hit          (GimpCanvasItem  *item,
66                                                         gdouble          x,
67                                                         gdouble          y);
68 
69 static void             gimp_canvas_group_child_update (GimpCanvasItem  *item,
70                                                         cairo_region_t  *region,
71                                                         GimpCanvasGroup *group);
72 
73 
G_DEFINE_TYPE_WITH_PRIVATE(GimpCanvasGroup,gimp_canvas_group,GIMP_TYPE_CANVAS_ITEM)74 G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasGroup, gimp_canvas_group,
75                             GIMP_TYPE_CANVAS_ITEM)
76 
77 #define parent_class gimp_canvas_group_parent_class
78 
79 
80 static void
81 gimp_canvas_group_class_init (GimpCanvasGroupClass *klass)
82 {
83   GObjectClass        *object_class = G_OBJECT_CLASS (klass);
84   GimpCanvasItemClass *item_class   = GIMP_CANVAS_ITEM_CLASS (klass);
85 
86   object_class->finalize     = gimp_canvas_group_finalize;
87   object_class->set_property = gimp_canvas_group_set_property;
88   object_class->get_property = gimp_canvas_group_get_property;
89 
90   item_class->draw           = gimp_canvas_group_draw;
91   item_class->get_extents    = gimp_canvas_group_get_extents;
92   item_class->hit            = gimp_canvas_group_hit;
93 
94   g_object_class_install_property (object_class, PROP_GROUP_STROKING,
95                                    g_param_spec_boolean ("group-stroking",
96                                                          NULL, NULL,
97                                                          FALSE,
98                                                          GIMP_PARAM_READWRITE));
99 
100   g_object_class_install_property (object_class, PROP_GROUP_FILLING,
101                                    g_param_spec_boolean ("group-filling",
102                                                          NULL, NULL,
103                                                          FALSE,
104                                                          GIMP_PARAM_READWRITE));
105 }
106 
107 static void
gimp_canvas_group_init(GimpCanvasGroup * group)108 gimp_canvas_group_init (GimpCanvasGroup *group)
109 {
110   group->priv = gimp_canvas_group_get_instance_private (group);
111 
112   group->priv->items = g_queue_new ();
113 }
114 
115 static void
gimp_canvas_group_finalize(GObject * object)116 gimp_canvas_group_finalize (GObject *object)
117 {
118   GimpCanvasGroup *group = GIMP_CANVAS_GROUP (object);
119   GimpCanvasItem  *item;
120 
121   while ((item = g_queue_peek_head (group->priv->items)))
122     gimp_canvas_group_remove_item (group, item);
123 
124   g_queue_free (group->priv->items);
125   group->priv->items = NULL;
126 
127   G_OBJECT_CLASS (parent_class)->finalize (object);
128 }
129 
130 static void
gimp_canvas_group_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)131 gimp_canvas_group_set_property (GObject      *object,
132                                 guint         property_id,
133                                 const GValue *value,
134                                 GParamSpec   *pspec)
135 {
136   GimpCanvasGroup *group = GIMP_CANVAS_GROUP (object);
137 
138   switch (property_id)
139     {
140     case PROP_GROUP_STROKING:
141       group->priv->group_stroking = g_value_get_boolean (value);
142       break;
143     case PROP_GROUP_FILLING:
144       group->priv->group_filling = g_value_get_boolean (value);
145       break;
146 
147     default:
148       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
149       break;
150     }
151 }
152 
153 static void
gimp_canvas_group_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)154 gimp_canvas_group_get_property (GObject    *object,
155                                 guint       property_id,
156                                 GValue     *value,
157                                 GParamSpec *pspec)
158 {
159   GimpCanvasGroup *group = GIMP_CANVAS_GROUP (object);
160 
161   switch (property_id)
162     {
163     case PROP_GROUP_STROKING:
164       g_value_set_boolean (value, group->priv->group_stroking);
165       break;
166     case PROP_GROUP_FILLING:
167       g_value_set_boolean (value, group->priv->group_filling);
168       break;
169 
170     default:
171       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
172       break;
173     }
174 }
175 
176 static void
gimp_canvas_group_draw(GimpCanvasItem * item,cairo_t * cr)177 gimp_canvas_group_draw (GimpCanvasItem *item,
178                         cairo_t        *cr)
179 {
180   GimpCanvasGroup *group = GIMP_CANVAS_GROUP (item);
181   GList           *list;
182 
183   for (list = group->priv->items->head; list; list = g_list_next (list))
184     {
185       GimpCanvasItem *sub_item = list->data;
186 
187       gimp_canvas_item_draw (sub_item, cr);
188     }
189 
190   if (group->priv->group_stroking)
191     _gimp_canvas_item_stroke (item, cr);
192 
193   if (group->priv->group_filling)
194     _gimp_canvas_item_fill (item, cr);
195 }
196 
197 static cairo_region_t *
gimp_canvas_group_get_extents(GimpCanvasItem * item)198 gimp_canvas_group_get_extents (GimpCanvasItem *item)
199 {
200   GimpCanvasGroup *group  = GIMP_CANVAS_GROUP (item);
201   cairo_region_t  *region = NULL;
202   GList           *list;
203 
204   for (list = group->priv->items->head; list; list = g_list_next (list))
205     {
206       GimpCanvasItem *sub_item   = list->data;
207       cairo_region_t *sub_region = gimp_canvas_item_get_extents (sub_item);
208 
209       if (! region)
210         {
211           region = sub_region;
212         }
213       else if (sub_region)
214         {
215           cairo_region_union (region, sub_region);
216           cairo_region_destroy (sub_region);
217         }
218     }
219 
220   return region;
221 }
222 
223 static gboolean
gimp_canvas_group_hit(GimpCanvasItem * item,gdouble x,gdouble y)224 gimp_canvas_group_hit (GimpCanvasItem *item,
225                        gdouble         x,
226                        gdouble         y)
227 {
228   GimpCanvasGroup *group = GIMP_CANVAS_GROUP (item);
229   GList           *list;
230 
231   for (list = group->priv->items->head; list; list = g_list_next (list))
232     {
233       if (gimp_canvas_item_hit (list->data, x, y))
234         return TRUE;
235     }
236 
237   return FALSE;
238 }
239 
240 static void
gimp_canvas_group_child_update(GimpCanvasItem * item,cairo_region_t * region,GimpCanvasGroup * group)241 gimp_canvas_group_child_update (GimpCanvasItem  *item,
242                                 cairo_region_t  *region,
243                                 GimpCanvasGroup *group)
244 {
245   if (_gimp_canvas_item_needs_update (GIMP_CANVAS_ITEM (group)))
246     _gimp_canvas_item_update (GIMP_CANVAS_ITEM (group), region);
247 }
248 
249 
250 /*  public functions  */
251 
252 GimpCanvasItem *
gimp_canvas_group_new(GimpDisplayShell * shell)253 gimp_canvas_group_new (GimpDisplayShell *shell)
254 {
255   g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
256 
257   return g_object_new (GIMP_TYPE_CANVAS_GROUP,
258                        "shell", shell,
259                        NULL);
260 }
261 
262 void
gimp_canvas_group_add_item(GimpCanvasGroup * group,GimpCanvasItem * item)263 gimp_canvas_group_add_item (GimpCanvasGroup *group,
264                             GimpCanvasItem  *item)
265 {
266   g_return_if_fail (GIMP_IS_CANVAS_GROUP (group));
267   g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
268   g_return_if_fail (GIMP_CANVAS_ITEM (group) != item);
269 
270   if (group->priv->group_stroking)
271     gimp_canvas_item_suspend_stroking (item);
272 
273   if (group->priv->group_filling)
274     gimp_canvas_item_suspend_filling (item);
275 
276   g_queue_push_tail (group->priv->items, g_object_ref (item));
277 
278   if (_gimp_canvas_item_needs_update (GIMP_CANVAS_ITEM (group)))
279     {
280       cairo_region_t *region = gimp_canvas_item_get_extents (item);
281 
282       if (region)
283         {
284           _gimp_canvas_item_update (GIMP_CANVAS_ITEM (group), region);
285           cairo_region_destroy (region);
286         }
287     }
288 
289   g_signal_connect (item, "update",
290                     G_CALLBACK (gimp_canvas_group_child_update),
291                     group);
292 }
293 
294 void
gimp_canvas_group_remove_item(GimpCanvasGroup * group,GimpCanvasItem * item)295 gimp_canvas_group_remove_item (GimpCanvasGroup *group,
296                                GimpCanvasItem  *item)
297 {
298   GList *list;
299 
300   g_return_if_fail (GIMP_IS_CANVAS_GROUP (group));
301   g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
302 
303   list = g_queue_find (group->priv->items, item);
304 
305   g_return_if_fail (list != NULL);
306 
307   g_queue_delete_link (group->priv->items, list);
308 
309   if (group->priv->group_stroking)
310     gimp_canvas_item_resume_stroking (item);
311 
312   if (group->priv->group_filling)
313     gimp_canvas_item_resume_filling (item);
314 
315   if (_gimp_canvas_item_needs_update (GIMP_CANVAS_ITEM (group)))
316     {
317       cairo_region_t *region = gimp_canvas_item_get_extents (item);
318 
319       if (region)
320         {
321           _gimp_canvas_item_update (GIMP_CANVAS_ITEM (group), region);
322           cairo_region_destroy (region);
323         }
324     }
325 
326   g_signal_handlers_disconnect_by_func (item,
327                                         gimp_canvas_group_child_update,
328                                         group);
329 
330   g_object_unref (item);
331 }
332 
333 void
gimp_canvas_group_set_group_stroking(GimpCanvasGroup * group,gboolean group_stroking)334 gimp_canvas_group_set_group_stroking (GimpCanvasGroup *group,
335                                       gboolean         group_stroking)
336 {
337   g_return_if_fail (GIMP_IS_CANVAS_GROUP (group));
338 
339   if (group->priv->group_stroking != group_stroking)
340     {
341       GList *list;
342 
343       gimp_canvas_item_begin_change (GIMP_CANVAS_ITEM (group));
344 
345       g_object_set (group,
346                     "group-stroking", group_stroking ? TRUE : FALSE,
347                     NULL);
348 
349       for (list = group->priv->items->head; list; list = g_list_next (list))
350         {
351           if (group->priv->group_stroking)
352             gimp_canvas_item_suspend_stroking (list->data);
353           else
354             gimp_canvas_item_resume_stroking (list->data);
355         }
356 
357       gimp_canvas_item_end_change (GIMP_CANVAS_ITEM (group));
358     }
359 }
360 
361 void
gimp_canvas_group_set_group_filling(GimpCanvasGroup * group,gboolean group_filling)362 gimp_canvas_group_set_group_filling (GimpCanvasGroup *group,
363                                      gboolean         group_filling)
364 {
365   g_return_if_fail (GIMP_IS_CANVAS_GROUP (group));
366 
367   if (group->priv->group_filling != group_filling)
368     {
369       GList *list;
370 
371       gimp_canvas_item_begin_change (GIMP_CANVAS_ITEM (group));
372 
373       g_object_set (group,
374                     "group-filling", group_filling ? TRUE : FALSE,
375                     NULL);
376 
377       for (list = group->priv->items->head; list; list = g_list_next (list))
378         {
379           if (group->priv->group_filling)
380             gimp_canvas_item_suspend_filling (list->data);
381           else
382             gimp_canvas_item_resume_filling (list->data);
383         }
384 
385       gimp_canvas_item_end_change (GIMP_CANVAS_ITEM (group));
386     }
387 }
388