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;
83 /**
84 * Create the stroke style widget, and hook up all the signals.
85 */
86 GtkWidget *
87 sp_stroke_style_paint_widget_new(void)
88 {
89 GtkWidget *spw, *psel;
91 spw = sp_widget_new_global(INKSCAPE);
93 psel = sp_paint_selector_new(false); // without fillrule selector
94 gtk_widget_show(psel);
95 gtk_container_add(GTK_CONTAINER(spw), psel);
96 gtk_object_set_data(GTK_OBJECT(spw), "paint-selector", psel);
98 gtk_signal_connect(GTK_OBJECT(spw), "construct",
99 GTK_SIGNAL_FUNC(sp_stroke_style_paint_construct),
100 psel);
101 gtk_signal_connect(GTK_OBJECT(spw), "modify_selection",
102 GTK_SIGNAL_FUNC(sp_stroke_style_paint_selection_modified),
103 psel);
104 gtk_signal_connect(GTK_OBJECT(spw), "change_selection",
105 GTK_SIGNAL_FUNC(sp_stroke_style_paint_selection_changed),
106 psel);
108 g_signal_connect (INKSCAPE, "change_subselection", G_CALLBACK (sp_stroke_style_widget_change_subselection), spw);
110 gtk_signal_connect(GTK_OBJECT(psel), "mode_changed",
111 GTK_SIGNAL_FUNC(sp_stroke_style_paint_mode_changed),
112 spw);
113 gtk_signal_connect(GTK_OBJECT(psel), "dragged",
114 GTK_SIGNAL_FUNC(sp_stroke_style_paint_dragged),
115 spw);
116 gtk_signal_connect(GTK_OBJECT(psel), "changed",
117 GTK_SIGNAL_FUNC(sp_stroke_style_paint_changed),
118 spw);
120 sp_stroke_style_paint_update (SP_WIDGET(spw));
121 return spw;
122 }
124 /**
125 * On construction, simply does an update of the stroke style paint object.
126 */
127 static void
128 sp_stroke_style_paint_construct(SPWidget *spw, SPPaintSelector *psel)
129 {
130 #ifdef SP_SS_VERBOSE
131 g_print( "Stroke style widget constructed: inkscape %p repr %p\n",
132 spw->inkscape, spw->repr );
133 #endif
134 if (spw->inkscape) {
135 sp_stroke_style_paint_update (spw);
136 }
137 }
139 /**
140 * On signal modified, invokes an update of the stroke style paint object.
141 */
142 static void
143 sp_stroke_style_paint_selection_modified ( SPWidget *spw,
144 Inkscape::Selection *selection,
145 guint flags,
146 SPPaintSelector *psel)
147 {
148 if (flags & ( SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG |
149 SP_OBJECT_STYLE_MODIFIED_FLAG) ) {
150 sp_stroke_style_paint_update(spw);
151 }
152 }
155 /**
156 * On signal selection changed, invokes an update of the stroke style paint object.
157 */
158 static void
159 sp_stroke_style_paint_selection_changed ( SPWidget *spw,
160 Inkscape::Selection *selection,
161 SPPaintSelector *psel )
162 {
163 sp_stroke_style_paint_update (spw);
164 }
167 /**
168 * On signal change subselection, invoke an update of the stroke style widget.
169 */
170 static void
171 sp_stroke_style_widget_change_subselection ( Inkscape::Application *inkscape,
172 SPDesktop *desktop,
173 SPWidget *spw )
174 {
175 sp_stroke_style_paint_update (spw);
176 }
178 /**
179 * Gets the active stroke style property, then sets the appropriate color, alpha, gradient,
180 * pattern, etc. for the paint-selector.
181 */
182 static void
183 sp_stroke_style_paint_update (SPWidget *spw)
184 {
185 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
186 return;
187 }
189 gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(TRUE));
191 SPPaintSelector *psel = SP_PAINT_SELECTOR(gtk_object_get_data(GTK_OBJECT(spw), "paint-selector"));
193 // create temporary style
194 SPStyle *query = sp_style_new ();
195 // query into it
196 int result = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKE);
198 switch (result) {
199 case QUERY_STYLE_NOTHING:
200 {
201 /* No paint at all */
202 sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_EMPTY);
203 break;
204 }
206 case QUERY_STYLE_SINGLE:
207 case QUERY_STYLE_MULTIPLE_AVERAGED: // TODO: treat this slightly differently, e.g. display "averaged" somewhere in paint selector
208 case QUERY_STYLE_MULTIPLE_SAME:
209 {
210 SPPaintSelectorMode pselmode = sp_style_determine_paint_selector_mode (query, false);
211 sp_paint_selector_set_mode (psel, pselmode);
213 if (query->stroke.set && query->stroke.type == SP_PAINT_TYPE_COLOR) {
214 gfloat d[3];
215 sp_color_get_rgb_floatv (&query->stroke.value.color, d);
216 SPColor color;
217 sp_color_set_rgb_float (&color, d[0], d[1], d[2]);
218 sp_paint_selector_set_color_alpha (psel, &color, SP_SCALE24_TO_FLOAT (query->stroke_opacity.value));
220 } else if (query->stroke.set && query->stroke.type == SP_PAINT_TYPE_PAINTSERVER) {
222 SPPaintServer *server = SP_STYLE_STROKE_SERVER (query);
224 if (SP_IS_LINEARGRADIENT (server)) {
225 SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE);
226 sp_paint_selector_set_gradient_linear (psel, vector);
228 SPLinearGradient *lg = SP_LINEARGRADIENT (server);
229 sp_paint_selector_set_gradient_properties (psel,
230 SP_GRADIENT_UNITS (lg),
231 SP_GRADIENT_SPREAD (lg));
232 } else if (SP_IS_RADIALGRADIENT (server)) {
233 SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE);
234 sp_paint_selector_set_gradient_radial (psel, vector);
236 SPRadialGradient *rg = SP_RADIALGRADIENT (server);
237 sp_paint_selector_set_gradient_properties (psel,
238 SP_GRADIENT_UNITS (rg),
239 SP_GRADIENT_SPREAD (rg));
240 } else if (SP_IS_PATTERN (server)) {
241 SPPattern *pat = pattern_getroot (SP_PATTERN (server));
242 sp_update_pattern_list (psel, pat);
243 }
244 }
245 break;
246 }
248 case QUERY_STYLE_MULTIPLE_DIFFERENT:
249 {
250 sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_MULTIPLE);
251 break;
252 }
253 }
255 g_free (query);
257 gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(FALSE));
258 }
260 /**
261 * When the mode is changed, invoke a regular changed handler.
262 */
263 static void
264 sp_stroke_style_paint_mode_changed( SPPaintSelector *psel,
265 SPPaintSelectorMode mode,
266 SPWidget *spw )
267 {
268 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
269 return;
270 }
272 /* TODO: Does this work?
273 * Not really, here we have to get old color back from object
274 * Instead of relying on paint widget having meaningful colors set
275 */
276 sp_stroke_style_paint_changed(psel, spw);
277 }
279 static gchar *undo_label_1 = "stroke:flatcolor:1";
280 static gchar *undo_label_2 = "stroke:flatcolor:2";
281 static gchar *undo_label = undo_label_1;
283 /**
284 * When a drag callback occurs on a paint selector object, if it is a RGB or CMYK
285 * color mode, then set the stroke opacity to psel's flat color.
286 */
287 static void
288 sp_stroke_style_paint_dragged(SPPaintSelector *psel, SPWidget *spw)
289 {
290 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
291 return;
292 }
294 switch (psel->mode) {
295 case SP_PAINT_SELECTOR_MODE_COLOR_RGB:
296 case SP_PAINT_SELECTOR_MODE_COLOR_CMYK:
297 {
298 sp_paint_selector_set_flat_color (psel, SP_ACTIVE_DESKTOP, "stroke", "stroke-opacity");
299 sp_document_maybe_done (sp_desktop_document(SP_ACTIVE_DESKTOP), undo_label, SP_VERB_DIALOG_FILL_STROKE,
300 _("Set stroke color"));
301 break;
302 }
304 default:
305 g_warning( "file %s: line %d: Paint %d should not emit 'dragged'",
306 __FILE__, __LINE__, psel->mode);
307 break;
308 }
309 }
311 /**
312 * When the stroke style's paint settings change, this handler updates the
313 * repr's stroke css style and applies the style to relevant drawing items.
314 */
315 static void
316 sp_stroke_style_paint_changed(SPPaintSelector *psel, SPWidget *spw)
317 {
318 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
319 return;
320 }
321 g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE));
323 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
324 SPDocument *document = sp_desktop_document (desktop);
325 Inkscape::Selection *selection = sp_desktop_selection (desktop);
327 GSList const *items = selection->itemList();
329 switch (psel->mode) {
330 case SP_PAINT_SELECTOR_MODE_EMPTY:
331 // This should not happen.
332 g_warning ( "file %s: line %d: Paint %d should not emit 'changed'",
333 __FILE__, __LINE__, psel->mode);
334 break;
335 case SP_PAINT_SELECTOR_MODE_MULTIPLE:
336 // This happens when you switch multiple objects with different gradients to flat color;
337 // nothing to do here.
338 break;
340 case SP_PAINT_SELECTOR_MODE_NONE:
341 {
342 SPCSSAttr *css = sp_repr_css_attr_new();
343 sp_repr_css_set_property(css, "stroke", "none");
345 sp_desktop_set_style (desktop, css);
347 sp_repr_css_attr_unref(css);
349 sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE,
350 _("Remove stroke"));
351 break;
352 }
354 case SP_PAINT_SELECTOR_MODE_COLOR_RGB:
355 case SP_PAINT_SELECTOR_MODE_COLOR_CMYK:
356 {
357 sp_paint_selector_set_flat_color (psel, desktop, "stroke", "stroke-opacity");
358 sp_document_maybe_done (sp_desktop_document(desktop), undo_label, SP_VERB_DIALOG_FILL_STROKE,
359 _("Set stroke color"));
361 // on release, toggle undo_label so that the next drag will not be lumped with this one
362 if (undo_label == undo_label_1)
363 undo_label = undo_label_2;
364 else
365 undo_label = undo_label_1;
367 break;
368 }
370 case SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR:
371 case SP_PAINT_SELECTOR_MODE_GRADIENT_RADIAL:
372 if (items) {
373 SPGradientType const gradient_type = ( psel->mode == SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR
374 ? SP_GRADIENT_TYPE_LINEAR
375 : SP_GRADIENT_TYPE_RADIAL );
376 SPGradient *vector = sp_paint_selector_get_gradient_vector(psel);
377 if (!vector) {
378 /* No vector in paint selector should mean that we just changed mode */
380 SPStyle *query = sp_style_new ();
381 int result = objects_query_fillstroke ((GSList *) items, query, false);
382 guint32 common_rgb = 0;
383 if (result == QUERY_STYLE_MULTIPLE_SAME) {
384 if (query->fill.type != SP_PAINT_TYPE_COLOR) {
385 common_rgb = sp_desktop_get_color(desktop, false);
386 } else {
387 common_rgb = sp_color_get_rgba32_ualpha(&query->stroke.value.color, 0xff);
388 }
389 vector = sp_document_default_gradient_vector(document, common_rgb);
390 }
391 g_free (query);
393 for (GSList const *i = items; i != NULL; i = i->next) {
394 if (!vector) {
395 sp_item_set_gradient(SP_ITEM(i->data),
396 sp_gradient_vector_for_object(document, desktop, SP_OBJECT(i->data), false),
397 gradient_type, false);
398 } else {
399 sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, false);
400 }
401 }
402 } else {
403 vector = sp_gradient_ensure_vector_normalized(vector);
404 for (GSList const *i = items; i != NULL; i = i->next) {
405 SPGradient *gr = sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, false);
406 sp_gradient_selector_attrs_to_gradient(gr, psel);
407 }
408 }
410 sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE,
411 _("Set gradient on stroke"));
412 }
413 break;
415 case SP_PAINT_SELECTOR_MODE_PATTERN:
417 if (items) {
419 SPPattern *pattern = sp_paint_selector_get_pattern (psel);
420 if (!pattern) {
422 /* No Pattern in paint selector should mean that we just
423 * changed mode - dont do jack.
424 */
426 } else {
427 Inkscape::XML::Node *patrepr = SP_OBJECT_REPR(pattern);
428 SPCSSAttr *css = sp_repr_css_attr_new ();
429 gchar *urltext = g_strdup_printf ("url(#%s)", patrepr->attribute("id"));
430 sp_repr_css_set_property (css, "stroke", urltext);
432 for (GSList const *i = items; i != NULL; i = i->next) {
433 Inkscape::XML::Node *selrepr = SP_OBJECT_REPR (i->data);
434 SPObject *selobj = SP_OBJECT (i->data);
435 if (!selrepr)
436 continue;
438 SPStyle *style = SP_OBJECT_STYLE (selobj);
439 if (style && style->stroke.type == SP_PAINT_TYPE_PAINTSERVER) {
440 SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (selobj);
441 if (SP_IS_PATTERN (server) && pattern_getroot (SP_PATTERN(server)) == pattern)
442 // only if this object's pattern is not rooted in our selected pattern, apply
443 continue;
444 }
446 sp_repr_css_change_recursive (selrepr, css, "style");
447 }
449 sp_repr_css_attr_unref (css);
450 g_free (urltext);
452 } // end if
454 sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE,
455 _("Set pattern on stroke"));
456 } // end if
458 break;
460 case SP_PAINT_SELECTOR_MODE_UNSET:
461 if (items) {
462 SPCSSAttr *css = sp_repr_css_attr_new ();
463 sp_repr_css_unset_property (css, "stroke");
465 sp_desktop_set_style (desktop, css);
466 sp_repr_css_attr_unref (css);
468 sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE,
469 _("Unset stroke"));
470 }
471 break;
473 default:
474 g_warning( "file %s: line %d: Paint selector should not be in "
475 "mode %d",
476 __FILE__, __LINE__,
477 psel->mode );
478 break;
479 }
481 g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
482 }
488 /* Line */
490 static void sp_stroke_style_line_construct(SPWidget *spw, gpointer data);
491 static void sp_stroke_style_line_selection_modified (SPWidget *spw,
492 Inkscape::Selection *selection,
493 guint flags,
494 gpointer data);
496 static void sp_stroke_style_line_selection_changed (SPWidget *spw,
497 Inkscape::Selection *selection,
498 gpointer data );
500 static void sp_stroke_style_line_update(SPWidget *spw, Inkscape::Selection *sel);
502 static void sp_stroke_style_set_join_buttons(SPWidget *spw,
503 GtkWidget *active);
505 static void sp_stroke_style_set_cap_buttons(SPWidget *spw,
506 GtkWidget *active);
508 static void sp_stroke_style_width_changed(GtkAdjustment *adj, SPWidget *spw);
509 static void sp_stroke_style_miterlimit_changed(GtkAdjustment *adj, SPWidget *spw);
510 static void sp_stroke_style_any_toggled(GtkToggleButton *tb, SPWidget *spw);
511 static void sp_stroke_style_line_dash_changed(SPDashSelector *dsel,
512 SPWidget *spw);
514 static void sp_stroke_style_update_marker_menus(SPWidget *spw, GSList const *objects);
516 static SPObject *ink_extract_marker_name(gchar const *n);
519 /**
520 * Helper function for creating radio buttons. This should probably be re-thought out
521 * when reimplementing this with Gtkmm.
522 */
523 static GtkWidget *
524 sp_stroke_radio_button(GtkWidget *tb, char const *icon,
525 GtkWidget *hb, GtkWidget *spw,
526 gchar const *key, gchar const *data)
527 {
528 g_assert(icon != NULL);
529 g_assert(hb != NULL);
530 g_assert(spw != NULL);
532 if (tb == NULL) {
533 tb = gtk_radio_button_new(NULL);
534 } else {
535 tb = gtk_radio_button_new(gtk_radio_button_group(GTK_RADIO_BUTTON(tb)) );
536 }
538 gtk_widget_show(tb);
539 gtk_toggle_button_set_mode(GTK_TOGGLE_BUTTON(tb), FALSE);
540 gtk_box_pack_start(GTK_BOX(hb), tb, FALSE, FALSE, 0);
541 gtk_object_set_data(GTK_OBJECT(spw), icon, tb);
542 gtk_object_set_data(GTK_OBJECT(tb), key, (gpointer*)data);
543 gtk_signal_connect(GTK_OBJECT(tb), "toggled",
544 GTK_SIGNAL_FUNC(sp_stroke_style_any_toggled),
545 spw);
546 GtkWidget *px = sp_icon_new(Inkscape::ICON_SIZE_LARGE_TOOLBAR, icon);
547 g_assert(px != NULL);
548 gtk_widget_show(px);
549 gtk_container_add(GTK_CONTAINER(tb), px);
551 return tb;
553 }
555 /**
556 * Creates a copy of the marker named mname, determines its visible and renderable
557 * area in menu_id's bounding box, and then renders it. This allows us to fill in
558 * preview images of each marker in the marker menu.
559 */
560 static GtkWidget *
561 sp_marker_prev_new(unsigned psize, gchar const *mname,
562 SPDocument *source, SPDocument *sandbox,
563 gchar *menu_id, NRArena const *arena, unsigned visionkey, NRArenaItem *root)
564 {
565 // Retrieve the marker named 'mname' from the source SVG document
566 SPObject const *marker = source->getObjectById(mname);
567 if (marker == NULL)
568 return NULL;
570 // Create a copy repr of the marker with id="sample"
571 Inkscape::XML::Node *mrepr = SP_OBJECT_REPR (marker)->duplicate();
572 mrepr->setAttribute("id", "sample");
574 // Replace the old sample in the sandbox by the new one
575 Inkscape::XML::Node *defsrepr = SP_OBJECT_REPR (sandbox->getObjectById("defs"));
576 SPObject *oldmarker = sandbox->getObjectById("sample");
577 if (oldmarker)
578 oldmarker->deleteObject(false);
579 defsrepr->appendChild(mrepr);
580 Inkscape::GC::release(mrepr);
582 // Uncomment this to get the sandbox documents saved (useful for debugging)
583 //FILE *fp = fopen (g_strconcat(mname, ".svg", NULL), "w");
584 //sp_repr_save_stream (sp_document_repr_doc (sandbox), fp);
585 //fclose (fp);
587 // object to render; note that the id is the same as that of the menu we're building
588 SPObject *object = sandbox->getObjectById(menu_id);
589 sp_document_root (sandbox)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
590 sp_document_ensure_up_to_date(sandbox);
592 if (object == NULL || !SP_IS_ITEM(object))
593 return NULL; // sandbox broken?
595 // Find object's bbox in document
596 NR::Matrix const i2doc(sp_item_i2doc_affine(SP_ITEM(object)));
597 NR::Rect const dbox = SP_ITEM(object)->invokeBbox(i2doc);
599 if (dbox.isEmpty()) {
600 return NULL;
601 }
603 /* Update to renderable state */
604 double sf = 0.8;
605 GdkPixbuf* pixbuf = render_pixbuf(root, sf, dbox, psize);
607 // Create widget
608 GtkWidget *pb = gtk_image_new_from_pixbuf(pixbuf);
610 return pb;
611 }
614 /**
615 * Returns a list of markers in the defs of the given source document as a GSList object
616 * Returns NULL if there are no markers in the document.
617 */
618 GSList *
619 ink_marker_list_get (SPDocument *source)
620 {
621 if (source == NULL)
622 return NULL;
624 GSList *ml = NULL;
625 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS (source);
626 for ( SPObject *child = sp_object_first_child(SP_OBJECT(defs));
627 child != NULL;
628 child = SP_OBJECT_NEXT (child) )
629 {
630 if (SP_IS_MARKER(child)) {
631 ml = g_slist_prepend (ml, child);
632 }
633 }
634 return ml;
635 }
637 #define MARKER_ITEM_MARGIN 0
639 /**
640 * Adds previews of markers in marker_list to the given menu widget
641 */
642 static void
643 sp_marker_menu_build (GtkWidget *m, GSList *marker_list, SPDocument *source, SPDocument *sandbox, gchar *menu_id)
644 {
645 // Do this here, outside of loop, to speed up preview generation:
646 NRArena const *arena = NRArena::create();
647 unsigned const visionkey = sp_item_display_key_new(1);
648 NRArenaItem *root = sp_item_invoke_show( SP_ITEM(SP_DOCUMENT_ROOT (sandbox)), (NRArena *) arena, visionkey, SP_ITEM_SHOW_DISPLAY );
650 for (; marker_list != NULL; marker_list = marker_list->next) {
651 Inkscape::XML::Node *repr = SP_OBJECT_REPR((SPItem *) marker_list->data);
652 GtkWidget *i = gtk_menu_item_new();
653 gtk_widget_show(i);
655 if (repr->attribute("inkscape:stockid"))
656 g_object_set_data (G_OBJECT(i), "stockid", (void *) "true");
657 else
658 g_object_set_data (G_OBJECT(i), "stockid", (void *) "false");
660 gchar const *markid = repr->attribute("id");
661 g_object_set_data (G_OBJECT(i), "marker", (void *) markid);
663 GtkWidget *hb = gtk_hbox_new(FALSE, MARKER_ITEM_MARGIN);
664 gtk_widget_show(hb);
666 // generate preview
667 GtkWidget *prv = sp_marker_prev_new (22, markid, source, sandbox, menu_id, arena, visionkey, root);
668 gtk_widget_show(prv);
669 gtk_box_pack_start(GTK_BOX(hb), prv, FALSE, FALSE, 6);
671 // create label
672 GtkWidget *l = gtk_label_new(repr->attribute("id"));
673 gtk_widget_show(l);
674 gtk_misc_set_alignment(GTK_MISC(l), 0.0, 0.5);
676 gtk_box_pack_start(GTK_BOX(hb), l, TRUE, TRUE, 0);
678 gtk_widget_show(hb);
679 gtk_container_add(GTK_CONTAINER(i), hb);
681 gtk_menu_append(GTK_MENU(m), i);
682 }
683 }
685 /**
686 * sp_marker_list_from_doc()
687 *
688 * \brief Pick up all markers from source, except those that are in
689 * current_doc (if non-NULL), and add items to the m menu
690 *
691 */
692 static void
693 sp_marker_list_from_doc (GtkWidget *m, SPDocument *current_doc, SPDocument *source, SPDocument *markers_doc, SPDocument *sandbox, gchar *menu_id)
694 {
695 GSList *ml = ink_marker_list_get(source);
696 GSList *clean_ml = NULL;
698 // Do this here, outside of loop, to speed up preview generation:
699 /* Create new arena */
700 NRArena const *arena = NRArena::create();
701 /* Create ArenaItem and set transform */
702 unsigned const visionkey = sp_item_display_key_new(1);
703 NRArenaItem *root = sp_item_invoke_show( SP_ITEM(SP_DOCUMENT_ROOT (sandbox)), (NRArena *) arena, visionkey, SP_ITEM_SHOW_DISPLAY );
705 for (; ml != NULL; ml = ml->next) {
706 if (!SP_IS_MARKER(ml->data))
707 continue;
709 /*
710 Bug 980157 wants to have stock markers show up at the top of the dropdown menu
711 Thus we can skip all of this code, which simply looks for duplicate stock markers
713 Inkscape::XML::Node *repr = SP_OBJECT_REPR((SPItem *) ml->data);
714 bool stock_dupe = false;
716 if (repr->attribute("inkscape:stockid")) {
717 GSList * markers_doc_ml = ink_marker_list_get(markers_doc);
718 for (; markers_doc_ml != NULL; markers_doc_ml = markers_doc_ml->next) {
719 const gchar* stockid = SP_OBJECT_REPR(markers_doc_ml->data)->attribute("inkscape:stockid");
720 if (stockid && !strcmp(repr->attribute("inkscape:stockid"), stockid))
721 stock_dupe = true;
722 }
723 }
725 if (stock_dupe) // stock item, dont add to list from current doc
726 continue;
727 */
729 // Add to the list of markers we really do wish to show
730 clean_ml = g_slist_prepend (clean_ml, ml->data);
731 }
732 sp_marker_menu_build (m, clean_ml, source, sandbox, menu_id);
734 g_slist_free (ml);
735 g_slist_free (clean_ml);
736 }
739 /**
740 * Returns a new document containing default start, mid, and end markers.
741 */
742 SPDocument *
743 ink_markers_preview_doc ()
744 {
745 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\">"
746 " <defs id=\"defs\" />"
748 " <g id=\"marker-start\">"
749 " <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:url(#sample);marker-mid:none;marker-end:none\""
750 " d=\"M 12.5,13 L 25,13\" id=\"path1\" />"
751 " <rect style=\"fill:none;stroke:none\" id=\"rect2\""
752 " width=\"25\" height=\"25\" x=\"0\" y=\"0\" />"
753 " </g>"
755 " <g id=\"marker-mid\">"
756 " <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:none;marker-mid:url(#sample);marker-end:none\""
757 " d=\"M 0,113 L 12.5,113 L 25,113\" id=\"path11\" />"
758 " <rect style=\"fill:none;stroke:none\" id=\"rect22\""
759 " width=\"25\" height=\"25\" x=\"0\" y=\"100\" />"
760 " </g>"
762 " <g id=\"marker-end\">"
763 " <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:none;marker-mid:none;marker-end:url(#sample)\""
764 " d=\"M 0,213 L 12.5,213\" id=\"path111\" />"
765 " <rect style=\"fill:none;stroke:none\" id=\"rect222\""
766 " width=\"25\" height=\"25\" x=\"0\" y=\"200\" />"
767 " </g>"
769 "</svg>";
771 return sp_document_new_from_mem (buffer, strlen(buffer), FALSE);
772 }
775 /**
776 * Creates a menu widget to display markers from markers.svg
777 */
778 static GtkWidget *
779 ink_marker_menu( GtkWidget *tbl, gchar *menu_id, SPDocument *sandbox)
780 {
781 SPDesktop *desktop = inkscape_active_desktop();
782 SPDocument *doc = sp_desktop_document(desktop);
783 GtkWidget *mnu = gtk_option_menu_new();
785 /* Create new menu widget */
786 GtkWidget *m = gtk_menu_new();
787 gtk_widget_show(m);
789 g_object_set_data(G_OBJECT(mnu), "updating", (gpointer) FALSE);
791 if (!doc) {
792 GtkWidget *i = gtk_menu_item_new_with_label(_("No document selected"));
793 gtk_widget_show(i);
794 gtk_menu_append(GTK_MENU(m), i);
795 gtk_widget_set_sensitive(mnu, FALSE);
797 } else {
799 // add "None"
800 {
801 GtkWidget *i = gtk_menu_item_new();
802 gtk_widget_show(i);
804 g_object_set_data(G_OBJECT(i), "marker", (void *) "none");
806 GtkWidget *hb = gtk_hbox_new(FALSE, MARKER_ITEM_MARGIN);
807 gtk_widget_show(hb);
809 GtkWidget *l = gtk_label_new( _("None") );
810 gtk_widget_show(l);
811 gtk_misc_set_alignment(GTK_MISC(l), 0.0, 0.5);
813 gtk_box_pack_start(GTK_BOX(hb), l, TRUE, TRUE, 0);
815 gtk_widget_show(hb);
816 gtk_container_add(GTK_CONTAINER(i), hb);
817 gtk_menu_append(GTK_MENU(m), i);
818 }
820 // find and load markers.svg
821 static SPDocument *markers_doc = NULL;
822 char *markers_source = g_build_filename(INKSCAPE_MARKERSDIR, "markers.svg", NULL);
823 if (Inkscape::IO::file_test(markers_source, G_FILE_TEST_IS_REGULAR)) {
824 markers_doc = sp_document_new(markers_source, FALSE);
825 }
826 g_free(markers_source);
828 // suck in from current doc
829 sp_marker_list_from_doc ( m, NULL, doc, markers_doc, sandbox, menu_id );
831 // add separator
832 {
833 GtkWidget *i = gtk_separator_menu_item_new();
834 gtk_widget_show(i);
835 gtk_menu_append(GTK_MENU(m), i);
836 }
838 // suck in from markers.svg
839 if (markers_doc) {
840 sp_document_ensure_up_to_date(doc);
841 sp_marker_list_from_doc ( m, doc, markers_doc, NULL, sandbox, menu_id );
842 }
844 gtk_widget_set_sensitive(mnu, TRUE);
845 }
847 gtk_object_set_data(GTK_OBJECT(mnu), "menu_id", menu_id);
848 gtk_option_menu_set_menu(GTK_OPTION_MENU(mnu), m);
850 /* Set history */
851 gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 0);
853 return mnu;
854 }
857 /**
858 * Handles when user selects one of the markers from the marker menu.
859 * Defines a uri string to refer to it, then applies it to all selected
860 * items in the current desktop.
861 */
862 static void
863 sp_marker_select(GtkOptionMenu *mnu, GtkWidget *spw)
864 {
865 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
866 return;
867 }
869 SPDesktop *desktop = inkscape_active_desktop();
870 SPDocument *document = sp_desktop_document(desktop);
871 if (!document) {
872 return;
873 }
875 /* Get Marker */
876 if (!g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))),
877 "marker"))
878 {
879 return;
880 }
881 gchar *markid = (gchar *) g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))),
882 "marker");
883 gchar *marker = "";
884 if (strcmp(markid, "none")){
885 gchar *stockid = (gchar *) g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))),
886 "stockid");
888 gchar *markurn = markid;
889 if (!strcmp(stockid,"true")) markurn = g_strconcat("urn:inkscape:marker:",markid,NULL);
890 SPObject *mark = get_stock_item(markurn);
891 if (mark) {
892 Inkscape::XML::Node *repr = SP_OBJECT_REPR(mark);
893 marker = g_strconcat("url(#", repr->attribute("id"), ")", NULL);
894 }
895 } else {
896 marker = markid;
897 }
899 SPCSSAttr *css = sp_repr_css_attr_new();
900 gchar *menu_id = (gchar *) g_object_get_data(G_OBJECT(mnu), "menu_id");
901 sp_repr_css_set_property(css, menu_id, marker);
903 Inkscape::Selection *selection = sp_desktop_selection(desktop);
904 GSList const *items = selection->itemList();
905 for (; items != NULL; items = items->next) {
906 SPItem *item = (SPItem *) items->data;
907 if (!SP_IS_SHAPE(item) || SP_IS_RECT(item)) // can't set marker to rect, until it's converted to using <path>
908 continue;
909 Inkscape::XML::Node *selrepr = SP_OBJECT_REPR((SPItem *) items->data);
910 if (selrepr) {
911 sp_repr_css_change_recursive(selrepr, css, "style");
912 }
913 SP_OBJECT(items->data)->requestModified(SP_OBJECT_MODIFIED_FLAG);
914 SP_OBJECT(items->data)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
915 }
917 sp_repr_css_attr_unref(css);
919 sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE,
920 _("Set markers"));
921 }
923 /**
924 * Sets the stroke width units for all selected items.
925 * Also handles absolute and dimensionless units.
926 */
927 static gboolean stroke_width_set_unit(SPUnitSelector *,
928 SPUnit const *old,
929 SPUnit const *new_units,
930 GObject *spw)
931 {
932 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
934 if (!desktop) {
935 return FALSE;
936 }
938 Inkscape::Selection *selection = sp_desktop_selection (desktop);
940 if (selection->isEmpty())
941 return FALSE;
943 GSList const *objects = selection->itemList();
945 if ((old->base == SP_UNIT_ABSOLUTE || old->base == SP_UNIT_DEVICE) &&
946 (new_units->base == SP_UNIT_DIMENSIONLESS)) {
948 /* Absolute to percentage */
949 g_object_set_data (spw, "update", GUINT_TO_POINTER (TRUE));
951 GtkAdjustment *a = GTK_ADJUSTMENT(g_object_get_data (spw, "width"));
952 float w = sp_units_get_pixels (a->value, *old);
954 gdouble average = stroke_average_width (objects);
956 if (average == NR_HUGE || average == 0)
957 return FALSE;
959 gtk_adjustment_set_value (a, 100.0 * w / average);
961 g_object_set_data (spw, "update", GUINT_TO_POINTER (FALSE));
962 return TRUE;
964 } else if ((old->base == SP_UNIT_DIMENSIONLESS) &&
965 (new_units->base == SP_UNIT_ABSOLUTE || new_units->base == SP_UNIT_DEVICE)) {
967 /* Percentage to absolute */
968 g_object_set_data (spw, "update", GUINT_TO_POINTER (TRUE));
970 GtkAdjustment *a = GTK_ADJUSTMENT(g_object_get_data (spw, "width"));
972 gdouble average = stroke_average_width (objects);
974 gtk_adjustment_set_value (a, sp_pixels_get_units (0.01 * a->value * average, *new_units));
976 g_object_set_data (spw, "update", GUINT_TO_POINTER (FALSE));
977 return TRUE;
978 }
980 return FALSE;
981 }
984 /**
985 * \brief Creates a new widget for the line stroke style.
986 *
987 */
988 GtkWidget *
989 sp_stroke_style_line_widget_new(void)
990 {
991 GtkWidget *spw, *f, *t, *hb, *sb, *us, *tb, *ds;
992 GtkObject *a;
994 GtkTooltips *tt = gtk_tooltips_new();
996 spw = sp_widget_new_global(INKSCAPE);
998 f = gtk_hbox_new (FALSE, 0);
999 gtk_widget_show(f);
1000 gtk_container_add(GTK_CONTAINER(spw), f);
1002 t = gtk_table_new(3, 6, FALSE);
1003 gtk_widget_show(t);
1004 gtk_container_set_border_width(GTK_CONTAINER(t), 4);
1005 gtk_table_set_row_spacings(GTK_TABLE(t), 4);
1006 gtk_container_add(GTK_CONTAINER(f), t);
1007 gtk_object_set_data(GTK_OBJECT(spw), "stroke", t);
1009 gint i = 0;
1011 /* Stroke width */
1012 spw_label(t, _("Width:"), 0, i);
1014 hb = spw_hbox(t, 3, 1, i);
1016 // TODO: when this is gtkmmified, use an Inkscape::UI::Widget::ScalarUnit instead of the separate
1017 // spinbutton and unit selector for stroke width. In sp_stroke_style_line_update, use
1018 // setHundredPercent to remember the aeraged width corresponding to 100%. Then the
1019 // stroke_width_set_unit will be removed (because ScalarUnit takes care of conversions itself), and
1020 // with it, the two remaining calls of stroke_average_width, allowing us to get rid of that
1021 // function in desktop-style.
1023 a = gtk_adjustment_new(1.0, 0.0, 1000.0, 0.1, 10.0, 10.0);
1024 gtk_object_set_data(GTK_OBJECT(spw), "width", a);
1025 sb = gtk_spin_button_new(GTK_ADJUSTMENT(a), 0.1, 3);
1026 gtk_tooltips_set_tip(tt, sb, _("Stroke width"), NULL);
1027 gtk_widget_show(sb);
1029 sp_dialog_defocus_on_enter(sb);
1031 gtk_box_pack_start(GTK_BOX(hb), sb, FALSE, FALSE, 0);
1032 us = sp_unit_selector_new(SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE);
1033 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1034 if (desktop)
1035 sp_unit_selector_set_unit (SP_UNIT_SELECTOR(us), sp_desktop_namedview(desktop)->doc_units);
1036 sp_unit_selector_add_unit(SP_UNIT_SELECTOR(us), &sp_unit_get_by_id(SP_UNIT_PERCENT), 0);
1037 g_signal_connect ( G_OBJECT (us), "set_unit", G_CALLBACK (stroke_width_set_unit), spw );
1038 gtk_widget_show(us);
1039 sp_unit_selector_add_adjustment( SP_UNIT_SELECTOR(us), GTK_ADJUSTMENT(a) );
1040 gtk_box_pack_start(GTK_BOX(hb), us, FALSE, FALSE, 0);
1041 gtk_object_set_data(GTK_OBJECT(spw), "units", us);
1043 gtk_signal_connect( GTK_OBJECT(a), "value_changed", GTK_SIGNAL_FUNC(sp_stroke_style_width_changed), spw );
1044 i++;
1046 /* Join type */
1047 // TRANSLATORS: The line join style specifies the shape to be used at the
1048 // corners of paths. It can be "miter", "round" or "bevel".
1049 spw_label(t, _("Join:"), 0, i);
1051 hb = spw_hbox(t, 3, 1, i);
1053 tb = NULL;
1055 tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_JOIN_MITER,
1056 hb, spw, "join", "miter");
1058 // TRANSLATORS: Miter join: joining lines with a sharp (pointed) corner.
1059 // For an example, draw a triangle with a large stroke width and modify the
1060 // "Join" option (in the Fill and Stroke dialog).
1061 gtk_tooltips_set_tip(tt, tb, _("Miter join"), NULL);
1063 tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_JOIN_ROUND,
1064 hb, spw, "join", "round");
1066 // TRANSLATORS: Round join: joining lines with a rounded corner.
1067 // For an example, draw a triangle with a large stroke width and modify the
1068 // "Join" option (in the Fill and Stroke dialog).
1069 gtk_tooltips_set_tip(tt, tb, _("Round join"), NULL);
1071 tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_JOIN_BEVEL,
1072 hb, spw, "join", "bevel");
1074 // TRANSLATORS: Bevel join: joining lines with a blunted (flattened) corner.
1075 // For an example, draw a triangle with a large stroke width and modify the
1076 // "Join" option (in the Fill and Stroke dialog).
1077 gtk_tooltips_set_tip(tt, tb, _("Bevel join"), NULL);
1079 i++;
1081 /* Miterlimit */
1082 // TRANSLATORS: Miter limit: only for "miter join", this limits the length
1083 // of the sharp "spike" when the lines connect at too sharp an angle.
1084 // When two line segments meet at a sharp angle, a miter join results in a
1085 // spike that extends well beyond the connection point. The purpose of the
1086 // miter limit is to cut off such spikes (i.e. convert them into bevels)
1087 // when they become too long.
1088 spw_label(t, _("Miter limit:"), 0, i);
1090 hb = spw_hbox(t, 3, 1, i);
1092 a = gtk_adjustment_new(4.0, 0.0, 100.0, 0.1, 10.0, 10.0);
1093 gtk_object_set_data(GTK_OBJECT(spw), "miterlimit", a);
1095 sb = gtk_spin_button_new(GTK_ADJUSTMENT(a), 0.1, 2);
1096 gtk_tooltips_set_tip(tt, sb, _("Maximum length of the miter (in units of stroke width)"), NULL);
1097 gtk_widget_show(sb);
1098 gtk_object_set_data(GTK_OBJECT(spw), "miterlimit_sb", sb);
1099 sp_dialog_defocus_on_enter(sb);
1101 gtk_box_pack_start(GTK_BOX(hb), sb, FALSE, FALSE, 0);
1103 gtk_signal_connect( GTK_OBJECT(a), "value_changed",
1104 GTK_SIGNAL_FUNC(sp_stroke_style_miterlimit_changed), spw );
1105 i++;
1107 /* Cap type */
1108 // TRANSLATORS: cap type specifies the shape for the ends of lines
1109 spw_label(t, _("Cap:"), 0, i);
1111 hb = spw_hbox(t, 3, 1, i);
1113 tb = NULL;
1115 tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_CAP_BUTT,
1116 hb, spw, "cap", "butt");
1118 // TRANSLATORS: Butt cap: the line shape does not extend beyond the end point
1119 // of the line; the ends of the line are square
1120 gtk_tooltips_set_tip(tt, tb, _("Butt cap"), NULL);
1122 tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_CAP_ROUND,
1123 hb, spw, "cap", "round");
1125 // TRANSLATORS: Round cap: the line shape extends beyond the end point of the
1126 // line; the ends of the line are rounded
1127 gtk_tooltips_set_tip(tt, tb, _("Round cap"), NULL);
1129 tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_CAP_SQUARE,
1130 hb, spw, "cap", "square");
1132 // TRANSLATORS: Square cap: the line shape extends beyond the end point of the
1133 // line; the ends of the line are square
1134 gtk_tooltips_set_tip(tt, tb, _("Square cap"), NULL);
1136 i++;
1139 /* Dash */
1140 spw_label(t, _("Dashes:"), 0, i);
1141 ds = sp_dash_selector_new( inkscape_get_repr( INKSCAPE,
1142 "palette.dashes") );
1144 gtk_widget_show(ds);
1145 gtk_table_attach( GTK_TABLE(t), ds, 1, 4, i, i+1,
1146 (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ),
1147 (GtkAttachOptions)0, 0, 0 );
1148 gtk_object_set_data(GTK_OBJECT(spw), "dash", ds);
1149 gtk_signal_connect( GTK_OBJECT(ds), "changed",
1150 GTK_SIGNAL_FUNC(sp_stroke_style_line_dash_changed),
1151 spw );
1152 i++;
1154 /* Drop down marker selectors*/
1156 // doing this here once, instead of for each preview, to speed things up
1157 SPDocument *sandbox = ink_markers_preview_doc ();
1159 // TRANSLATORS: Path markers are an SVG feature that allows you to attach arbitrary shapes
1160 // (arrowheads, bullets, faces, whatever) to the start, end, or middle nodes of a path.
1161 spw_label(t, _("Start Markers:"), 0, i);
1162 marker_start_menu = ink_marker_menu( spw ,"marker-start", sandbox);
1163 gtk_signal_connect( GTK_OBJECT(marker_start_menu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw );
1164 gtk_widget_show(marker_start_menu);
1165 gtk_table_attach( GTK_TABLE(t), marker_start_menu, 1, 4, i, i+1,
1166 (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ),
1167 (GtkAttachOptions)0, 0, 0 );
1168 gtk_object_set_data(GTK_OBJECT(spw), "start_mark_menu", marker_start_menu);
1170 i++;
1171 spw_label(t, _("Mid Markers:"), 0, i);
1172 marker_mid_menu = ink_marker_menu( spw ,"marker-mid", sandbox);
1173 gtk_signal_connect( GTK_OBJECT(marker_mid_menu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw );
1174 gtk_widget_show(marker_mid_menu);
1175 gtk_table_attach( GTK_TABLE(t), marker_mid_menu, 1, 4, i, i+1,
1176 (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ),
1177 (GtkAttachOptions)0, 0, 0 );
1178 gtk_object_set_data(GTK_OBJECT(spw), "mid_mark_menu", marker_mid_menu);
1180 i++;
1181 spw_label(t, _("End Markers:"), 0, i);
1182 marker_end_menu = ink_marker_menu( spw ,"marker-end", sandbox);
1183 gtk_signal_connect( GTK_OBJECT(marker_end_menu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw );
1184 gtk_widget_show(marker_end_menu);
1185 gtk_table_attach( GTK_TABLE(t), marker_end_menu, 1, 4, i, i+1,
1186 (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ),
1187 (GtkAttachOptions)0, 0, 0 );
1188 gtk_object_set_data(GTK_OBJECT(spw), "end_mark_menu", marker_end_menu);
1190 i++;
1192 gtk_signal_connect( GTK_OBJECT(spw), "construct",
1193 GTK_SIGNAL_FUNC(sp_stroke_style_line_construct),
1194 NULL );
1195 gtk_signal_connect( GTK_OBJECT(spw), "modify_selection",
1196 GTK_SIGNAL_FUNC(sp_stroke_style_line_selection_modified),
1197 NULL );
1198 gtk_signal_connect( GTK_OBJECT(spw), "change_selection",
1199 GTK_SIGNAL_FUNC(sp_stroke_style_line_selection_changed),
1200 NULL );
1202 sp_stroke_style_line_update( SP_WIDGET(spw), desktop ? sp_desktop_selection(desktop) : NULL);
1204 return spw;
1205 }
1208 /**
1209 * Callback for when the stroke style widget is called. It causes
1210 * the stroke line style to be updated.
1211 */
1212 static void
1213 sp_stroke_style_line_construct(SPWidget *spw, gpointer data)
1214 {
1216 #ifdef SP_SS_VERBOSE
1217 g_print( "Stroke style widget constructed: inkscape %p repr %p\n",
1218 spw->inkscape, spw->repr );
1219 #endif
1220 if (spw->inkscape) {
1221 sp_stroke_style_line_update(spw,
1222 ( SP_ACTIVE_DESKTOP
1223 ? sp_desktop_selection(SP_ACTIVE_DESKTOP)
1224 : NULL ));
1225 }
1226 }
1228 /**
1229 * Callback for when stroke style widget is modified.
1230 * Triggers update action.
1231 */
1232 static void
1233 sp_stroke_style_line_selection_modified ( SPWidget *spw,
1234 Inkscape::Selection *selection,
1235 guint flags,
1236 gpointer data )
1237 {
1238 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) {
1239 sp_stroke_style_line_update (spw, selection);
1240 }
1242 }
1244 /**
1245 * Callback for when stroke style widget is changed.
1246 * Triggers update action.
1247 */
1248 static void
1249 sp_stroke_style_line_selection_changed ( SPWidget *spw,
1250 Inkscape::Selection *selection,
1251 gpointer data )
1252 {
1253 sp_stroke_style_line_update (spw, selection);
1254 }
1257 /**
1258 * Sets selector widgets' dash style from an SPStyle object.
1259 */
1260 static void
1261 sp_dash_selector_set_from_style (GtkWidget *dsel, SPStyle *style)
1262 {
1263 if (style->stroke_dash.n_dash > 0) {
1264 double d[64];
1265 int len = MIN(style->stroke_dash.n_dash, 64);
1266 for (int i = 0; i < len; i++) {
1267 if (style->stroke_width.computed != 0)
1268 d[i] = style->stroke_dash.dash[i] / style->stroke_width.computed;
1269 else
1270 d[i] = style->stroke_dash.dash[i]; // is there a better thing to do for stroke_width==0?
1271 }
1272 sp_dash_selector_set_dash(SP_DASH_SELECTOR(dsel), len, d,
1273 style->stroke_width.computed != 0?
1274 style->stroke_dash.offset / style->stroke_width.computed :
1275 style->stroke_dash.offset);
1276 } else {
1277 sp_dash_selector_set_dash(SP_DASH_SELECTOR(dsel), 0, NULL, 0.0);
1278 }
1279 }
1281 /**
1282 * Sets the join type for a line, and updates the stroke style widget's buttons
1283 */
1284 static void
1285 sp_jointype_set (SPWidget *spw, unsigned const jointype)
1286 {
1287 GtkWidget *tb = NULL;
1288 switch (jointype) {
1289 case SP_STROKE_LINEJOIN_MITER:
1290 tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_MITER));
1291 break;
1292 case SP_STROKE_LINEJOIN_ROUND:
1293 tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_ROUND));
1294 break;
1295 case SP_STROKE_LINEJOIN_BEVEL:
1296 tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_BEVEL));
1297 break;
1298 default:
1299 break;
1300 }
1301 sp_stroke_style_set_join_buttons (spw, tb);
1302 }
1304 /**
1305 * Sets the cap type for a line, and updates the stroke style widget's buttons
1306 */
1307 static void
1308 sp_captype_set (SPWidget *spw, unsigned const captype)
1309 {
1310 GtkWidget *tb = NULL;
1311 switch (captype) {
1312 case SP_STROKE_LINECAP_BUTT:
1313 tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_BUTT));
1314 break;
1315 case SP_STROKE_LINECAP_ROUND:
1316 tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_ROUND));
1317 break;
1318 case SP_STROKE_LINECAP_SQUARE:
1319 tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_SQUARE));
1320 break;
1321 default:
1322 break;
1323 }
1324 sp_stroke_style_set_cap_buttons (spw, tb);
1325 }
1327 /**
1328 * Callback for when stroke style widget is updated, including markers, cap type,
1329 * join type, etc.
1330 */
1331 static void
1332 sp_stroke_style_line_update(SPWidget *spw, Inkscape::Selection *sel)
1333 {
1334 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1335 return;
1336 }
1338 gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(TRUE));
1340 GtkWidget *sset = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "stroke"));
1341 GtkObject *width = GTK_OBJECT(gtk_object_get_data(GTK_OBJECT(spw), "width"));
1342 GtkObject *ml = GTK_OBJECT(gtk_object_get_data(GTK_OBJECT(spw), "miterlimit"));
1343 GtkWidget *us = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "units"));
1344 GtkWidget *dsel = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "dash"));
1346 // create temporary style
1347 SPStyle *query = sp_style_new ();
1348 // query into it
1349 int result_sw = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEWIDTH);
1350 int result_ml = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEMITERLIMIT);
1351 int result_cap = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKECAP);
1352 int result_join = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEJOIN);
1354 if (result_sw == QUERY_STYLE_NOTHING) {
1355 /* No objects stroked, set insensitive */
1356 gtk_widget_set_sensitive(sset, FALSE);
1358 gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(FALSE));
1359 return;
1360 } else {
1361 gtk_widget_set_sensitive(sset, TRUE);
1363 SPUnit const *unit = sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us));
1365 if (result_sw == QUERY_STYLE_MULTIPLE_AVERAGED) {
1366 sp_unit_selector_set_unit(SP_UNIT_SELECTOR(us), &sp_unit_get_by_id(SP_UNIT_PERCENT));
1367 } else {
1368 // same width, or only one object; no sense to keep percent, switch to absolute
1369 if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) {
1370 sp_unit_selector_set_unit(SP_UNIT_SELECTOR(us), sp_desktop_namedview(SP_ACTIVE_DESKTOP)->doc_units);
1371 }
1372 }
1374 unit = sp_unit_selector_get_unit (SP_UNIT_SELECTOR (us));
1376 if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) {
1377 double avgwidth = sp_pixels_get_units (query->stroke_width.computed, *unit);
1378 gtk_adjustment_set_value(GTK_ADJUSTMENT(width), avgwidth);
1379 } else {
1380 gtk_adjustment_set_value(GTK_ADJUSTMENT(width), 100);
1381 }
1382 }
1384 if (result_ml != QUERY_STYLE_NOTHING)
1385 gtk_adjustment_set_value(GTK_ADJUSTMENT(ml), query->stroke_miterlimit.value); // TODO: reflect averagedness?
1387 if (result_join != QUERY_STYLE_MULTIPLE_DIFFERENT) {
1388 sp_jointype_set (spw, query->stroke_linejoin.value);
1389 } else {
1390 sp_stroke_style_set_join_buttons(spw, NULL);
1391 }
1393 if (result_cap != QUERY_STYLE_MULTIPLE_DIFFERENT) {
1394 sp_captype_set (spw, query->stroke_linecap.value);
1395 } else {
1396 sp_stroke_style_set_cap_buttons(spw, NULL);
1397 }
1399 g_free (query);
1401 if (!sel || sel->isEmpty())
1402 return;
1404 GSList const *objects = sel->itemList();
1405 SPObject * const object = SP_OBJECT(objects->data);
1406 SPStyle * const style = SP_OBJECT_STYLE(object);
1408 /* Markers */
1409 sp_stroke_style_update_marker_menus(spw, objects); // FIXME: make this desktop query too
1411 /* Dash */
1412 sp_dash_selector_set_from_style (dsel, style); // FIXME: make this desktop query too
1414 gtk_widget_set_sensitive(sset, TRUE);
1416 gtk_object_set_data(GTK_OBJECT(spw), "update",
1417 GINT_TO_POINTER(FALSE));
1418 }
1420 /**
1421 * Sets a line's dash properties in a CSS style object.
1422 */
1423 static void
1424 sp_stroke_style_set_scaled_dash(SPCSSAttr *css,
1425 int ndash, double *dash, double offset,
1426 double scale)
1427 {
1428 if (ndash > 0) {
1429 Inkscape::CSSOStringStream osarray;
1430 for (int i = 0; i < ndash; i++) {
1431 osarray << dash[i] * scale;
1432 if (i < (ndash - 1)) {
1433 osarray << ",";
1434 }
1435 }
1436 sp_repr_css_set_property(css, "stroke-dasharray", osarray.str().c_str());
1438 Inkscape::CSSOStringStream osoffset;
1439 osoffset << offset * scale;
1440 sp_repr_css_set_property(css, "stroke-dashoffset", osoffset.str().c_str());
1441 } else {
1442 sp_repr_css_set_property(css, "stroke-dasharray", "none");
1443 sp_repr_css_set_property(css, "stroke-dashoffset", NULL);
1444 }
1445 }
1447 /**
1448 * Sets line properties like width, dashes, markers, etc. on all currently selected items.
1449 */
1450 static void
1451 sp_stroke_style_scale_line(SPWidget *spw)
1452 {
1453 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1454 return;
1455 }
1457 gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(TRUE));
1459 GtkAdjustment *wadj = GTK_ADJUSTMENT(gtk_object_get_data(GTK_OBJECT(spw), "width"));
1460 SPUnitSelector *us = SP_UNIT_SELECTOR(gtk_object_get_data(GTK_OBJECT(spw), "units"));
1461 SPDashSelector *dsel = SP_DASH_SELECTOR(gtk_object_get_data(GTK_OBJECT(spw), "dash"));
1462 GtkAdjustment *ml = GTK_ADJUSTMENT(gtk_object_get_data(GTK_OBJECT(spw), "miterlimit"));
1464 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1465 SPDocument *document = sp_desktop_document (desktop);
1466 Inkscape::Selection *selection = sp_desktop_selection (desktop);
1468 GSList const *items = selection->itemList();
1470 /* TODO: Create some standardized method */
1471 SPCSSAttr *css = sp_repr_css_attr_new();
1473 if (items) {
1475 double width_typed = wadj->value;
1476 double const miterlimit = ml->value;
1478 SPUnit const *const unit = sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us));
1480 double *dash, offset;
1481 int ndash;
1482 sp_dash_selector_get_dash(dsel, &ndash, &dash, &offset);
1484 for (GSList const *i = items; i != NULL; i = i->next) {
1485 /* Set stroke width */
1486 double width;
1487 if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) {
1488 width = sp_units_get_pixels (width_typed, *unit);
1489 } else { // percentage
1490 gdouble old_w = SP_OBJECT_STYLE (i->data)->stroke_width.computed;
1491 width = old_w * width_typed / 100;
1492 }
1494 {
1495 Inkscape::CSSOStringStream os_width;
1496 os_width << width;
1497 sp_repr_css_set_property(css, "stroke-width", os_width.str().c_str());
1498 }
1500 {
1501 Inkscape::CSSOStringStream os_ml;
1502 os_ml << miterlimit;
1503 sp_repr_css_set_property(css, "stroke-miterlimit", os_ml.str().c_str());
1504 }
1506 /* Set dash */
1507 sp_stroke_style_set_scaled_dash(css, ndash, dash, offset, width);
1509 sp_desktop_apply_css_recursive (SP_OBJECT(i->data), css, true);
1510 }
1512 g_free(dash);
1514 if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) {
1515 // reset to 100 percent
1516 gtk_adjustment_set_value (wadj, 100.0);
1517 }
1519 }
1521 // we have already changed the items, so set style without changing selection
1522 // FIXME: move the above stroke-setting stuff, including percentages, to desktop-style
1523 sp_desktop_set_style (desktop, css, false);
1525 sp_repr_css_attr_unref(css);
1527 sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE,
1528 _("Set stroke style"));
1530 gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(FALSE));
1531 }
1534 /**
1535 * Callback for when the stroke style's width changes.
1536 * Causes all line styles to be applied to all selected items.
1537 */
1538 static void
1539 sp_stroke_style_width_changed(GtkAdjustment *adj, SPWidget *spw)
1540 {
1541 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1542 return;
1543 }
1545 sp_stroke_style_scale_line(spw);
1546 }
1548 /**
1549 * Callback for when the stroke style's miterlimit changes.
1550 * Causes all line styles to be applied to all selected items.
1551 */
1552 static void
1553 sp_stroke_style_miterlimit_changed(GtkAdjustment *adj, SPWidget *spw)
1554 {
1555 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1556 return;
1557 }
1559 sp_stroke_style_scale_line(spw);
1560 }
1562 /**
1563 * Callback for when the stroke style's dash changes.
1564 * Causes all line styles to be applied to all selected items.
1565 */
1566 static void
1567 sp_stroke_style_line_dash_changed(SPDashSelector *dsel, SPWidget *spw)
1568 {
1569 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1570 return;
1571 }
1573 sp_stroke_style_scale_line(spw);
1574 }
1578 /**
1579 * \brief This routine handles toggle events for buttons in the stroke style
1580 * dialog.
1581 * When activated, this routine gets the data for the various widgets, and then
1582 * calls the respective routines to update css properties, etc.
1583 *
1584 */
1585 static void
1586 sp_stroke_style_any_toggled(GtkToggleButton *tb, SPWidget *spw)
1587 {
1588 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1589 return;
1590 }
1592 if (gtk_toggle_button_get_active(tb)) {
1594 gchar const *join
1595 = static_cast<gchar const *>(gtk_object_get_data(GTK_OBJECT(tb), "join"));
1596 gchar const *cap
1597 = static_cast<gchar const *>(gtk_object_get_data(GTK_OBJECT(tb), "cap"));
1599 if (join) {
1600 GtkWidget *ml = GTK_WIDGET(g_object_get_data(G_OBJECT(spw), "miterlimit_sb"));
1601 gtk_widget_set_sensitive (ml, !strcmp(join, "miter"));
1602 }
1604 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1606 /* TODO: Create some standardized method */
1607 SPCSSAttr *css = sp_repr_css_attr_new();
1609 if (join) {
1610 sp_repr_css_set_property(css, "stroke-linejoin", join);
1612 sp_desktop_set_style (desktop, css);
1614 sp_stroke_style_set_join_buttons(spw, GTK_WIDGET(tb));
1615 } else if (cap) {
1616 sp_repr_css_set_property(css, "stroke-linecap", cap);
1618 sp_desktop_set_style (desktop, css);
1620 sp_stroke_style_set_cap_buttons(spw, GTK_WIDGET(tb));
1621 }
1623 sp_repr_css_attr_unref(css);
1625 sp_document_done(sp_desktop_document(desktop), SP_VERB_DIALOG_FILL_STROKE,
1626 _("Set stroke style"));
1627 }
1628 }
1631 /**
1632 * Updates the join style toggle buttons
1633 */
1634 static void
1635 sp_stroke_style_set_join_buttons(SPWidget *spw, GtkWidget *active)
1636 {
1637 GtkWidget *tb;
1639 tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1640 INKSCAPE_STOCK_JOIN_MITER) );
1641 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1643 GtkWidget *ml = GTK_WIDGET(g_object_get_data(G_OBJECT(spw), "miterlimit_sb"));
1644 gtk_widget_set_sensitive(ml, (active == tb));
1646 tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1647 INKSCAPE_STOCK_JOIN_ROUND) );
1648 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1649 tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1650 INKSCAPE_STOCK_JOIN_BEVEL) );
1651 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1652 }
1656 /**
1657 * Updates the cap style toggle buttons
1658 */
1659 static void
1660 sp_stroke_style_set_cap_buttons(SPWidget *spw, GtkWidget *active)
1661 {
1662 GtkWidget *tb;
1664 tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1665 INKSCAPE_STOCK_CAP_BUTT));
1666 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1667 tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1668 INKSCAPE_STOCK_CAP_ROUND) );
1669 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1670 tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1671 INKSCAPE_STOCK_CAP_SQUARE) );
1672 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1673 }
1675 /**
1676 * Sets the current marker in the marker menu.
1677 */
1678 static void
1679 ink_marker_menu_set_current(SPObject *marker, GtkOptionMenu *mnu)
1680 {
1681 gtk_object_set_data(GTK_OBJECT(mnu), "update", GINT_TO_POINTER(TRUE));
1683 GtkMenu *m = GTK_MENU(gtk_option_menu_get_menu(mnu));
1684 if (marker != NULL) {
1685 bool mark_is_stock = false;
1686 if (SP_OBJECT_REPR(marker)->attribute("inkscape:stockid"))
1687 mark_is_stock = true;
1689 gchar *markname;
1690 if (mark_is_stock)
1691 markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("inkscape:stockid"));
1692 else
1693 markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("id"));
1695 int markpos = 0;
1696 GList *kids = GTK_MENU_SHELL(m)->children;
1697 int i = 0;
1698 for (; kids != NULL; kids = kids->next) {
1699 gchar *mark = (gchar *) g_object_get_data(G_OBJECT(kids->data), "marker");
1700 if ( mark && strcmp(mark, markname) == 0 ) {
1701 if ( mark_is_stock && !strcmp((gchar *) g_object_get_data(G_OBJECT(kids->data), "stockid"), "true"))
1702 markpos = i;
1703 if ( !mark_is_stock && !strcmp((gchar *) g_object_get_data(G_OBJECT(kids->data), "stockid"), "false"))
1704 markpos = i;
1705 }
1706 i++;
1707 }
1708 gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), markpos);
1710 g_free (markname);
1711 }
1712 else {
1713 gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 0);
1714 }
1715 gtk_object_set_data(GTK_OBJECT(mnu), "update", GINT_TO_POINTER(FALSE));
1716 }
1718 /**
1719 * Updates the marker menus to highlight the appropriate marker and scroll to
1720 * that marker.
1721 */
1722 static void
1723 sp_stroke_style_update_marker_menus( SPWidget *spw,
1724 GSList const *objects)
1725 {
1726 struct { char const *key; int loc; } const keyloc[] = {
1727 { "start_mark_menu", SP_MARKER_LOC_START },
1728 { "mid_mark_menu", SP_MARKER_LOC_MID },
1729 { "end_mark_menu", SP_MARKER_LOC_END }
1730 };
1732 bool all_texts = true;
1733 for (GSList *i = (GSList *) objects; i != NULL; i = i->next) {
1734 if (!SP_IS_TEXT (i->data)) {
1735 all_texts = false;
1736 }
1737 }
1739 for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) {
1740 GtkOptionMenu *mnu = (GtkOptionMenu *) g_object_get_data(G_OBJECT(spw), keyloc[i].key);
1741 if (all_texts) {
1742 // Per SVG spec, text objects cannot have markers; disable menus if only texts are selected
1743 gtk_widget_set_sensitive (GTK_WIDGET(mnu), FALSE);
1744 } else {
1745 gtk_widget_set_sensitive (GTK_WIDGET(mnu), TRUE);
1746 }
1747 }
1749 // We show markers of the first object in the list only
1750 // FIXME: use the first in the list that has the marker of each type, if any
1751 SPObject *object = SP_OBJECT(objects->data);
1753 for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) {
1754 // For all three marker types,
1756 // find the corresponding menu
1757 GtkOptionMenu *mnu = (GtkOptionMenu *) g_object_get_data(G_OBJECT(spw), keyloc[i].key);
1759 // Quit if we're in update state
1760 if (gtk_object_get_data(GTK_OBJECT(mnu), "update")) {
1761 return;
1762 }
1764 if (object->style->marker[keyloc[i].loc].value != NULL && !all_texts) {
1765 // If the object has this type of markers,
1767 // Extract the name of the marker that the object uses
1768 SPObject *marker = ink_extract_marker_name(object->style->marker[keyloc[i].loc].value);
1769 // Scroll the menu to that marker
1770 ink_marker_menu_set_current (marker, mnu);
1772 } else {
1773 gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 0);
1774 }
1775 }
1776 }
1779 /**
1780 * Extract the actual name of the link
1781 * e.g. get mTriangle from url(#mTriangle).
1782 * \return Buffer containing the actual name, allocated from GLib;
1783 * the caller should free the buffer when they no longer need it.
1784 */
1785 static SPObject*
1786 ink_extract_marker_name(gchar const *n)
1787 {
1788 gchar const *p = n;
1789 while (*p != '\0' && *p != '#') {
1790 p++;
1791 }
1793 if (*p == '\0' || p[1] == '\0') {
1794 return NULL;
1795 }
1797 p++;
1798 int c = 0;
1799 while (p[c] != '\0' && p[c] != ')') {
1800 c++;
1801 }
1803 if (p[c] == '\0') {
1804 return NULL;
1805 }
1807 gchar* b = g_strdup(p);
1808 b[c] = '\0';
1811 SPDesktop *desktop = inkscape_active_desktop();
1812 SPDocument *doc = sp_desktop_document(desktop);
1813 SPObject *marker = doc->getObjectById(b);
1814 return marker;
1815 }
1818 /*
1819 Local Variables:
1820 mode:c++
1821 c-file-style:"stroustrup"
1822 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1823 indent-tabs-mode:nil
1824 fill-column:99
1825 End:
1826 */
1827 // vim: filetype=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :