1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995-1999 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 "libgimpbase/gimpbase.h"
24 #include "libgimpwidgets/gimpwidgets.h"
25 
26 #include "widgets-types.h"
27 
28 #include "core/gimpdashpattern.h"
29 #include "core/gimpstrokeoptions.h"
30 
31 #include "gimpcellrendererdashes.h"
32 #include "gimpdasheditor.h"
33 #include "gimpstrokeeditor.h"
34 
35 #include "gimp-intl.h"
36 
37 
38 enum
39 {
40   PROP_0,
41   PROP_OPTIONS,
42   PROP_RESOLUTION
43 };
44 
45 
46 static void      gimp_stroke_editor_constructed  (GObject           *object);
47 static void      gimp_stroke_editor_set_property (GObject           *object,
48                                                   guint              property_id,
49                                                   const GValue      *value,
50                                                   GParamSpec        *pspec);
51 static void      gimp_stroke_editor_get_property (GObject           *object,
52                                                   guint              property_id,
53                                                   GValue            *value,
54                                                   GParamSpec        *pspec);
55 
56 static gboolean  gimp_stroke_editor_paint_button (GtkWidget         *widget,
57                                                   GdkEventExpose    *event,
58                                                   gpointer           data);
59 static void      gimp_stroke_editor_dash_preset  (GtkWidget         *widget,
60                                                   GimpStrokeOptions *options);
61 
62 static void      gimp_stroke_editor_combo_fill   (GimpStrokeOptions *options,
63                                                   GtkComboBox       *box);
64 
65 
G_DEFINE_TYPE(GimpStrokeEditor,gimp_stroke_editor,GIMP_TYPE_FILL_EDITOR)66 G_DEFINE_TYPE (GimpStrokeEditor, gimp_stroke_editor, GIMP_TYPE_FILL_EDITOR)
67 
68 #define parent_class gimp_stroke_editor_parent_class
69 
70 
71 static void
72 gimp_stroke_editor_class_init (GimpStrokeEditorClass *klass)
73 {
74   GObjectClass *object_class = G_OBJECT_CLASS (klass);
75 
76   object_class->constructed  = gimp_stroke_editor_constructed;
77   object_class->set_property = gimp_stroke_editor_set_property;
78   object_class->get_property = gimp_stroke_editor_get_property;
79 
80   g_object_class_install_property (object_class, PROP_OPTIONS,
81                                    g_param_spec_object ("options", NULL, NULL,
82                                                         GIMP_TYPE_STROKE_OPTIONS,
83                                                         GIMP_PARAM_READWRITE |
84                                                         G_PARAM_CONSTRUCT_ONLY));
85 
86   g_object_class_install_property (object_class, PROP_RESOLUTION,
87                                    g_param_spec_double ("resolution", NULL, NULL,
88                                                         GIMP_MIN_RESOLUTION,
89                                                         GIMP_MAX_RESOLUTION,
90                                                         72.0,
91                                                         GIMP_PARAM_READWRITE |
92                                                         G_PARAM_CONSTRUCT_ONLY));
93 }
94 
95 static void
gimp_stroke_editor_init(GimpStrokeEditor * editor)96 gimp_stroke_editor_init (GimpStrokeEditor *editor)
97 {
98 }
99 
100 static void
gimp_stroke_editor_constructed(GObject * object)101 gimp_stroke_editor_constructed (GObject *object)
102 {
103   GimpFillEditor    *fill_editor = GIMP_FILL_EDITOR (object);
104   GimpStrokeEditor  *editor      = GIMP_STROKE_EDITOR (object);
105   GimpStrokeOptions *options;
106   GimpEnumStore     *store;
107   GEnumValue        *value;
108   GtkWidget         *box;
109   GtkWidget         *size;
110   GtkWidget         *label;
111   GtkWidget         *frame;
112   GtkWidget         *table;
113   GtkWidget         *expander;
114   GtkWidget         *dash_editor;
115   GtkWidget         *button;
116   GtkCellRenderer   *cell;
117   gint               row = 0;
118 
119   G_OBJECT_CLASS (parent_class)->constructed (object);
120 
121   gimp_assert (GIMP_IS_STROKE_OPTIONS (fill_editor->options));
122 
123   options = GIMP_STROKE_OPTIONS (fill_editor->options);
124 
125   box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
126   gtk_box_pack_start (GTK_BOX (editor), box, FALSE, FALSE, 0);
127   gtk_widget_show (box);
128 
129   label = gtk_label_new (_("Line width:"));
130   gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
131   gtk_widget_show (label);
132 
133   size = gimp_prop_size_entry_new (G_OBJECT (options),
134                                    "width", FALSE, "unit",
135                                    "%a", GIMP_SIZE_ENTRY_UPDATE_SIZE,
136                                    editor->resolution);
137   gimp_size_entry_set_pixel_digits (GIMP_SIZE_ENTRY (size), 1);
138   gtk_box_pack_start (GTK_BOX (box), size, FALSE, FALSE, 0);
139   gtk_widget_show (size);
140 
141   expander = gtk_expander_new_with_mnemonic (_("_Line Style"));
142   gtk_box_pack_start (GTK_BOX (editor), expander, FALSE, FALSE, 0);
143   gtk_widget_show (expander);
144 
145   frame = gimp_frame_new ("<expander>");
146   gtk_container_add (GTK_CONTAINER (expander), frame);
147   gtk_widget_show (frame);
148 
149   table = gtk_table_new (5, 3, FALSE);
150   gtk_table_set_col_spacings (GTK_TABLE (table), 6);
151   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
152   gtk_table_set_row_spacing (GTK_TABLE (table), 2, 6);
153   gtk_table_set_row_spacing (GTK_TABLE (table), 4, 6);
154   gtk_container_add (GTK_CONTAINER (frame), table);
155   gtk_widget_show (table);
156 
157   box = gimp_prop_enum_icon_box_new (G_OBJECT (options), "cap-style",
158                                      "gimp-cap", 0, 0);
159   gimp_table_attach_aligned (GTK_TABLE (table), 0, row++,
160                              _("_Cap style:"), 0.0, 0.5,
161                              box, 2, TRUE);
162 
163   box = gimp_prop_enum_icon_box_new (G_OBJECT (options), "join-style",
164                                      "gimp-join", 0, 0);
165   gimp_table_attach_aligned (GTK_TABLE (table), 0, row++,
166                              _("_Join style:"), 0.0, 0.5,
167                              box, 2, TRUE);
168 
169   gimp_prop_scale_entry_new (G_OBJECT (options), "miter-limit",
170                              GTK_TABLE (table), 0, row++,
171                              _("_Miter limit:"),
172                              1.0, 1.0, 1,
173                              FALSE, 0.0, 0.0);
174 
175   frame = gtk_frame_new (NULL);
176   gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
177   gimp_table_attach_aligned (GTK_TABLE (table), 0, row++,
178                              _("Dash pattern:"), 0.0, 0.5,
179                              frame, 2, FALSE);
180 
181   box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
182   gtk_container_add (GTK_CONTAINER (frame), box);
183   gtk_widget_show (box);
184 
185   dash_editor = gimp_dash_editor_new (options);
186 
187   button = g_object_new (GTK_TYPE_BUTTON,
188                          "width-request", 14,
189                          NULL);
190   gtk_box_pack_start (GTK_BOX (box), button, FALSE, TRUE, 0);
191   gtk_widget_show (button);
192 
193   g_signal_connect_object (button, "clicked",
194                            G_CALLBACK (gimp_dash_editor_shift_left),
195                            dash_editor, G_CONNECT_SWAPPED);
196   g_signal_connect_after (button, "expose-event",
197                           G_CALLBACK (gimp_stroke_editor_paint_button),
198                           button);
199 
200   gtk_box_pack_start (GTK_BOX (box), dash_editor, TRUE, TRUE, 0);
201   gtk_widget_show (dash_editor);
202 
203   button = g_object_new (GTK_TYPE_BUTTON,
204                          "width-request", 14,
205                          NULL);
206   gtk_box_pack_start (GTK_BOX (box), button, FALSE, TRUE, 0);
207   gtk_widget_show (button);
208 
209   g_signal_connect_object (button, "clicked",
210                            G_CALLBACK (gimp_dash_editor_shift_right),
211                            dash_editor, G_CONNECT_SWAPPED);
212   g_signal_connect_after (button, "expose-event",
213                           G_CALLBACK (gimp_stroke_editor_paint_button),
214                           NULL);
215 
216 
217   store = g_object_new (GIMP_TYPE_ENUM_STORE,
218                         "enum-type",      GIMP_TYPE_DASH_PRESET,
219                         "user-data-type", GIMP_TYPE_DASH_PATTERN,
220                         NULL);
221 
222   for (value = store->enum_class->values; value->value_name; value++)
223     {
224       GtkTreeIter  iter = { 0, };
225       const gchar *desc;
226 
227       desc = gimp_enum_value_get_desc (store->enum_class, value);
228 
229       gtk_list_store_append (GTK_LIST_STORE (store), &iter);
230       gtk_list_store_set (GTK_LIST_STORE (store), &iter,
231                           GIMP_INT_STORE_VALUE, value->value,
232                           GIMP_INT_STORE_LABEL, desc,
233                           -1);
234     }
235 
236   box = gimp_enum_combo_box_new_with_model (store);
237   g_object_unref (store);
238 
239   gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (box), GIMP_DASH_CUSTOM);
240   gimp_table_attach_aligned (GTK_TABLE (table), 0, row++,
241                              _("Dash _preset:"), 0.0, 0.5,
242                              box, 2, FALSE);
243 
244   cell = g_object_new (GIMP_TYPE_CELL_RENDERER_DASHES,
245                        "xpad", 2,
246                        NULL);
247   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (box), cell, FALSE);
248   gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (box), cell,
249                                  "pattern", GIMP_INT_STORE_USER_DATA);
250 
251   gimp_stroke_editor_combo_fill (options, GTK_COMBO_BOX (box));
252 
253   g_signal_connect (box, "changed",
254                     G_CALLBACK (gimp_stroke_editor_dash_preset),
255                     options);
256   g_signal_connect_object (options, "dash-info-changed",
257                            G_CALLBACK (gimp_int_combo_box_set_active),
258                            box, G_CONNECT_SWAPPED);
259 }
260 
261 static void
gimp_stroke_editor_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)262 gimp_stroke_editor_set_property (GObject      *object,
263                                  guint         property_id,
264                                  const GValue *value,
265                                  GParamSpec   *pspec)
266 {
267   GimpFillEditor   *fill_editor = GIMP_FILL_EDITOR (object);
268   GimpStrokeEditor *editor      = GIMP_STROKE_EDITOR (object);
269 
270   switch (property_id)
271     {
272     case PROP_OPTIONS:
273       if (fill_editor->options)
274         g_object_unref (fill_editor->options);
275       fill_editor->options = g_value_dup_object (value);
276       break;
277 
278     case PROP_RESOLUTION:
279       editor->resolution = g_value_get_double (value);
280       break;
281 
282     default:
283       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
284       break;
285     }
286 }
287 
288 static void
gimp_stroke_editor_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)289 gimp_stroke_editor_get_property (GObject    *object,
290                                  guint       property_id,
291                                  GValue     *value,
292                                  GParamSpec *pspec)
293 {
294   GimpFillEditor   *fill_editor = GIMP_FILL_EDITOR (object);
295   GimpStrokeEditor *editor      = GIMP_STROKE_EDITOR (object);
296 
297   switch (property_id)
298     {
299     case PROP_OPTIONS:
300       g_value_set_object (value, fill_editor->options);
301       break;
302 
303     case PROP_RESOLUTION:
304       g_value_set_double (value, editor->resolution);
305       break;
306 
307     default:
308       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
309       break;
310     }
311 }
312 
313 GtkWidget *
gimp_stroke_editor_new(GimpStrokeOptions * options,gdouble resolution,gboolean edit_context)314 gimp_stroke_editor_new (GimpStrokeOptions *options,
315                         gdouble            resolution,
316                         gboolean           edit_context)
317 {
318   g_return_val_if_fail (GIMP_IS_STROKE_OPTIONS (options), NULL);
319 
320   return g_object_new (GIMP_TYPE_STROKE_EDITOR,
321                        "options",      options,
322                        "resolution",   resolution,
323                        "edit-context", edit_context ? TRUE : FALSE,
324                        NULL);
325 }
326 
327 static gboolean
gimp_stroke_editor_paint_button(GtkWidget * widget,GdkEventExpose * event,gpointer data)328 gimp_stroke_editor_paint_button (GtkWidget       *widget,
329                                  GdkEventExpose  *event,
330                                  gpointer         data)
331 {
332   GtkStyle      *style = gtk_widget_get_style (widget);
333   GtkAllocation  allocation;
334   gint           w;
335 
336   gtk_widget_get_allocation (widget, &allocation);
337 
338   w = MIN (allocation.width, allocation.height) * 2 / 3;
339 
340   gtk_paint_arrow (style,
341                    gtk_widget_get_window (widget),
342                    gtk_widget_get_state (widget),
343                    GTK_SHADOW_IN,
344                    &event->area, widget, NULL,
345                    data ? GTK_ARROW_LEFT : GTK_ARROW_RIGHT, TRUE,
346                    allocation.x + (allocation.width - w) / 2,
347                    allocation.y + (allocation.height - w) / 2,
348                    w, w);
349   return FALSE;
350 }
351 
352 static void
gimp_stroke_editor_dash_preset(GtkWidget * widget,GimpStrokeOptions * options)353 gimp_stroke_editor_dash_preset (GtkWidget         *widget,
354                                 GimpStrokeOptions *options)
355 {
356   gint value;
357 
358   if (gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &value) &&
359       value != GIMP_DASH_CUSTOM)
360     {
361       gimp_stroke_options_take_dash_pattern (options, value, NULL);
362     }
363 }
364 
365 static void
gimp_stroke_editor_combo_update(GtkTreeModel * model,GParamSpec * pspec,GimpStrokeOptions * options)366 gimp_stroke_editor_combo_update (GtkTreeModel      *model,
367                                  GParamSpec        *pspec,
368                                  GimpStrokeOptions *options)
369 {
370   GtkTreeIter iter;
371 
372   if (gimp_int_store_lookup_by_value (model, GIMP_DASH_CUSTOM, &iter))
373     {
374       gtk_list_store_set (GTK_LIST_STORE (model), &iter,
375                           GIMP_INT_STORE_USER_DATA,
376                           gimp_stroke_options_get_dash_info (options),
377                           -1);
378     }
379 }
380 
381 static void
gimp_stroke_editor_combo_fill(GimpStrokeOptions * options,GtkComboBox * box)382 gimp_stroke_editor_combo_fill (GimpStrokeOptions *options,
383                                GtkComboBox       *box)
384 {
385   GtkTreeModel *model = gtk_combo_box_get_model (box);
386   GtkTreeIter   iter;
387   gboolean      iter_valid;
388 
389   for (iter_valid = gtk_tree_model_get_iter_first (model, &iter);
390        iter_valid;
391        iter_valid = gtk_tree_model_iter_next (model, &iter))
392     {
393       gint value;
394 
395       gtk_tree_model_get (model, &iter,
396                           GIMP_INT_STORE_VALUE, &value,
397                           -1);
398 
399       if (value == GIMP_DASH_CUSTOM)
400         {
401           gtk_list_store_set (GTK_LIST_STORE (model), &iter,
402                               GIMP_INT_STORE_USER_DATA,
403                               gimp_stroke_options_get_dash_info (options),
404                               -1);
405 
406           g_signal_connect_object (options, "notify::dash-info",
407                                    G_CALLBACK (gimp_stroke_editor_combo_update),
408                                    model, G_CONNECT_SWAPPED);
409         }
410       else
411         {
412           GArray *pattern = gimp_dash_pattern_new_from_preset (value);
413 
414           gtk_list_store_set (GTK_LIST_STORE (model), &iter,
415                               GIMP_INT_STORE_USER_DATA, pattern,
416                               -1);
417           gimp_dash_pattern_free (pattern);
418         }
419     }
420 }
421