From 49ffbbbbc0fa7e958f3ade393d177f96b95938a4 Mon Sep 17 00:00:00 2001 From: Michael Natterer Date: Tue, 15 Mar 2016 20:52:22 +0100 Subject: [PATCH] app: add menu items and a dialog for GimpItem::fill() which are essentially a copy of the stroking GUI. We now can fill the exact shape outlined by stroking selections and paths. Suggestions for the menu item labels are welcome. --- app/actions/select-actions.c | 14 +++ app/actions/select-commands.c | 77 +++++++++++++ app/actions/select-commands.h | 5 + app/actions/vectors-actions.c | 18 +++ app/actions/vectors-commands.c | 79 ++++++++++++- app/actions/vectors-commands.h | 5 + app/dialogs/Makefile.am | 2 + app/dialogs/fill-dialog.c | 200 +++++++++++++++++++++++++++++++++ app/dialogs/fill-dialog.h | 33 ++++++ app/widgets/gimphelp-ids.h | 2 + menus/image-menu.xml.in | 2 + po/POTFILES.in | 1 + 12 files changed, 437 insertions(+), 1 deletion(-) create mode 100644 app/dialogs/fill-dialog.c create mode 100644 app/dialogs/fill-dialog.h diff --git a/app/actions/select-actions.c b/app/actions/select-actions.c index 02af2afa65..ea30175aa5 100644 --- a/app/actions/select-actions.c +++ b/app/actions/select-actions.c @@ -113,6 +113,18 @@ static const GimpActionEntry select_actions[] = G_CALLBACK (select_save_cmd_callback), GIMP_HELP_SELECTION_TO_CHANNEL }, + { "select-fill", GIMP_STOCK_TOOL_BUCKET_FILL, + NC_("select-action", "_Fill Selection Outline..."), NULL, + NC_("select-action", "Fill the selection outline"), + G_CALLBACK (select_fill_cmd_callback), + GIMP_HELP_SELECTION_FILL }, + + { "select-fill-last-values", GIMP_STOCK_TOOL_BUCKET_FILL, + NC_("select-action", "_Fill Selection Outline"), NULL, + NC_("select-action", "Fill the selection outline with last used values"), + G_CALLBACK (select_fill_last_vals_cmd_callback), + GIMP_HELP_SELECTION_FILL }, + { "select-stroke", GIMP_STOCK_SELECTION_STROKE, NC_("select-action", "_Stroke Selection..."), NULL, NC_("select-action", "Paint along the selection outline"), @@ -178,6 +190,8 @@ select_actions_update (GimpActionGroup *group, SET_SENSITIVE ("select-flood", drawable && sel); SET_SENSITIVE ("select-save", drawable && !fs); + SET_SENSITIVE ("select-fill", writable && !children && sel); + SET_SENSITIVE ("select-fill-last-values", writable && !children && sel); SET_SENSITIVE ("select-stroke", writable && !children && sel); SET_SENSITIVE ("select-stroke-last-values", writable && !children && sel); diff --git a/app/actions/select-commands.c b/app/actions/select-commands.c index 1f8398c9a3..cc35eb71b9 100644 --- a/app/actions/select-commands.c +++ b/app/actions/select-commands.c @@ -39,6 +39,7 @@ #include "display/gimpdisplay.h" #include "display/gimpdisplayshell.h" +#include "dialogs/fill-dialog.h" #include "dialogs/stroke-dialog.h" #include "actions.h" @@ -338,6 +339,82 @@ select_save_cmd_callback (GtkAction *action, "gimp-channel-list"); } +void +select_fill_cmd_callback (GtkAction *action, + gpointer data) +{ + GimpImage *image; + GimpDrawable *drawable; + GtkWidget *widget; + GtkWidget *dialog; + return_if_no_image (image, data); + return_if_no_widget (widget, data); + + drawable = gimp_image_get_active_drawable (image); + + if (! drawable) + { + gimp_message_literal (image->gimp, + G_OBJECT (widget), GIMP_MESSAGE_WARNING, + _("There is no active layer or channel to fill.")); + return; + } + + dialog = fill_dialog_new (GIMP_ITEM (gimp_image_get_mask (image)), + action_data_get_context (data), + _("Fill Selection Outline"), + GIMP_STOCK_TOOL_BUCKET_FILL, + GIMP_HELP_SELECTION_FILL, + widget); + gtk_widget_show (dialog); +} + +void +select_fill_last_vals_cmd_callback (GtkAction *action, + gpointer data) +{ + GimpImage *image; + GimpDrawable *drawable; + GtkWidget *widget; + GimpFillOptions *options; + GError *error = NULL; + return_if_no_image (image, data); + return_if_no_widget (widget, data); + + drawable = gimp_image_get_active_drawable (image); + + if (! drawable) + { + gimp_message_literal (image->gimp, + G_OBJECT (widget), GIMP_MESSAGE_WARNING, + _("There is no active layer or channel to fill.")); + return; + } + + options = g_object_get_data (G_OBJECT (image->gimp), "saved-fill-options"); + + if (options) + g_object_ref (options); + else + options = gimp_fill_options_new (image->gimp, + action_data_get_context (data), TRUE); + + if (! gimp_item_fill (GIMP_ITEM (gimp_image_get_mask (image)), + drawable, options, TRUE, NULL, &error)) + { + gimp_message_literal (image->gimp, + G_OBJECT (widget), GIMP_MESSAGE_WARNING, + error->message); + g_clear_error (&error); + } + else + { + gimp_image_flush (image); + } + + g_object_unref (options); +} + void select_stroke_cmd_callback (GtkAction *action, gpointer data) diff --git a/app/actions/select-commands.h b/app/actions/select-commands.h index 3691491219..eb7475907c 100644 --- a/app/actions/select-commands.h +++ b/app/actions/select-commands.h @@ -41,6 +41,11 @@ void select_flood_cmd_callback (GtkAction *action, gpointer data); void select_save_cmd_callback (GtkAction *action, gpointer data); + +void select_fill_cmd_callback (GtkAction *action, + gpointer data); +void select_fill_last_vals_cmd_callback (GtkAction *action, + gpointer data); void select_stroke_cmd_callback (GtkAction *action, gpointer data); void select_stroke_last_vals_cmd_callback (GtkAction *action, diff --git a/app/actions/vectors-actions.c b/app/actions/vectors-actions.c index 1c222897d2..537f1d6def 100644 --- a/app/actions/vectors-actions.c +++ b/app/actions/vectors-actions.c @@ -108,6 +108,18 @@ static const GimpActionEntry vectors_actions[] = G_CALLBACK (vectors_lower_to_bottom_cmd_callback), GIMP_HELP_PATH_LOWER_TO_BOTTOM }, + { "vectors-fill", GIMP_STOCK_TOOL_BUCKET_FILL, + NC_("vectors-action", "Fill Path..."), NULL, + NC_("vectors-action", "Fill the path"), + G_CALLBACK (vectors_fill_cmd_callback), + GIMP_HELP_PATH_FILL }, + + { "vectors-fill-last-values", GIMP_STOCK_TOOL_BUCKET_FILL, + NC_("vectors-action", "Fill Path"), NULL, + NC_("vectors-action", "Fill the path with last values"), + G_CALLBACK (vectors_fill_last_vals_cmd_callback), + GIMP_HELP_PATH_FILL }, + { "vectors-stroke", GIMP_STOCK_PATH_STROKE, NC_("vectors-action", "Stro_ke Path..."), NULL, NC_("vectors-action", "Paint along the path"), @@ -345,6 +357,12 @@ vectors_actions_update (GimpActionGroup *group, SET_SENSITIVE ("vectors-selection-to-vectors", image && !mask_empty); SET_SENSITIVE ("vectors-selection-to-vectors-short", image && !mask_empty); SET_SENSITIVE ("vectors-selection-to-vectors-advanced", image && !mask_empty); + SET_SENSITIVE ("vectors-fill", vectors && + dr_writable && + !dr_children); + SET_SENSITIVE ("vectors-fill-last-values", vectors && + dr_writable && + !dr_children); SET_SENSITIVE ("vectors-stroke", vectors && dr_writable && !dr_children); diff --git a/app/actions/vectors-commands.c b/app/actions/vectors-commands.c index 5d4292f060..7e010a3d68 100644 --- a/app/actions/vectors-commands.c +++ b/app/actions/vectors-commands.c @@ -56,6 +56,7 @@ #include "tools/gimpvectortool.h" #include "tools/tool_manager.h" +#include "dialogs/fill-dialog.h" #include "dialogs/stroke-dialog.h" #include "dialogs/vectors-export-dialog.h" #include "dialogs/vectors-import-dialog.h" @@ -368,6 +369,83 @@ vectors_selection_to_vectors_cmd_callback (GtkAction *action, } } +void +vectors_fill_cmd_callback (GtkAction *action, + gpointer data) +{ + GimpImage *image; + GimpVectors *vectors; + GimpDrawable *drawable; + GtkWidget *widget; + GtkWidget *dialog; + return_if_no_vectors (image, vectors, data); + return_if_no_widget (widget, data); + + drawable = gimp_image_get_active_drawable (image); + + if (! drawable) + { + gimp_message_literal (image->gimp, + G_OBJECT (widget), GIMP_MESSAGE_WARNING, + _("There is no active layer or channel to fill.")); + return; + } + + dialog = fill_dialog_new (GIMP_ITEM (vectors), + action_data_get_context (data), + _("Fill Path"), + GIMP_STOCK_TOOL_BUCKET_FILL, + GIMP_HELP_PATH_FILL, + widget); + gtk_widget_show (dialog); +} + +void +vectors_fill_last_vals_cmd_callback (GtkAction *action, + gpointer data) +{ + GimpImage *image; + GimpVectors *vectors; + GimpDrawable *drawable; + GtkWidget *widget; + GimpFillOptions *options; + GError *error = NULL; + return_if_no_vectors (image, vectors, data); + return_if_no_widget (widget, data); + + drawable = gimp_image_get_active_drawable (image); + + if (! drawable) + { + gimp_message_literal (image->gimp, + G_OBJECT (widget), GIMP_MESSAGE_WARNING, + _("There is no active layer or channel to fill.")); + return; + } + + options = g_object_get_data (G_OBJECT (image->gimp), "saved-fill-options"); + + if (options) + g_object_ref (options); + else + options = gimp_fill_options_new (image->gimp, + action_data_get_context (data), TRUE); + + if (! gimp_item_fill (GIMP_ITEM (vectors), + drawable, options, TRUE, NULL, &error)) + { + gimp_message_literal (image->gimp, G_OBJECT (widget), + GIMP_MESSAGE_WARNING, error->message); + g_clear_error (&error); + } + else + { + gimp_image_flush (image); + } + + g_object_unref (options); +} + void vectors_stroke_cmd_callback (GtkAction *action, gpointer data) @@ -424,7 +502,6 @@ vectors_stroke_last_vals_cmd_callback (GtkAction *action, return; } - options = g_object_get_data (G_OBJECT (image->gimp), "saved-stroke-options"); if (options) diff --git a/app/actions/vectors-commands.h b/app/actions/vectors-commands.h index af485c3978..8b929897c7 100644 --- a/app/actions/vectors-commands.h +++ b/app/actions/vectors-commands.h @@ -49,6 +49,11 @@ void vectors_to_selection_cmd_callback (GtkAction *action, void vectors_selection_to_vectors_cmd_callback (GtkAction *action, gint value, gpointer data); + +void vectors_fill_cmd_callback (GtkAction *action, + gpointer data); +void vectors_fill_last_vals_cmd_callback (GtkAction *action, + gpointer data); void vectors_stroke_cmd_callback (GtkAction *action, gpointer data); void vectors_stroke_last_vals_cmd_callback (GtkAction *action, diff --git a/app/dialogs/Makefile.am b/app/dialogs/Makefile.am index 411e49532b..8374b9bc95 100644 --- a/app/dialogs/Makefile.am +++ b/app/dialogs/Makefile.am @@ -44,6 +44,8 @@ libappdialogs_a_sources = \ file-open-location-dialog.h \ file-save-dialog.c \ file-save-dialog.h \ + fill-dialog.c \ + fill-dialog.h \ grid-dialog.h \ grid-dialog.c \ image-merge-layers-dialog.c \ diff --git a/app/dialogs/fill-dialog.c b/app/dialogs/fill-dialog.c new file mode 100644 index 0000000000..1eb0f02cae --- /dev/null +++ b/app/dialogs/fill-dialog.c @@ -0,0 +1,200 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * fill-dialog.c + * Copyright (C) 2016 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "libgimpconfig/gimpconfig.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "dialogs-types.h" + +#include "core/gimp.h" +#include "core/gimpdrawable.h" +#include "core/gimpimage.h" +#include "core/gimpfilloptions.h" + +#include "widgets/gimpfilleditor.h" +#include "widgets/gimpviewabledialog.h" + +#include "fill-dialog.h" + +#include "gimp-intl.h" + + +#define RESPONSE_RESET 1 + + +/* local functions */ + +static void fill_dialog_response (GtkWidget *widget, + gint response_id, + GtkWidget *dialog); + + +/* public function */ + +GtkWidget * +fill_dialog_new (GimpItem *item, + GimpContext *context, + const gchar *title, + const gchar *icon_name, + const gchar *help_id, + GtkWidget *parent) +{ + GimpFillOptions *options; + GimpFillOptions *saved_options; + GtkWidget *dialog; + GtkWidget *main_vbox; + GtkWidget *fill_editor; + + g_return_val_if_fail (GIMP_IS_ITEM (item), NULL); + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (icon_name != NULL, NULL); + g_return_val_if_fail (help_id != NULL, NULL); + g_return_val_if_fail (parent == NULL || GTK_IS_WIDGET (parent), NULL); + + options = gimp_fill_options_new (context->gimp, context, TRUE); + + saved_options = g_object_get_data (G_OBJECT (context->gimp), + "saved-fill-options"); + + if (saved_options) + gimp_config_sync (G_OBJECT (saved_options), G_OBJECT (options), 0); + + dialog = gimp_viewable_dialog_new (GIMP_VIEWABLE (item), context, + title, "gimp-fill-options", + icon_name, + _("Choose Fill Style"), + parent, + gimp_standard_help_func, + help_id, + + GIMP_STOCK_RESET, RESPONSE_RESET, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + _("_Fill"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + RESPONSE_RESET, + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + + g_signal_connect (dialog, "response", + G_CALLBACK (fill_dialog_response), + dialog); + + g_object_set_data (G_OBJECT (dialog), "gimp-item", item); + g_object_set_data_full (G_OBJECT (dialog), "gimp-fill-options", options, + (GDestroyNotify) g_object_unref); + + main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + main_vbox, TRUE, TRUE, 0); + gtk_widget_show (main_vbox); + + fill_editor = gimp_fill_editor_new (options, FALSE); + gtk_box_pack_start (GTK_BOX (main_vbox), fill_editor, FALSE, FALSE, 0); + gtk_widget_show (fill_editor); + + return dialog; +} + + +/* private functions */ + +static void +fill_dialog_response (GtkWidget *widget, + gint response_id, + GtkWidget *dialog) +{ + GimpFillOptions *options; + GimpItem *item; + GimpImage *image; + GimpContext *context; + + item = g_object_get_data (G_OBJECT (dialog), "gimp-item"); + options = g_object_get_data (G_OBJECT (dialog), "gimp-fill-options"); + + image = gimp_item_get_image (item); + context = GIMP_VIEWABLE_DIALOG (dialog)->context; + + switch (response_id) + { + case RESPONSE_RESET: + gimp_config_reset (GIMP_CONFIG (options)); + break; + + case GTK_RESPONSE_OK: + { + GimpDrawable *drawable = gimp_image_get_active_drawable (image); + GimpFillOptions *saved_options; + GError *error = NULL; + + if (! drawable) + { + gimp_message_literal (context->gimp, G_OBJECT (widget), + GIMP_MESSAGE_WARNING, + _("There is no active layer or channel " + "to fill.")); + return; + } + + saved_options = g_object_get_data (G_OBJECT (context->gimp), + "saved-fill-options"); + + if (saved_options) + g_object_ref (saved_options); + else + saved_options = gimp_fill_options_new (context->gimp, context, TRUE); + + gimp_config_sync (G_OBJECT (options), G_OBJECT (saved_options), 0); + + g_object_set_data_full (G_OBJECT (context->gimp), "saved-fill-options", + saved_options, + (GDestroyNotify) g_object_unref); + + if (! gimp_item_fill (item, drawable, options, TRUE, NULL, &error)) + { + gimp_message_literal (context->gimp, + G_OBJECT (widget), + GIMP_MESSAGE_WARNING, + error ? error->message : "NULL"); + + g_clear_error (&error); + return; + } + + gimp_image_flush (image); + } + /* fallthrough */ + + default: + gtk_widget_destroy (dialog); + break; + } +} diff --git a/app/dialogs/fill-dialog.h b/app/dialogs/fill-dialog.h new file mode 100644 index 0000000000..da3e90016f --- /dev/null +++ b/app/dialogs/fill-dialog.h @@ -0,0 +1,33 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * fill-dialog.h + * Copyright (C) 2016 Michael Natterer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __FILL_DIALOG_H__ +#define __FILL_DIALOG_H__ + + +GtkWidget * fill_dialog_new (GimpItem *item, + GimpContext *context, + const gchar *title, + const gchar *icon_name, + const gchar *help_id, + GtkWidget *parent); + + +#endif /* __FILL_DIALOG_H__ */ diff --git a/app/widgets/gimphelp-ids.h b/app/widgets/gimphelp-ids.h index dfc1e3ab4d..bae40f2ec5 100644 --- a/app/widgets/gimphelp-ids.h +++ b/app/widgets/gimphelp-ids.h @@ -75,6 +75,7 @@ #define GIMP_HELP_SELECTION_GROW "gimp-selection-grow" #define GIMP_HELP_SELECTION_BORDER "gimp-selection-border" #define GIMP_HELP_SELECTION_FLOOD "gimp-selection-flood" +#define GIMP_HELP_SELECTION_FILL "gimp-selection-fill" #define GIMP_HELP_SELECTION_STROKE "gimp-selection-stroke" #define GIMP_HELP_SELECTION_TO_CHANNEL "gimp-selection-to-channel" #define GIMP_HELP_SELECTION_TO_PATH "gimp-selection-to-path" @@ -258,6 +259,7 @@ #define GIMP_HELP_PATH_SELECTION_ADD "gimp-path-selection-add" #define GIMP_HELP_PATH_SELECTION_SUBTRACT "gimp-path-selection-subtract" #define GIMP_HELP_PATH_SELECTION_INTERSECT "gimp-path-selection-intersect" +#define GIMP_HELP_PATH_FILL "gimp-path-fill" #define GIMP_HELP_PATH_STROKE "gimp-path-stroke" #define GIMP_HELP_PATH_COPY "gimp-path-copy" #define GIMP_HELP_PATH_PASTE "gimp-path-paste" diff --git a/menus/image-menu.xml.in b/menus/image-menu.xml.in index 037324c4b4..1ea7b4557c 100644 --- a/menus/image-menu.xml.in +++ b/menus/image-menu.xml.in @@ -213,6 +213,8 @@ + + diff --git a/po/POTFILES.in b/po/POTFILES.in index 130b0dcc78..d8bdd0351a 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -200,6 +200,7 @@ app/dialogs/fade-dialog.c app/dialogs/file-open-dialog.c app/dialogs/file-open-location-dialog.c app/dialogs/file-save-dialog.c +app/dialogs/fill-dialog.c app/dialogs/grid-dialog.c app/dialogs/image-merge-layers-dialog.c app/dialogs/image-new-dialog.c