1 #define __SP_STROKE_STYLE_C__
3 /**
4 * \brief Stroke style dialog
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * Bryce Harrington <brycehar@bryceharrington.org>
9 * bulia byak <buliabyak@users.sf.net>
10 *
11 * Copyright (C) 2001-2005 authors
12 * Copyright (C) 2001 Ximian, Inc.
13 * Copyright (C) 2004 John Cliff
14 *
15 * Released under GNU GPL, read the file 'COPYING' for more information
16 */
18 #define noSP_SS_VERBOSE
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
26 #include <glib/gmem.h>
27 #include <gtk/gtk.h>
29 #include <glibmm/i18n.h>
30 #include "helper/unit-menu.h"
31 #include "helper/units.h"
32 #include "svg/css-ostringstream.h"
33 #include "widgets/sp-widget.h"
34 #include "widgets/spw-utilities.h"
35 #include "sp-linear-gradient.h"
36 #include "sp-radial-gradient.h"
37 #include "marker.h"
38 #include "sp-pattern.h"
39 #include "widgets/paint-selector.h"
40 #include "widgets/dash-selector.h"
41 #include "style.h"
42 #include "gradient-chemistry.h"
43 #include "sp-namedview.h"
44 #include "desktop-handles.h"
45 #include "desktop-style.h"
46 #include "selection.h"
47 #include "inkscape.h"
48 #include "inkscape-stock.h"
49 #include "dialogs/dialog-events.h"
50 #include "sp-text.h"
51 #include "sp-rect.h"
52 #include "document-private.h"
53 #include "display/nr-arena.h"
54 #include "display/nr-arena-item.h"
55 #include "path-prefix.h"
56 #include "widgets/icon.h"
57 #include "helper/stock-items.h"
58 #include "io/sys.h"
59 #include "ui/cache/svg_preview_cache.h"
61 #include "dialogs/stroke-style.h"
64 /* Paint */
66 static void sp_stroke_style_paint_construct(SPWidget *spw, SPPaintSelector *psel);
67 static void sp_stroke_style_paint_selection_modified (SPWidget *spw, Inkscape::Selection *selection, guint flags, SPPaintSelector *psel);
68 static void sp_stroke_style_paint_selection_changed (SPWidget *spw, Inkscape::Selection *selection, SPPaintSelector *psel);
69 static void sp_stroke_style_paint_update(SPWidget *spw);
71 static void sp_stroke_style_paint_mode_changed(SPPaintSelector *psel, SPPaintSelectorMode mode, SPWidget *spw);
72 static void sp_stroke_style_paint_dragged(SPPaintSelector *psel, SPWidget *spw);
73 static void sp_stroke_style_paint_changed(SPPaintSelector *psel, SPWidget *spw);
75 static void sp_stroke_style_widget_change_subselection ( Inkscape::Application *inkscape, SPDesktop *desktop, SPWidget *spw );
77 /** Marker selection option menus */
78 static GtkWidget * marker_start_menu = NULL;
79 static GtkWidget * marker_mid_menu = NULL;
80 static GtkWidget * marker_end_menu = NULL;
82 static Inkscape::UI::Cache::SvgPreview svg_preview_cache;
84 /**
85 * Create the stroke style widget, and hook up all the signals.
86 */
87 GtkWidget *
88 sp_stroke_style_paint_widget_new(void)
89 {
90 GtkWidget *spw, *psel;
92 spw = sp_widget_new_global(INKSCAPE);
94 psel = sp_paint_selector_new(false); // without fillrule selector
95 gtk_widget_show(psel);
96 gtk_container_add(GTK_CONTAINER(spw), psel);
97 gtk_object_set_data(GTK_OBJECT(spw), "paint-selector", psel);
99 gtk_signal_connect(GTK_OBJECT(spw), "construct",
100 GTK_SIGNAL_FUNC(sp_stroke_style_paint_construct),
101 psel);
102 gtk_signal_connect(GTK_OBJECT(spw), "modify_selection",
103 GTK_SIGNAL_FUNC(sp_stroke_style_paint_selection_modified),
104 psel);
105 gtk_signal_connect(GTK_OBJECT(spw), "change_selection",
106 GTK_SIGNAL_FUNC(sp_stroke_style_paint_selection_changed),
107 psel);
109 g_signal_connect (INKSCAPE, "change_subselection", G_CALLBACK (sp_stroke_style_widget_change_subselection), spw);
111 gtk_signal_connect(GTK_OBJECT(psel), "mode_changed",
112 GTK_SIGNAL_FUNC(sp_stroke_style_paint_mode_changed),
113 spw);
114 gtk_signal_connect(GTK_OBJECT(psel), "dragged",
115 GTK_SIGNAL_FUNC(sp_stroke_style_paint_dragged),
116 spw);
117 gtk_signal_connect(GTK_OBJECT(psel), "changed",
118 GTK_SIGNAL_FUNC(sp_stroke_style_paint_changed),
119 spw);
121 sp_stroke_style_paint_update (SP_WIDGET(spw));
122 return spw;
123 }
125 /**
126 * On construction, simply does an update of the stroke style paint object.
127 */
128 static void
129 sp_stroke_style_paint_construct(SPWidget *spw, SPPaintSelector *psel)
130 {
131 #ifdef SP_SS_VERBOSE
132 g_print( "Stroke style widget constructed: inkscape %p repr %p\n",
133 spw->inkscape, spw->repr );
134 #endif
135 if (spw->inkscape) {
136 sp_stroke_style_paint_update (spw);
137 }
138 }
140 /**
141 * On signal modified, invokes an update of the stroke style paint object.
142 */
143 static void
144 sp_stroke_style_paint_selection_modified ( SPWidget *spw,
145 Inkscape::Selection *selection,
146 guint flags,
147 SPPaintSelector *psel)
148 {
149 if (flags & ( SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG |
150 SP_OBJECT_STYLE_MODIFIED_FLAG) ) {
151 sp_stroke_style_paint_update(spw);
152 }
153 }
156 /**
157 * On signal selection changed, invokes an update of the stroke style paint object.
158 */
159 static void
160 sp_stroke_style_paint_selection_changed ( SPWidget *spw,
161 Inkscape::Selection *selection,
162 SPPaintSelector *psel )
163 {
164 sp_stroke_style_paint_update (spw);
165 }
168 /**
169 * On signal change subselection, invoke an update of the stroke style widget.
170 */
171 static void
172 sp_stroke_style_widget_change_subselection ( Inkscape::Application *inkscape,
173 SPDesktop *desktop,
174 SPWidget *spw )
175 {
176 sp_stroke_style_paint_update (spw);
177 }
179 /**
180 * Gets the active stroke style property, then sets the appropriate color, alpha, gradient,
181 * pattern, etc. for the paint-selector.
182 */
183 static void
184 sp_stroke_style_paint_update (SPWidget *spw)
185 {
186 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
187 return;
188 }
190 gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(TRUE));
192 SPPaintSelector *psel = SP_PAINT_SELECTOR(gtk_object_get_data(GTK_OBJECT(spw), "paint-selector"));
194 // create temporary style
195 SPStyle *query = sp_style_new ();
196 // query into it
197 int result = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKE);
199 switch (result) {
200 case QUERY_STYLE_NOTHING:
201 {
202 /* No paint at all */
203 sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_EMPTY);
204 break;
205 }
207 case QUERY_STYLE_SINGLE:
208 case QUERY_STYLE_MULTIPLE_AVERAGED: // TODO: treat this slightly differently, e.g. display "averaged" somewhere in paint selector
209 case QUERY_STYLE_MULTIPLE_SAME:
210 {
211 SPPaintSelectorMode pselmode = sp_style_determine_paint_selector_mode (query, false);
212 sp_paint_selector_set_mode (psel, pselmode);
214 if (query->stroke.set && query->stroke.type == SP_PAINT_TYPE_COLOR) {
215 gfloat d[3];
216 sp_color_get_rgb_floatv (&query->stroke.value.color, d);
217 SPColor color;
218 sp_color_set_rgb_float (&color, d[0], d[1], d[2]);
219 sp_paint_selector_set_color_alpha (psel, &color, SP_SCALE24_TO_FLOAT (query->stroke_opacity.value));
221 } else if (query->stroke.set && query->stroke.type == SP_PAINT_TYPE_PAINTSERVER) {
223 SPPaintServer *server = SP_STYLE_STROKE_SERVER (query);
225 if (SP_IS_LINEARGRADIENT (server)) {
226 SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE);
227 sp_paint_selector_set_gradient_linear (psel, vector);
229 SPLinearGradient *lg = SP_LINEARGRADIENT (server);
230 sp_paint_selector_set_gradient_properties (psel,
231 SP_GRADIENT_UNITS (lg),
232 SP_GRADIENT_SPREAD (lg));
233 } else if (SP_IS_RADIALGRADIENT (server)) {
234 SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE);
235 sp_paint_selector_set_gradient_radial (psel, vector);
237 SPRadialGradient *rg = SP_RADIALGRADIENT (server);
238 sp_paint_selector_set_gradient_properties (psel,
239 SP_GRADIENT_UNITS (rg),
240 SP_GRADIENT_SPREAD (rg));
241 } else if (SP_IS_PATTERN (server)) {
242 SPPattern *pat = pattern_getroot (SP_PATTERN (server));
243 sp_update_pattern_list (psel, pat);
244 }
245 }
246 break;
247 }
249 case QUERY_STYLE_MULTIPLE_DIFFERENT:
250 {
251 sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_MULTIPLE);
252 break;
253 }
254 }
256 g_free (query);
258 gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(FALSE));
259 }
261 /**
262 * When the mode is changed, invoke a regular changed handler.
263 */
264 static void
265 sp_stroke_style_paint_mode_changed( SPPaintSelector *psel,
266 SPPaintSelectorMode mode,
267 SPWidget *spw )
268 {
269 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
270 return;
271 }
273 /* TODO: Does this work?
274 * Not really, here we have to get old color back from object
275 * Instead of relying on paint widget having meaningful colors set
276 */
277 sp_stroke_style_paint_changed(psel, spw);
278 }
280 static gchar *undo_label_1 = "stroke:flatcolor:1";
281 static gchar *undo_label_2 = "stroke:flatcolor:2";
282 static gchar *undo_label = undo_label_1;
284 /**
285 * When a drag callback occurs on a paint selector object, if it is a RGB or CMYK
286 * color mode, then set the stroke opacity to psel's flat color.
287 */
288 static void
289 sp_stroke_style_paint_dragged(SPPaintSelector *psel, SPWidget *spw)
290 {
291 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
292 return;
293 }
295 switch (psel->mode) {
296 case SP_PAINT_SELECTOR_MODE_COLOR_RGB:
297 case SP_PAINT_SELECTOR_MODE_COLOR_CMYK:
298 {
299 sp_paint_selector_set_flat_color (psel, SP_ACTIVE_DESKTOP, "stroke", "stroke-opacity");
300 sp_document_maybe_done (sp_desktop_document(SP_ACTIVE_DESKTOP), undo_label, SP_VERB_DIALOG_FILL_STROKE,
301 _("Set stroke color"));
302 break;
303 }
305 default:
306 g_warning( "file %s: line %d: Paint %d should not emit 'dragged'",
307 __FILE__, __LINE__, psel->mode);
308 break;
309 }
310 }
312 /**
313 * When the stroke style's paint settings change, this handler updates the
314 * repr's stroke css style and applies the style to relevant drawing items.
315 */
316 static void
317 sp_stroke_style_paint_changed(SPPaintSelector *psel, SPWidget *spw)
318 {
319 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
320 return;
321 }
322 g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE));
324 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
325 SPDocument *document = sp_desktop_document (desktop);
326 Inkscape::Selection *selection = sp_desktop_selection (desktop);
328 GSList const *items = selection->itemList();
330 switch (psel->mode) {
331 case SP_PAINT_SELECTOR_MODE_EMPTY:
332 // This should not happen.
333 g_warning ( "file %s: line %d: Paint %d should not emit 'changed'",
334 __FILE__, __LINE__, psel->mode);
335 break;
336 case SP_PAINT_SELECTOR_MODE_MULTIPLE:
337 // This happens when you switch multiple objects with different gradients to flat color;
338 // nothing to do here.
339 break;
341 case SP_PAINT_SELECTOR_MODE_NONE:
342 {
343 SPCSSAttr *css = sp_repr_css_attr_new();
344 sp_repr_css_set_property(css, "stroke", "none");
346 sp_desktop_set_style (desktop, css);
348 sp_repr_css_attr_unref(css);
350 sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE,
351 _("Remove stroke"));
352 break;
353 }
355 case SP_PAINT_SELECTOR_MODE_COLOR_RGB:
356 case SP_PAINT_SELECTOR_MODE_COLOR_CMYK:
357 {
358 sp_paint_selector_set_flat_color (psel, desktop, "stroke", "stroke-opacity");
359 sp_document_maybe_done (sp_desktop_document(desktop), undo_label, SP_VERB_DIALOG_FILL_STROKE,
360 _("Set stroke color"));
362 // on release, toggle undo_label so that the next drag will not be lumped with this one
363 if (undo_label == undo_label_1)
364 undo_label = undo_label_2;
365 else
366 undo_label = undo_label_1;
368 break;
369 }
371 case SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR:
372 case SP_PAINT_SELECTOR_MODE_GRADIENT_RADIAL:
373 if (items) {
374 SPGradientType const gradient_type = ( psel->mode == SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR
375 ? SP_GRADIENT_TYPE_LINEAR
376 : SP_GRADIENT_TYPE_RADIAL );
377 SPGradient *vector = sp_paint_selector_get_gradient_vector(psel);
378 if (!vector) {
379 /* No vector in paint selector should mean that we just changed mode */
381 SPStyle *query = sp_style_new ();
382 int result = objects_query_fillstroke ((GSList *) items, query, false);
383 guint32 common_rgb = 0;
384 if (result == QUERY_STYLE_MULTIPLE_SAME) {
385 if (query->fill.type != SP_PAINT_TYPE_COLOR) {
386 common_rgb = sp_desktop_get_color(desktop, false);
387 } else {
388 common_rgb = sp_color_get_rgba32_ualpha(&query->stroke.value.color, 0xff);
389 }
390 vector = sp_document_default_gradient_vector(document, common_rgb);
391 }
392 g_free (query);
394 for (GSList const *i = items; i != NULL; i = i->next) {
395 if (!vector) {
396 sp_item_set_gradient(SP_ITEM(i->data),
397 sp_gradient_vector_for_object(document, desktop, SP_OBJECT(i->data), false),
398 gradient_type, false);
399 } else {
400 sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, false);
401 }
402 }
403 } else {
404 vector = sp_gradient_ensure_vector_normalized(vector);
405 for (GSList const *i = items; i != NULL; i = i->next) {
406 SPGradient *gr = sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, false);
407 sp_gradient_selector_attrs_to_gradient(gr, psel);
408 }
409 }
411 sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE,
412 _("Set gradient on stroke"));
413 }
414 break;
416 case SP_PAINT_SELECTOR_MODE_PATTERN:
418 if (items) {
420 SPPattern *pattern = sp_paint_selector_get_pattern (psel);
421 if (!pattern) {
423 /* No Pattern in paint selector should mean that we just
424 * changed mode - dont do jack.
425 */
427 } else {
428 Inkscape::XML::Node *patrepr = SP_OBJECT_REPR(pattern);
429 SPCSSAttr *css = sp_repr_css_attr_new ();
430 gchar *urltext = g_strdup_printf ("url(#%s)", patrepr->attribute("id"));
431 sp_repr_css_set_property (css, "stroke", urltext);
433 for (GSList const *i = items; i != NULL; i = i->next) {
434 Inkscape::XML::Node *selrepr = SP_OBJECT_REPR (i->data);
435 SPObject *selobj = SP_OBJECT (i->data);
436 if (!selrepr)
437 continue;
439 SPStyle *style = SP_OBJECT_STYLE (selobj);
440 if (style && style->stroke.type == SP_PAINT_TYPE_PAINTSERVER) {
441 SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (selobj);
442 if (SP_IS_PATTERN (server) && pattern_getroot (SP_PATTERN(server)) == pattern)
443 // only if this object's pattern is not rooted in our selected pattern, apply
444 continue;
445 }
447 sp_repr_css_change_recursive (selrepr, css, "style");
448 }
450 sp_repr_css_attr_unref (css);
451 g_free (urltext);
453 } // end if
455 sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE,
456 _("Set pattern on stroke"));
457 } // end if
459 break;
461 case SP_PAINT_SELECTOR_MODE_UNSET:
462 if (items) {
463 SPCSSAttr *css = sp_repr_css_attr_new ();
464 sp_repr_css_unset_property (css, "stroke");
466 sp_desktop_set_style (desktop, css);
467 sp_repr_css_attr_unref (css);
469 sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE,
470 _("Unset stroke"));
471 }
472 break;
474 default:
475 g_warning( "file %s: line %d: Paint selector should not be in "
476 "mode %d",
477 __FILE__, __LINE__,
478 psel->mode );
479 break;
480 }
482 g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
483 }
489 /* Line */
491 static void sp_stroke_style_line_construct(SPWidget *spw, gpointer data);
492 static void sp_stroke_style_line_selection_modified (SPWidget *spw,
493 Inkscape::Selection *selection,
494 guint flags,
495 gpointer data);
497 static void sp_stroke_style_line_selection_changed (SPWidget *spw,
498 Inkscape::Selection *selection,
499 gpointer data );
501 static void sp_stroke_style_line_update(SPWidget *spw, Inkscape::Selection *sel);
503 static void sp_stroke_style_set_join_buttons(SPWidget *spw,
504 GtkWidget *active);
506 static void sp_stroke_style_set_cap_buttons(SPWidget *spw,
507 GtkWidget *active);
509 static void sp_stroke_style_width_changed(GtkAdjustment *adj, SPWidget *spw);
510 static void sp_stroke_style_miterlimit_changed(GtkAdjustment *adj, SPWidget *spw);
511 static void sp_stroke_style_any_toggled(GtkToggleButton *tb, SPWidget *spw);
512 static void sp_stroke_style_line_dash_changed(SPDashSelector *dsel,
513 SPWidget *spw);
515 static void sp_stroke_style_update_marker_menus(SPWidget *spw, GSList const *objects);
517 static SPObject *ink_extract_marker_name(gchar const *n);
520 /**
521 * Helper function for creating radio buttons. This should probably be re-thought out
522 * when reimplementing this with Gtkmm.
523 */
524 static GtkWidget *
525 sp_stroke_radio_button(GtkWidget *tb, char const *icon,
526 GtkWidget *hb, GtkWidget *spw,
527 gchar const *key, gchar const *data)
528 {
529 g_assert(icon != NULL);
530 g_assert(hb != NULL);
531 g_assert(spw != NULL);
533 if (tb == NULL) {
534 tb = gtk_radio_button_new(NULL);
535 } else {
536 tb = gtk_radio_button_new(gtk_radio_button_group(GTK_RADIO_BUTTON(tb)) );
537 }
539 gtk_widget_show(tb);
540 gtk_toggle_button_set_mode(GTK_TOGGLE_BUTTON(tb), FALSE);
541 gtk_box_pack_start(GTK_BOX(hb), tb, FALSE, FALSE, 0);
542 gtk_object_set_data(GTK_OBJECT(spw), icon, tb);
543 gtk_object_set_data(GTK_OBJECT(tb), key, (gpointer*)data);
544 gtk_signal_connect(GTK_OBJECT(tb), "toggled",
545 GTK_SIGNAL_FUNC(sp_stroke_style_any_toggled),
546 spw);
547 GtkWidget *px = sp_icon_new(Inkscape::ICON_SIZE_LARGE_TOOLBAR, icon);
548 g_assert(px != NULL);
549 gtk_widget_show(px);
550 gtk_container_add(GTK_CONTAINER(tb), px);
552 return tb;
554 }
556 /**
557 * Creates a copy of the marker named mname, determines its visible and renderable
558 * area in menu_id's bounding box, and then renders it. This allows us to fill in
559 * preview images of each marker in the marker menu.
560 */
561 static GtkWidget *
562 sp_marker_prev_new(unsigned psize, gchar const *mname,
563 SPDocument *source, SPDocument *sandbox,
564 gchar *menu_id, NRArena const *arena, unsigned visionkey, NRArenaItem *root)
565 {
566 // Retrieve the marker named 'mname' from the source SVG document
567 SPObject const *marker = source->getObjectById(mname);
568 if (marker == NULL)
569 return NULL;
571 // Create a copy repr of the marker with id="sample"
572 Inkscape::XML::Node *mrepr = SP_OBJECT_REPR (marker)->duplicate();
573 mrepr->setAttribute("id", "sample");
575 // Replace the old sample in the sandbox by the new one
576 Inkscape::XML::Node *defsrepr = SP_OBJECT_REPR (sandbox->getObjectById("defs"));
577 SPObject *oldmarker = sandbox->getObjectById("sample");
578 if (oldmarker)
579 oldmarker->deleteObject(false);
580 defsrepr->appendChild(mrepr);
581 Inkscape::GC::release(mrepr);
583 // Uncomment this to get the sandbox documents saved (useful for debugging)
584 //FILE *fp = fopen (g_strconcat(mname, ".svg", NULL), "w");
585 //sp_repr_save_stream (sp_document_repr_doc (sandbox), fp);
586 //fclose (fp);
588 // object to render; note that the id is the same as that of the menu we're building
589 SPObject *object = sandbox->getObjectById(menu_id);
590 sp_document_root (sandbox)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
591 sp_document_ensure_up_to_date(sandbox);
593 if (object == NULL || !SP_IS_ITEM(object))
594 return NULL; // sandbox broken?
596 // Find object's bbox in document
597 NR::Matrix const i2doc(sp_item_i2doc_affine(SP_ITEM(object)));
598 NR::Rect const dbox = SP_ITEM(object)->invokeBbox(i2doc);
600 if (dbox.isEmpty()) {
601 return NULL;
602 }
604 /* Update to renderable state */
605 double sf = 0.8;
606 GdkPixbuf* pixbuf = NULL;
608 Glib::ustring key = svg_preview_cache.cache_key(mname, psize);
609 pixbuf = svg_preview_cache.get_preview_from_cache(key);
611 if (pixbuf == NULL) {
612 pixbuf = render_pixbuf(root, sf, dbox, psize);
613 svg_preview_cache.set_preview_in_cache(key, pixbuf);
614 }
616 // Create widget
617 GtkWidget *pb = gtk_image_new_from_pixbuf(pixbuf);
619 return pb;
620 }
623 /**
624 * Returns a list of markers in the defs of the given source document as a GSList object
625 * Returns NULL if there are no markers in the document.
626 */
627 GSList *
628 ink_marker_list_get (SPDocument *source)
629 {
630 if (source == NULL)
631 return NULL;
633 GSList *ml = NULL;
634 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS (source);
635 for ( SPObject *child = sp_object_first_child(SP_OBJECT(defs));
636 child != NULL;
637 child = SP_OBJECT_NEXT (child) )
638 {
639 if (SP_IS_MARKER(child)) {
640 ml = g_slist_prepend (ml, child);
641 }
642 }
643 return ml;
644 }
646 #define MARKER_ITEM_MARGIN 0
648 /**
649 * Adds previews of markers in marker_list to the given menu widget
650 */
651 static void
652 sp_marker_menu_build (GtkWidget *m, GSList *marker_list, SPDocument *source, SPDocument *sandbox, gchar *menu_id)
653 {
654 // Do this here, outside of loop, to speed up preview generation:
655 NRArena const *arena = NRArena::create();
656 unsigned const visionkey = sp_item_display_key_new(1);
657 NRArenaItem *root = sp_item_invoke_show( SP_ITEM(SP_DOCUMENT_ROOT (sandbox)), (NRArena *) arena, visionkey, SP_ITEM_SHOW_DISPLAY );
659 for (; marker_list != NULL; marker_list = marker_list->next) {
660 Inkscape::XML::Node *repr = SP_OBJECT_REPR((SPItem *) marker_list->data);
661 GtkWidget *i = gtk_menu_item_new();
662 gtk_widget_show(i);
664 if (repr->attribute("inkscape:stockid"))
665 g_object_set_data (G_OBJECT(i), "stockid", (void *) "true");
666 else
667 g_object_set_data (G_OBJECT(i), "stockid", (void *) "false");
669 gchar const *markid = repr->attribute("id");
670 g_object_set_data (G_OBJECT(i), "marker", (void *) markid);
672 GtkWidget *hb = gtk_hbox_new(FALSE, MARKER_ITEM_MARGIN);
673 gtk_widget_show(hb);
675 // generate preview
676 GtkWidget *prv = sp_marker_prev_new (22, markid, source, sandbox, menu_id, arena, visionkey, root);
677 gtk_widget_show(prv);
678 gtk_box_pack_start(GTK_BOX(hb), prv, FALSE, FALSE, 6);
680 // create label
681 GtkWidget *l = gtk_label_new(repr->attribute("id"));
682 gtk_widget_show(l);
683 gtk_misc_set_alignment(GTK_MISC(l), 0.0, 0.5);
685 gtk_box_pack_start(GTK_BOX(hb), l, TRUE, TRUE, 0);
687 gtk_widget_show(hb);
688 gtk_container_add(GTK_CONTAINER(i), hb);
690 gtk_menu_append(GTK_MENU(m), i);
691 }
692 }
694 /**
695 * sp_marker_list_from_doc()
696 *
697 * \brief Pick up all markers from source, except those that are in
698 * current_doc (if non-NULL), and add items to the m menu
699 *
700 */
701 static void
702 sp_marker_list_from_doc (GtkWidget *m, SPDocument *current_doc, SPDocument *source, SPDocument *markers_doc, SPDocument *sandbox, gchar *menu_id)
703 {
704 GSList *ml = ink_marker_list_get(source);
705 GSList *clean_ml = NULL;
707 // Do this here, outside of loop, to speed up preview generation:
708 /* Create new arena */
709 NRArena const *arena = NRArena::create();
710 /* Create ArenaItem and set transform */
711 unsigned const visionkey = sp_item_display_key_new(1);
712 /*
713 NRArenaItem *root = sp_item_invoke_show( SP_ITEM(SP_DOCUMENT_ROOT (sandbox)), (NRArena *) arena, visionkey, SP_ITEM_SHOW_DISPLAY );
714 */
716 for (; ml != NULL; ml = ml->next) {
717 if (!SP_IS_MARKER(ml->data))
718 continue;
720 /*
721 Bug 980157 wants to have stock markers show up at the top of the dropdown menu
722 Thus we can skip all of this code, which simply looks for duplicate stock markers
724 Inkscape::XML::Node *repr = SP_OBJECT_REPR((SPItem *) ml->data);
725 bool stock_dupe = false;
727 if (repr->attribute("inkscape:stockid")) {
728 GSList * markers_doc_ml = ink_marker_list_get(markers_doc);
729 for (; markers_doc_ml != NULL; markers_doc_ml = markers_doc_ml->next) {
730 const gchar* stockid = SP_OBJECT_REPR(markers_doc_ml->data)->attribute("inkscape:stockid");
731 if (stockid && !strcmp(repr->attribute("inkscape:stockid"), stockid))
732 stock_dupe = true;
733 }
734 }
736 if (stock_dupe) // stock item, dont add to list from current doc
737 continue;
738 */
740 // Add to the list of markers we really do wish to show
741 clean_ml = g_slist_prepend (clean_ml, ml->data);
742 }
743 sp_marker_menu_build (m, clean_ml, source, sandbox, menu_id);
745 g_slist_free (ml);
746 g_slist_free (clean_ml);
747 }
750 /**
751 * Returns a new document containing default start, mid, and end markers.
752 */
753 SPDocument *
754 ink_markers_preview_doc ()
755 {
756 gchar const *buffer = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">"
757 " <defs id=\"defs\" />"
759 " <g id=\"marker-start\">"
760 " <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:url(#sample);marker-mid:none;marker-end:none\""
761 " d=\"M 12.5,13 L 25,13\" id=\"path1\" />"
762 " <rect style=\"fill:none;stroke:none\" id=\"rect2\""
763 " width=\"25\" height=\"25\" x=\"0\" y=\"0\" />"
764 " </g>"
766 " <g id=\"marker-mid\">"
767 " <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:none;marker-mid:url(#sample);marker-end:none\""
768 " d=\"M 0,113 L 12.5,113 L 25,113\" id=\"path11\" />"
769 " <rect style=\"fill:none;stroke:none\" id=\"rect22\""
770 " width=\"25\" height=\"25\" x=\"0\" y=\"100\" />"
771 " </g>"
773 " <g id=\"marker-end\">"
774 " <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:none;marker-mid:none;marker-end:url(#sample)\""
775 " d=\"M 0,213 L 12.5,213\" id=\"path111\" />"
776 " <rect style=\"fill:none;stroke:none\" id=\"rect222\""
777 " width=\"25\" height=\"25\" x=\"0\" y=\"200\" />"
778 " </g>"
780 "</svg>";
782 return sp_document_new_from_mem (buffer, strlen(buffer), FALSE);
783 }
785 static void
786 ink_marker_menu_create_menu(GtkWidget *m, gchar *menu_id, SPDocument *doc, SPDocument *sandbox)
787 {
788 static SPDocument *markers_doc = NULL;
790 // add "None"
791 GtkWidget *i = gtk_menu_item_new();
792 gtk_widget_show(i);
794 // g_object_set_data(G_OBJECT(i), "marker", (void *) "none");
796 GtkWidget *hb = gtk_hbox_new(FALSE, MARKER_ITEM_MARGIN);
797 gtk_widget_show(hb);
799 GtkWidget *l = gtk_label_new( _("None") );
800 gtk_widget_show(l);
801 gtk_misc_set_alignment(GTK_MISC(l), 0.0, 0.5);
803 gtk_box_pack_start(GTK_BOX(hb), l, TRUE, TRUE, 0);
805 gtk_widget_show(hb);
806 gtk_container_add(GTK_CONTAINER(i), hb);
807 gtk_menu_append(GTK_MENU(m), i);
809 // find and load markers.svg
810 if (markers_doc == NULL) {
811 g_warning("Reloading markers_doc");
812 char *markers_source = g_build_filename(INKSCAPE_MARKERSDIR, "markers.svg", NULL);
813 if (Inkscape::IO::file_test(markers_source, G_FILE_TEST_IS_REGULAR)) {
814 markers_doc = sp_document_new(markers_source, FALSE);
815 }
816 g_free(markers_source);
817 }
819 // suck in from current doc
820 sp_marker_list_from_doc ( m, NULL, doc, markers_doc, sandbox, menu_id );
822 // add separator
823 {
824 GtkWidget *i = gtk_separator_menu_item_new();
825 gtk_widget_show(i);
826 gtk_menu_append(GTK_MENU(m), i);
827 }
829 // suck in from markers.svg
830 if (markers_doc) {
831 sp_document_ensure_up_to_date(doc);
832 sp_marker_list_from_doc ( m, doc, markers_doc, NULL, sandbox, menu_id );
833 }
834 }
837 /**
838 * Creates a menu widget to display markers from markers.svg
839 */
840 static GtkWidget *
841 ink_marker_menu( GtkWidget *tbl, gchar *menu_id, SPDocument *sandbox)
842 {
843 SPDesktop *desktop = inkscape_active_desktop();
844 SPDocument *doc = sp_desktop_document(desktop);
845 GtkWidget *mnu = gtk_option_menu_new();
847 /* Create new menu widget */
848 GtkWidget *m = gtk_menu_new();
849 gtk_widget_show(m);
851 g_object_set_data(G_OBJECT(mnu), "updating", (gpointer) FALSE);
853 if (!doc) {
854 GtkWidget *i = gtk_menu_item_new_with_label(_("No document selected"));
855 gtk_widget_show(i);
856 gtk_menu_append(GTK_MENU(m), i);
857 gtk_widget_set_sensitive(mnu, FALSE);
859 } else {
860 ink_marker_menu_create_menu(m, menu_id, doc, sandbox);
862 gtk_widget_set_sensitive(mnu, TRUE);
863 }
865 gtk_object_set_data(GTK_OBJECT(mnu), "menu_id", menu_id);
866 gtk_option_menu_set_menu(GTK_OPTION_MENU(mnu), m);
868 /* Set history */
869 gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 0);
871 return mnu;
872 }
875 /**
876 * Handles when user selects one of the markers from the marker menu.
877 * Defines a uri string to refer to it, then applies it to all selected
878 * items in the current desktop.
879 */
880 static void
881 sp_marker_select(GtkOptionMenu *mnu, GtkWidget *spw)
882 {
883 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
884 return;
885 }
887 SPDesktop *desktop = inkscape_active_desktop();
888 SPDocument *document = sp_desktop_document(desktop);
889 if (!document) {
890 return;
891 }
893 /* Get Marker */
894 if (!g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))),
895 "marker"))
896 {
897 return;
898 }
899 gchar *markid = (gchar *) g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))),
900 "marker");
901 gchar *marker = "";
902 if (strcmp(markid, "none")){
903 gchar *stockid = (gchar *) g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))),
904 "stockid");
906 gchar *markurn = markid;
907 if (!strcmp(stockid,"true")) markurn = g_strconcat("urn:inkscape:marker:",markid,NULL);
908 SPObject *mark = get_stock_item(markurn);
909 if (mark) {
910 Inkscape::XML::Node *repr = SP_OBJECT_REPR(mark);
911 marker = g_strconcat("url(#", repr->attribute("id"), ")", NULL);
912 }
913 } else {
914 marker = markid;
915 }
917 SPCSSAttr *css = sp_repr_css_attr_new();
918 gchar *menu_id = (gchar *) g_object_get_data(G_OBJECT(mnu), "menu_id");
919 sp_repr_css_set_property(css, menu_id, marker);
921 Inkscape::Selection *selection = sp_desktop_selection(desktop);
922 GSList const *items = selection->itemList();
923 for (; items != NULL; items = items->next) {
924 SPItem *item = (SPItem *) items->data;
925 if (!SP_IS_SHAPE(item) || SP_IS_RECT(item)) // can't set marker to rect, until it's converted to using <path>
926 continue;
927 Inkscape::XML::Node *selrepr = SP_OBJECT_REPR((SPItem *) items->data);
928 if (selrepr) {
929 sp_repr_css_change_recursive(selrepr, css, "style");
930 }
931 SP_OBJECT(items->data)->requestModified(SP_OBJECT_MODIFIED_FLAG);
932 SP_OBJECT(items->data)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
933 }
935 sp_repr_css_attr_unref(css);
937 sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE,
938 _("Set markers"));
940 // Lastly, also update the marker dropdown menus, so the document's markers
941 // show up at the top of the menu
942 SPDocument *sandbox = ink_markers_preview_doc ();
943 GtkWidget *m;
945 m = gtk_menu_new();
946 gtk_widget_show(m);
947 ink_marker_menu_create_menu(m, "marker-start", document, sandbox);
948 gtk_option_menu_remove_menu(GTK_OPTION_MENU(marker_start_menu));
949 gtk_option_menu_set_menu(GTK_OPTION_MENU(marker_start_menu), m);
951 m = gtk_menu_new();
952 gtk_widget_show(m);
953 ink_marker_menu_create_menu(m, "marker-mid", document, sandbox);
954 gtk_option_menu_remove_menu(GTK_OPTION_MENU(marker_mid_menu));
955 gtk_option_menu_set_menu(GTK_OPTION_MENU(marker_mid_menu), m);
957 m = gtk_menu_new();
958 gtk_widget_show(m);
959 ink_marker_menu_create_menu(m, "marker-end", document, sandbox);
960 gtk_option_menu_remove_menu(GTK_OPTION_MENU(marker_end_menu));
961 gtk_option_menu_set_menu(GTK_OPTION_MENU(marker_end_menu), m);
962 }
964 /**
965 * Sets the stroke width units for all selected items.
966 * Also handles absolute and dimensionless units.
967 */
968 static gboolean stroke_width_set_unit(SPUnitSelector *,
969 SPUnit const *old,
970 SPUnit const *new_units,
971 GObject *spw)
972 {
973 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
975 if (!desktop) {
976 return FALSE;
977 }
979 Inkscape::Selection *selection = sp_desktop_selection (desktop);
981 if (selection->isEmpty())
982 return FALSE;
984 GSList const *objects = selection->itemList();
986 if ((old->base == SP_UNIT_ABSOLUTE || old->base == SP_UNIT_DEVICE) &&
987 (new_units->base == SP_UNIT_DIMENSIONLESS)) {
989 /* Absolute to percentage */
990 g_object_set_data (spw, "update", GUINT_TO_POINTER (TRUE));
992 GtkAdjustment *a = GTK_ADJUSTMENT(g_object_get_data (spw, "width"));
993 float w = sp_units_get_pixels (a->value, *old);
995 gdouble average = stroke_average_width (objects);
997 if (average == NR_HUGE || average == 0)
998 return FALSE;
1000 gtk_adjustment_set_value (a, 100.0 * w / average);
1002 g_object_set_data (spw, "update", GUINT_TO_POINTER (FALSE));
1003 return TRUE;
1005 } else if ((old->base == SP_UNIT_DIMENSIONLESS) &&
1006 (new_units->base == SP_UNIT_ABSOLUTE || new_units->base == SP_UNIT_DEVICE)) {
1008 /* Percentage to absolute */
1009 g_object_set_data (spw, "update", GUINT_TO_POINTER (TRUE));
1011 GtkAdjustment *a = GTK_ADJUSTMENT(g_object_get_data (spw, "width"));
1013 gdouble average = stroke_average_width (objects);
1015 gtk_adjustment_set_value (a, sp_pixels_get_units (0.01 * a->value * average, *new_units));
1017 g_object_set_data (spw, "update", GUINT_TO_POINTER (FALSE));
1018 return TRUE;
1019 }
1021 return FALSE;
1022 }
1025 /**
1026 * \brief Creates a new widget for the line stroke style.
1027 *
1028 */
1029 GtkWidget *
1030 sp_stroke_style_line_widget_new(void)
1031 {
1032 GtkWidget *spw, *f, *t, *hb, *sb, *us, *tb, *ds;
1033 GtkObject *a;
1035 GtkTooltips *tt = gtk_tooltips_new();
1037 spw = sp_widget_new_global(INKSCAPE);
1039 f = gtk_hbox_new (FALSE, 0);
1040 gtk_widget_show(f);
1041 gtk_container_add(GTK_CONTAINER(spw), f);
1043 t = gtk_table_new(3, 6, FALSE);
1044 gtk_widget_show(t);
1045 gtk_container_set_border_width(GTK_CONTAINER(t), 4);
1046 gtk_table_set_row_spacings(GTK_TABLE(t), 4);
1047 gtk_container_add(GTK_CONTAINER(f), t);
1048 gtk_object_set_data(GTK_OBJECT(spw), "stroke", t);
1050 gint i = 0;
1052 /* Stroke width */
1053 spw_label(t, _("Width:"), 0, i);
1055 hb = spw_hbox(t, 3, 1, i);
1057 // TODO: when this is gtkmmified, use an Inkscape::UI::Widget::ScalarUnit instead of the separate
1058 // spinbutton and unit selector for stroke width. In sp_stroke_style_line_update, use
1059 // setHundredPercent to remember the aeraged width corresponding to 100%. Then the
1060 // stroke_width_set_unit will be removed (because ScalarUnit takes care of conversions itself), and
1061 // with it, the two remaining calls of stroke_average_width, allowing us to get rid of that
1062 // function in desktop-style.
1064 a = gtk_adjustment_new(1.0, 0.0, 1000.0, 0.1, 10.0, 10.0);
1065 gtk_object_set_data(GTK_OBJECT(spw), "width", a);
1066 sb = gtk_spin_button_new(GTK_ADJUSTMENT(a), 0.1, 3);
1067 gtk_tooltips_set_tip(tt, sb, _("Stroke width"), NULL);
1068 gtk_widget_show(sb);
1070 sp_dialog_defocus_on_enter(sb);
1072 gtk_box_pack_start(GTK_BOX(hb), sb, FALSE, FALSE, 0);
1073 us = sp_unit_selector_new(SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE);
1074 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1075 if (desktop)
1076 sp_unit_selector_set_unit (SP_UNIT_SELECTOR(us), sp_desktop_namedview(desktop)->doc_units);
1077 sp_unit_selector_add_unit(SP_UNIT_SELECTOR(us), &sp_unit_get_by_id(SP_UNIT_PERCENT), 0);
1078 g_signal_connect ( G_OBJECT (us), "set_unit", G_CALLBACK (stroke_width_set_unit), spw );
1079 gtk_widget_show(us);
1080 sp_unit_selector_add_adjustment( SP_UNIT_SELECTOR(us), GTK_ADJUSTMENT(a) );
1081 gtk_box_pack_start(GTK_BOX(hb), us, FALSE, FALSE, 0);
1082 gtk_object_set_data(GTK_OBJECT(spw), "units", us);
1084 gtk_signal_connect( GTK_OBJECT(a), "value_changed", GTK_SIGNAL_FUNC(sp_stroke_style_width_changed), spw );
1085 i++;
1087 /* Join type */
1088 // TRANSLATORS: The line join style specifies the shape to be used at the
1089 // corners of paths. It can be "miter", "round" or "bevel".
1090 spw_label(t, _("Join:"), 0, i);
1092 hb = spw_hbox(t, 3, 1, i);
1094 tb = NULL;
1096 tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_JOIN_MITER,
1097 hb, spw, "join", "miter");
1099 // TRANSLATORS: Miter join: joining lines with a sharp (pointed) corner.
1100 // For an example, draw a triangle with a large stroke width and modify the
1101 // "Join" option (in the Fill and Stroke dialog).
1102 gtk_tooltips_set_tip(tt, tb, _("Miter join"), NULL);
1104 tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_JOIN_ROUND,
1105 hb, spw, "join", "round");
1107 // TRANSLATORS: Round join: joining lines with a rounded corner.
1108 // For an example, draw a triangle with a large stroke width and modify the
1109 // "Join" option (in the Fill and Stroke dialog).
1110 gtk_tooltips_set_tip(tt, tb, _("Round join"), NULL);
1112 tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_JOIN_BEVEL,
1113 hb, spw, "join", "bevel");
1115 // TRANSLATORS: Bevel join: joining lines with a blunted (flattened) corner.
1116 // For an example, draw a triangle with a large stroke width and modify the
1117 // "Join" option (in the Fill and Stroke dialog).
1118 gtk_tooltips_set_tip(tt, tb, _("Bevel join"), NULL);
1120 i++;
1122 /* Miterlimit */
1123 // TRANSLATORS: Miter limit: only for "miter join", this limits the length
1124 // of the sharp "spike" when the lines connect at too sharp an angle.
1125 // When two line segments meet at a sharp angle, a miter join results in a
1126 // spike that extends well beyond the connection point. The purpose of the
1127 // miter limit is to cut off such spikes (i.e. convert them into bevels)
1128 // when they become too long.
1129 spw_label(t, _("Miter limit:"), 0, i);
1131 hb = spw_hbox(t, 3, 1, i);
1133 a = gtk_adjustment_new(4.0, 0.0, 100.0, 0.1, 10.0, 10.0);
1134 gtk_object_set_data(GTK_OBJECT(spw), "miterlimit", a);
1136 sb = gtk_spin_button_new(GTK_ADJUSTMENT(a), 0.1, 2);
1137 gtk_tooltips_set_tip(tt, sb, _("Maximum length of the miter (in units of stroke width)"), NULL);
1138 gtk_widget_show(sb);
1139 gtk_object_set_data(GTK_OBJECT(spw), "miterlimit_sb", sb);
1140 sp_dialog_defocus_on_enter(sb);
1142 gtk_box_pack_start(GTK_BOX(hb), sb, FALSE, FALSE, 0);
1144 gtk_signal_connect( GTK_OBJECT(a), "value_changed",
1145 GTK_SIGNAL_FUNC(sp_stroke_style_miterlimit_changed), spw );
1146 i++;
1148 /* Cap type */
1149 // TRANSLATORS: cap type specifies the shape for the ends of lines
1150 spw_label(t, _("Cap:"), 0, i);
1152 hb = spw_hbox(t, 3, 1, i);
1154 tb = NULL;
1156 tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_CAP_BUTT,
1157 hb, spw, "cap", "butt");
1159 // TRANSLATORS: Butt cap: the line shape does not extend beyond the end point
1160 // of the line; the ends of the line are square
1161 gtk_tooltips_set_tip(tt, tb, _("Butt cap"), NULL);
1163 tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_CAP_ROUND,
1164 hb, spw, "cap", "round");
1166 // TRANSLATORS: Round cap: the line shape extends beyond the end point of the
1167 // line; the ends of the line are rounded
1168 gtk_tooltips_set_tip(tt, tb, _("Round cap"), NULL);
1170 tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_CAP_SQUARE,
1171 hb, spw, "cap", "square");
1173 // TRANSLATORS: Square cap: the line shape extends beyond the end point of the
1174 // line; the ends of the line are square
1175 gtk_tooltips_set_tip(tt, tb, _("Square cap"), NULL);
1177 i++;
1180 /* Dash */
1181 spw_label(t, _("Dashes:"), 0, i);
1182 ds = sp_dash_selector_new( inkscape_get_repr( INKSCAPE,
1183 "palette.dashes") );
1185 gtk_widget_show(ds);
1186 gtk_table_attach( GTK_TABLE(t), ds, 1, 4, i, i+1,
1187 (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ),
1188 (GtkAttachOptions)0, 0, 0 );
1189 gtk_object_set_data(GTK_OBJECT(spw), "dash", ds);
1190 gtk_signal_connect( GTK_OBJECT(ds), "changed",
1191 GTK_SIGNAL_FUNC(sp_stroke_style_line_dash_changed),
1192 spw );
1193 i++;
1195 /* Drop down marker selectors*/
1197 // doing this here once, instead of for each preview, to speed things up
1198 SPDocument *sandbox = ink_markers_preview_doc ();
1200 // TRANSLATORS: Path markers are an SVG feature that allows you to attach arbitrary shapes
1201 // (arrowheads, bullets, faces, whatever) to the start, end, or middle nodes of a path.
1202 spw_label(t, _("Start Markers:"), 0, i);
1203 marker_start_menu = ink_marker_menu( spw ,"marker-start", sandbox);
1204 gtk_signal_connect( GTK_OBJECT(marker_start_menu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw );
1205 gtk_widget_show(marker_start_menu);
1206 gtk_table_attach( GTK_TABLE(t), marker_start_menu, 1, 4, i, i+1,
1207 (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ),
1208 (GtkAttachOptions)0, 0, 0 );
1209 gtk_object_set_data(GTK_OBJECT(spw), "start_mark_menu", marker_start_menu);
1211 i++;
1212 spw_label(t, _("Mid Markers:"), 0, i);
1213 marker_mid_menu = ink_marker_menu( spw ,"marker-mid", sandbox);
1214 gtk_signal_connect( GTK_OBJECT(marker_mid_menu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw );
1215 gtk_widget_show(marker_mid_menu);
1216 gtk_table_attach( GTK_TABLE(t), marker_mid_menu, 1, 4, i, i+1,
1217 (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ),
1218 (GtkAttachOptions)0, 0, 0 );
1219 gtk_object_set_data(GTK_OBJECT(spw), "mid_mark_menu", marker_mid_menu);
1221 i++;
1222 spw_label(t, _("End Markers:"), 0, i);
1223 marker_end_menu = ink_marker_menu( spw ,"marker-end", sandbox);
1224 gtk_signal_connect( GTK_OBJECT(marker_end_menu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw );
1225 gtk_widget_show(marker_end_menu);
1226 gtk_table_attach( GTK_TABLE(t), marker_end_menu, 1, 4, i, i+1,
1227 (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ),
1228 (GtkAttachOptions)0, 0, 0 );
1229 gtk_object_set_data(GTK_OBJECT(spw), "end_mark_menu", marker_end_menu);
1231 i++;
1233 gtk_signal_connect( GTK_OBJECT(spw), "construct",
1234 GTK_SIGNAL_FUNC(sp_stroke_style_line_construct),
1235 NULL );
1236 gtk_signal_connect( GTK_OBJECT(spw), "modify_selection",
1237 GTK_SIGNAL_FUNC(sp_stroke_style_line_selection_modified),
1238 NULL );
1239 gtk_signal_connect( GTK_OBJECT(spw), "change_selection",
1240 GTK_SIGNAL_FUNC(sp_stroke_style_line_selection_changed),
1241 NULL );
1243 sp_stroke_style_line_update( SP_WIDGET(spw), desktop ? sp_desktop_selection(desktop) : NULL);
1245 return spw;
1246 }
1249 /**
1250 * Callback for when the stroke style widget is called. It causes
1251 * the stroke line style to be updated.
1252 */
1253 static void
1254 sp_stroke_style_line_construct(SPWidget *spw, gpointer data)
1255 {
1257 #ifdef SP_SS_VERBOSE
1258 g_print( "Stroke style widget constructed: inkscape %p repr %p\n",
1259 spw->inkscape, spw->repr );
1260 #endif
1261 if (spw->inkscape) {
1262 sp_stroke_style_line_update(spw,
1263 ( SP_ACTIVE_DESKTOP
1264 ? sp_desktop_selection(SP_ACTIVE_DESKTOP)
1265 : NULL ));
1266 }
1267 }
1269 /**
1270 * Callback for when stroke style widget is modified.
1271 * Triggers update action.
1272 */
1273 static void
1274 sp_stroke_style_line_selection_modified ( SPWidget *spw,
1275 Inkscape::Selection *selection,
1276 guint flags,
1277 gpointer data )
1278 {
1279 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) {
1280 sp_stroke_style_line_update (spw, selection);
1281 }
1283 }
1285 /**
1286 * Callback for when stroke style widget is changed.
1287 * Triggers update action.
1288 */
1289 static void
1290 sp_stroke_style_line_selection_changed ( SPWidget *spw,
1291 Inkscape::Selection *selection,
1292 gpointer data )
1293 {
1294 sp_stroke_style_line_update (spw, selection);
1295 }
1298 /**
1299 * Sets selector widgets' dash style from an SPStyle object.
1300 */
1301 static void
1302 sp_dash_selector_set_from_style (GtkWidget *dsel, SPStyle *style)
1303 {
1304 if (style->stroke_dash.n_dash > 0) {
1305 double d[64];
1306 int len = MIN(style->stroke_dash.n_dash, 64);
1307 for (int i = 0; i < len; i++) {
1308 if (style->stroke_width.computed != 0)
1309 d[i] = style->stroke_dash.dash[i] / style->stroke_width.computed;
1310 else
1311 d[i] = style->stroke_dash.dash[i]; // is there a better thing to do for stroke_width==0?
1312 }
1313 sp_dash_selector_set_dash(SP_DASH_SELECTOR(dsel), len, d,
1314 style->stroke_width.computed != 0?
1315 style->stroke_dash.offset / style->stroke_width.computed :
1316 style->stroke_dash.offset);
1317 } else {
1318 sp_dash_selector_set_dash(SP_DASH_SELECTOR(dsel), 0, NULL, 0.0);
1319 }
1320 }
1322 /**
1323 * Sets the join type for a line, and updates the stroke style widget's buttons
1324 */
1325 static void
1326 sp_jointype_set (SPWidget *spw, unsigned const jointype)
1327 {
1328 GtkWidget *tb = NULL;
1329 switch (jointype) {
1330 case SP_STROKE_LINEJOIN_MITER:
1331 tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_MITER));
1332 break;
1333 case SP_STROKE_LINEJOIN_ROUND:
1334 tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_ROUND));
1335 break;
1336 case SP_STROKE_LINEJOIN_BEVEL:
1337 tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_BEVEL));
1338 break;
1339 default:
1340 break;
1341 }
1342 sp_stroke_style_set_join_buttons (spw, tb);
1343 }
1345 /**
1346 * Sets the cap type for a line, and updates the stroke style widget's buttons
1347 */
1348 static void
1349 sp_captype_set (SPWidget *spw, unsigned const captype)
1350 {
1351 GtkWidget *tb = NULL;
1352 switch (captype) {
1353 case SP_STROKE_LINECAP_BUTT:
1354 tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_BUTT));
1355 break;
1356 case SP_STROKE_LINECAP_ROUND:
1357 tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_ROUND));
1358 break;
1359 case SP_STROKE_LINECAP_SQUARE:
1360 tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_SQUARE));
1361 break;
1362 default:
1363 break;
1364 }
1365 sp_stroke_style_set_cap_buttons (spw, tb);
1366 }
1368 /**
1369 * Callback for when stroke style widget is updated, including markers, cap type,
1370 * join type, etc.
1371 */
1372 static void
1373 sp_stroke_style_line_update(SPWidget *spw, Inkscape::Selection *sel)
1374 {
1375 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1376 return;
1377 }
1379 gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(TRUE));
1381 GtkWidget *sset = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "stroke"));
1382 GtkObject *width = GTK_OBJECT(gtk_object_get_data(GTK_OBJECT(spw), "width"));
1383 GtkObject *ml = GTK_OBJECT(gtk_object_get_data(GTK_OBJECT(spw), "miterlimit"));
1384 GtkWidget *us = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "units"));
1385 GtkWidget *dsel = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "dash"));
1387 // create temporary style
1388 SPStyle *query = sp_style_new ();
1389 // query into it
1390 int result_sw = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEWIDTH);
1391 int result_ml = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEMITERLIMIT);
1392 int result_cap = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKECAP);
1393 int result_join = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEJOIN);
1395 if (result_sw == QUERY_STYLE_NOTHING) {
1396 /* No objects stroked, set insensitive */
1397 gtk_widget_set_sensitive(sset, FALSE);
1399 gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(FALSE));
1400 return;
1401 } else {
1402 gtk_widget_set_sensitive(sset, TRUE);
1404 SPUnit const *unit = sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us));
1406 if (result_sw == QUERY_STYLE_MULTIPLE_AVERAGED) {
1407 sp_unit_selector_set_unit(SP_UNIT_SELECTOR(us), &sp_unit_get_by_id(SP_UNIT_PERCENT));
1408 } else {
1409 // same width, or only one object; no sense to keep percent, switch to absolute
1410 if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) {
1411 sp_unit_selector_set_unit(SP_UNIT_SELECTOR(us), sp_desktop_namedview(SP_ACTIVE_DESKTOP)->doc_units);
1412 }
1413 }
1415 unit = sp_unit_selector_get_unit (SP_UNIT_SELECTOR (us));
1417 if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) {
1418 double avgwidth = sp_pixels_get_units (query->stroke_width.computed, *unit);
1419 gtk_adjustment_set_value(GTK_ADJUSTMENT(width), avgwidth);
1420 } else {
1421 gtk_adjustment_set_value(GTK_ADJUSTMENT(width), 100);
1422 }
1423 }
1425 if (result_ml != QUERY_STYLE_NOTHING)
1426 gtk_adjustment_set_value(GTK_ADJUSTMENT(ml), query->stroke_miterlimit.value); // TODO: reflect averagedness?
1428 if (result_join != QUERY_STYLE_MULTIPLE_DIFFERENT) {
1429 sp_jointype_set (spw, query->stroke_linejoin.value);
1430 } else {
1431 sp_stroke_style_set_join_buttons(spw, NULL);
1432 }
1434 if (result_cap != QUERY_STYLE_MULTIPLE_DIFFERENT) {
1435 sp_captype_set (spw, query->stroke_linecap.value);
1436 } else {
1437 sp_stroke_style_set_cap_buttons(spw, NULL);
1438 }
1440 g_free (query);
1442 if (!sel || sel->isEmpty())
1443 return;
1445 GSList const *objects = sel->itemList();
1446 SPObject * const object = SP_OBJECT(objects->data);
1447 SPStyle * const style = SP_OBJECT_STYLE(object);
1449 /* Markers */
1450 sp_stroke_style_update_marker_menus(spw, objects); // FIXME: make this desktop query too
1452 /* Dash */
1453 sp_dash_selector_set_from_style (dsel, style); // FIXME: make this desktop query too
1455 gtk_widget_set_sensitive(sset, TRUE);
1457 gtk_object_set_data(GTK_OBJECT(spw), "update",
1458 GINT_TO_POINTER(FALSE));
1459 }
1461 /**
1462 * Sets a line's dash properties in a CSS style object.
1463 */
1464 static void
1465 sp_stroke_style_set_scaled_dash(SPCSSAttr *css,
1466 int ndash, double *dash, double offset,
1467 double scale)
1468 {
1469 if (ndash > 0) {
1470 Inkscape::CSSOStringStream osarray;
1471 for (int i = 0; i < ndash; i++) {
1472 osarray << dash[i] * scale;
1473 if (i < (ndash - 1)) {
1474 osarray << ",";
1475 }
1476 }
1477 sp_repr_css_set_property(css, "stroke-dasharray", osarray.str().c_str());
1479 Inkscape::CSSOStringStream osoffset;
1480 osoffset << offset * scale;
1481 sp_repr_css_set_property(css, "stroke-dashoffset", osoffset.str().c_str());
1482 } else {
1483 sp_repr_css_set_property(css, "stroke-dasharray", "none");
1484 sp_repr_css_set_property(css, "stroke-dashoffset", NULL);
1485 }
1486 }
1488 /**
1489 * Sets line properties like width, dashes, markers, etc. on all currently selected items.
1490 */
1491 static void
1492 sp_stroke_style_scale_line(SPWidget *spw)
1493 {
1494 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1495 return;
1496 }
1498 gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(TRUE));
1500 GtkAdjustment *wadj = GTK_ADJUSTMENT(gtk_object_get_data(GTK_OBJECT(spw), "width"));
1501 SPUnitSelector *us = SP_UNIT_SELECTOR(gtk_object_get_data(GTK_OBJECT(spw), "units"));
1502 SPDashSelector *dsel = SP_DASH_SELECTOR(gtk_object_get_data(GTK_OBJECT(spw), "dash"));
1503 GtkAdjustment *ml = GTK_ADJUSTMENT(gtk_object_get_data(GTK_OBJECT(spw), "miterlimit"));
1505 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1506 SPDocument *document = sp_desktop_document (desktop);
1507 Inkscape::Selection *selection = sp_desktop_selection (desktop);
1509 GSList const *items = selection->itemList();
1511 /* TODO: Create some standardized method */
1512 SPCSSAttr *css = sp_repr_css_attr_new();
1514 if (items) {
1516 double width_typed = wadj->value;
1517 double const miterlimit = ml->value;
1519 SPUnit const *const unit = sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us));
1521 double *dash, offset;
1522 int ndash;
1523 sp_dash_selector_get_dash(dsel, &ndash, &dash, &offset);
1525 for (GSList const *i = items; i != NULL; i = i->next) {
1526 /* Set stroke width */
1527 double width;
1528 if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) {
1529 width = sp_units_get_pixels (width_typed, *unit);
1530 } else { // percentage
1531 gdouble old_w = SP_OBJECT_STYLE (i->data)->stroke_width.computed;
1532 width = old_w * width_typed / 100;
1533 }
1535 {
1536 Inkscape::CSSOStringStream os_width;
1537 os_width << width;
1538 sp_repr_css_set_property(css, "stroke-width", os_width.str().c_str());
1539 }
1541 {
1542 Inkscape::CSSOStringStream os_ml;
1543 os_ml << miterlimit;
1544 sp_repr_css_set_property(css, "stroke-miterlimit", os_ml.str().c_str());
1545 }
1547 /* Set dash */
1548 sp_stroke_style_set_scaled_dash(css, ndash, dash, offset, width);
1550 sp_desktop_apply_css_recursive (SP_OBJECT(i->data), css, true);
1551 }
1553 g_free(dash);
1555 if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) {
1556 // reset to 100 percent
1557 gtk_adjustment_set_value (wadj, 100.0);
1558 }
1560 }
1562 // we have already changed the items, so set style without changing selection
1563 // FIXME: move the above stroke-setting stuff, including percentages, to desktop-style
1564 sp_desktop_set_style (desktop, css, false);
1566 sp_repr_css_attr_unref(css);
1568 sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE,
1569 _("Set stroke style"));
1571 gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(FALSE));
1572 }
1575 /**
1576 * Callback for when the stroke style's width changes.
1577 * Causes all line styles to be applied to all selected items.
1578 */
1579 static void
1580 sp_stroke_style_width_changed(GtkAdjustment *adj, SPWidget *spw)
1581 {
1582 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1583 return;
1584 }
1586 sp_stroke_style_scale_line(spw);
1587 }
1589 /**
1590 * Callback for when the stroke style's miterlimit changes.
1591 * Causes all line styles to be applied to all selected items.
1592 */
1593 static void
1594 sp_stroke_style_miterlimit_changed(GtkAdjustment *adj, SPWidget *spw)
1595 {
1596 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1597 return;
1598 }
1600 sp_stroke_style_scale_line(spw);
1601 }
1603 /**
1604 * Callback for when the stroke style's dash changes.
1605 * Causes all line styles to be applied to all selected items.
1606 */
1607 static void
1608 sp_stroke_style_line_dash_changed(SPDashSelector *dsel, SPWidget *spw)
1609 {
1610 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1611 return;
1612 }
1614 sp_stroke_style_scale_line(spw);
1615 }
1619 /**
1620 * \brief This routine handles toggle events for buttons in the stroke style
1621 * dialog.
1622 * When activated, this routine gets the data for the various widgets, and then
1623 * calls the respective routines to update css properties, etc.
1624 *
1625 */
1626 static void
1627 sp_stroke_style_any_toggled(GtkToggleButton *tb, SPWidget *spw)
1628 {
1629 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1630 return;
1631 }
1633 if (gtk_toggle_button_get_active(tb)) {
1635 gchar const *join
1636 = static_cast<gchar const *>(gtk_object_get_data(GTK_OBJECT(tb), "join"));
1637 gchar const *cap
1638 = static_cast<gchar const *>(gtk_object_get_data(GTK_OBJECT(tb), "cap"));
1640 if (join) {
1641 GtkWidget *ml = GTK_WIDGET(g_object_get_data(G_OBJECT(spw), "miterlimit_sb"));
1642 gtk_widget_set_sensitive (ml, !strcmp(join, "miter"));
1643 }
1645 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1647 /* TODO: Create some standardized method */
1648 SPCSSAttr *css = sp_repr_css_attr_new();
1650 if (join) {
1651 sp_repr_css_set_property(css, "stroke-linejoin", join);
1653 sp_desktop_set_style (desktop, css);
1655 sp_stroke_style_set_join_buttons(spw, GTK_WIDGET(tb));
1656 } else if (cap) {
1657 sp_repr_css_set_property(css, "stroke-linecap", cap);
1659 sp_desktop_set_style (desktop, css);
1661 sp_stroke_style_set_cap_buttons(spw, GTK_WIDGET(tb));
1662 }
1664 sp_repr_css_attr_unref(css);
1666 sp_document_done(sp_desktop_document(desktop), SP_VERB_DIALOG_FILL_STROKE,
1667 _("Set stroke style"));
1668 }
1669 }
1672 /**
1673 * Updates the join style toggle buttons
1674 */
1675 static void
1676 sp_stroke_style_set_join_buttons(SPWidget *spw, GtkWidget *active)
1677 {
1678 GtkWidget *tb;
1680 tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1681 INKSCAPE_STOCK_JOIN_MITER) );
1682 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1684 GtkWidget *ml = GTK_WIDGET(g_object_get_data(G_OBJECT(spw), "miterlimit_sb"));
1685 gtk_widget_set_sensitive(ml, (active == tb));
1687 tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1688 INKSCAPE_STOCK_JOIN_ROUND) );
1689 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1690 tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1691 INKSCAPE_STOCK_JOIN_BEVEL) );
1692 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1693 }
1697 /**
1698 * Updates the cap style toggle buttons
1699 */
1700 static void
1701 sp_stroke_style_set_cap_buttons(SPWidget *spw, GtkWidget *active)
1702 {
1703 GtkWidget *tb;
1705 tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1706 INKSCAPE_STOCK_CAP_BUTT));
1707 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1708 tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1709 INKSCAPE_STOCK_CAP_ROUND) );
1710 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1711 tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1712 INKSCAPE_STOCK_CAP_SQUARE) );
1713 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1714 }
1716 /**
1717 * Sets the current marker in the marker menu.
1718 */
1719 static void
1720 ink_marker_menu_set_current(SPObject *marker, GtkOptionMenu *mnu)
1721 {
1722 gtk_object_set_data(GTK_OBJECT(mnu), "update", GINT_TO_POINTER(TRUE));
1724 GtkMenu *m = GTK_MENU(gtk_option_menu_get_menu(mnu));
1725 if (marker != NULL) {
1726 bool mark_is_stock = false;
1727 if (SP_OBJECT_REPR(marker)->attribute("inkscape:stockid"))
1728 mark_is_stock = true;
1730 gchar *markname;
1731 if (mark_is_stock)
1732 markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("inkscape:stockid"));
1733 else
1734 markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("id"));
1736 int markpos = 0;
1737 GList *kids = GTK_MENU_SHELL(m)->children;
1738 int i = 0;
1739 for (; kids != NULL; kids = kids->next) {
1740 gchar *mark = (gchar *) g_object_get_data(G_OBJECT(kids->data), "marker");
1741 if ( mark && strcmp(mark, markname) == 0 ) {
1742 if ( mark_is_stock && !strcmp((gchar *) g_object_get_data(G_OBJECT(kids->data), "stockid"), "true"))
1743 markpos = i;
1744 if ( !mark_is_stock && !strcmp((gchar *) g_object_get_data(G_OBJECT(kids->data), "stockid"), "false"))
1745 markpos = i;
1746 }
1747 i++;
1748 }
1749 gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), markpos);
1751 g_free (markname);
1752 }
1753 else {
1754 gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 0);
1755 }
1756 gtk_object_set_data(GTK_OBJECT(mnu), "update", GINT_TO_POINTER(FALSE));
1757 }
1759 /**
1760 * Updates the marker menus to highlight the appropriate marker and scroll to
1761 * that marker.
1762 */
1763 static void
1764 sp_stroke_style_update_marker_menus( SPWidget *spw,
1765 GSList const *objects)
1766 {
1767 struct { char const *key; int loc; } const keyloc[] = {
1768 { "start_mark_menu", SP_MARKER_LOC_START },
1769 { "mid_mark_menu", SP_MARKER_LOC_MID },
1770 { "end_mark_menu", SP_MARKER_LOC_END }
1771 };
1773 bool all_texts = true;
1774 for (GSList *i = (GSList *) objects; i != NULL; i = i->next) {
1775 if (!SP_IS_TEXT (i->data)) {
1776 all_texts = false;
1777 }
1778 }
1780 for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) {
1781 GtkOptionMenu *mnu = (GtkOptionMenu *) g_object_get_data(G_OBJECT(spw), keyloc[i].key);
1782 if (all_texts) {
1783 // Per SVG spec, text objects cannot have markers; disable menus if only texts are selected
1784 gtk_widget_set_sensitive (GTK_WIDGET(mnu), FALSE);
1785 } else {
1786 gtk_widget_set_sensitive (GTK_WIDGET(mnu), TRUE);
1787 }
1788 }
1790 // We show markers of the first object in the list only
1791 // FIXME: use the first in the list that has the marker of each type, if any
1792 SPObject *object = SP_OBJECT(objects->data);
1794 for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) {
1795 // For all three marker types,
1797 // find the corresponding menu
1798 GtkOptionMenu *mnu = (GtkOptionMenu *) g_object_get_data(G_OBJECT(spw), keyloc[i].key);
1800 // Quit if we're in update state
1801 if (gtk_object_get_data(GTK_OBJECT(mnu), "update")) {
1802 return;
1803 }
1805 if (object->style->marker[keyloc[i].loc].value != NULL && !all_texts) {
1806 // If the object has this type of markers,
1808 // Extract the name of the marker that the object uses
1809 SPObject *marker = ink_extract_marker_name(object->style->marker[keyloc[i].loc].value);
1810 // Scroll the menu to that marker
1811 ink_marker_menu_set_current (marker, mnu);
1813 } else {
1814 gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 0);
1815 }
1816 }
1817 }
1820 /**
1821 * Extract the actual name of the link
1822 * e.g. get mTriangle from url(#mTriangle).
1823 * \return Buffer containing the actual name, allocated from GLib;
1824 * the caller should free the buffer when they no longer need it.
1825 */
1826 static SPObject*
1827 ink_extract_marker_name(gchar const *n)
1828 {
1829 gchar const *p = n;
1830 while (*p != '\0' && *p != '#') {
1831 p++;
1832 }
1834 if (*p == '\0' || p[1] == '\0') {
1835 return NULL;
1836 }
1838 p++;
1839 int c = 0;
1840 while (p[c] != '\0' && p[c] != ')') {
1841 c++;
1842 }
1844 if (p[c] == '\0') {
1845 return NULL;
1846 }
1848 gchar* b = g_strdup(p);
1849 b[c] = '\0';
1852 SPDesktop *desktop = inkscape_active_desktop();
1853 SPDocument *doc = sp_desktop_document(desktop);
1854 SPObject *marker = doc->getObjectById(b);
1855 return marker;
1856 }
1859 /*
1860 Local Variables:
1861 mode:c++
1862 c-file-style:"stroustrup"
1863 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1864 indent-tabs-mode:nil
1865 fill-column:99
1866 End:
1867 */
1868 // vim: filetype=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :