1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program 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
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18 #include "config.h"
19
20 #include <gegl.h>
21 #include <gtk/gtk.h>
22
23 #include "display-types.h"
24
25 #include "config/gimpdisplayconfig.h"
26
27 #include "core/gimp.h"
28 #include "core/gimp-cairo.h"
29 #include "core/gimpboundary.h"
30 #include "core/gimpchannel.h"
31 #include "core/gimpimage.h"
32
33 #include "gimpdisplay.h"
34 #include "gimpdisplayshell.h"
35 #include "gimpdisplayshell-appearance.h"
36 #include "gimpdisplayshell-draw.h"
37 #include "gimpdisplayshell-expose.h"
38 #include "gimpdisplayshell-selection.h"
39 #include "gimpdisplayshell-transform.h"
40
41
42 struct _Selection
43 {
44 GimpDisplayShell *shell; /* shell that owns the selection */
45
46 GimpSegment *segs_in; /* gdk segments of area boundary */
47 gint n_segs_in; /* number of segments in segs_in */
48
49 GimpSegment *segs_out; /* gdk segments of area boundary */
50 gint n_segs_out; /* number of segments in segs_out */
51
52 guint index; /* index of current stipple pattern */
53 gint paused; /* count of pause requests */
54 gboolean shell_visible; /* visility of the display shell */
55 gboolean show_selection; /* is the selection visible? */
56 guint timeout; /* timer for successive draws */
57 cairo_pattern_t *segs_in_mask; /* cache for rendered segments */
58 };
59
60
61 /* local function prototypes */
62
63 static void selection_start (Selection *selection);
64 static void selection_stop (Selection *selection);
65
66 static void selection_undraw (Selection *selection);
67
68 static void selection_render_mask (Selection *selection);
69
70 static void selection_zoom_segs (Selection *selection,
71 const GimpBoundSeg *src_segs,
72 GimpSegment *dest_segs,
73 gint n_segs,
74 gint canvas_offset_x,
75 gint canvas_offset_y);
76 static void selection_generate_segs (Selection *selection);
77 static void selection_free_segs (Selection *selection);
78
79 static gboolean selection_timeout (Selection *selection);
80
81 static gboolean selection_window_state_event (GtkWidget *shell,
82 GdkEventWindowState *event,
83 Selection *selection);
84 static gboolean selection_visibility_notify_event (GtkWidget *shell,
85 GdkEventVisibility *event,
86 Selection *selection);
87
88
89 /* public functions */
90
91 void
gimp_display_shell_selection_init(GimpDisplayShell * shell)92 gimp_display_shell_selection_init (GimpDisplayShell *shell)
93 {
94 Selection *selection;
95
96 g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
97 g_return_if_fail (shell->selection == NULL);
98
99 selection = g_slice_new0 (Selection);
100
101 selection->shell = shell;
102 selection->shell_visible = TRUE;
103 selection->show_selection = gimp_display_shell_get_show_selection (shell);
104
105 shell->selection = selection;
106 shell->selection_update = g_get_monotonic_time ();
107
108 g_signal_connect (shell, "window-state-event",
109 G_CALLBACK (selection_window_state_event),
110 selection);
111 g_signal_connect (shell, "visibility-notify-event",
112 G_CALLBACK (selection_visibility_notify_event),
113 selection);
114 }
115
116 void
gimp_display_shell_selection_free(GimpDisplayShell * shell)117 gimp_display_shell_selection_free (GimpDisplayShell *shell)
118 {
119 Selection *selection;
120
121 g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
122 g_return_if_fail (shell->selection != NULL);
123
124 selection = shell->selection;
125
126 selection_stop (selection);
127
128 g_signal_handlers_disconnect_by_func (shell,
129 selection_window_state_event,
130 selection);
131 g_signal_handlers_disconnect_by_func (shell,
132 selection_visibility_notify_event,
133 selection);
134
135 selection_free_segs (selection);
136
137 g_slice_free (Selection, selection);
138
139 shell->selection = NULL;
140 }
141
142 void
gimp_display_shell_selection_undraw(GimpDisplayShell * shell)143 gimp_display_shell_selection_undraw (GimpDisplayShell *shell)
144 {
145 g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
146 g_return_if_fail (shell->selection != NULL);
147
148 if (gimp_display_get_image (shell->display))
149 {
150 selection_undraw (shell->selection);
151 }
152 else
153 {
154 selection_stop (shell->selection);
155 selection_free_segs (shell->selection);
156 }
157 }
158
159 void
gimp_display_shell_selection_restart(GimpDisplayShell * shell)160 gimp_display_shell_selection_restart (GimpDisplayShell *shell)
161 {
162 g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
163 g_return_if_fail (shell->selection != NULL);
164
165 if (gimp_display_get_image (shell->display))
166 {
167 selection_start (shell->selection);
168 }
169 }
170
171 void
gimp_display_shell_selection_pause(GimpDisplayShell * shell)172 gimp_display_shell_selection_pause (GimpDisplayShell *shell)
173 {
174 g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
175 g_return_if_fail (shell->selection != NULL);
176
177 if (gimp_display_get_image (shell->display))
178 {
179 if (shell->selection->paused == 0)
180 selection_stop (shell->selection);
181
182 shell->selection->paused++;
183 }
184 }
185
186 void
gimp_display_shell_selection_resume(GimpDisplayShell * shell)187 gimp_display_shell_selection_resume (GimpDisplayShell *shell)
188 {
189 g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
190 g_return_if_fail (shell->selection != NULL);
191
192 if (gimp_display_get_image (shell->display))
193 {
194 shell->selection->paused--;
195
196 if (shell->selection->paused == 0)
197 selection_start (shell->selection);
198 }
199 }
200
201 void
gimp_display_shell_selection_set_show(GimpDisplayShell * shell,gboolean show)202 gimp_display_shell_selection_set_show (GimpDisplayShell *shell,
203 gboolean show)
204 {
205 g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
206 g_return_if_fail (shell->selection != NULL);
207
208 if (gimp_display_get_image (shell->display))
209 {
210 Selection *selection = shell->selection;
211
212 if (show != selection->show_selection)
213 {
214 selection_undraw (selection);
215
216 selection->show_selection = show;
217
218 selection_start (selection);
219 }
220 }
221 }
222
223
224 /* private functions */
225
226 static void
selection_start(Selection * selection)227 selection_start (Selection *selection)
228 {
229 selection_stop (selection);
230
231 /* If this selection is paused, do not start it */
232 if (selection->paused == 0 &&
233 gimp_display_get_image (selection->shell->display) &&
234 selection->show_selection)
235 {
236 /* Draw the ants once */
237 selection_timeout (selection);
238
239 if (selection->segs_in && selection->shell_visible)
240 {
241 GimpDisplayConfig *config = selection->shell->display->config;
242
243 selection->timeout = g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE,
244 config->marching_ants_speed,
245 (GSourceFunc) selection_timeout,
246 selection, NULL);
247 }
248 }
249 }
250
251 static void
selection_stop(Selection * selection)252 selection_stop (Selection *selection)
253 {
254 if (selection->timeout)
255 {
256 g_source_remove (selection->timeout);
257 selection->timeout = 0;
258 }
259 }
260
261 void
gimp_display_shell_selection_draw(GimpDisplayShell * shell,cairo_t * cr)262 gimp_display_shell_selection_draw (GimpDisplayShell *shell,
263 cairo_t *cr)
264 {
265 if (gimp_display_get_image (shell->display) &&
266 shell->selection && shell->selection->show_selection)
267 {
268 GimpDisplayConfig *config = shell->display->config;
269 gint64 time = g_get_monotonic_time ();
270
271 if ((time - shell->selection_update) / 1000 > config->marching_ants_speed &&
272 shell->selection->paused == 0)
273 {
274 shell->selection_update = time;
275 shell->selection->index++;
276 }
277
278 selection_generate_segs (shell->selection);
279
280 if (shell->selection->segs_in)
281 {
282 gimp_display_shell_draw_selection_in (shell->selection->shell, cr,
283 shell->selection->segs_in_mask,
284 shell->selection->index % 8);
285 }
286
287 if (shell->selection->segs_out)
288 {
289 if (shell->selection->shell->rotate_transform)
290 cairo_transform (cr, shell->selection->shell->rotate_transform);
291
292 gimp_display_shell_draw_selection_out (shell->selection->shell, cr,
293 shell->selection->segs_out,
294 shell->selection->n_segs_out);
295 }
296 }
297 }
298
299 static void
selection_undraw(Selection * selection)300 selection_undraw (Selection *selection)
301 {
302 gint x, y, w, h;
303
304 selection_stop (selection);
305
306 if (gimp_display_shell_mask_bounds (selection->shell, &x, &y, &w, &h))
307 {
308 /* expose will restart the selection */
309 gimp_display_shell_expose_area (selection->shell, x, y, w, h);
310 }
311 else
312 {
313 selection_start (selection);
314 }
315 }
316
317 static void
selection_render_mask(Selection * selection)318 selection_render_mask (Selection *selection)
319 {
320 GdkWindow *window;
321 cairo_surface_t *surface;
322 cairo_t *cr;
323
324 window = gtk_widget_get_window (GTK_WIDGET (selection->shell));
325 surface = gdk_window_create_similar_surface (window, CAIRO_CONTENT_ALPHA,
326 gdk_window_get_width (window),
327 gdk_window_get_height (window));
328 cr = cairo_create (surface);
329
330 cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
331 cairo_set_line_width (cr, 1.0);
332
333 if (selection->shell->rotate_transform)
334 cairo_transform (cr, selection->shell->rotate_transform);
335
336 gimp_cairo_segments (cr,
337 selection->segs_in,
338 selection->n_segs_in);
339 cairo_stroke (cr);
340
341 selection->segs_in_mask = cairo_pattern_create_for_surface (surface);
342
343 cairo_destroy (cr);
344 cairo_surface_destroy (surface);
345 }
346
347 static void
selection_zoom_segs(Selection * selection,const GimpBoundSeg * src_segs,GimpSegment * dest_segs,gint n_segs,gint canvas_offset_x,gint canvas_offset_y)348 selection_zoom_segs (Selection *selection,
349 const GimpBoundSeg *src_segs,
350 GimpSegment *dest_segs,
351 gint n_segs,
352 gint canvas_offset_x,
353 gint canvas_offset_y)
354 {
355 const gint xclamp = selection->shell->disp_width + 1;
356 const gint yclamp = selection->shell->disp_height + 1;
357 gint i;
358
359 gimp_display_shell_zoom_segments (selection->shell,
360 src_segs, dest_segs, n_segs,
361 0.0, 0.0);
362
363 for (i = 0; i < n_segs; i++)
364 {
365 if (! selection->shell->rotate_transform)
366 {
367 dest_segs[i].x1 = CLAMP (dest_segs[i].x1, -1, xclamp) + canvas_offset_x;
368 dest_segs[i].y1 = CLAMP (dest_segs[i].y1, -1, yclamp) + canvas_offset_y;
369
370 dest_segs[i].x2 = CLAMP (dest_segs[i].x2, -1, xclamp) + canvas_offset_x;
371 dest_segs[i].y2 = CLAMP (dest_segs[i].y2, -1, yclamp) + canvas_offset_y;
372 }
373
374 /* If this segment is a closing segment && the segments lie inside
375 * the region, OR if this is an opening segment and the segments
376 * lie outside the region...
377 * we need to transform it by one display pixel
378 */
379 if (! src_segs[i].open)
380 {
381 /* If it is vertical */
382 if (dest_segs[i].x1 == dest_segs[i].x2)
383 {
384 dest_segs[i].x1 -= 1;
385 dest_segs[i].x2 -= 1;
386 }
387 else
388 {
389 dest_segs[i].y1 -= 1;
390 dest_segs[i].y2 -= 1;
391 }
392 }
393 }
394 }
395
396 static void
selection_generate_segs(Selection * selection)397 selection_generate_segs (Selection *selection)
398 {
399 GimpImage *image = gimp_display_get_image (selection->shell->display);
400 const GimpBoundSeg *segs_in;
401 const GimpBoundSeg *segs_out;
402 gint canvas_offset_x = 0;
403 gint canvas_offset_y = 0;
404
405 selection_free_segs (selection);
406
407 /* Ask the image for the boundary of its selected region...
408 * Then transform that information into a new buffer of GimpSegments
409 */
410 gimp_channel_boundary (gimp_image_get_mask (image),
411 &segs_in, &segs_out,
412 &selection->n_segs_in, &selection->n_segs_out,
413 0, 0, 0, 0);
414
415 if (selection->n_segs_in)
416 {
417 selection->segs_in = g_new (GimpSegment, selection->n_segs_in);
418 selection_zoom_segs (selection, segs_in,
419 selection->segs_in, selection->n_segs_in,
420 canvas_offset_x, canvas_offset_y);
421
422 selection_render_mask (selection);
423 }
424
425 /* Possible secondary boundary representation */
426 if (selection->n_segs_out)
427 {
428 selection->segs_out = g_new (GimpSegment, selection->n_segs_out);
429 selection_zoom_segs (selection, segs_out,
430 selection->segs_out, selection->n_segs_out,
431 canvas_offset_x, canvas_offset_y);
432 }
433 }
434
435 static void
selection_free_segs(Selection * selection)436 selection_free_segs (Selection *selection)
437 {
438 g_clear_pointer (&selection->segs_in, g_free);
439 selection->n_segs_in = 0;
440
441 g_clear_pointer (&selection->segs_out, g_free);
442 selection->n_segs_out = 0;
443
444 g_clear_pointer (&selection->segs_in_mask, cairo_pattern_destroy);
445 }
446
447 static gboolean
selection_timeout(Selection * selection)448 selection_timeout (Selection *selection)
449 {
450 GimpDisplayConfig *config = selection->shell->display->config;
451 gint64 time = g_get_monotonic_time ();
452
453 if ((time - selection->shell->selection_update) / 1000 > config->marching_ants_speed)
454 {
455 GdkWindow *window;
456
457 window = gtk_widget_get_window (GTK_WIDGET (selection->shell));
458
459 gtk_widget_queue_draw_area (GTK_WIDGET (selection->shell),
460 0, 0,
461 gdk_window_get_width (window),
462 gdk_window_get_height (window));
463 }
464
465 return G_SOURCE_CONTINUE;
466 }
467
468 static void
selection_set_shell_visible(Selection * selection,gboolean shell_visible)469 selection_set_shell_visible (Selection *selection,
470 gboolean shell_visible)
471 {
472 if (selection->shell_visible != shell_visible)
473 {
474 selection->shell_visible = shell_visible;
475
476 if (shell_visible)
477 selection_start (selection);
478 else
479 selection_stop (selection);
480 }
481 }
482
483 static gboolean
selection_window_state_event(GtkWidget * shell,GdkEventWindowState * event,Selection * selection)484 selection_window_state_event (GtkWidget *shell,
485 GdkEventWindowState *event,
486 Selection *selection)
487 {
488 selection_set_shell_visible (selection,
489 (event->new_window_state & (GDK_WINDOW_STATE_WITHDRAWN |
490 GDK_WINDOW_STATE_ICONIFIED)) == 0);
491
492 return FALSE;
493 }
494
495 static gboolean
selection_visibility_notify_event(GtkWidget * shell,GdkEventVisibility * event,Selection * selection)496 selection_visibility_notify_event (GtkWidget *shell,
497 GdkEventVisibility *event,
498 Selection *selection)
499 {
500 selection_set_shell_visible (selection,
501 event->state != GDK_VISIBILITY_FULLY_OBSCURED);
502
503 return FALSE;
504 }
505