78303c60b8335a4080922da8054b23002468c0fe
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
21 #include <libnr/nr-macros.h>
22 #include <gtk/gtk.h>
24 #include <glibmm/i18n.h>
25 #include "../style.h"
26 #include "../dialogs/dialog-events.h"
28 #include "dash-selector.h"
30 enum {CHANGED, LAST_SIGNAL};
32 struct SPDashSelector {
33 GtkHBox hbox;
35 GtkWidget *dash;
36 GtkObject *offset;
37 };
39 struct SPDashSelectorClass {
40 GtkHBoxClass parent_class;
42 void (* changed) (SPDashSelector *dsel);
43 };
45 double dash_0[] = {-1.0};
46 double dash_1_1[] = {1.0, 1.0, -1.0};
47 double dash_2_1[] = {2.0, 1.0, -1.0};
48 double dash_4_1[] = {4.0, 1.0, -1.0};
49 double dash_1_2[] = {1.0, 2.0, -1.0};
50 double dash_1_4[] = {1.0, 4.0, -1.0};
52 double *builtin_dashes[] = {dash_0, dash_1_1, dash_2_1, dash_4_1, dash_1_2, dash_1_4, NULL};
54 static double **dashes = NULL;
56 static void sp_dash_selector_class_init (SPDashSelectorClass *klass);
57 static void sp_dash_selector_init (SPDashSelector *dsel);
58 static GtkWidget *sp_dash_selector_menu_item_new (SPDashSelector *dsel, double *pattern);
59 static void sp_dash_selector_menu_item_image_realize (GtkWidget *mi, double *pattern);
60 static void sp_dash_selector_dash_activate (GtkObject *object, SPDashSelector *dsel);
61 static void sp_dash_selector_offset_value_changed (GtkAdjustment *adj, SPDashSelector *dsel);
63 static GtkHBoxClass *parent_class;
64 static guint signals[LAST_SIGNAL] = {0};
66 GtkType
67 sp_dash_selector_get_type (void)
68 {
69 static GtkType type = 0;
70 if (!type) {
71 GtkTypeInfo info = {
72 "SPDashSelector",
73 sizeof (SPDashSelector),
74 sizeof (SPDashSelectorClass),
75 (GtkClassInitFunc) sp_dash_selector_class_init,
76 (GtkObjectInitFunc) sp_dash_selector_init,
77 NULL, NULL, NULL
78 };
79 type = gtk_type_unique (GTK_TYPE_HBOX, &info);
80 }
81 return type;
82 }
84 static void
85 sp_dash_selector_class_init (SPDashSelectorClass *klass)
86 {
87 parent_class = (GtkHBoxClass*)gtk_type_class (GTK_TYPE_HBOX);
89 signals[CHANGED] = gtk_signal_new ("changed",
90 (GtkSignalRunType)(GTK_RUN_FIRST | GTK_RUN_NO_RECURSE),
91 G_TYPE_FROM_CLASS (klass),
92 GTK_SIGNAL_OFFSET (SPDashSelectorClass, changed),
93 gtk_marshal_NONE__NONE,
94 GTK_TYPE_NONE, 0);
95 }
97 static void
98 sp_dash_selector_init (SPDashSelector *dsel)
99 {
100 GtkTooltips *tt = gtk_tooltips_new();
102 dsel->dash = gtk_option_menu_new ();
103 gtk_tooltips_set_tip (tt, dsel->dash, _("Dash pattern"), NULL);
104 gtk_widget_show (dsel->dash);
105 gtk_box_pack_start (GTK_BOX (dsel), dsel->dash, FALSE, FALSE, 0);
107 GtkWidget *m = gtk_menu_new ();
108 gtk_widget_show (m);
109 for (int i = 0; dashes[i]; i++) {
110 GtkWidget *mi = sp_dash_selector_menu_item_new (dsel, dashes[i]);
111 gtk_widget_show (mi);
112 gtk_menu_append (GTK_MENU (m), mi);
113 }
114 gtk_option_menu_set_menu (GTK_OPTION_MENU (dsel->dash), m);
116 dsel->offset = gtk_adjustment_new (0.0, 0.0, 10.0, 0.1, 1.0, 1.0);
117 GtkWidget *sb = gtk_spin_button_new (GTK_ADJUSTMENT (dsel->offset), 0.1, 2);
118 gtk_tooltips_set_tip (tt, sb, _("Pattern offset"), NULL);
120 sp_dialog_defocus_on_enter (sb);
121 gtk_widget_show (sb);
122 gtk_box_pack_start (GTK_BOX (dsel), sb, FALSE, FALSE, 0);
123 gtk_signal_connect (dsel->offset, "value_changed", GTK_SIGNAL_FUNC (sp_dash_selector_offset_value_changed), dsel);
125 gtk_object_set_data (GTK_OBJECT (dsel), "pattern", dashes[0]);
126 }
128 GtkWidget *
129 sp_dash_selector_new (Inkscape::XML::Node *drepr)
130 {
131 if (!dashes) {
132 int ndashes = 0;
133 if (drepr) {
134 for (Inkscape::XML::Node *dr = drepr->firstChild(); dr; dr = dr->next()) {
135 if (!strcmp (dr->name(), "dash"))
136 ndashes += 1;
137 }
138 }
140 if (ndashes > 0) {
141 int pos = 0;
142 SPStyle *style = sp_style_new ();
143 dashes = g_new (double *, ndashes + 1);
144 for (Inkscape::XML::Node *dr = drepr->firstChild(); dr; dr = dr->next()) {
145 if (!strcmp (dr->name(), "dash")) {
146 sp_style_read_from_repr (style, dr);
147 if (style->stroke_dash.n_dash > 0) {
148 dashes[pos] = g_new (double, style->stroke_dash.n_dash + 1);
149 double *d = dashes[pos];
150 int i = 0;
151 for (; i < style->stroke_dash.n_dash; i++) {
152 d[i] = style->stroke_dash.dash[i];
153 }
154 d[i] = -1;
155 } else {
156 dashes[pos] = dash_0;
157 }
158 pos += 1;
159 }
160 }
161 sp_style_unref (style);
162 dashes[pos] = NULL;
163 } else {
164 dashes = builtin_dashes;
165 }
166 }
168 GtkWidget *dsel = (GtkWidget*)gtk_type_new (SP_TYPE_DASH_SELECTOR);
170 return dsel;
171 }
173 void
174 sp_dash_selector_set_dash (SPDashSelector *dsel, int ndash, double *dash, double offset)
175 {
176 int pos = 0;
177 if (ndash > 0) {
178 double delta = 0.0;
179 for (int i = 0; i < ndash; i++)
180 delta += dash[i];
181 delta /= 1000.0;
183 for (int i = 0; dashes[i]; i++) {
184 double *pattern = dashes[i];
185 int np = 0;
186 while (pattern[np] >= 0.0)
187 np += 1;
188 if (np == ndash) {
189 int j;
190 for (j = 0; j < ndash; j++) {
191 if (!NR_DF_TEST_CLOSE (dash[j], pattern[j], delta))
192 break;
193 }
194 if (j == ndash) {
195 pos = i;
196 break;
197 }
198 }
199 }
200 }
202 gtk_object_set_data (GTK_OBJECT (dsel), "pattern", dashes[pos]);
203 gtk_option_menu_set_history (GTK_OPTION_MENU (dsel->dash), pos);
204 gtk_adjustment_set_value (GTK_ADJUSTMENT (dsel->offset), offset);
205 }
207 void
208 sp_dash_selector_get_dash (SPDashSelector *dsel, int *ndash, double **dash, double *offset)
209 {
210 double *pattern = (double*)gtk_object_get_data (GTK_OBJECT (dsel), "pattern");
212 int nd = 0;
213 while (pattern[nd] >= 0.0)
214 nd += 1;
216 if (nd > 0) {
217 if (ndash)
218 *ndash = nd;
219 if (dash) {
220 *dash = g_new (double, nd);
221 memcpy (*dash, pattern, nd * sizeof (double));
222 }
223 if (offset)
224 *offset = GTK_ADJUSTMENT (dsel->offset)->value;
225 } else {
226 if (ndash)
227 *ndash = 0;
228 if (dash)
229 *dash = NULL;
230 if (offset)
231 *offset = 0.0;
232 }
233 }
235 bool
236 all_even_are_zero (double *pattern, int n)
237 {
238 for (int i = 0; i < n; i += 2) {
239 if (pattern[i] != 0)
240 return false;
241 }
242 return true;
243 }
245 bool
246 all_odd_are_zero (double *pattern, int n)
247 {
248 for (int i = 1; i < n; i += 2) {
249 if (pattern[i] != 0)
250 return false;
251 }
252 return true;
253 }
255 static GtkWidget *
256 sp_dash_selector_menu_item_new (SPDashSelector *dsel, double *pattern)
257 {
258 GtkWidget *mi = gtk_menu_item_new ();
259 GtkWidget *px = gtk_image_new_from_pixmap (NULL, NULL);
261 gtk_widget_show (px);
262 gtk_container_add (GTK_CONTAINER (mi), px);
264 gtk_object_set_data (GTK_OBJECT (mi), "pattern", pattern);
265 gtk_object_set_data (GTK_OBJECT (mi), "px", px);
266 gtk_signal_connect (GTK_OBJECT (mi), "activate", G_CALLBACK (sp_dash_selector_dash_activate), dsel);
268 g_signal_connect_after(G_OBJECT(px), "realize", G_CALLBACK(sp_dash_selector_menu_item_image_realize), pattern);
270 return mi;
271 }
273 static void sp_dash_selector_menu_item_image_realize (GtkWidget *px, double *pattern) {
274 GdkPixmap *pixmap = gdk_pixmap_new(px->window, DASH_PREVIEW_LENGTH + 4, 16, -1);
275 GdkGC *gc = gdk_gc_new (pixmap);
277 gdk_rgb_gc_set_foreground (gc, 0xffffffff);
278 gdk_draw_rectangle (pixmap, gc, TRUE, 0, 0, DASH_PREVIEW_LENGTH + 4, 16);
280 // FIXME: all of the below twibblering is due to the limitations of gdk_gc_set_dashes (only integers, no zeroes).
281 // Perhaps would make sense to rework this with manually drawn dashes.
283 // Fill in the integer array of pixel-lengths, for display
284 gint8 pixels_i[64];
285 gdouble pixels_d[64];
286 int n_source_dashes = 0;
287 int n_pixel_dashes = 0;
289 signed int i_s, i_p;
290 for (i_s = 0, i_p = 0; pattern[i_s] >= 0.0; i_s ++, i_p ++) {
291 pixels_d[i_p] = 0.0;
292 }
294 n_source_dashes = i_s;
296 for (i_s = 0, i_p = 0; i_s < n_source_dashes; i_s ++, i_p ++) {
298 // calculate the pixel length corresponding to the current dash
299 gdouble pixels = DASH_PREVIEW_WIDTH * pattern[i_s];
301 if (pixels > 0.0)
302 pixels_d [i_p] += pixels;
303 else {
304 if (i_p >= 1) {
305 // dash is zero, skip this element in the array, and set pointer backwards so the next dash is added to the previous
306 i_p -= 2;
307 } else {
308 // the first dash is zero; bad luck, gdk cannot start pattern with non-stroke, so we put a 1-pixel stub here
309 // (it may turn out not shown, though, see special cases below)
310 pixels_d [i_p] = 1.0;
311 }
312 }
313 }
315 n_pixel_dashes = i_p;
317 gdouble longest_dash = 0.0;
319 // after summation, convert double dash lengths to ints
320 for (i_p = 0; i_p < n_pixel_dashes; i_p ++) {
321 pixels_i [i_p] = (gint8) (pixels_d [i_p] + 0.5);
322 // zero-length dashes are already eliminated, so the <1 dash is short but not zero;
323 // we approximate it with a one-pixel mark
324 if (pixels_i [i_p] < 1)
325 pixels_i [i_p] = 1;
326 if (i_p % 2 == 0) { // it's a dash
327 if (pixels_d [i_p] > longest_dash)
328 longest_dash = pixels_d [i_p];
329 }
330 }
332 if (longest_dash > 1e-18 && longest_dash < 0.5) {
333 // fake "shortening" of one-pixel marks by painting them lighter-than-black
334 gint rgb = 255 - (gint) (255 * longest_dash / 0.5);
335 gdk_rgb_gc_set_foreground (gc, SP_RGBA32_U_COMPOSE (rgb, rgb, rgb, rgb));
336 } else {
337 gdk_rgb_gc_set_foreground (gc, 0x00000000);
338 }
340 if (n_source_dashes > 0) {
341 // special cases:
342 if (all_even_are_zero (pattern, n_source_dashes)) {
343 ; // do not draw anything, only gaps are non-zero
344 } else if (all_odd_are_zero (pattern, n_source_dashes)) {
345 // draw solid line, only dashes are non-zero
346 gdk_gc_set_line_attributes (gc, DASH_PREVIEW_WIDTH,
347 GDK_LINE_SOLID, GDK_CAP_BUTT,
348 GDK_JOIN_MITER);
349 gdk_draw_line (pixmap, gc, 4, 8, DASH_PREVIEW_LENGTH, 8);
350 } else {
351 // regular pattern with both gaps and dashes non-zero
352 gdk_gc_set_line_attributes (gc, DASH_PREVIEW_WIDTH,
353 GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT,
354 GDK_JOIN_MITER);
355 gdk_gc_set_dashes (gc, 0, pixels_i, n_pixel_dashes);
356 gdk_draw_line (pixmap, gc, 4, 8, DASH_PREVIEW_LENGTH, 8);
357 }
358 } else {
359 // no pattern, draw solid line
360 gdk_gc_set_line_attributes (gc, DASH_PREVIEW_WIDTH,
361 GDK_LINE_SOLID, GDK_CAP_BUTT,
362 GDK_JOIN_MITER);
363 gdk_draw_line (pixmap, gc, 4, 8, DASH_PREVIEW_LENGTH, 8);
364 }
366 gdk_gc_unref (gc);
368 gtk_image_set_from_pixmap(GTK_IMAGE(px), pixmap, NULL);
369 gdk_pixmap_unref(pixmap);
370 }
372 static void
373 sp_dash_selector_dash_activate (GtkObject *object, SPDashSelector *dsel)
374 {
375 double *pattern = (double*)gtk_object_get_data (object, "pattern");
376 gtk_object_set_data (GTK_OBJECT (dsel), "pattern", pattern);
378 gtk_signal_emit (GTK_OBJECT (dsel), signals[CHANGED]);
379 }
381 static void
382 sp_dash_selector_offset_value_changed (GtkAdjustment *adj, SPDashSelector *dsel)
383 {
384 gtk_signal_emit (GTK_OBJECT (dsel), signals[CHANGED]);
385 }