Code

Partial fix for bug 980157.
[inkscape.git] / src / dialogs / stroke-style.cpp
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 /**
78  * Create the stroke style widget, and hook up all the signals.
79  */
80 GtkWidget *
81 sp_stroke_style_paint_widget_new(void)
82 {
83     GtkWidget *spw, *psel;
85     spw = sp_widget_new_global(INKSCAPE);
87     psel = sp_paint_selector_new(false); // without fillrule selector
88     gtk_widget_show(psel);
89     gtk_container_add(GTK_CONTAINER(spw), psel);
90     gtk_object_set_data(GTK_OBJECT(spw), "paint-selector", psel);
92     gtk_signal_connect(GTK_OBJECT(spw), "construct",
93                        GTK_SIGNAL_FUNC(sp_stroke_style_paint_construct),
94                        psel);
95     gtk_signal_connect(GTK_OBJECT(spw), "modify_selection",
96                        GTK_SIGNAL_FUNC(sp_stroke_style_paint_selection_modified),
97                        psel);
98     gtk_signal_connect(GTK_OBJECT(spw), "change_selection",
99                        GTK_SIGNAL_FUNC(sp_stroke_style_paint_selection_changed),
100                        psel);
102     g_signal_connect (INKSCAPE, "change_subselection", G_CALLBACK (sp_stroke_style_widget_change_subselection), spw);
104     gtk_signal_connect(GTK_OBJECT(psel), "mode_changed",
105                        GTK_SIGNAL_FUNC(sp_stroke_style_paint_mode_changed),
106                        spw);
107     gtk_signal_connect(GTK_OBJECT(psel), "dragged",
108                        GTK_SIGNAL_FUNC(sp_stroke_style_paint_dragged),
109                        spw);
110     gtk_signal_connect(GTK_OBJECT(psel), "changed",
111                        GTK_SIGNAL_FUNC(sp_stroke_style_paint_changed),
112                        spw);
114     sp_stroke_style_paint_update (SP_WIDGET(spw));
115     return spw;
118 /**
119  * On construction, simply does an update of the stroke style paint object.
120  */
121 static void
122 sp_stroke_style_paint_construct(SPWidget *spw, SPPaintSelector *psel)
124 #ifdef SP_SS_VERBOSE
125     g_print( "Stroke style widget constructed: inkscape %p repr %p\n",
126              spw->inkscape, spw->repr );
127 #endif
128     if (spw->inkscape) {
129         sp_stroke_style_paint_update (spw);
130     } 
133 /**
134  * On signal modified, invokes an update of the stroke style paint object.
135  */
136 static void
137 sp_stroke_style_paint_selection_modified ( SPWidget *spw,
138                                         Inkscape::Selection *selection,
139                                         guint flags,
140                                         SPPaintSelector *psel)
142     if (flags & ( SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG |
143                   SP_OBJECT_STYLE_MODIFIED_FLAG) ) {
144         sp_stroke_style_paint_update(spw);
145     }
149 /**
150  * On signal selection changed, invokes an update of the stroke style paint object.
151  */
152 static void
153 sp_stroke_style_paint_selection_changed ( SPWidget *spw,
154                                         Inkscape::Selection *selection,
155                                         SPPaintSelector *psel )
157     sp_stroke_style_paint_update (spw);
161 /**
162  * On signal change subselection, invoke an update of the stroke style widget.
163  */
164 static void
165 sp_stroke_style_widget_change_subselection ( Inkscape::Application *inkscape, 
166                                         SPDesktop *desktop,
167                                         SPWidget *spw )
169     sp_stroke_style_paint_update (spw);
172 /**
173  * Gets the active stroke style property, then sets the appropriate color, alpha, gradient,
174  * pattern, etc. for the paint-selector.
175  */
176 static void
177 sp_stroke_style_paint_update (SPWidget *spw)
179     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
180         return;
181     }
183     gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(TRUE));
185     SPPaintSelector *psel = SP_PAINT_SELECTOR(gtk_object_get_data(GTK_OBJECT(spw), "paint-selector"));
187     // create temporary style
188     SPStyle *query = sp_style_new ();
189     // query into it
190     int result = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKE); 
192     switch (result) {
193         case QUERY_STYLE_NOTHING:
194         {
195             /* No paint at all */
196             sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_EMPTY);
197             break;
198         }
200         case QUERY_STYLE_SINGLE:
201         case QUERY_STYLE_MULTIPLE_AVERAGED: // TODO: treat this slightly differently, e.g. display "averaged" somewhere in paint selector
202         case QUERY_STYLE_MULTIPLE_SAME:
203         {
204             SPPaintSelectorMode pselmode = sp_style_determine_paint_selector_mode (query, false);
205             sp_paint_selector_set_mode (psel, pselmode);
207             if (query->stroke.set && query->stroke.type == SP_PAINT_TYPE_COLOR) {
208                 gfloat d[3];
209                 sp_color_get_rgb_floatv (&query->stroke.value.color, d);
210                 SPColor color;
211                 sp_color_set_rgb_float (&color, d[0], d[1], d[2]);
212                 sp_paint_selector_set_color_alpha (psel, &color, SP_SCALE24_TO_FLOAT (query->stroke_opacity.value));
214             } else if (query->stroke.set && query->stroke.type == SP_PAINT_TYPE_PAINTSERVER) {
216                 SPPaintServer *server = SP_STYLE_STROKE_SERVER (query);
218                 if (SP_IS_LINEARGRADIENT (server)) {
219                     SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE);
220                     sp_paint_selector_set_gradient_linear (psel, vector);
222                     SPLinearGradient *lg = SP_LINEARGRADIENT (server);
223                     sp_paint_selector_set_gradient_properties (psel,
224                                                        SP_GRADIENT_UNITS (lg),
225                                                        SP_GRADIENT_SPREAD (lg));
226                 } else if (SP_IS_RADIALGRADIENT (server)) {
227                     SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE);
228                     sp_paint_selector_set_gradient_radial (psel, vector);
230                     SPRadialGradient *rg = SP_RADIALGRADIENT (server);
231                     sp_paint_selector_set_gradient_properties (psel,
232                                                        SP_GRADIENT_UNITS (rg),
233                                                        SP_GRADIENT_SPREAD (rg));
234                 } else if (SP_IS_PATTERN (server)) {
235                     SPPattern *pat = pattern_getroot (SP_PATTERN (server));
236                     sp_update_pattern_list (psel, pat);
237                 }
238             }
239             break;
240         }
242         case QUERY_STYLE_MULTIPLE_DIFFERENT:
243         {
244             sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_MULTIPLE);
245             break;
246         }
247     }
249     g_free (query);
251     gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(FALSE));
254 /**
255  * When the mode is changed, invoke a regular changed handler.
256  */
257 static void
258 sp_stroke_style_paint_mode_changed( SPPaintSelector *psel,
259                                     SPPaintSelectorMode mode,
260                                     SPWidget *spw )
262     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
263         return;
264     }
266     /* TODO: Does this work?
267      * Not really, here we have to get old color back from object
268      * Instead of relying on paint widget having meaningful colors set
269      */
270     sp_stroke_style_paint_changed(psel, spw);
273 static gchar *undo_label_1 = "stroke:flatcolor:1";
274 static gchar *undo_label_2 = "stroke:flatcolor:2";
275 static gchar *undo_label = undo_label_1;
277 /**
278  * When a drag callback occurs on a paint selector object, if it is a RGB or CMYK 
279  * color mode, then set the stroke opacity to psel's flat color.
280  */
281 static void
282 sp_stroke_style_paint_dragged(SPPaintSelector *psel, SPWidget *spw)
284     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
285         return;
286     }
288     switch (psel->mode) {
289         case SP_PAINT_SELECTOR_MODE_COLOR_RGB:
290         case SP_PAINT_SELECTOR_MODE_COLOR_CMYK:
291         {
292             sp_paint_selector_set_flat_color (psel, SP_ACTIVE_DESKTOP, "stroke", "stroke-opacity");
293             sp_document_maybe_done (sp_desktop_document(SP_ACTIVE_DESKTOP), undo_label, SP_VERB_DIALOG_FILL_STROKE, 
294                                     _("Set stroke color"));
295             break;
296         }
298         default:
299             g_warning( "file %s: line %d: Paint %d should not emit 'dragged'",
300                        __FILE__, __LINE__, psel->mode);
301             break;
302     }
305 /**
306  * When the stroke style's paint settings change, this handler updates the
307  * repr's stroke css style and applies the style to relevant drawing items.
308  */
309 static void
310 sp_stroke_style_paint_changed(SPPaintSelector *psel, SPWidget *spw)
312     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
313         return;
314     }
315     g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE));
317     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
318     SPDocument *document = sp_desktop_document (desktop);
319     Inkscape::Selection *selection = sp_desktop_selection (desktop);
321     GSList const *items = selection->itemList();
323     switch (psel->mode) {
324         case SP_PAINT_SELECTOR_MODE_EMPTY:
325             // This should not happen.
326             g_warning ( "file %s: line %d: Paint %d should not emit 'changed'",
327                         __FILE__, __LINE__, psel->mode);
328             break;
329         case SP_PAINT_SELECTOR_MODE_MULTIPLE:
330             // This happens when you switch multiple objects with different gradients to flat color;
331             // nothing to do here.
332             break;
334         case SP_PAINT_SELECTOR_MODE_NONE:
335         {
336             SPCSSAttr *css = sp_repr_css_attr_new();
337             sp_repr_css_set_property(css, "stroke", "none");
339             sp_desktop_set_style (desktop, css);
341             sp_repr_css_attr_unref(css);
343             sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE, 
344                              _("Remove stroke"));
345             break;
346         }
348         case SP_PAINT_SELECTOR_MODE_COLOR_RGB:
349         case SP_PAINT_SELECTOR_MODE_COLOR_CMYK:
350         {
351             sp_paint_selector_set_flat_color (psel, desktop, "stroke", "stroke-opacity");
352             sp_document_maybe_done (sp_desktop_document(desktop), undo_label, SP_VERB_DIALOG_FILL_STROKE, 
353                                     _("Set stroke color"));
355             // on release, toggle undo_label so that the next drag will not be lumped with this one
356             if (undo_label == undo_label_1)
357                 undo_label = undo_label_2;
358             else 
359                 undo_label = undo_label_1;
361             break;
362         }
364         case SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR:
365         case SP_PAINT_SELECTOR_MODE_GRADIENT_RADIAL:
366             if (items) {
367                 SPGradientType const gradient_type = ( psel->mode == SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR
368                                                        ? SP_GRADIENT_TYPE_LINEAR
369                                                        : SP_GRADIENT_TYPE_RADIAL );
370                 SPGradient *vector = sp_paint_selector_get_gradient_vector(psel);
371                 if (!vector) {
372                     /* No vector in paint selector should mean that we just changed mode */
374                     SPStyle *query = sp_style_new ();
375                     int result = objects_query_fillstroke ((GSList *) items, query, false); 
376                     guint32 common_rgb = 0;
377                     if (result == QUERY_STYLE_MULTIPLE_SAME) {
378                         if (query->fill.type != SP_PAINT_TYPE_COLOR) {
379                             common_rgb = sp_desktop_get_color(desktop, false);
380                         } else {
381                             common_rgb = sp_color_get_rgba32_ualpha(&query->stroke.value.color, 0xff);
382                         }
383                         vector = sp_document_default_gradient_vector(document, common_rgb);
384                     }
385                     g_free (query);
387                     for (GSList const *i = items; i != NULL; i = i->next) {
388                         if (!vector) {
389                             sp_item_set_gradient(SP_ITEM(i->data), 
390                                                  sp_gradient_vector_for_object(document, desktop, SP_OBJECT(i->data), false),
391                                                  gradient_type, false);
392                         } else {
393                             sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, false);
394                         }
395                     }
396                 } else {
397                     vector = sp_gradient_ensure_vector_normalized(vector);
398                     for (GSList const *i = items; i != NULL; i = i->next) {
399                         SPGradient *gr = sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, false);
400                         sp_gradient_selector_attrs_to_gradient(gr, psel);
401                     }
402                 }
404                 sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE, 
405                                  _("Set gradient on stroke"));
406             }
407             break;
409         case SP_PAINT_SELECTOR_MODE_PATTERN:
411             if (items) {
413                 SPPattern *pattern = sp_paint_selector_get_pattern (psel);
414                 if (!pattern) {
416                     /* No Pattern in paint selector should mean that we just
417                      * changed mode - dont do jack.
418                      */
420                 } else {
421                     Inkscape::XML::Node *patrepr = SP_OBJECT_REPR(pattern);
422                     SPCSSAttr *css = sp_repr_css_attr_new ();
423                     gchar *urltext = g_strdup_printf ("url(#%s)", patrepr->attribute("id"));
424                     sp_repr_css_set_property (css, "stroke", urltext);
426                     for (GSList const *i = items; i != NULL; i = i->next) {
427                          Inkscape::XML::Node *selrepr = SP_OBJECT_REPR (i->data);
428                          SPObject *selobj = SP_OBJECT (i->data);
429                          if (!selrepr)
430                              continue;
432                          SPStyle *style = SP_OBJECT_STYLE (selobj);
433                          if (style && style->stroke.type == SP_PAINT_TYPE_PAINTSERVER) {
434                              SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (selobj);
435                              if (SP_IS_PATTERN (server) && pattern_getroot (SP_PATTERN(server)) == pattern)
436                                  // only if this object's pattern is not rooted in our selected pattern, apply
437                                  continue;
438                          }
440                          sp_repr_css_change_recursive (selrepr, css, "style");
441                      }
443                     sp_repr_css_attr_unref (css);
444                     g_free (urltext);
446                 } // end if
448                 sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE, 
449                                   _("Set pattern on stroke"));
450             } // end if
452             break;
454         case SP_PAINT_SELECTOR_MODE_UNSET:
455             if (items) {
456                     SPCSSAttr *css = sp_repr_css_attr_new ();
457                     sp_repr_css_unset_property (css, "stroke");
459                     sp_desktop_set_style (desktop, css);
460                     sp_repr_css_attr_unref (css);
462                     sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE, 
463                                       _("Unset stroke"));
464             }
465             break;
467         default:
468             g_warning( "file %s: line %d: Paint selector should not be in "
469                        "mode %d",
470                        __FILE__, __LINE__,
471                        psel->mode );
472             break;
473     }
475     g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE));
482 /* Line */
484 static void sp_stroke_style_line_construct(SPWidget *spw, gpointer data);
485 static void sp_stroke_style_line_selection_modified (SPWidget *spw,
486                                                   Inkscape::Selection *selection,
487                                                   guint flags,
488                                                   gpointer data);
490 static void sp_stroke_style_line_selection_changed (SPWidget *spw,
491                                                    Inkscape::Selection *selection,
492                                                    gpointer data );
494 static void sp_stroke_style_line_update(SPWidget *spw, Inkscape::Selection *sel);
496 static void sp_stroke_style_set_join_buttons(SPWidget *spw,
497                                              GtkWidget *active);
499 static void sp_stroke_style_set_cap_buttons(SPWidget *spw,
500                                             GtkWidget *active);
502 static void sp_stroke_style_width_changed(GtkAdjustment *adj, SPWidget *spw);
503 static void sp_stroke_style_miterlimit_changed(GtkAdjustment *adj, SPWidget *spw);
504 static void sp_stroke_style_any_toggled(GtkToggleButton *tb, SPWidget *spw);
505 static void sp_stroke_style_line_dash_changed(SPDashSelector *dsel,
506                                               SPWidget *spw);
508 static void sp_stroke_style_update_marker_menus(SPWidget *spw, GSList const *objects);
510 static SPObject *ink_extract_marker_name(gchar const *n);
513 /**
514  * Helper function for creating radio buttons.  This should probably be re-thought out
515  * when reimplementing this with Gtkmm.
516  */
517 static GtkWidget *
518 sp_stroke_radio_button(GtkWidget *tb, char const *icon,
519                        GtkWidget *hb, GtkWidget *spw,
520                        gchar const *key, gchar const *data)
522     g_assert(icon != NULL);
523     g_assert(hb  != NULL);
524     g_assert(spw != NULL);
526     if (tb == NULL) {
527         tb = gtk_radio_button_new(NULL);
528     } else {
529         tb = gtk_radio_button_new(gtk_radio_button_group(GTK_RADIO_BUTTON(tb)) );
530     }
532     gtk_widget_show(tb);
533     gtk_toggle_button_set_mode(GTK_TOGGLE_BUTTON(tb), FALSE);
534     gtk_box_pack_start(GTK_BOX(hb), tb, FALSE, FALSE, 0);
535     gtk_object_set_data(GTK_OBJECT(spw), icon, tb);
536     gtk_object_set_data(GTK_OBJECT(tb), key, (gpointer*)data);
537     gtk_signal_connect(GTK_OBJECT(tb), "toggled",
538                        GTK_SIGNAL_FUNC(sp_stroke_style_any_toggled),
539                        spw);
540     GtkWidget *px = sp_icon_new(Inkscape::ICON_SIZE_LARGE_TOOLBAR, icon);
541     g_assert(px != NULL);
542     gtk_widget_show(px);
543     gtk_container_add(GTK_CONTAINER(tb), px);
545     return tb;
549 /**
550  * Creates a copy of the marker named mname, determines its visible and renderable
551  * area in menu_id's bounding box, and then renders it.  This allows us to fill in
552  * preview images of each marker in the marker menu.
553  */
554 static GtkWidget *
555 sp_marker_prev_new(unsigned psize, gchar const *mname,
556                    SPDocument *source, SPDocument *sandbox,
557                    gchar *menu_id, NRArena const *arena, unsigned visionkey, NRArenaItem *root)
559     // Retrieve the marker named 'mname' from the source SVG document
560     SPObject const *marker = source->getObjectById(mname);
561     if (marker == NULL)
562         return NULL;
564     // Create a copy repr of the marker with id="sample"
565     Inkscape::XML::Node *mrepr = SP_OBJECT_REPR (marker)->duplicate();
566     mrepr->setAttribute("id", "sample");
568     // Replace the old sample in the sandbox by the new one
569     Inkscape::XML::Node *defsrepr = SP_OBJECT_REPR (sandbox->getObjectById("defs"));
570     SPObject *oldmarker = sandbox->getObjectById("sample");
571     if (oldmarker)
572         oldmarker->deleteObject(false);
573     defsrepr->appendChild(mrepr);
574     Inkscape::GC::release(mrepr);
576 // Uncomment this to get the sandbox documents saved (useful for debugging)
577     //FILE *fp = fopen (g_strconcat(mname, ".svg", NULL), "w");
578     //sp_repr_save_stream (sp_document_repr_doc (sandbox), fp);
579     //fclose (fp);
581     // object to render; note that the id is the same as that of the menu we're building
582     SPObject *object = sandbox->getObjectById(menu_id);
583     sp_document_root (sandbox)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
584     sp_document_ensure_up_to_date(sandbox);
586     if (object == NULL || !SP_IS_ITEM(object))
587         return NULL; // sandbox broken?
589     // Find object's bbox in document
590     NR::Matrix const i2doc(sp_item_i2doc_affine(SP_ITEM(object)));
592     NR::Rect const dbox = SP_ITEM(object)->invokeBbox(i2doc);
594     if (dbox.isEmpty()) {
595         return NULL;
596     }
598     /* Update to renderable state */
599     double sf = 0.8;
600     GdkPixbuf* pixbuf = render_pixbuf(root, sf, dbox, psize);
602     // Create widget
603     GtkWidget *pb = gtk_image_new_from_pixbuf(get_pixbuf(pixbuf));
605     return pb;
609 /**
610  *  Returns a list of markers in the defs of the given source document as a GSList object
611  *  Returns NULL if there are no markers in the document.
612  */
613 GSList *
614 ink_marker_list_get (SPDocument *source)
616     if (source == NULL)
617         return NULL;
619     GSList *ml   = NULL;
620     SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS (source);
621     for ( SPObject *child = sp_object_first_child(SP_OBJECT(defs));
622           child != NULL;
623           child = SP_OBJECT_NEXT (child) )
624     {
625         if (SP_IS_MARKER(child)) {
626             ml = g_slist_prepend (ml, child);
627         }
628     }
629     return ml;
632 #define MARKER_ITEM_MARGIN 0
634 /**
635  * Adds previews of markers in marker_list to the given menu widget
636  */
637 static void
638 sp_marker_menu_build (GtkWidget *m, GSList *marker_list, SPDocument *source, SPDocument *sandbox, gchar *menu_id)
640     // Do this here, outside of loop, to speed up preview generation:
641     NRArena const *arena = NRArena::create();
642     unsigned const visionkey = sp_item_display_key_new(1);
643     NRArenaItem *root =  sp_item_invoke_show( SP_ITEM(SP_DOCUMENT_ROOT (sandbox)), (NRArena *) arena, visionkey, SP_ITEM_SHOW_DISPLAY );
645     for (; marker_list != NULL; marker_list = marker_list->next) {
646         Inkscape::XML::Node *repr = SP_OBJECT_REPR((SPItem *) marker_list->data);
647         GtkWidget *i = gtk_menu_item_new();
648         gtk_widget_show(i);
650         if (repr->attribute("inkscape:stockid"))
651             g_object_set_data (G_OBJECT(i), "stockid", (void *) "true");
652         else
653             g_object_set_data (G_OBJECT(i), "stockid", (void *) "false");
655         gchar const *markid = repr->attribute("id");
656         g_object_set_data (G_OBJECT(i), "marker", (void *) markid);
658         GtkWidget *hb = gtk_hbox_new(FALSE, MARKER_ITEM_MARGIN);
659         gtk_widget_show(hb);
661         // generate preview
662         GtkWidget *prv = sp_marker_prev_new (22, markid, source, sandbox, menu_id, arena, visionkey, root);
663         gtk_widget_show(prv);
664         gtk_box_pack_start(GTK_BOX(hb), prv, FALSE, FALSE, 6);
666         // create label
667         GtkWidget *l = gtk_label_new(repr->attribute("id"));
668         gtk_widget_show(l);
669         gtk_misc_set_alignment(GTK_MISC(l), 0.0, 0.5);
671         gtk_box_pack_start(GTK_BOX(hb), l, TRUE, TRUE, 0);
673         gtk_widget_show(hb);
674         gtk_container_add(GTK_CONTAINER(i), hb);
676         gtk_menu_append(GTK_MENU(m), i);
677     }
680 /**
681  * sp_marker_list_from_doc()
682  *
683  * \brief Pick up all markers from source, except those that are in
684  * current_doc (if non-NULL), and add items to the m menu
685  *
686  */
687 static void
688 sp_marker_list_from_doc (GtkWidget *m, SPDocument *current_doc, SPDocument *source, SPDocument *markers_doc, SPDocument *sandbox, gchar *menu_id)
690     GSList *ml = ink_marker_list_get(source);
691     GSList *clean_ml = NULL;
693     // Do this here, outside of loop, to speed up preview generation:
694     /* Create new arena */
695     NRArena const *arena = NRArena::create();
696     /* Create ArenaItem and set transform */
697     unsigned const visionkey = sp_item_display_key_new(1);
698     NRArenaItem *root =  sp_item_invoke_show( SP_ITEM(SP_DOCUMENT_ROOT (sandbox)), (NRArena *) arena, visionkey, SP_ITEM_SHOW_DISPLAY );
700     for (; ml != NULL; ml = ml->next) {
701         if (!SP_IS_MARKER(ml->data))
702             continue;
704 /*
705   Bug 980157 wants to have stock markers show up at the top of the dropdown menu
706   Thus we can skip all of this code, which simply looks for duplicate stock markers
708         Inkscape::XML::Node *repr = SP_OBJECT_REPR((SPItem *) ml->data);
709         bool stock_dupe = false;
711         if (repr->attribute("inkscape:stockid")) {
712             GSList * markers_doc_ml = ink_marker_list_get(markers_doc);
713             for (; markers_doc_ml != NULL; markers_doc_ml = markers_doc_ml->next) {
714                 const gchar* stockid = SP_OBJECT_REPR(markers_doc_ml->data)->attribute("inkscape:stockid");
715                 if (stockid && !strcmp(repr->attribute("inkscape:stockid"), stockid))
716                     stock_dupe = true;
717             }
718         }
720         if (stock_dupe) // stock item, dont add to list from current doc
721             continue;
722 */
724         // Add to the list of markers we really do wish to show
725         clean_ml = g_slist_prepend (clean_ml, ml->data);
726     }
727     sp_marker_menu_build (m, clean_ml, source, sandbox, menu_id);
729     g_slist_free (ml);
730     g_slist_free (clean_ml);
734 /**
735  * Returns a new document containing default start, mid, and end markers.
736  */
737 SPDocument *
738 ink_markers_preview_doc ()
740 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\">"
741 "  <defs id=\"defs\" />"
743 "  <g id=\"marker-start\">"
744 "    <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:url(#sample);marker-mid:none;marker-end:none\""
745 "       d=\"M 12.5,13 L 25,13\" id=\"path1\" />"
746 "    <rect style=\"fill:none;stroke:none\" id=\"rect2\""
747 "       width=\"25\" height=\"25\" x=\"0\" y=\"0\" />"
748 "  </g>"
750 "  <g id=\"marker-mid\">"
751 "    <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:none;marker-mid:url(#sample);marker-end:none\""
752 "       d=\"M 0,113 L 12.5,113 L 25,113\" id=\"path11\" />"
753 "    <rect style=\"fill:none;stroke:none\" id=\"rect22\""
754 "       width=\"25\" height=\"25\" x=\"0\" y=\"100\" />"
755 "  </g>"
757 "  <g id=\"marker-end\">"
758 "    <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:none;marker-mid:none;marker-end:url(#sample)\""
759 "       d=\"M 0,213 L 12.5,213\" id=\"path111\" />"
760 "    <rect style=\"fill:none;stroke:none\" id=\"rect222\""
761 "       width=\"25\" height=\"25\" x=\"0\" y=\"200\" />"
762 "  </g>"
764 "</svg>";
766     return sp_document_new_from_mem (buffer, strlen(buffer), FALSE);
770 /**
771  * Creates a menu widget to display markers from markers.svg
772  */
773 static GtkWidget *
774 ink_marker_menu( GtkWidget *tbl, gchar *menu_id, SPDocument *sandbox)
776     SPDesktop *desktop = inkscape_active_desktop();
777     SPDocument *doc = sp_desktop_document(desktop);
778     GtkWidget *mnu = gtk_option_menu_new();
780     /* Create new menu widget */
781     GtkWidget *m = gtk_menu_new();
782     gtk_widget_show(m);
784     g_object_set_data(G_OBJECT(mnu), "updating", (gpointer) FALSE);
786     if (!doc) {
787         GtkWidget *i = gtk_menu_item_new_with_label(_("No document selected"));
788         gtk_widget_show(i);
789         gtk_menu_append(GTK_MENU(m), i);
790         gtk_widget_set_sensitive(mnu, FALSE);
792     } else {
794         // add "None"
795         {
796             GtkWidget *i = gtk_menu_item_new();
797             gtk_widget_show(i);
799             g_object_set_data(G_OBJECT(i), "marker", (void *) "none");
801             GtkWidget *hb = gtk_hbox_new(FALSE,  MARKER_ITEM_MARGIN);
802             gtk_widget_show(hb);
804             GtkWidget *l = gtk_label_new( _("None") );
805             gtk_widget_show(l);
806             gtk_misc_set_alignment(GTK_MISC(l), 0.0, 0.5);
808             gtk_box_pack_start(GTK_BOX(hb), l, TRUE, TRUE, 0);
810             gtk_widget_show(hb);
811             gtk_container_add(GTK_CONTAINER(i), hb);
812             gtk_menu_append(GTK_MENU(m), i);
813         }
815         // find and load  markers.svg
816         static SPDocument *markers_doc = NULL;
817         char *markers_source = g_build_filename(INKSCAPE_MARKERSDIR, "markers.svg", NULL);
818         if (Inkscape::IO::file_test(markers_source, G_FILE_TEST_IS_REGULAR)) {
819             markers_doc = sp_document_new(markers_source, FALSE);
820         }
821         g_free(markers_source);
823         // suck in from current doc
824         sp_marker_list_from_doc ( m, NULL, doc, markers_doc, sandbox, menu_id );
826         // add separator
827         {
828             GtkWidget *i = gtk_separator_menu_item_new();
829             gtk_widget_show(i);
830             gtk_menu_append(GTK_MENU(m), i);
831         }
833         // suck in from markers.svg
834         if (markers_doc) {
835             sp_document_ensure_up_to_date(doc);
836             sp_marker_list_from_doc ( m, doc, markers_doc, NULL, sandbox, menu_id );
837         }
839         gtk_widget_set_sensitive(mnu, TRUE);
840     }
842     gtk_object_set_data(GTK_OBJECT(mnu), "menu_id", menu_id);
843     gtk_option_menu_set_menu(GTK_OPTION_MENU(mnu), m);
845     /* Set history */
846     gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 0);
848     return mnu;
852 /**
853  * Handles when user selects one of the markers from the marker menu.
854  * Defines a uri string to refer to it, then applies it to all selected
855  * items in the current desktop.
856  */
857 static void
858 sp_marker_select(GtkOptionMenu *mnu, GtkWidget *spw)
860     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
861         return;
862     }
864     SPDesktop *desktop = inkscape_active_desktop();
865     SPDocument *document = sp_desktop_document(desktop);
866     if (!document) {
867         return;
868     }
870     /* Get Marker */
871     if (!g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))),
872                            "marker"))
873     {
874         return;
875     }
876     gchar *markid = (gchar *) g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))),
877                                                 "marker");
878     gchar *marker = "";
879     if (strcmp(markid, "none")){
880        gchar *stockid = (gchar *) g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))),
881                                                 "stockid");
883        gchar *markurn = markid;
884        if (!strcmp(stockid,"true")) markurn = g_strconcat("urn:inkscape:marker:",markid,NULL);
885        SPObject *mark = get_stock_item(markurn);
886        if (mark) {
887             Inkscape::XML::Node *repr = SP_OBJECT_REPR(mark);
888             marker = g_strconcat("url(#", repr->attribute("id"), ")", NULL);
889         }
890     } else {
891         marker = markid;
892     }
894     SPCSSAttr *css = sp_repr_css_attr_new();
895     gchar *menu_id = (gchar *) g_object_get_data(G_OBJECT(mnu), "menu_id");
896     sp_repr_css_set_property(css, menu_id, marker);
898      Inkscape::Selection *selection = sp_desktop_selection(desktop);
899      GSList const *items = selection->itemList();
900      for (; items != NULL; items = items->next) {
901          SPItem *item = (SPItem *) items->data;
902          if (!SP_IS_SHAPE(item) || SP_IS_RECT(item)) // can't set marker to rect, until it's converted to using <path>
903              continue;
904          Inkscape::XML::Node *selrepr = SP_OBJECT_REPR((SPItem *) items->data);
905          if (selrepr) {
906              sp_repr_css_change_recursive(selrepr, css, "style");
907          }
908          SP_OBJECT(items->data)->requestModified(SP_OBJECT_MODIFIED_FLAG);
909          SP_OBJECT(items->data)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
910      }
912     sp_repr_css_attr_unref(css);
914     sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE, 
915                      _("Set markers"));
918 /**
919  * Sets the stroke width units for all selected items.
920  * Also handles absolute and dimensionless units.
921  */
922 static gboolean stroke_width_set_unit(SPUnitSelector *,
923                                                  SPUnit const *old,
924                                                  SPUnit const *new_units,
925                                                  GObject *spw)
927     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
929     if (!desktop) {
930         return FALSE;
931     }
933     Inkscape::Selection *selection = sp_desktop_selection (desktop);
935     if (selection->isEmpty())
936         return FALSE;
938     GSList const *objects = selection->itemList();
940     if ((old->base == SP_UNIT_ABSOLUTE || old->base == SP_UNIT_DEVICE) &&
941        (new_units->base == SP_UNIT_DIMENSIONLESS)) {
943         /* Absolute to percentage */
944         g_object_set_data (spw, "update", GUINT_TO_POINTER (TRUE));
946         GtkAdjustment *a = GTK_ADJUSTMENT(g_object_get_data (spw, "width"));
947         float w = sp_units_get_pixels (a->value, *old);
949         gdouble average = stroke_average_width (objects);
951         if (average == NR_HUGE || average == 0)
952             return FALSE;
954         gtk_adjustment_set_value (a, 100.0 * w / average);
956         g_object_set_data (spw, "update", GUINT_TO_POINTER (FALSE));
957         return TRUE;
959     } else if ((old->base == SP_UNIT_DIMENSIONLESS) &&
960               (new_units->base == SP_UNIT_ABSOLUTE || new_units->base == SP_UNIT_DEVICE)) {
962         /* Percentage to absolute */
963         g_object_set_data (spw, "update", GUINT_TO_POINTER (TRUE));
965         GtkAdjustment *a = GTK_ADJUSTMENT(g_object_get_data (spw, "width"));
967         gdouble average = stroke_average_width (objects);
969         gtk_adjustment_set_value (a, sp_pixels_get_units (0.01 * a->value * average, *new_units));
971         g_object_set_data (spw, "update", GUINT_TO_POINTER (FALSE));
972         return TRUE;
973     }
975     return FALSE;
979 /**
980  * \brief  Creates a new widget for the line stroke style.
981  *
982  */
983 GtkWidget *
984 sp_stroke_style_line_widget_new(void)
986     GtkWidget *spw, *f, *t, *hb, *sb, *us, *tb, *ds;
987     GtkObject *a;
989     GtkTooltips *tt = gtk_tooltips_new();
991     spw = sp_widget_new_global(INKSCAPE);
993     f = gtk_hbox_new (FALSE, 0);
994     gtk_widget_show(f);
995     gtk_container_add(GTK_CONTAINER(spw), f);
997     t = gtk_table_new(3, 6, FALSE);
998     gtk_widget_show(t);
999     gtk_container_set_border_width(GTK_CONTAINER(t), 4);
1000     gtk_table_set_row_spacings(GTK_TABLE(t), 4);
1001     gtk_container_add(GTK_CONTAINER(f), t);
1002     gtk_object_set_data(GTK_OBJECT(spw), "stroke", t);
1004     gint i = 0;
1006     /* Stroke width */
1007     spw_label(t, _("Width:"), 0, i);
1009     hb = spw_hbox(t, 3, 1, i);
1011 // TODO: when this is gtkmmified, use an Inkscape::UI::Widget::ScalarUnit instead of the separate
1012 // spinbutton and unit selector for stroke width. In sp_stroke_style_line_update, use
1013 // setHundredPercent to remember the aeraged width corresponding to 100%. Then the
1014 // stroke_width_set_unit will be removed (because ScalarUnit takes care of conversions itself), and
1015 // with it, the two remaining calls of stroke_average_width, allowing us to get rid of that
1016 // function in desktop-style.
1018     a = gtk_adjustment_new(1.0, 0.0, 1000.0, 0.1, 10.0, 10.0);
1019     gtk_object_set_data(GTK_OBJECT(spw), "width", a);
1020     sb = gtk_spin_button_new(GTK_ADJUSTMENT(a), 0.1, 3);
1021     gtk_tooltips_set_tip(tt, sb, _("Stroke width"), NULL);
1022     gtk_widget_show(sb);
1024     sp_dialog_defocus_on_enter(sb);
1026     gtk_box_pack_start(GTK_BOX(hb), sb, FALSE, FALSE, 0);
1027     us = sp_unit_selector_new(SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE);
1028     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1029     if (desktop)
1030         sp_unit_selector_set_unit (SP_UNIT_SELECTOR(us), sp_desktop_namedview(desktop)->doc_units);
1031     sp_unit_selector_add_unit(SP_UNIT_SELECTOR(us), &sp_unit_get_by_id(SP_UNIT_PERCENT), 0);
1032     g_signal_connect ( G_OBJECT (us), "set_unit", G_CALLBACK (stroke_width_set_unit), spw );
1033     gtk_widget_show(us);
1034     sp_unit_selector_add_adjustment( SP_UNIT_SELECTOR(us), GTK_ADJUSTMENT(a) );
1035     gtk_box_pack_start(GTK_BOX(hb), us, FALSE, FALSE, 0);
1036     gtk_object_set_data(GTK_OBJECT(spw), "units", us);
1038     gtk_signal_connect( GTK_OBJECT(a), "value_changed", GTK_SIGNAL_FUNC(sp_stroke_style_width_changed), spw );
1039     i++;
1041     /* Join type */
1042     // TRANSLATORS: The line join style specifies the shape to be used at the
1043     //  corners of paths. It can be "miter", "round" or "bevel".
1044     spw_label(t, _("Join:"), 0, i);
1046     hb = spw_hbox(t, 3, 1, i);
1048     tb = NULL;
1050     tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_JOIN_MITER,
1051                                 hb, spw, "join", "miter");
1053     // TRANSLATORS: Miter join: joining lines with a sharp (pointed) corner.
1054     //  For an example, draw a triangle with a large stroke width and modify the
1055     //  "Join" option (in the Fill and Stroke dialog).
1056     gtk_tooltips_set_tip(tt, tb, _("Miter join"), NULL);
1058     tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_JOIN_ROUND,
1059                                 hb, spw, "join", "round");
1061     // TRANSLATORS: Round join: joining lines with a rounded corner.
1062     //  For an example, draw a triangle with a large stroke width and modify the
1063     //  "Join" option (in the Fill and Stroke dialog).
1064     gtk_tooltips_set_tip(tt, tb, _("Round join"), NULL);
1066     tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_JOIN_BEVEL,
1067                                 hb, spw, "join", "bevel");
1069     // TRANSLATORS: Bevel join: joining lines with a blunted (flattened) corner.
1070     //  For an example, draw a triangle with a large stroke width and modify the
1071     //  "Join" option (in the Fill and Stroke dialog).
1072     gtk_tooltips_set_tip(tt, tb, _("Bevel join"), NULL);
1074     i++;
1076     /* Miterlimit  */
1077     // TRANSLATORS: Miter limit: only for "miter join", this limits the length
1078     //  of the sharp "spike" when the lines connect at too sharp an angle.
1079     // When two line segments meet at a sharp angle, a miter join results in a
1080     //  spike that extends well beyond the connection point. The purpose of the
1081     //  miter limit is to cut off such spikes (i.e. convert them into bevels)
1082     //  when they become too long.
1083     spw_label(t, _("Miter limit:"), 0, i);
1085     hb = spw_hbox(t, 3, 1, i);
1087     a = gtk_adjustment_new(4.0, 0.0, 100.0, 0.1, 10.0, 10.0);
1088     gtk_object_set_data(GTK_OBJECT(spw), "miterlimit", a);
1090     sb = gtk_spin_button_new(GTK_ADJUSTMENT(a), 0.1, 2);
1091     gtk_tooltips_set_tip(tt, sb, _("Maximum length of the miter (in units of stroke width)"), NULL);
1092     gtk_widget_show(sb);
1093     gtk_object_set_data(GTK_OBJECT(spw), "miterlimit_sb", sb);
1094     sp_dialog_defocus_on_enter(sb);
1096     gtk_box_pack_start(GTK_BOX(hb), sb, FALSE, FALSE, 0);
1098     gtk_signal_connect( GTK_OBJECT(a), "value_changed",
1099                         GTK_SIGNAL_FUNC(sp_stroke_style_miterlimit_changed), spw );
1100     i++;
1102     /* Cap type */
1103     // TRANSLATORS: cap type specifies the shape for the ends of lines
1104     spw_label(t, _("Cap:"), 0, i);
1106     hb = spw_hbox(t, 3, 1, i);
1108     tb = NULL;
1110     tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_CAP_BUTT,
1111                                 hb, spw, "cap", "butt");
1113     // TRANSLATORS: Butt cap: the line shape does not extend beyond the end point
1114     //  of the line; the ends of the line are square
1115     gtk_tooltips_set_tip(tt, tb, _("Butt cap"), NULL);
1117     tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_CAP_ROUND,
1118                                 hb, spw, "cap", "round");
1120     // TRANSLATORS: Round cap: the line shape extends beyond the end point of the
1121     //  line; the ends of the line are rounded
1122     gtk_tooltips_set_tip(tt, tb, _("Round cap"), NULL);
1124     tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_CAP_SQUARE,
1125                                 hb, spw, "cap", "square");
1127     // TRANSLATORS: Square cap: the line shape extends beyond the end point of the
1128     //  line; the ends of the line are square
1129     gtk_tooltips_set_tip(tt, tb, _("Square cap"), NULL);
1131     i++;
1134     /* Dash */
1135     spw_label(t, _("Dashes:"), 0, i);
1136     ds = sp_dash_selector_new( inkscape_get_repr( INKSCAPE,
1137                                                   "palette.dashes") );
1139     gtk_widget_show(ds);
1140     gtk_table_attach( GTK_TABLE(t), ds, 1, 4, i, i+1,
1141                       (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ),
1142                       (GtkAttachOptions)0, 0, 0 );
1143     gtk_object_set_data(GTK_OBJECT(spw), "dash", ds);
1144     gtk_signal_connect( GTK_OBJECT(ds), "changed",
1145                         GTK_SIGNAL_FUNC(sp_stroke_style_line_dash_changed),
1146                         spw );
1147     i++;
1149     /* Drop down marker selectors*/
1151     // doing this here once, instead of for each preview, to speed things up
1152     SPDocument *sandbox = ink_markers_preview_doc ();
1154     // TRANSLATORS: Path markers are an SVG feature that allows you to attach arbitrary shapes
1155     // (arrowheads, bullets, faces, whatever) to the start, end, or middle nodes of a path.
1156     spw_label(t, _("Start Markers:"), 0, i);
1157     GtkWidget *mnu  = ink_marker_menu( spw ,"marker-start", sandbox);
1158     gtk_signal_connect( GTK_OBJECT(mnu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw );
1159     gtk_widget_show(mnu);
1160     gtk_table_attach( GTK_TABLE(t), mnu, 1, 4, i, i+1,
1161                       (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ),
1162                       (GtkAttachOptions)0, 0, 0 );
1163     gtk_object_set_data(GTK_OBJECT(spw), "start_mark_menu", mnu);
1165     i++;
1166     spw_label(t, _("Mid Markers:"), 0, i);
1167     mnu = NULL;
1168     mnu  = ink_marker_menu( spw ,"marker-mid", sandbox);
1169     gtk_signal_connect( GTK_OBJECT(mnu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw );
1170     gtk_widget_show(mnu);
1171     gtk_table_attach( GTK_TABLE(t), mnu, 1, 4, i, i+1,
1172                       (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ),
1173                       (GtkAttachOptions)0, 0, 0 );
1174     gtk_object_set_data(GTK_OBJECT(spw), "mid_mark_menu", mnu);
1176     i++;
1177     spw_label(t, _("End Markers:"), 0, i);
1178     mnu = NULL;
1179     mnu  = ink_marker_menu( spw ,"marker-end", sandbox);
1180     gtk_signal_connect( GTK_OBJECT(mnu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw );
1181     gtk_widget_show(mnu);
1182     gtk_table_attach( GTK_TABLE(t), mnu, 1, 4, i, i+1,
1183                       (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ),
1184                       (GtkAttachOptions)0, 0, 0 );
1185     gtk_object_set_data(GTK_OBJECT(spw), "end_mark_menu", mnu);
1187     i++;
1189     gtk_signal_connect( GTK_OBJECT(spw), "construct",
1190                         GTK_SIGNAL_FUNC(sp_stroke_style_line_construct),
1191                         NULL );
1192     gtk_signal_connect( GTK_OBJECT(spw), "modify_selection",
1193                         GTK_SIGNAL_FUNC(sp_stroke_style_line_selection_modified),
1194                         NULL );
1195     gtk_signal_connect( GTK_OBJECT(spw), "change_selection",
1196                         GTK_SIGNAL_FUNC(sp_stroke_style_line_selection_changed),
1197                         NULL );
1199     sp_stroke_style_line_update( SP_WIDGET(spw), desktop ? sp_desktop_selection(desktop) : NULL);
1201     return spw;
1205 /**
1206  * Callback for when the stroke style widget is called.  It causes
1207  * the stroke line style to be updated.
1208  */
1209 static void
1210 sp_stroke_style_line_construct(SPWidget *spw, gpointer data)
1213 #ifdef SP_SS_VERBOSE
1214     g_print( "Stroke style widget constructed: inkscape %p repr %p\n",
1215              spw->inkscape, spw->repr );
1216 #endif
1217     if (spw->inkscape) {
1218         sp_stroke_style_line_update(spw,
1219                                     ( SP_ACTIVE_DESKTOP
1220                                       ? sp_desktop_selection(SP_ACTIVE_DESKTOP)
1221                                       : NULL ));
1222     } 
1225 /**
1226  * Callback for when stroke style widget is modified.  
1227  * Triggers update action.
1228  */
1229 static void
1230 sp_stroke_style_line_selection_modified ( SPWidget *spw,
1231                                        Inkscape::Selection *selection,
1232                                        guint flags,
1233                                        gpointer data )
1235     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) {
1236         sp_stroke_style_line_update (spw, selection);
1237     }
1241 /**
1242  * Callback for when stroke style widget is changed.
1243  * Triggers update action.
1244  */
1245 static void
1246 sp_stroke_style_line_selection_changed ( SPWidget *spw,
1247                                        Inkscape::Selection *selection,
1248                                        gpointer data )
1250     sp_stroke_style_line_update (spw, selection);
1254 /**
1255  * Sets selector widgets' dash style from an SPStyle object.
1256  */
1257 static void
1258 sp_dash_selector_set_from_style (GtkWidget *dsel, SPStyle *style)
1260     if (style->stroke_dash.n_dash > 0) {
1261         double d[64];
1262         int len = MIN(style->stroke_dash.n_dash, 64);
1263         for (int i = 0; i < len; i++) {
1264             if (style->stroke_width.computed != 0)
1265                 d[i] = style->stroke_dash.dash[i] / style->stroke_width.computed;
1266             else
1267                 d[i] = style->stroke_dash.dash[i]; // is there a better thing to do for stroke_width==0?
1268         }
1269         sp_dash_selector_set_dash(SP_DASH_SELECTOR(dsel), len, d,
1270                style->stroke_width.computed != 0?
1271                     style->stroke_dash.offset / style->stroke_width.computed  :
1272                     style->stroke_dash.offset);
1273     } else {
1274         sp_dash_selector_set_dash(SP_DASH_SELECTOR(dsel), 0, NULL, 0.0);
1275     }
1278 /**
1279  * Sets the join type for a line, and updates the stroke style widget's buttons
1280  */
1281 static void
1282 sp_jointype_set (SPWidget *spw, unsigned const jointype)
1284     GtkWidget *tb = NULL;
1285     switch (jointype) {
1286         case SP_STROKE_LINEJOIN_MITER:
1287             tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_MITER));
1288             break;
1289         case SP_STROKE_LINEJOIN_ROUND:
1290             tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_ROUND));
1291             break;
1292         case SP_STROKE_LINEJOIN_BEVEL:
1293             tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_BEVEL));
1294             break;
1295         default:
1296             break;
1297     }
1298     sp_stroke_style_set_join_buttons (spw, tb);
1301 /**
1302  * Sets the cap type for a line, and updates the stroke style widget's buttons
1303  */
1304 static void
1305 sp_captype_set (SPWidget *spw, unsigned const captype)
1307     GtkWidget *tb = NULL;
1308     switch (captype) {
1309         case SP_STROKE_LINECAP_BUTT:
1310             tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_BUTT));
1311             break;
1312         case SP_STROKE_LINECAP_ROUND:
1313             tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_ROUND));
1314             break;
1315         case SP_STROKE_LINECAP_SQUARE:
1316             tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_SQUARE));
1317             break;
1318         default:
1319             break;
1320     }
1321     sp_stroke_style_set_cap_buttons (spw, tb);
1324 /**
1325  * Callback for when stroke style widget is updated, including markers, cap type,
1326  * join type, etc.
1327  */
1328 static void
1329 sp_stroke_style_line_update(SPWidget *spw, Inkscape::Selection *sel)
1331     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1332         return;
1333     }
1335     gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(TRUE));
1337     GtkWidget *sset = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "stroke"));
1338     GtkObject *width = GTK_OBJECT(gtk_object_get_data(GTK_OBJECT(spw), "width"));
1339     GtkObject *ml = GTK_OBJECT(gtk_object_get_data(GTK_OBJECT(spw), "miterlimit"));
1340     GtkWidget *us = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "units"));
1341     GtkWidget *dsel = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "dash"));
1343     // create temporary style
1344     SPStyle *query = sp_style_new ();
1345     // query into it
1346     int result_sw = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEWIDTH); 
1347     int result_ml = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEMITERLIMIT); 
1348     int result_cap = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKECAP); 
1349     int result_join = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEJOIN); 
1351     if (result_sw == QUERY_STYLE_NOTHING) {
1352         /* No objects stroked, set insensitive */
1353         gtk_widget_set_sensitive(sset, FALSE);
1355         gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(FALSE));
1356         return;
1357     } else {
1358         gtk_widget_set_sensitive(sset, TRUE);
1360         SPUnit const *unit = sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us));
1362         if (result_sw == QUERY_STYLE_MULTIPLE_AVERAGED) {
1363             sp_unit_selector_set_unit(SP_UNIT_SELECTOR(us), &sp_unit_get_by_id(SP_UNIT_PERCENT));
1364         } else {
1365             // same width, or only one object; no sense to keep percent, switch to absolute
1366             if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) {
1367                 sp_unit_selector_set_unit(SP_UNIT_SELECTOR(us), sp_desktop_namedview(SP_ACTIVE_DESKTOP)->doc_units);
1368             }
1369         }
1371         unit = sp_unit_selector_get_unit (SP_UNIT_SELECTOR (us));
1373         if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) {
1374             double avgwidth = sp_pixels_get_units (query->stroke_width.computed, *unit);
1375             gtk_adjustment_set_value(GTK_ADJUSTMENT(width), avgwidth);
1376         } else {
1377             gtk_adjustment_set_value(GTK_ADJUSTMENT(width), 100);
1378         }
1379     }
1381     if (result_ml != QUERY_STYLE_NOTHING)
1382         gtk_adjustment_set_value(GTK_ADJUSTMENT(ml), query->stroke_miterlimit.value); // TODO: reflect averagedness?
1384     if (result_join != QUERY_STYLE_MULTIPLE_DIFFERENT) {
1385         sp_jointype_set (spw, query->stroke_linejoin.value);
1386     } else {
1387         sp_stroke_style_set_join_buttons(spw, NULL);
1388     }
1390     if (result_cap != QUERY_STYLE_MULTIPLE_DIFFERENT) {
1391         sp_captype_set (spw, query->stroke_linecap.value);
1392     } else {
1393         sp_stroke_style_set_cap_buttons(spw, NULL);
1394     }
1396     g_free (query);
1398     if (!sel || sel->isEmpty())
1399         return;
1401     GSList const *objects = sel->itemList();
1402     SPObject * const object = SP_OBJECT(objects->data);
1403     SPStyle * const style = SP_OBJECT_STYLE(object);
1405     /* Markers */
1406     sp_stroke_style_update_marker_menus(spw, objects); // FIXME: make this desktop query too
1408     /* Dash */
1409     sp_dash_selector_set_from_style (dsel, style); // FIXME: make this desktop query too
1411     gtk_widget_set_sensitive(sset, TRUE);
1413     gtk_object_set_data(GTK_OBJECT(spw), "update",
1414                         GINT_TO_POINTER(FALSE));
1417 /**
1418  * Sets a line's dash properties in a CSS style object.
1419  */
1420 static void
1421 sp_stroke_style_set_scaled_dash(SPCSSAttr *css,
1422                                 int ndash, double *dash, double offset,
1423                                 double scale)
1425     if (ndash > 0) {
1426         Inkscape::CSSOStringStream osarray;
1427         for (int i = 0; i < ndash; i++) {
1428             osarray << dash[i] * scale;
1429             if (i < (ndash - 1)) {
1430                 osarray << ",";
1431             }
1432         }
1433         sp_repr_css_set_property(css, "stroke-dasharray", osarray.str().c_str());
1435         Inkscape::CSSOStringStream osoffset;
1436         osoffset << offset * scale;
1437         sp_repr_css_set_property(css, "stroke-dashoffset", osoffset.str().c_str());
1438     } else {
1439         sp_repr_css_set_property(css, "stroke-dasharray", "none");
1440         sp_repr_css_set_property(css, "stroke-dashoffset", NULL);
1441     }
1444 /**
1445  * Sets line properties like width, dashes, markers, etc. on all currently selected items.
1446  */
1447 static void
1448 sp_stroke_style_scale_line(SPWidget *spw)
1450     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1451         return;
1452     }
1454     gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(TRUE));
1456     GtkAdjustment *wadj = GTK_ADJUSTMENT(gtk_object_get_data(GTK_OBJECT(spw), "width"));
1457     SPUnitSelector *us = SP_UNIT_SELECTOR(gtk_object_get_data(GTK_OBJECT(spw), "units"));
1458     SPDashSelector *dsel = SP_DASH_SELECTOR(gtk_object_get_data(GTK_OBJECT(spw), "dash"));
1459     GtkAdjustment *ml = GTK_ADJUSTMENT(gtk_object_get_data(GTK_OBJECT(spw), "miterlimit"));
1461     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1462     SPDocument *document = sp_desktop_document (desktop);
1463     Inkscape::Selection *selection = sp_desktop_selection (desktop);
1465     GSList const *items = selection->itemList();
1467     /* TODO: Create some standardized method */
1468     SPCSSAttr *css = sp_repr_css_attr_new();
1470     if (items) {
1472         double width_typed = wadj->value;
1473         double const miterlimit = ml->value;
1475         SPUnit const *const unit = sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us));
1477         double *dash, offset;
1478         int ndash;
1479         sp_dash_selector_get_dash(dsel, &ndash, &dash, &offset);
1481         for (GSList const *i = items; i != NULL; i = i->next) {
1482             /* Set stroke width */
1483             double width;
1484             if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) {
1485                 width = sp_units_get_pixels (width_typed, *unit);
1486             } else { // percentage
1487                 gdouble old_w = SP_OBJECT_STYLE (i->data)->stroke_width.computed;
1488                 width = old_w * width_typed / 100;
1489             }
1491             {
1492                 Inkscape::CSSOStringStream os_width;
1493                 os_width << width;
1494                 sp_repr_css_set_property(css, "stroke-width", os_width.str().c_str());
1495             }
1497             {
1498                 Inkscape::CSSOStringStream os_ml;
1499                 os_ml << miterlimit;
1500                 sp_repr_css_set_property(css, "stroke-miterlimit", os_ml.str().c_str());
1501             }
1503             /* Set dash */
1504             sp_stroke_style_set_scaled_dash(css, ndash, dash, offset, width);
1506             sp_desktop_apply_css_recursive (SP_OBJECT(i->data), css, true);
1507         }
1509         g_free(dash);
1511         if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) {
1512             // reset to 100 percent
1513             gtk_adjustment_set_value (wadj, 100.0);
1514         }
1516     }
1518     // we have already changed the items, so set style without changing selection
1519     // FIXME: move the above stroke-setting stuff, including percentages, to desktop-style
1520     sp_desktop_set_style (desktop, css, false);
1522     sp_repr_css_attr_unref(css);
1524     sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE, 
1525                      _("Set stroke style"));
1527     gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(FALSE));
1531 /**
1532  * Callback for when the stroke style's width changes.  
1533  * Causes all line styles to be applied to all selected items.
1534  */
1535 static void
1536 sp_stroke_style_width_changed(GtkAdjustment *adj, SPWidget *spw)
1538     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1539         return;
1540     }
1542     sp_stroke_style_scale_line(spw);
1545 /**
1546  * Callback for when the stroke style's miterlimit changes.  
1547  * Causes all line styles to be applied to all selected items.
1548  */
1549 static void
1550 sp_stroke_style_miterlimit_changed(GtkAdjustment *adj, SPWidget *spw)
1552     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1553         return;
1554     }
1556     sp_stroke_style_scale_line(spw);
1559 /**
1560  * Callback for when the stroke style's dash changes.  
1561  * Causes all line styles to be applied to all selected items.
1562  */
1563 static void
1564 sp_stroke_style_line_dash_changed(SPDashSelector *dsel, SPWidget *spw)
1566     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1567         return;
1568     }
1570     sp_stroke_style_scale_line(spw);
1575 /**
1576  * \brief  This routine handles toggle events for buttons in the stroke style
1577  *         dialog.
1578  * When activated, this routine gets the data for the various widgets, and then
1579  * calls the respective routines to update css properties, etc.
1580  *
1581  */
1582 static void
1583 sp_stroke_style_any_toggled(GtkToggleButton *tb, SPWidget *spw)
1585     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1586         return;
1587     }
1589     if (gtk_toggle_button_get_active(tb)) {
1591         gchar const *join
1592             = static_cast<gchar const *>(gtk_object_get_data(GTK_OBJECT(tb), "join"));
1593         gchar const *cap
1594             = static_cast<gchar const *>(gtk_object_get_data(GTK_OBJECT(tb), "cap"));
1596         if (join) {
1597             GtkWidget *ml = GTK_WIDGET(g_object_get_data(G_OBJECT(spw), "miterlimit_sb"));
1598             gtk_widget_set_sensitive (ml, !strcmp(join, "miter"));
1599         }
1601         SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1603         /* TODO: Create some standardized method */
1604         SPCSSAttr *css = sp_repr_css_attr_new();
1606         if (join) {
1607             sp_repr_css_set_property(css, "stroke-linejoin", join);
1609             sp_desktop_set_style (desktop, css);
1611             sp_stroke_style_set_join_buttons(spw, GTK_WIDGET(tb));
1612         } else if (cap) {
1613             sp_repr_css_set_property(css, "stroke-linecap", cap);
1615             sp_desktop_set_style (desktop, css);
1617             sp_stroke_style_set_cap_buttons(spw, GTK_WIDGET(tb));
1618         }
1620         sp_repr_css_attr_unref(css);
1622         sp_document_done(sp_desktop_document(desktop), SP_VERB_DIALOG_FILL_STROKE,
1623                          _("Set stroke style"));
1624     }
1628 /**
1629  * Updates the join style toggle buttons
1630  */
1631 static void
1632 sp_stroke_style_set_join_buttons(SPWidget *spw, GtkWidget *active)
1634     GtkWidget *tb;
1636     tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1637                                          INKSCAPE_STOCK_JOIN_MITER) );
1638     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1640     GtkWidget *ml = GTK_WIDGET(g_object_get_data(G_OBJECT(spw), "miterlimit_sb"));
1641     gtk_widget_set_sensitive(ml, (active == tb));
1643     tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1644                                          INKSCAPE_STOCK_JOIN_ROUND) );
1645     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1646     tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1647                                          INKSCAPE_STOCK_JOIN_BEVEL) );
1648     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1653 /**
1654  * Updates the cap style toggle buttons
1655  */
1656 static void
1657 sp_stroke_style_set_cap_buttons(SPWidget *spw, GtkWidget *active)
1659     GtkWidget *tb;
1661     tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1662                                          INKSCAPE_STOCK_CAP_BUTT));
1663     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1664     tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1665                                          INKSCAPE_STOCK_CAP_ROUND) );
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_SQUARE) );
1669     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1672 /**
1673  * Sets the current marker in the marker menu.
1674  */
1675 static void
1676 ink_marker_menu_set_current(SPObject *marker, GtkOptionMenu *mnu)
1678     gtk_object_set_data(GTK_OBJECT(mnu), "update", GINT_TO_POINTER(TRUE));
1680     GtkMenu *m = GTK_MENU(gtk_option_menu_get_menu(mnu));
1681     if (marker != NULL) {
1682         bool mark_is_stock = false;
1683         if (SP_OBJECT_REPR(marker)->attribute("inkscape:stockid"))
1684             mark_is_stock = true;
1686         gchar *markname;
1687         if (mark_is_stock)
1688             markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("inkscape:stockid"));
1689         else
1690             markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("id"));
1692         int markpos = 0;
1693         GList *kids = GTK_MENU_SHELL(m)->children;
1694         int i = 0;
1695         for (; kids != NULL; kids = kids->next) {
1696             gchar *mark = (gchar *) g_object_get_data(G_OBJECT(kids->data), "marker");
1697             if ( mark && strcmp(mark, markname) == 0 ) {
1698                 if ( mark_is_stock && !strcmp((gchar *) g_object_get_data(G_OBJECT(kids->data), "stockid"), "true"))
1699                     markpos = i;
1700                 if ( !mark_is_stock && !strcmp((gchar *) g_object_get_data(G_OBJECT(kids->data), "stockid"), "false"))
1701                     markpos = i;
1702             }
1703             i++;
1704         }
1705         gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), markpos);
1707         g_free (markname);
1708     }
1709     else {
1710         gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 0);
1711     }
1712     gtk_object_set_data(GTK_OBJECT(mnu), "update", GINT_TO_POINTER(FALSE));
1715 /**
1716  * Updates the marker menus to highlight the appropriate marker and scroll to 
1717  * that marker.
1718  */
1719 static void
1720 sp_stroke_style_update_marker_menus( SPWidget *spw,
1721                                      GSList const *objects)
1723     struct { char const *key; int loc; } const keyloc[] = {
1724         { "start_mark_menu", SP_MARKER_LOC_START },
1725         { "mid_mark_menu", SP_MARKER_LOC_MID },
1726         { "end_mark_menu", SP_MARKER_LOC_END }
1727     };
1729     bool all_texts = true;
1730     for (GSList *i = (GSList *) objects; i != NULL; i = i->next) {
1731         if (!SP_IS_TEXT (i->data)) {
1732             all_texts = false;
1733         }
1734     }
1736     for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) {
1737         GtkOptionMenu *mnu = (GtkOptionMenu *) g_object_get_data(G_OBJECT(spw), keyloc[i].key);
1738         if (all_texts) {
1739             // Per SVG spec, text objects cannot have markers; disable menus if only texts are selected
1740             gtk_widget_set_sensitive (GTK_WIDGET(mnu), FALSE);
1741         } else {
1742             gtk_widget_set_sensitive (GTK_WIDGET(mnu), TRUE);
1743         }
1744     }
1746     // We show markers of the first object in the list only
1747     // FIXME: use the first in the list that has the marker of each type, if any
1748     SPObject *object = SP_OBJECT(objects->data);
1750     for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) {
1751         // For all three marker types,
1753         // find the corresponding menu
1754         GtkOptionMenu *mnu = (GtkOptionMenu *) g_object_get_data(G_OBJECT(spw), keyloc[i].key);
1756         // Quit if we're in update state
1757         if (gtk_object_get_data(GTK_OBJECT(mnu), "update")) {
1758             return;
1759         }
1761         if (object->style->marker[keyloc[i].loc].value != NULL && !all_texts) {
1762             // If the object has this type of markers,
1764             // Extract the name of the marker that the object uses
1765             SPObject *marker = ink_extract_marker_name(object->style->marker[keyloc[i].loc].value);
1766             // Scroll the menu to that marker
1767             ink_marker_menu_set_current (marker, mnu);
1769         } else {
1770             gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 0);
1771         }
1772     }
1776 /**
1777  * Extract the actual name of the link
1778  * e.g. get mTriangle from url(#mTriangle).
1779  * \return Buffer containing the actual name, allocated from GLib;
1780  * the caller should free the buffer when they no longer need it.
1781  */
1782 static SPObject*
1783 ink_extract_marker_name(gchar const *n)
1785     gchar const *p = n;
1786     while (*p != '\0' && *p != '#') {
1787         p++;
1788     }
1790     if (*p == '\0' || p[1] == '\0') {
1791         return NULL;
1792     }
1794     p++;
1795     int c = 0;
1796     while (p[c] != '\0' && p[c] != ')') {
1797         c++;
1798     }
1800     if (p[c] == '\0') {
1801         return NULL;
1802     }
1804     gchar* b = g_strdup(p);
1805     b[c] = '\0';
1808     SPDesktop *desktop = inkscape_active_desktop();
1809     SPDocument *doc = sp_desktop_document(desktop);
1810     SPObject *marker = doc->getObjectById(b);
1811     return marker;
1815 /*
1816   Local Variables:
1817   mode:c++
1818   c-file-style:"stroustrup"
1819   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1820   indent-tabs-mode:nil
1821   fill-column:99
1822   End:
1823 */
1824 // vim: filetype=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :