1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * gimpdasheditor.c
5  * Copyright (C) 2003 Simon Budig  <simon@gimp.org>
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 
23 #include <gegl.h>
24 #include <gtk/gtk.h>
25 
26 #include "libgimpmath/gimpmath.h"
27 #include "libgimpconfig/gimpconfig.h"
28 
29 #include "widgets-types.h"
30 
31 #include "core/gimpdashpattern.h"
32 #include "core/gimpstrokeoptions.h"
33 
34 #include "gimpdasheditor.h"
35 
36 
37 #define MIN_WIDTH          64
38 #define MIN_HEIGHT         20
39 
40 #define DEFAULT_N_SEGMENTS 24
41 
42 
43 enum
44 {
45   PROP_0,
46   PROP_STROKE_OPTIONS,
47   PROP_N_SEGMENTS,
48   PROP_LENGTH
49 };
50 
51 
52 static void gimp_dash_editor_finalize           (GObject        *object);
53 static void gimp_dash_editor_set_property       (GObject        *object,
54                                                  guint           property_id,
55                                                  const GValue   *value,
56                                                  GParamSpec     *pspec);
57 static void gimp_dash_editor_get_property       (GObject        *object,
58                                                  guint           property_id,
59                                                  GValue         *value,
60                                                  GParamSpec     *pspec);
61 
62 static void gimp_dash_editor_size_request       (GtkWidget      *widget,
63                                                  GtkRequisition *requisition);
64 static gboolean gimp_dash_editor_expose         (GtkWidget      *widget,
65                                                  GdkEventExpose *event);
66 static gboolean gimp_dash_editor_button_press   (GtkWidget      *widget,
67                                                  GdkEventButton *bevent);
68 static gboolean gimp_dash_editor_button_release (GtkWidget      *widget,
69                                                  GdkEventButton *bevent);
70 static gboolean gimp_dash_editor_motion_notify  (GtkWidget      *widget,
71                                                  GdkEventMotion *bevent);
72 
73 /* helper function */
74 static void update_segments_from_options        (GimpDashEditor *editor);
75 static void update_options_from_segments        (GimpDashEditor *editor);
76 static void update_blocksize                    (GimpDashEditor *editor);
77 static gint dash_x_to_index                     (GimpDashEditor *editor,
78                                                  gint            x);
79 
80 
G_DEFINE_TYPE(GimpDashEditor,gimp_dash_editor,GTK_TYPE_DRAWING_AREA)81 G_DEFINE_TYPE (GimpDashEditor, gimp_dash_editor, GTK_TYPE_DRAWING_AREA)
82 
83 #define parent_class gimp_dash_editor_parent_class
84 
85 
86 static void
87 gimp_dash_editor_class_init (GimpDashEditorClass *klass)
88 {
89   GObjectClass   *object_class = G_OBJECT_CLASS (klass);
90   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
91 
92   object_class->finalize     = gimp_dash_editor_finalize;
93   object_class->get_property = gimp_dash_editor_get_property;
94   object_class->set_property = gimp_dash_editor_set_property;
95 
96   widget_class->size_request         = gimp_dash_editor_size_request;
97   widget_class->expose_event         = gimp_dash_editor_expose;
98   widget_class->button_press_event   = gimp_dash_editor_button_press;
99   widget_class->button_release_event = gimp_dash_editor_button_release;
100   widget_class->motion_notify_event  = gimp_dash_editor_motion_notify;
101 
102   g_object_class_install_property (object_class, PROP_STROKE_OPTIONS,
103                                    g_param_spec_object ("stroke-options",
104                                                         NULL, NULL,
105                                                         GIMP_TYPE_STROKE_OPTIONS,
106                                                         GIMP_PARAM_READWRITE |
107                                                         G_PARAM_CONSTRUCT_ONLY));
108 
109   g_object_class_install_property (object_class, PROP_N_SEGMENTS,
110                                    g_param_spec_int ("n-segments",
111                                                      NULL, NULL,
112                                                      2, 120, DEFAULT_N_SEGMENTS,
113                                                      GIMP_PARAM_READWRITE |
114                                                      G_PARAM_CONSTRUCT));
115 
116   g_object_class_install_property (object_class, PROP_LENGTH,
117                                    g_param_spec_double ("dash-length",
118                                                         NULL, NULL,
119                                                         0.0, 2000.0,
120                                                         0.5 * DEFAULT_N_SEGMENTS,
121                                                         GIMP_PARAM_READWRITE |
122                                                         G_PARAM_CONSTRUCT));
123 }
124 
125 static void
gimp_dash_editor_init(GimpDashEditor * editor)126 gimp_dash_editor_init (GimpDashEditor *editor)
127 {
128   editor->segments       = NULL;
129   editor->block_width    = 6;
130   editor->block_height   = 6;
131   editor->edit_mode      = TRUE;
132   editor->edit_button_x0 = 0;
133 
134   gtk_widget_add_events (GTK_WIDGET (editor),
135                          GDK_BUTTON_PRESS_MASK   |
136                          GDK_BUTTON_RELEASE_MASK |
137                          GDK_BUTTON1_MOTION_MASK);
138 }
139 
140 static void
gimp_dash_editor_finalize(GObject * object)141 gimp_dash_editor_finalize (GObject *object)
142 {
143   GimpDashEditor *editor = GIMP_DASH_EDITOR (object);
144 
145   g_clear_object (&editor->stroke_options);
146   g_clear_pointer (&editor->segments, g_free);
147 
148   G_OBJECT_CLASS (parent_class)->finalize (object);
149 }
150 
151 static void
gimp_dash_editor_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)152 gimp_dash_editor_set_property (GObject      *object,
153                                guint         property_id,
154                                const GValue *value,
155                                GParamSpec   *pspec)
156 {
157   GimpDashEditor *editor = GIMP_DASH_EDITOR (object);
158 
159   switch (property_id)
160     {
161     case PROP_STROKE_OPTIONS:
162       g_return_if_fail (editor->stroke_options == NULL);
163 
164       editor->stroke_options = g_value_dup_object (value);
165       g_signal_connect_object (editor->stroke_options, "notify::dash-info",
166                                G_CALLBACK (update_segments_from_options),
167                                editor, G_CONNECT_SWAPPED);
168       break;
169 
170     case PROP_N_SEGMENTS:
171       editor->n_segments = g_value_get_int (value);
172 
173       if (editor->segments)
174         g_free (editor->segments);
175       editor->segments = g_new0 (gboolean, editor->n_segments);
176       break;
177 
178     case PROP_LENGTH:
179       editor->dash_length = g_value_get_double (value);
180       break;
181 
182     default:
183       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
184       break;
185     }
186 
187   update_segments_from_options (editor);
188 }
189 
190 static void
gimp_dash_editor_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)191 gimp_dash_editor_get_property (GObject      *object,
192                                guint         property_id,
193                                GValue       *value,
194                                GParamSpec   *pspec)
195 {
196   GimpDashEditor *editor = GIMP_DASH_EDITOR (object);
197 
198   switch (property_id)
199     {
200     case PROP_STROKE_OPTIONS:
201       g_value_set_object (value, editor->stroke_options);
202       break;
203     case PROP_N_SEGMENTS:
204       g_value_set_int (value, editor->n_segments);
205       break;
206     case PROP_LENGTH:
207       g_value_set_double (value, editor->dash_length);
208       break;
209 
210     default:
211       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
212       break;
213     }
214 }
215 
216 static void
gimp_dash_editor_size_request(GtkWidget * widget,GtkRequisition * requisition)217 gimp_dash_editor_size_request (GtkWidget      *widget,
218                                GtkRequisition *requisition)
219 {
220   GimpDashEditor *editor = GIMP_DASH_EDITOR (widget);
221 
222   requisition->width  = MAX (editor->block_width * editor->n_segments + 20,
223                              MIN_WIDTH);
224   requisition->height = MAX (editor->block_height + 10, MIN_HEIGHT);
225 }
226 
227 static gboolean
gimp_dash_editor_expose(GtkWidget * widget,GdkEventExpose * event)228 gimp_dash_editor_expose (GtkWidget      *widget,
229                          GdkEventExpose *event)
230 {
231   GimpDashEditor *editor = GIMP_DASH_EDITOR (widget);
232   GtkStyle       *style  = gtk_widget_get_style (widget);
233   cairo_t        *cr     = gdk_cairo_create (gtk_widget_get_window (widget));
234   GtkAllocation   allocation;
235   gint            x;
236   gint            w, h;
237 
238   gtk_widget_get_allocation (widget, &allocation);
239 
240   update_blocksize (editor);
241 
242   gdk_cairo_rectangle (cr, &event->area);
243   cairo_clip (cr);
244 
245   /*  draw the background  */
246 
247   gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_NORMAL]);
248   cairo_paint (cr);
249 
250   w = editor->block_width;
251   h = editor->block_height;
252 
253   editor->x0 = (allocation.width - w * editor->n_segments) / 2;
254   editor->y0 = (allocation.height - h) / 2;
255 
256   /*  draw the dash segments  */
257 
258   x = editor->x0 % w;
259 
260   if (x > 0)
261     x -= w;
262 
263   for (; x < editor->x0; x += w)
264     {
265       gint index = dash_x_to_index (editor, x);
266 
267       if (editor->segments[index])
268         cairo_rectangle (cr, x, editor->y0, w, h);
269     }
270 
271   gdk_cairo_set_source_color (cr, &style->text_aa[GTK_STATE_NORMAL]);
272   cairo_fill (cr);
273 
274   for (; x < editor->x0 + editor->n_segments * w; x += w)
275     {
276       gint index = dash_x_to_index (editor, x);
277 
278       if (editor->segments[index])
279         cairo_rectangle (cr, x, editor->y0, w, h);
280     }
281 
282   gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]);
283   cairo_fill (cr);
284 
285   for (; x < allocation.width + w; x += w)
286     {
287       gint index = dash_x_to_index (editor, x);
288 
289       if (editor->segments[index])
290         cairo_rectangle (cr, x, editor->y0, w, h);
291     }
292 
293   gdk_cairo_set_source_color (cr, &style->text_aa[GTK_STATE_NORMAL]);
294   cairo_fill (cr);
295 
296   /*  draw rulers  */
297 
298   x = editor->x0 % w;
299 
300   if (x > 0)
301     x -= w;
302 
303   for (; x < allocation.width + w; x += w)
304     {
305       gint index = dash_x_to_index (editor, x);
306 
307       if (editor->n_segments % 4 == 0 &&
308           (index + 1) % (editor->n_segments / 4) == 0)
309         {
310           cairo_move_to (cr, x + w - 0.5, editor->y0 - 2);
311           cairo_line_to (cr, x + w - 0.5, editor->y0 + h + 2);
312         }
313       else if (index % 2 == 1)
314         {
315           cairo_move_to (cr, x + w - 0.5, editor->y0 + 1);
316           cairo_line_to (cr, x + w - 0.5, editor->y0 + h - 1);
317         }
318       else
319         {
320           cairo_move_to (cr, x + w - 0.5, editor->y0 + h / 2 - 1);
321           cairo_line_to (cr, x + w - 0.5, editor->y0 + h / 2 + 1);
322         }
323     }
324 
325   cairo_move_to (cr, editor->x0 - 0.5, editor->y0 - 1);
326   cairo_move_to (cr, editor->x0 - 0.5, editor->y0 + h);
327 
328   gdk_cairo_set_source_color (cr, &style->text_aa[GTK_STATE_NORMAL]);
329   cairo_set_line_width (cr, 1.0);
330   cairo_stroke (cr);
331 
332   cairo_destroy (cr);
333 
334   return FALSE;
335 }
336 
337 static gboolean
gimp_dash_editor_button_press(GtkWidget * widget,GdkEventButton * bevent)338 gimp_dash_editor_button_press (GtkWidget      *widget,
339                                GdkEventButton *bevent)
340 {
341   GimpDashEditor *editor = GIMP_DASH_EDITOR (widget);
342   gint            index;
343 
344   if (bevent->button == 1 && bevent->type == GDK_BUTTON_PRESS)
345     {
346       gtk_grab_add (widget);
347 
348       index = dash_x_to_index (editor, bevent->x);
349 
350       editor->edit_mode = ! editor->segments [index];
351       editor->edit_button_x0 = bevent->x;
352 
353       editor->segments [index] = editor->edit_mode;
354 
355       gtk_widget_queue_draw (widget);
356     }
357 
358   return TRUE;
359 }
360 
361 static gboolean
gimp_dash_editor_button_release(GtkWidget * widget,GdkEventButton * bevent)362 gimp_dash_editor_button_release (GtkWidget      *widget,
363                                  GdkEventButton *bevent)
364 {
365   GimpDashEditor *editor = GIMP_DASH_EDITOR (widget);
366 
367   if (bevent->button == 1)
368     {
369       gtk_grab_remove (widget);
370 
371       update_options_from_segments (editor);
372     }
373 
374   return TRUE;
375 }
376 
377 static gboolean
gimp_dash_editor_motion_notify(GtkWidget * widget,GdkEventMotion * mevent)378 gimp_dash_editor_motion_notify (GtkWidget      *widget,
379                                 GdkEventMotion *mevent)
380 {
381   GimpDashEditor *editor = GIMP_DASH_EDITOR (widget);
382   gint            x, index;
383 
384   index = dash_x_to_index (editor, mevent->x);
385   editor->segments [index] = editor->edit_mode;
386 
387   if (mevent->x > editor->edit_button_x0)
388     {
389       for (x = editor->edit_button_x0; x < mevent->x; x += editor->block_width)
390         {
391           index = dash_x_to_index (editor, x);
392           editor->segments[index] = editor->edit_mode;
393         }
394     }
395 
396   if (mevent->x < editor->edit_button_x0)
397     {
398       for (x = editor->edit_button_x0; x > mevent->x; x -= editor->block_width)
399         {
400           index = dash_x_to_index (editor, x);
401           editor->segments[index] = editor->edit_mode;
402         }
403     }
404 
405   gtk_widget_queue_draw (widget);
406 
407   return TRUE;
408 }
409 
410 GtkWidget *
gimp_dash_editor_new(GimpStrokeOptions * stroke_options)411 gimp_dash_editor_new (GimpStrokeOptions *stroke_options)
412 {
413   g_return_val_if_fail (GIMP_IS_STROKE_OPTIONS (stroke_options), NULL);
414 
415   return g_object_new (GIMP_TYPE_DASH_EDITOR,
416                        "stroke-options", stroke_options,
417                        NULL);
418 }
419 
420 void
gimp_dash_editor_shift_right(GimpDashEditor * editor)421 gimp_dash_editor_shift_right (GimpDashEditor *editor)
422 {
423   gboolean swap;
424   gint     i;
425 
426   g_return_if_fail (GIMP_IS_DASH_EDITOR (editor));
427   g_return_if_fail (editor->n_segments > 0);
428 
429   swap = editor->segments[editor->n_segments - 1];
430   for (i = editor->n_segments - 1; i > 0; i--)
431     editor->segments[i] = editor->segments[i-1];
432   editor->segments[0] = swap;
433 
434   update_options_from_segments (editor);
435 }
436 
437 void
gimp_dash_editor_shift_left(GimpDashEditor * editor)438 gimp_dash_editor_shift_left (GimpDashEditor *editor)
439 {
440   gboolean swap;
441   gint     i;
442 
443   g_return_if_fail (GIMP_IS_DASH_EDITOR (editor));
444   g_return_if_fail (editor->n_segments > 0);
445 
446   swap = editor->segments[0];
447   for (i = 1; i < editor->n_segments; i++)
448     editor->segments[i-1] = editor->segments[i];
449   editor->segments[editor->n_segments - 1] = swap;
450 
451   update_options_from_segments (editor);
452 }
453 
454 static void
update_segments_from_options(GimpDashEditor * editor)455 update_segments_from_options (GimpDashEditor *editor)
456 {
457   GArray *dash_info;
458 
459   if (editor->stroke_options == NULL || editor->segments == NULL)
460     return;
461 
462   g_return_if_fail (GIMP_IS_STROKE_OPTIONS (editor->stroke_options));
463 
464   gtk_widget_queue_draw (GTK_WIDGET (editor));
465 
466   dash_info = gimp_stroke_options_get_dash_info (editor->stroke_options);
467 
468   gimp_dash_pattern_fill_segments (dash_info,
469                                    editor->segments, editor->n_segments);
470 }
471 
472 static void
update_options_from_segments(GimpDashEditor * editor)473 update_options_from_segments (GimpDashEditor *editor)
474 {
475   GArray *pattern = gimp_dash_pattern_new_from_segments (editor->segments,
476                                                          editor->n_segments,
477                                                          editor->dash_length);
478 
479   gimp_stroke_options_take_dash_pattern (editor->stroke_options,
480                                          GIMP_DASH_CUSTOM, pattern);
481 }
482 
483 static void
update_blocksize(GimpDashEditor * editor)484 update_blocksize (GimpDashEditor *editor)
485 {
486   GtkWidget     *widget = GTK_WIDGET (editor);
487   GtkAllocation  allocation;
488 
489   gtk_widget_get_allocation (widget, &allocation);
490 
491   editor->block_height = 6;
492 
493   editor->block_width = MAX (ROUND (editor->dash_length /
494                                     editor->n_segments * editor->block_height),
495                              4);
496   editor->block_height = MIN (ROUND (((float) editor->block_width) *
497                                      editor->n_segments / editor->dash_length),
498                               allocation.height - 4);
499 }
500 
501 static gint
dash_x_to_index(GimpDashEditor * editor,gint x)502 dash_x_to_index (GimpDashEditor *editor,
503                  gint            x)
504 {
505   gint index = x - editor->x0;
506 
507   while (index < 0)
508     index += editor->n_segments * editor->block_width;
509 
510   index = (index / editor->block_width) % editor->n_segments;
511 
512   return index;
513 }
514