Code

fix 198818
[inkscape.git] / src / widgets / dash-selector.cpp
1 #define __SP_DASH_SELECTOR_C__
3 /*
4  * Optionmenu for selecting dash patterns
5  *
6  * Author:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   bulia byak <buliabyak@users.sf.net>
9  *
10  * Copyright (C) 2002 Lauris Kaplinski
11  *
12  * Released under GNU GPL, read the file 'COPYING' for more information
13  */
15 #define DASH_PREVIEW_WIDTH 2
16 #define DASH_PREVIEW_LENGTH 80
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
22 #include <cstring>
23 #include <string>
24 #include <libnr/nr-macros.h>
25 #include <gtk/gtk.h>
26 #include <glibmm/i18n.h>
28 #include "../style.h"
29 #include "../dialogs/dialog-events.h"
31 #include "dash-selector.h"
33 enum {CHANGED, LAST_SIGNAL};
35 struct SPDashSelector {
36         GtkHBox hbox;
38         GtkWidget *dash;
39         GtkObject *offset;
40 };
42 struct SPDashSelectorClass {
43         GtkHBoxClass parent_class;
45         void (* changed) (SPDashSelector *dsel);
46 };
48 double dash_0[] = {-1.0};
49 double dash_1_1[] = {1.0, 1.0, -1.0};
50 double dash_2_1[] = {2.0, 1.0, -1.0};
51 double dash_4_1[] = {4.0, 1.0, -1.0};
52 double dash_1_2[] = {1.0, 2.0, -1.0};
53 double dash_1_4[] = {1.0, 4.0, -1.0};
55 double *builtin_dashes[] = {dash_0, dash_1_1, dash_2_1, dash_4_1, dash_1_2, dash_1_4, NULL};
57 static double **dashes = NULL;
59 static void sp_dash_selector_class_init (SPDashSelectorClass *klass);
60 static void sp_dash_selector_init (SPDashSelector *dsel);
61 static GtkWidget *sp_dash_selector_menu_item_new (SPDashSelector *dsel, double *pattern);
62 static void sp_dash_selector_menu_item_image_realize (GtkWidget *mi, double *pattern);
63 static void sp_dash_selector_dash_activate (GtkObject *object, SPDashSelector *dsel);
64 static void sp_dash_selector_offset_value_changed (GtkAdjustment *adj, SPDashSelector *dsel);
66 static GtkHBoxClass *parent_class;
67 static guint signals[LAST_SIGNAL] = {0};
69 GtkType
70 sp_dash_selector_get_type (void)
71 {
72         static GtkType type = 0;
73         if (!type) {
74                 GtkTypeInfo info = {
75                         "SPDashSelector",
76                         sizeof (SPDashSelector),
77                         sizeof (SPDashSelectorClass),
78                         (GtkClassInitFunc) sp_dash_selector_class_init,
79                         (GtkObjectInitFunc) sp_dash_selector_init,
80                         NULL, NULL, NULL
81                 };
82                 type = gtk_type_unique (GTK_TYPE_HBOX, &info);
83         }
84         return type;
85 }
87 static void
88 sp_dash_selector_class_init (SPDashSelectorClass *klass)
89 {
90         parent_class = (GtkHBoxClass*)gtk_type_class (GTK_TYPE_HBOX);
92         signals[CHANGED] = gtk_signal_new ("changed",
93                                            (GtkSignalRunType)(GTK_RUN_FIRST | GTK_RUN_NO_RECURSE),
94                                            G_TYPE_FROM_CLASS (klass),
95                                            GTK_SIGNAL_OFFSET (SPDashSelectorClass, changed),
96                                            gtk_marshal_NONE__NONE,
97                                            GTK_TYPE_NONE, 0);
98 }
100 static void
101 sp_dash_selector_init (SPDashSelector *dsel)
103         GtkTooltips *tt = gtk_tooltips_new();
105         dsel->dash = gtk_option_menu_new ();
106         gtk_tooltips_set_tip (tt, dsel->dash, _("Dash pattern"), NULL);
107         gtk_widget_show (dsel->dash);
108         gtk_box_pack_start (GTK_BOX (dsel), dsel->dash, FALSE, FALSE, 0);
110         GtkWidget *m = gtk_menu_new ();
111         gtk_widget_show (m);
112         for (int i = 0; dashes[i]; i++) {
113                 GtkWidget *mi = sp_dash_selector_menu_item_new (dsel, dashes[i]);
114                 gtk_widget_show (mi);
115                 gtk_menu_append (GTK_MENU (m), mi);
116         }
117         gtk_option_menu_set_menu (GTK_OPTION_MENU (dsel->dash), m);
119         dsel->offset = gtk_adjustment_new (0.0, 0.0, 10.0, 0.1, 1.0, 1.0);
120         GtkWidget *sb = gtk_spin_button_new (GTK_ADJUSTMENT (dsel->offset), 0.1, 2);
121         gtk_tooltips_set_tip (tt, sb, _("Pattern offset"), NULL);
123         sp_dialog_defocus_on_enter (sb);
124         gtk_widget_show (sb);
125         gtk_box_pack_start (GTK_BOX (dsel), sb, FALSE, FALSE, 0);
126         gtk_signal_connect (dsel->offset, "value_changed", GTK_SIGNAL_FUNC (sp_dash_selector_offset_value_changed), dsel);
128         gtk_object_set_data (GTK_OBJECT (dsel), "pattern", dashes[0]);
131 GtkWidget *
132 sp_dash_selector_new (Inkscape::XML::Node *drepr)
134         if (!dashes) {
135                 int ndashes = 0;
136                 if (drepr) {
137                         for (Inkscape::XML::Node *dr = drepr->firstChild(); dr; dr = dr->next()) {
138                                 if (!strcmp (dr->name(), "dash"))
139                                         ndashes += 1;
140                         }
141                 }
143                 if (ndashes > 0) {
144                         int pos = 0;
145                         SPStyle *style = sp_style_new (NULL);
146                         dashes = g_new (double *, ndashes + 1);
147                         for (Inkscape::XML::Node *dr = drepr->firstChild(); dr; dr = dr->next()) {
148                                 if (!strcmp (dr->name(), "dash")) {
149                                         sp_style_read_from_repr (style, dr);
150                                         if (style->stroke_dash.n_dash > 0) {
151                                                 dashes[pos] = g_new (double, style->stroke_dash.n_dash + 1);
152                                                 double *d = dashes[pos];
153                                                 int i = 0;
154                                                 for (; i < style->stroke_dash.n_dash; i++) {
155                                                         d[i] = style->stroke_dash.dash[i];
156                                                 }
157                                                 d[i] = -1;
158                                         } else {
159                                                 dashes[pos] = dash_0;
160                                         }
161                                         pos += 1;
162                                 }
163                         }
164                         sp_style_unref (style);
165                         dashes[pos] = NULL;
166                 } else {
167                         dashes = builtin_dashes;
168                 }
169         }
171         GtkWidget *dsel = (GtkWidget*)gtk_type_new (SP_TYPE_DASH_SELECTOR);
173         return dsel;
176 void
177 sp_dash_selector_set_dash (SPDashSelector *dsel, int ndash, double *dash, double offset)
179         int pos = 0;
180         if (ndash > 0) {
181                 double delta = 0.0;
182                 for (int i = 0; i < ndash; i++)
183                         delta += dash[i];
184                 delta /= 1000.0;
186                 for (int i = 0; dashes[i]; i++) {
187                         double *pattern = dashes[i];
188                         int np = 0;
189                         while (pattern[np] >= 0.0)
190                                 np += 1;
191                         if (np == ndash) {
192                                 int j;
193                                 for (j = 0; j < ndash; j++) {
194                                         if (!NR_DF_TEST_CLOSE (dash[j], pattern[j], delta))
195                                                 break;
196                                 }
197                                 if (j == ndash) {
198                                         pos = i;
199                                         break;
200                                 }
201                         }
202                 }
203         }
205         gtk_object_set_data (GTK_OBJECT (dsel), "pattern", dashes[pos]);
206         gtk_option_menu_set_history (GTK_OPTION_MENU (dsel->dash), pos);
207         gtk_adjustment_set_value (GTK_ADJUSTMENT (dsel->offset), offset);
210 void
211 sp_dash_selector_get_dash (SPDashSelector *dsel, int *ndash, double **dash, double *offset)
213         double *pattern = (double*)gtk_object_get_data (GTK_OBJECT (dsel), "pattern");
215         int nd = 0;
216         while (pattern[nd] >= 0.0)
217                 nd += 1;
219         if (nd > 0) {
220                 if (ndash)
221                         *ndash = nd;
222                 if (dash) {
223                         *dash = g_new (double, nd);
224                         memcpy (*dash, pattern, nd * sizeof (double));
225                 }
226                 if (offset)
227                         *offset = GTK_ADJUSTMENT (dsel->offset)->value;
228         } else {
229                 if (ndash)
230                         *ndash = 0;
231                 if (dash)
232                         *dash = NULL;
233                 if (offset)
234                         *offset = 0.0;
235         }
238 bool
239 all_even_are_zero (double *pattern, int n)
241         for (int i = 0; i < n; i += 2) {
242                 if (pattern[i] != 0)
243                         return false;
244         }
245         return true;
248 bool
249 all_odd_are_zero (double *pattern, int n)
251         for (int i = 1; i < n; i += 2) {
252                 if (pattern[i] != 0)
253                         return false;
254         }
255         return true;
258 static GtkWidget *
259 sp_dash_selector_menu_item_new (SPDashSelector *dsel, double *pattern)
261         GtkWidget *mi = gtk_menu_item_new ();
262         GtkWidget *px = gtk_image_new_from_pixmap (NULL, NULL);
264         gtk_widget_show (px);
265         gtk_container_add (GTK_CONTAINER (mi), px);
267         gtk_object_set_data (GTK_OBJECT (mi), "pattern", pattern);
268         gtk_object_set_data (GTK_OBJECT (mi), "px", px);
269         gtk_signal_connect (GTK_OBJECT (mi), "activate", G_CALLBACK (sp_dash_selector_dash_activate), dsel);
271         g_signal_connect_after(G_OBJECT(px), "realize", G_CALLBACK(sp_dash_selector_menu_item_image_realize), pattern);
273         return mi;
276 static void sp_dash_selector_menu_item_image_realize (GtkWidget *px, double *pattern) {
277         GdkPixmap *pixmap = gdk_pixmap_new(px->window, DASH_PREVIEW_LENGTH + 4, 16, -1);
278         GdkGC *gc = gdk_gc_new (pixmap);
280         gdk_rgb_gc_set_foreground (gc, 0xffffffff);
281         gdk_draw_rectangle (pixmap, gc, TRUE, 0, 0, DASH_PREVIEW_LENGTH + 4, 16);
283         // FIXME: all of the below twibblering is due to the limitations of gdk_gc_set_dashes (only integers, no zeroes).
284         // Perhaps would make sense to rework this with manually drawn dashes.
286         // Fill in the integer array of pixel-lengths, for display
287         gint8 pixels_i[64];
288         gdouble pixels_d[64];
289         int n_source_dashes = 0;
290         int n_pixel_dashes = 0;
292         signed int i_s, i_p;
293         for (i_s = 0, i_p = 0; pattern[i_s] >= 0.0; i_s ++, i_p ++) {
294                 pixels_d[i_p] = 0.0;
295         }
297         n_source_dashes = i_s;
299         for (i_s = 0, i_p = 0; i_s < n_source_dashes; i_s ++, i_p ++) {
301                 // calculate the pixel length corresponding to the current dash
302                 gdouble pixels = DASH_PREVIEW_WIDTH * pattern[i_s];
304                 if (pixels > 0.0)
305                         pixels_d [i_p] += pixels;
306                 else {
307                         if (i_p >= 1) {
308                                 // dash is zero, skip this element in the array, and set pointer backwards so the next dash is added to the previous
309                                 i_p -= 2;
310                         } else {
311                                 // the first dash is zero; bad luck, gdk cannot start pattern with non-stroke, so we put a 1-pixel stub here
312                                 // (it may turn out not shown, though, see special cases below)
313                                 pixels_d [i_p] = 1.0;
314                         }
315                 }
316         }
318         n_pixel_dashes = i_p;
320         gdouble longest_dash = 0.0;
322         // after summation, convert double dash lengths to ints
323         for (i_p = 0; i_p < n_pixel_dashes; i_p ++) {
324                 pixels_i [i_p] = (gint8) (pixels_d [i_p] + 0.5);
325                 // zero-length dashes are already eliminated, so the <1 dash is short but not zero;
326                 // we approximate it with a one-pixel mark
327                 if (pixels_i [i_p] < 1)
328                         pixels_i [i_p] = 1;
329                 if (i_p % 2 == 0) { // it's a dash
330                         if (pixels_d [i_p] > longest_dash)
331                                 longest_dash = pixels_d [i_p];
332                 }
333         }
335         if (longest_dash > 1e-18 && longest_dash < 0.5) {
336                 // fake "shortening" of one-pixel marks by painting them lighter-than-black
337                 gint rgb = 255 - (gint) (255 * longest_dash / 0.5);
338                 gdk_rgb_gc_set_foreground (gc, SP_RGBA32_U_COMPOSE (rgb, rgb, rgb, rgb));
339         } else {
340                 gdk_rgb_gc_set_foreground (gc, 0x00000000);
341         }
343         if (n_source_dashes > 0) {
344                 // special cases:
345                 if (all_even_are_zero (pattern, n_source_dashes)) {
346                         ; // do not draw anything, only gaps are non-zero
347                 } else if (all_odd_are_zero (pattern, n_source_dashes)) {
348                         // draw solid line, only dashes are non-zero
349                         gdk_gc_set_line_attributes (gc, DASH_PREVIEW_WIDTH,
350                                                                                 GDK_LINE_SOLID, GDK_CAP_BUTT,
351                                                                                 GDK_JOIN_MITER);
352                         gdk_draw_line (pixmap, gc, 4, 8, DASH_PREVIEW_LENGTH, 8);
353                 } else {
354                         // regular pattern with both gaps and dashes non-zero
355                         gdk_gc_set_line_attributes (gc, DASH_PREVIEW_WIDTH,
356                                                                                 GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT,
357                                                                                 GDK_JOIN_MITER);
358                         gdk_gc_set_dashes (gc, 0, pixels_i, n_pixel_dashes);
359                         gdk_draw_line (pixmap, gc, 4, 8, DASH_PREVIEW_LENGTH, 8);
360                 }
361         } else {
362                 // no pattern, draw solid line
363                 gdk_gc_set_line_attributes (gc, DASH_PREVIEW_WIDTH,
364                                                                         GDK_LINE_SOLID, GDK_CAP_BUTT,
365                                                                         GDK_JOIN_MITER);
366                 gdk_draw_line (pixmap, gc, 4, 8, DASH_PREVIEW_LENGTH, 8);
367         }
369         gdk_gc_unref (gc);
371         gtk_image_set_from_pixmap(GTK_IMAGE(px), pixmap, NULL);
372         gdk_pixmap_unref(pixmap);
375 static void
376 sp_dash_selector_dash_activate (GtkObject *object, SPDashSelector *dsel)
378         double *pattern = (double*)gtk_object_get_data (object, "pattern");
379         gtk_object_set_data (GTK_OBJECT (dsel), "pattern", pattern);
381         gtk_signal_emit (GTK_OBJECT (dsel), signals[CHANGED]);
384 static void
385 sp_dash_selector_offset_value_changed (GtkAdjustment *adj, SPDashSelector *dsel)
387         gtk_signal_emit (GTK_OBJECT (dsel), signals[CHANGED]);