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