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