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 "libgimpmath/gimpmath.h"
24 #include "libgimpwidgets/gimpwidgets.h"
25
26 #include "actions-types.h"
27
28 #include "config/gimpdialogconfig.h"
29
30 #include "core/gimp.h"
31 #include "core/gimpchannel.h"
32 #include "core/gimpimage.h"
33 #include "core/gimpselection.h"
34
35 #include "widgets/gimphelp-ids.h"
36 #include "widgets/gimpdialogfactory.h"
37 #include "widgets/gimpwidgets-utils.h"
38 #include "widgets/gimpwindowstrategy.h"
39
40 #include "display/gimpdisplay.h"
41 #include "display/gimpdisplayshell.h"
42
43 #include "dialogs/dialogs.h"
44
45 #include "actions.h"
46 #include "items-commands.h"
47 #include "select-commands.h"
48
49 #include "gimp-intl.h"
50
51
52 /* local function prototypes */
53
54 static void select_feather_callback (GtkWidget *widget,
55 gdouble size,
56 GimpUnit unit,
57 gpointer data);
58 static void select_border_callback (GtkWidget *widget,
59 gdouble size,
60 GimpUnit unit,
61 gpointer data);
62 static void select_grow_callback (GtkWidget *widget,
63 gdouble size,
64 GimpUnit unit,
65 gpointer data);
66 static void select_shrink_callback (GtkWidget *widget,
67 gdouble size,
68 GimpUnit unit,
69 gpointer data);
70
71
72 /* public functions */
73
74 void
select_all_cmd_callback(GimpAction * action,GVariant * value,gpointer data)75 select_all_cmd_callback (GimpAction *action,
76 GVariant *value,
77 gpointer data)
78 {
79 GimpImage *image;
80 return_if_no_image (image, data);
81
82 gimp_channel_all (gimp_image_get_mask (image), TRUE);
83 gimp_image_flush (image);
84 }
85
86 void
select_none_cmd_callback(GimpAction * action,GVariant * value,gpointer data)87 select_none_cmd_callback (GimpAction *action,
88 GVariant *value,
89 gpointer data)
90 {
91 GimpImage *image;
92 return_if_no_image (image, data);
93
94 gimp_channel_clear (gimp_image_get_mask (image), NULL, TRUE);
95 gimp_image_flush (image);
96 }
97
98 void
select_invert_cmd_callback(GimpAction * action,GVariant * value,gpointer data)99 select_invert_cmd_callback (GimpAction *action,
100 GVariant *value,
101 gpointer data)
102 {
103 GimpImage *image;
104 return_if_no_image (image, data);
105
106 gimp_channel_invert (gimp_image_get_mask (image), TRUE);
107 gimp_image_flush (image);
108 }
109
110 void
select_float_cmd_callback(GimpAction * action,GVariant * value,gpointer data)111 select_float_cmd_callback (GimpAction *action,
112 GVariant *value,
113 gpointer data)
114 {
115 GimpImage *image;
116 GtkWidget *widget;
117 GError *error = NULL;
118 return_if_no_image (image, data);
119 return_if_no_widget (widget, data);
120
121 if (gimp_selection_float (GIMP_SELECTION (gimp_image_get_mask (image)),
122 gimp_image_get_active_drawable (image),
123 action_data_get_context (data),
124 TRUE, 0, 0, &error))
125 {
126 gimp_image_flush (image);
127 }
128 else
129 {
130 gimp_message_literal (image->gimp,
131 G_OBJECT (widget), GIMP_MESSAGE_WARNING,
132 error->message);
133 g_clear_error (&error);
134 }
135 }
136
137 void
select_feather_cmd_callback(GimpAction * action,GVariant * value,gpointer data)138 select_feather_cmd_callback (GimpAction *action,
139 GVariant *value,
140 gpointer data)
141 {
142 GimpDisplay *display;
143 GimpImage *image;
144 GtkWidget *dialog;
145 return_if_no_display (display, data);
146
147 image = gimp_display_get_image (display);
148
149 #define FEATHER_DIALOG_KEY "gimp-selection-feather-dialog"
150
151 dialog = dialogs_get_dialog (G_OBJECT (image), FEATHER_DIALOG_KEY);
152
153 if (! dialog)
154 {
155 GimpDialogConfig *config = GIMP_DIALOG_CONFIG (image->gimp->config);
156 GtkWidget *button;
157 gdouble xres;
158 gdouble yres;
159
160 gimp_image_get_resolution (image, &xres, &yres);
161
162 dialog = gimp_query_size_box (_("Feather Selection"),
163 GTK_WIDGET (gimp_display_get_shell (display)),
164 gimp_standard_help_func,
165 GIMP_HELP_SELECTION_FEATHER,
166 _("Feather selection by"),
167 config->selection_feather_radius, 0, 32767, 3,
168 gimp_display_get_shell (display)->unit,
169 MIN (xres, yres),
170 FALSE,
171 G_OBJECT (image), "disconnect",
172 select_feather_callback, image);
173
174 /* Edge lock button */
175 button = gtk_check_button_new_with_mnemonic (_("_Selected areas continue outside the image"));
176 g_object_set_data (G_OBJECT (dialog), "edge-lock-toggle", button);
177 gimp_help_set_help_data (button,
178 _("When feathering, act as if selected areas "
179 "continued outside the image."),
180 NULL);
181 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
182 config->selection_feather_edge_lock);
183 gtk_box_pack_start (GTK_BOX (GIMP_QUERY_BOX_VBOX (dialog)), button,
184 FALSE, FALSE, 0);
185 gtk_widget_show (button);
186
187 dialogs_attach_dialog (G_OBJECT (image), FEATHER_DIALOG_KEY, dialog);
188 }
189
190 gtk_window_present (GTK_WINDOW (dialog));
191 }
192
193 void
select_sharpen_cmd_callback(GimpAction * action,GVariant * value,gpointer data)194 select_sharpen_cmd_callback (GimpAction *action,
195 GVariant *value,
196 gpointer data)
197 {
198 GimpImage *image;
199 return_if_no_image (image, data);
200
201 gimp_channel_sharpen (gimp_image_get_mask (image), TRUE);
202 gimp_image_flush (image);
203 }
204
205 void
select_shrink_cmd_callback(GimpAction * action,GVariant * value,gpointer data)206 select_shrink_cmd_callback (GimpAction *action,
207 GVariant *value,
208 gpointer data)
209 {
210 GimpDisplay *display;
211 GimpImage *image;
212 GtkWidget *dialog;
213 return_if_no_display (display, data);
214
215 image = gimp_display_get_image (display);
216
217 #define SHRINK_DIALOG_KEY "gimp-selection-shrink-dialog"
218
219 dialog = dialogs_get_dialog (G_OBJECT (image), SHRINK_DIALOG_KEY);
220
221 if (! dialog)
222 {
223 GimpDialogConfig *config = GIMP_DIALOG_CONFIG (image->gimp->config);
224 GtkWidget *button;
225 gint width;
226 gint height;
227 gint max_value;
228 gdouble xres;
229 gdouble yres;
230
231 gimp_item_bounds (GIMP_ITEM (gimp_image_get_mask (image)),
232 NULL, NULL, &width, &height);
233 max_value = MIN (width, height) / 2;
234
235 gimp_image_get_resolution (image, &xres, &yres);
236
237 dialog = gimp_query_size_box (_("Shrink Selection"),
238 GTK_WIDGET (gimp_display_get_shell (display)),
239 gimp_standard_help_func,
240 GIMP_HELP_SELECTION_SHRINK,
241 _("Shrink selection by"),
242 config->selection_shrink_radius,
243 1, max_value, 0,
244 gimp_display_get_shell (display)->unit,
245 MIN (xres, yres),
246 FALSE,
247 G_OBJECT (image), "disconnect",
248 select_shrink_callback, image);
249
250 /* Edge lock button */
251 button = gtk_check_button_new_with_mnemonic (_("_Selected areas continue outside the image"));
252 g_object_set_data (G_OBJECT (dialog), "edge-lock-toggle", button);
253 gimp_help_set_help_data (button,
254 _("When shrinking, act as if selected areas "
255 "continued outside the image."),
256 NULL);
257 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
258 config->selection_shrink_edge_lock);
259 gtk_box_pack_start (GTK_BOX (GIMP_QUERY_BOX_VBOX (dialog)), button,
260 FALSE, FALSE, 0);
261 gtk_widget_show (button);
262
263 dialogs_attach_dialog (G_OBJECT (image), SHRINK_DIALOG_KEY, dialog);
264 }
265
266 gtk_window_present (GTK_WINDOW (dialog));
267 }
268
269 void
select_grow_cmd_callback(GimpAction * action,GVariant * value,gpointer data)270 select_grow_cmd_callback (GimpAction *action,
271 GVariant *value,
272 gpointer data)
273 {
274 GimpDisplay *display;
275 GimpImage *image;
276 GtkWidget *dialog;
277 return_if_no_display (display, data);
278
279 image = gimp_display_get_image (display);
280
281 #define GROW_DIALOG_KEY "gimp-selection-grow-dialog"
282
283 dialog = dialogs_get_dialog (G_OBJECT (image), GROW_DIALOG_KEY);
284
285 if (! dialog)
286 {
287 GimpDialogConfig *config = GIMP_DIALOG_CONFIG (image->gimp->config);
288 gint width;
289 gint height;
290 gint max_value;
291 gdouble xres;
292 gdouble yres;
293
294 width = gimp_image_get_width (image);
295 height = gimp_image_get_height (image);
296 max_value = MAX (width, height);
297
298 gimp_image_get_resolution (image, &xres, &yres);
299
300 dialog = gimp_query_size_box (_("Grow Selection"),
301 GTK_WIDGET (gimp_display_get_shell (display)),
302 gimp_standard_help_func,
303 GIMP_HELP_SELECTION_GROW,
304 _("Grow selection by"),
305 config->selection_grow_radius,
306 1, max_value, 0,
307 gimp_display_get_shell (display)->unit,
308 MIN (xres, yres),
309 FALSE,
310 G_OBJECT (image), "disconnect",
311 select_grow_callback, image);
312
313 dialogs_attach_dialog (G_OBJECT (image), GROW_DIALOG_KEY, dialog);
314 }
315
316 gtk_window_present (GTK_WINDOW (dialog));
317 }
318
319 void
select_border_cmd_callback(GimpAction * action,GVariant * value,gpointer data)320 select_border_cmd_callback (GimpAction *action,
321 GVariant *value,
322 gpointer data)
323 {
324 GimpDisplay *display;
325 GimpImage *image;
326 GtkWidget *dialog;
327 return_if_no_display (display, data);
328
329 image = gimp_display_get_image (display);
330
331 #define BORDER_DIALOG_KEY "gimp-selection-border-dialog"
332
333 dialog = dialogs_get_dialog (G_OBJECT (image), BORDER_DIALOG_KEY);
334
335 if (! dialog)
336 {
337 GimpDialogConfig *config = GIMP_DIALOG_CONFIG (image->gimp->config);
338 GtkWidget *combo;
339 GtkWidget *button;
340 gint width;
341 gint height;
342 gint max_value;
343 gdouble xres;
344 gdouble yres;
345
346 gimp_item_bounds (GIMP_ITEM (gimp_image_get_mask (image)),
347 NULL, NULL, &width, &height);
348 max_value = MIN (width, height) / 2;
349
350 gimp_image_get_resolution (image, &xres, &yres);
351
352 dialog = gimp_query_size_box (_("Border Selection"),
353 GTK_WIDGET (gimp_display_get_shell (display)),
354 gimp_standard_help_func,
355 GIMP_HELP_SELECTION_BORDER,
356 _("Border selection by"),
357 config->selection_border_radius,
358 1, max_value, 0,
359 gimp_display_get_shell (display)->unit,
360 MIN (xres, yres),
361 FALSE,
362 G_OBJECT (image), "disconnect",
363 select_border_callback, image);
364
365 /* Border style combo */
366 combo = gimp_enum_combo_box_new (GIMP_TYPE_CHANNEL_BORDER_STYLE);
367 gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo),
368 _("Border style"));
369
370 gtk_box_pack_start (GTK_BOX (GIMP_QUERY_BOX_VBOX (dialog)), combo,
371 FALSE, FALSE, 0);
372
373 g_object_set_data (G_OBJECT (dialog), "border-style-combo", combo);
374 gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo),
375 config->selection_border_style);
376 gtk_widget_show (combo);
377
378 /* Edge lock button */
379 button = gtk_check_button_new_with_mnemonic (_("_Selected areas continue outside the image"));
380 g_object_set_data (G_OBJECT (dialog), "edge-lock-toggle", button);
381 gimp_help_set_help_data (button,
382 _("When bordering, act as if selected areas "
383 "continued outside the image."),
384 NULL);
385 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
386 config->selection_border_edge_lock);
387 gtk_box_pack_start (GTK_BOX (GIMP_QUERY_BOX_VBOX (dialog)), button,
388 FALSE, FALSE, 0);
389 gtk_widget_show (button);
390
391 dialogs_attach_dialog (G_OBJECT (image), BORDER_DIALOG_KEY, dialog);
392 }
393
394 gtk_window_present (GTK_WINDOW (dialog));
395 }
396
397 void
select_flood_cmd_callback(GimpAction * action,GVariant * value,gpointer data)398 select_flood_cmd_callback (GimpAction *action,
399 GVariant *value,
400 gpointer data)
401 {
402 GimpImage *image;
403 return_if_no_image (image, data);
404
405 gimp_channel_flood (gimp_image_get_mask (image), TRUE);
406 gimp_image_flush (image);
407 }
408
409 void
select_save_cmd_callback(GimpAction * action,GVariant * value,gpointer data)410 select_save_cmd_callback (GimpAction *action,
411 GVariant *value,
412 gpointer data)
413 {
414 GimpImage *image;
415 GimpChannel *channel;
416 GtkWidget *widget;
417 return_if_no_image (image, data);
418 return_if_no_widget (widget, data);
419
420 channel = GIMP_CHANNEL (gimp_item_duplicate (GIMP_ITEM (gimp_image_get_mask (image)),
421 GIMP_TYPE_CHANNEL));
422
423 /* saved selections are not visible by default */
424 gimp_item_set_visible (GIMP_ITEM (channel), FALSE, FALSE);
425
426 gimp_image_add_channel (image, channel,
427 GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);
428 gimp_image_flush (image);
429
430 gimp_window_strategy_show_dockable_dialog (GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (image->gimp)),
431 image->gimp,
432 gimp_dialog_factory_get_singleton (),
433 gtk_widget_get_screen (widget),
434 gimp_widget_get_monitor (widget),
435 "gimp-channel-list");
436 }
437
438 void
select_fill_cmd_callback(GimpAction * action,GVariant * value,gpointer data)439 select_fill_cmd_callback (GimpAction *action,
440 GVariant *value,
441 gpointer data)
442 {
443 GimpImage *image;
444 return_if_no_image (image, data);
445
446 items_fill_cmd_callback (action,
447 image, GIMP_ITEM (gimp_image_get_mask (image)),
448 "gimp-selection-fill-dialog",
449 _("Fill Selection Outline"),
450 GIMP_ICON_TOOL_BUCKET_FILL,
451 GIMP_HELP_SELECTION_FILL,
452 data);
453 }
454
455 void
select_fill_last_vals_cmd_callback(GimpAction * action,GVariant * value,gpointer data)456 select_fill_last_vals_cmd_callback (GimpAction *action,
457 GVariant *value,
458 gpointer data)
459 {
460 GimpImage *image;
461 return_if_no_image (image, data);
462
463 items_fill_last_vals_cmd_callback (action,
464 image,
465 GIMP_ITEM (gimp_image_get_mask (image)),
466 data);
467 }
468
469 void
select_stroke_cmd_callback(GimpAction * action,GVariant * value,gpointer data)470 select_stroke_cmd_callback (GimpAction *action,
471 GVariant *value,
472 gpointer data)
473 {
474 GimpImage *image;
475 return_if_no_image (image, data);
476
477 items_stroke_cmd_callback (action,
478 image, GIMP_ITEM (gimp_image_get_mask (image)),
479 "gimp-selection-stroke-dialog",
480 _("Stroke Selection"),
481 GIMP_ICON_SELECTION_STROKE,
482 GIMP_HELP_SELECTION_STROKE,
483 data);
484 }
485
486 void
select_stroke_last_vals_cmd_callback(GimpAction * action,GVariant * value,gpointer data)487 select_stroke_last_vals_cmd_callback (GimpAction *action,
488 GVariant *value,
489 gpointer data)
490 {
491 GimpImage *image;
492 return_if_no_image (image, data);
493
494 items_stroke_last_vals_cmd_callback (action,
495 image,
496 GIMP_ITEM (gimp_image_get_mask (image)),
497 data);
498 }
499
500
501 /* private functions */
502
503 static void
select_feather_callback(GtkWidget * widget,gdouble size,GimpUnit unit,gpointer data)504 select_feather_callback (GtkWidget *widget,
505 gdouble size,
506 GimpUnit unit,
507 gpointer data)
508 {
509 GimpImage *image = GIMP_IMAGE (data);
510 GimpDialogConfig *config = GIMP_DIALOG_CONFIG (image->gimp->config);
511 GtkWidget *button;
512 gdouble radius_x;
513 gdouble radius_y;
514
515 button = g_object_get_data (G_OBJECT (widget), "edge-lock-toggle");
516
517 g_object_set (config,
518 "selection-feather-radius", size,
519 "selection-feather-edge-lock",
520 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)),
521 NULL);
522
523 radius_x = config->selection_feather_radius;
524 radius_y = config->selection_feather_radius;
525
526 if (unit != GIMP_UNIT_PIXEL)
527 {
528 gdouble xres;
529 gdouble yres;
530 gdouble factor;
531
532 gimp_image_get_resolution (image, &xres, &yres);
533
534 factor = (MAX (xres, yres) /
535 MIN (xres, yres));
536
537 if (xres == MIN (xres, yres))
538 radius_y *= factor;
539 else
540 radius_x *= factor;
541 }
542
543 gimp_channel_feather (gimp_image_get_mask (image), radius_x, radius_y,
544 config->selection_feather_edge_lock,
545 TRUE);
546 gimp_image_flush (image);
547 }
548
549 static void
select_border_callback(GtkWidget * widget,gdouble size,GimpUnit unit,gpointer data)550 select_border_callback (GtkWidget *widget,
551 gdouble size,
552 GimpUnit unit,
553 gpointer data)
554 {
555 GimpImage *image = GIMP_IMAGE (data);
556 GimpDialogConfig *config = GIMP_DIALOG_CONFIG (image->gimp->config);
557 GtkWidget *combo;
558 GtkWidget *button;
559 gdouble radius_x;
560 gdouble radius_y;
561 gint border_style;
562
563 combo = g_object_get_data (G_OBJECT (widget), "border-style-combo");
564 button = g_object_get_data (G_OBJECT (widget), "edge-lock-toggle");
565
566 gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (combo), &border_style);
567
568 g_object_set (config,
569 "selection-border-radius", size,
570 "selection-border-style", border_style,
571 "selection-border-edge-lock",
572 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)),
573 NULL);
574
575 radius_x = ROUND (config->selection_border_radius);
576 radius_y = ROUND (config->selection_border_radius);
577
578 if (unit != GIMP_UNIT_PIXEL)
579 {
580 gdouble xres;
581 gdouble yres;
582 gdouble factor;
583
584 gimp_image_get_resolution (image, &xres, &yres);
585
586 factor = (MAX (xres, yres) /
587 MIN (xres, yres));
588
589 if (xres == MIN (xres, yres))
590 radius_y *= factor;
591 else
592 radius_x *= factor;
593 }
594
595 gimp_channel_border (gimp_image_get_mask (image), radius_x, radius_y,
596 config->selection_border_style,
597 config->selection_border_edge_lock,
598 TRUE);
599 gimp_image_flush (image);
600 }
601
602 static void
select_grow_callback(GtkWidget * widget,gdouble size,GimpUnit unit,gpointer data)603 select_grow_callback (GtkWidget *widget,
604 gdouble size,
605 GimpUnit unit,
606 gpointer data)
607 {
608 GimpImage *image = GIMP_IMAGE (data);
609 GimpDialogConfig *config = GIMP_DIALOG_CONFIG (image->gimp->config);
610 gdouble radius_x;
611 gdouble radius_y;
612
613 g_object_set (config,
614 "selection-grow-radius", size,
615 NULL);
616
617 radius_x = ROUND (config->selection_grow_radius);
618 radius_y = ROUND (config->selection_grow_radius);
619
620 if (unit != GIMP_UNIT_PIXEL)
621 {
622 gdouble xres;
623 gdouble yres;
624 gdouble factor;
625
626 gimp_image_get_resolution (image, &xres, &yres);
627
628 factor = (MAX (xres, yres) /
629 MIN (xres, yres));
630
631 if (xres == MIN (xres, yres))
632 radius_y *= factor;
633 else
634 radius_x *= factor;
635 }
636
637 gimp_channel_grow (gimp_image_get_mask (image), radius_x, radius_y, TRUE);
638 gimp_image_flush (image);
639 }
640
641 static void
select_shrink_callback(GtkWidget * widget,gdouble size,GimpUnit unit,gpointer data)642 select_shrink_callback (GtkWidget *widget,
643 gdouble size,
644 GimpUnit unit,
645 gpointer data)
646 {
647 GimpImage *image = GIMP_IMAGE (data);
648 GimpDialogConfig *config = GIMP_DIALOG_CONFIG (image->gimp->config);
649 GtkWidget *button;
650 gint radius_x;
651 gint radius_y;
652
653 button = g_object_get_data (G_OBJECT (widget), "edge-lock-toggle");
654
655 g_object_set (config,
656 "selection-shrink-radius", size,
657 "selection-shrink-edge-lock",
658 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)),
659 NULL);
660
661 radius_x = ROUND (config->selection_shrink_radius);
662 radius_y = ROUND (config->selection_shrink_radius);
663
664 if (unit != GIMP_UNIT_PIXEL)
665 {
666 gdouble xres;
667 gdouble yres;
668 gdouble factor;
669
670 gimp_image_get_resolution (image, &xres, &yres);
671
672 factor = (MAX (xres, yres) /
673 MIN (xres, yres));
674
675 if (xres == MIN (xres, yres))
676 radius_y *= factor;
677 else
678 radius_x *= factor;
679 }
680
681 gimp_channel_shrink (gimp_image_get_mask (image), radius_x, radius_y,
682 config->selection_shrink_edge_lock,
683 TRUE);
684 gimp_image_flush (image);
685 }
686