Code

Moving functions from svg_preview_cache.h to a new .cpp
[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)));
591     NR::Rect const dbox = SP_ITEM(object)->invokeBbox(i2doc);
593     if (dbox.isEmpty()) {
594         return NULL;
595     }
597     /* Update to renderable state */
598     double sf = 0.8;
599     GdkPixbuf* pixbuf = render_pixbuf(root, sf, dbox, psize);
601     // Create widget
602     GtkWidget *pb = gtk_image_new_from_pixbuf(pixbuf);
604     return pb;
608 /**
609  *  Returns a list of markers in the defs of the given source document as a GSList object
610  *  Returns NULL if there are no markers in the document.
611  */
612 GSList *
613 ink_marker_list_get (SPDocument *source)
615     if (source == NULL)
616         return NULL;
618     GSList *ml   = NULL;
619     SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS (source);
620     for ( SPObject *child = sp_object_first_child(SP_OBJECT(defs));
621           child != NULL;
622           child = SP_OBJECT_NEXT (child) )
623     {
624         if (SP_IS_MARKER(child)) {
625             ml = g_slist_prepend (ml, child);
626         }
627     }
628     return ml;
631 #define MARKER_ITEM_MARGIN 0
633 /**
634  * Adds previews of markers in marker_list to the given menu widget
635  */
636 static void
637 sp_marker_menu_build (GtkWidget *m, GSList *marker_list, SPDocument *source, SPDocument *sandbox, gchar *menu_id)
639     // Do this here, outside of loop, to speed up preview generation:
640     NRArena const *arena = NRArena::create();
641     unsigned const visionkey = sp_item_display_key_new(1);
642     NRArenaItem *root =  sp_item_invoke_show( SP_ITEM(SP_DOCUMENT_ROOT (sandbox)), (NRArena *) arena, visionkey, SP_ITEM_SHOW_DISPLAY );
644     for (; marker_list != NULL; marker_list = marker_list->next) {
645         Inkscape::XML::Node *repr = SP_OBJECT_REPR((SPItem *) marker_list->data);
646         GtkWidget *i = gtk_menu_item_new();
647         gtk_widget_show(i);
649         if (repr->attribute("inkscape:stockid"))
650             g_object_set_data (G_OBJECT(i), "stockid", (void *) "true");
651         else
652             g_object_set_data (G_OBJECT(i), "stockid", (void *) "false");
654         gchar const *markid = repr->attribute("id");
655         g_object_set_data (G_OBJECT(i), "marker", (void *) markid);
657         GtkWidget *hb = gtk_hbox_new(FALSE, MARKER_ITEM_MARGIN);
658         gtk_widget_show(hb);
660         // generate preview
661         GtkWidget *prv = sp_marker_prev_new (22, markid, source, sandbox, menu_id, arena, visionkey, root);
662         gtk_widget_show(prv);
663         gtk_box_pack_start(GTK_BOX(hb), prv, FALSE, FALSE, 6);
665         // create label
666         GtkWidget *l = gtk_label_new(repr->attribute("id"));
667         gtk_widget_show(l);
668         gtk_misc_set_alignment(GTK_MISC(l), 0.0, 0.5);
670         gtk_box_pack_start(GTK_BOX(hb), l, TRUE, TRUE, 0);
672         gtk_widget_show(hb);
673         gtk_container_add(GTK_CONTAINER(i), hb);
675         gtk_menu_append(GTK_MENU(m), i);
676     }
679 /**
680  * sp_marker_list_from_doc()
681  *
682  * \brief Pick up all markers from source, except those that are in
683  * current_doc (if non-NULL), and add items to the m menu
684  *
685  */
686 static void
687 sp_marker_list_from_doc (GtkWidget *m, SPDocument *current_doc, SPDocument *source, SPDocument *markers_doc, SPDocument *sandbox, gchar *menu_id)
689     GSList *ml = ink_marker_list_get(source);
690     GSList *clean_ml = NULL;
692     // Do this here, outside of loop, to speed up preview generation:
693     /* Create new arena */
694     NRArena const *arena = NRArena::create();
695     /* Create ArenaItem and set transform */
696     unsigned const visionkey = sp_item_display_key_new(1);
697     NRArenaItem *root =  sp_item_invoke_show( SP_ITEM(SP_DOCUMENT_ROOT (sandbox)), (NRArena *) arena, visionkey, SP_ITEM_SHOW_DISPLAY );
699     for (; ml != NULL; ml = ml->next) {
700         if (!SP_IS_MARKER(ml->data))
701             continue;
703 /*
704   Bug 980157 wants to have stock markers show up at the top of the dropdown menu
705   Thus we can skip all of this code, which simply looks for duplicate stock markers
707         Inkscape::XML::Node *repr = SP_OBJECT_REPR((SPItem *) ml->data);
708         bool stock_dupe = false;
710         if (repr->attribute("inkscape:stockid")) {
711             GSList * markers_doc_ml = ink_marker_list_get(markers_doc);
712             for (; markers_doc_ml != NULL; markers_doc_ml = markers_doc_ml->next) {
713                 const gchar* stockid = SP_OBJECT_REPR(markers_doc_ml->data)->attribute("inkscape:stockid");
714                 if (stockid && !strcmp(repr->attribute("inkscape:stockid"), stockid))
715                     stock_dupe = true;
716             }
717         }
719         if (stock_dupe) // stock item, dont add to list from current doc
720             continue;
721 */
723         // Add to the list of markers we really do wish to show
724         clean_ml = g_slist_prepend (clean_ml, ml->data);
725     }
726     sp_marker_menu_build (m, clean_ml, source, sandbox, menu_id);
728     g_slist_free (ml);
729     g_slist_free (clean_ml);
733 /**
734  * Returns a new document containing default start, mid, and end markers.
735  */
736 SPDocument *
737 ink_markers_preview_doc ()
739 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\">"
740 "  <defs id=\"defs\" />"
742 "  <g id=\"marker-start\">"
743 "    <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:url(#sample);marker-mid:none;marker-end:none\""
744 "       d=\"M 12.5,13 L 25,13\" id=\"path1\" />"
745 "    <rect style=\"fill:none;stroke:none\" id=\"rect2\""
746 "       width=\"25\" height=\"25\" x=\"0\" y=\"0\" />"
747 "  </g>"
749 "  <g id=\"marker-mid\">"
750 "    <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:none;marker-mid:url(#sample);marker-end:none\""
751 "       d=\"M 0,113 L 12.5,113 L 25,113\" id=\"path11\" />"
752 "    <rect style=\"fill:none;stroke:none\" id=\"rect22\""
753 "       width=\"25\" height=\"25\" x=\"0\" y=\"100\" />"
754 "  </g>"
756 "  <g id=\"marker-end\">"
757 "    <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:none;marker-mid:none;marker-end:url(#sample)\""
758 "       d=\"M 0,213 L 12.5,213\" id=\"path111\" />"
759 "    <rect style=\"fill:none;stroke:none\" id=\"rect222\""
760 "       width=\"25\" height=\"25\" x=\"0\" y=\"200\" />"
761 "  </g>"
763 "</svg>";
765     return sp_document_new_from_mem (buffer, strlen(buffer), FALSE);
769 /**
770  * Creates a menu widget to display markers from markers.svg
771  */
772 static GtkWidget *
773 ink_marker_menu( GtkWidget *tbl, gchar *menu_id, SPDocument *sandbox)
775     SPDesktop *desktop = inkscape_active_desktop();
776     SPDocument *doc = sp_desktop_document(desktop);
777     GtkWidget *mnu = gtk_option_menu_new();
779     /* Create new menu widget */
780     GtkWidget *m = gtk_menu_new();
781     gtk_widget_show(m);
783     g_object_set_data(G_OBJECT(mnu), "updating", (gpointer) FALSE);
785     if (!doc) {
786         GtkWidget *i = gtk_menu_item_new_with_label(_("No document selected"));
787         gtk_widget_show(i);
788         gtk_menu_append(GTK_MENU(m), i);
789         gtk_widget_set_sensitive(mnu, FALSE);
791     } else {
793         // add "None"
794         {
795             GtkWidget *i = gtk_menu_item_new();
796             gtk_widget_show(i);
798             g_object_set_data(G_OBJECT(i), "marker", (void *) "none");
800             GtkWidget *hb = gtk_hbox_new(FALSE,  MARKER_ITEM_MARGIN);
801             gtk_widget_show(hb);
803             GtkWidget *l = gtk_label_new( _("None") );
804             gtk_widget_show(l);
805             gtk_misc_set_alignment(GTK_MISC(l), 0.0, 0.5);
807             gtk_box_pack_start(GTK_BOX(hb), l, TRUE, TRUE, 0);
809             gtk_widget_show(hb);
810             gtk_container_add(GTK_CONTAINER(i), hb);
811             gtk_menu_append(GTK_MENU(m), i);
812         }
814         // find and load  markers.svg
815         static SPDocument *markers_doc = NULL;
816         char *markers_source = g_build_filename(INKSCAPE_MARKERSDIR, "markers.svg", NULL);
817         if (Inkscape::IO::file_test(markers_source, G_FILE_TEST_IS_REGULAR)) {
818             markers_doc = sp_document_new(markers_source, FALSE);
819         }
820         g_free(markers_source);
822         // suck in from current doc
823         sp_marker_list_from_doc ( m, NULL, doc, markers_doc, sandbox, menu_id );
825         // add separator
826         {
827             GtkWidget *i = gtk_separator_menu_item_new();
828             gtk_widget_show(i);
829             gtk_menu_append(GTK_MENU(m), i);
830         }
832         // suck in from markers.svg
833         if (markers_doc) {
834             sp_document_ensure_up_to_date(doc);
835             sp_marker_list_from_doc ( m, doc, markers_doc, NULL, sandbox, menu_id );
836         }
838         gtk_widget_set_sensitive(mnu, TRUE);
839     }
841     gtk_object_set_data(GTK_OBJECT(mnu), "menu_id", menu_id);
842     gtk_option_menu_set_menu(GTK_OPTION_MENU(mnu), m);
844     /* Set history */
845     gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 0);
847     return mnu;
851 /**
852  * Handles when user selects one of the markers from the marker menu.
853  * Defines a uri string to refer to it, then applies it to all selected
854  * items in the current desktop.
855  */
856 static void
857 sp_marker_select(GtkOptionMenu *mnu, GtkWidget *spw)
859     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
860         return;
861     }
863     SPDesktop *desktop = inkscape_active_desktop();
864     SPDocument *document = sp_desktop_document(desktop);
865     if (!document) {
866         return;
867     }
869     /* Get Marker */
870     if (!g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))),
871                            "marker"))
872     {
873         return;
874     }
875     gchar *markid = (gchar *) g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))),
876                                                 "marker");
877     gchar *marker = "";
878     if (strcmp(markid, "none")){
879        gchar *stockid = (gchar *) g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))),
880                                                 "stockid");
882        gchar *markurn = markid;
883        if (!strcmp(stockid,"true")) markurn = g_strconcat("urn:inkscape:marker:",markid,NULL);
884        SPObject *mark = get_stock_item(markurn);
885        if (mark) {
886             Inkscape::XML::Node *repr = SP_OBJECT_REPR(mark);
887             marker = g_strconcat("url(#", repr->attribute("id"), ")", NULL);
888         }
889     } else {
890         marker = markid;
891     }
893     SPCSSAttr *css = sp_repr_css_attr_new();
894     gchar *menu_id = (gchar *) g_object_get_data(G_OBJECT(mnu), "menu_id");
895     sp_repr_css_set_property(css, menu_id, marker);
897      Inkscape::Selection *selection = sp_desktop_selection(desktop);
898      GSList const *items = selection->itemList();
899      for (; items != NULL; items = items->next) {
900          SPItem *item = (SPItem *) items->data;
901          if (!SP_IS_SHAPE(item) || SP_IS_RECT(item)) // can't set marker to rect, until it's converted to using <path>
902              continue;
903          Inkscape::XML::Node *selrepr = SP_OBJECT_REPR((SPItem *) items->data);
904          if (selrepr) {
905              sp_repr_css_change_recursive(selrepr, css, "style");
906          }
907          SP_OBJECT(items->data)->requestModified(SP_OBJECT_MODIFIED_FLAG);
908          SP_OBJECT(items->data)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
909      }
911     sp_repr_css_attr_unref(css);
913     sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE, 
914                      _("Set markers"));
917 /**
918  * Sets the stroke width units for all selected items.
919  * Also handles absolute and dimensionless units.
920  */
921 static gboolean stroke_width_set_unit(SPUnitSelector *,
922                                                  SPUnit const *old,
923                                                  SPUnit const *new_units,
924                                                  GObject *spw)
926     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
928     if (!desktop) {
929         return FALSE;
930     }
932     Inkscape::Selection *selection = sp_desktop_selection (desktop);
934     if (selection->isEmpty())
935         return FALSE;
937     GSList const *objects = selection->itemList();
939     if ((old->base == SP_UNIT_ABSOLUTE || old->base == SP_UNIT_DEVICE) &&
940        (new_units->base == SP_UNIT_DIMENSIONLESS)) {
942         /* Absolute to percentage */
943         g_object_set_data (spw, "update", GUINT_TO_POINTER (TRUE));
945         GtkAdjustment *a = GTK_ADJUSTMENT(g_object_get_data (spw, "width"));
946         float w = sp_units_get_pixels (a->value, *old);
948         gdouble average = stroke_average_width (objects);
950         if (average == NR_HUGE || average == 0)
951             return FALSE;
953         gtk_adjustment_set_value (a, 100.0 * w / average);
955         g_object_set_data (spw, "update", GUINT_TO_POINTER (FALSE));
956         return TRUE;
958     } else if ((old->base == SP_UNIT_DIMENSIONLESS) &&
959               (new_units->base == SP_UNIT_ABSOLUTE || new_units->base == SP_UNIT_DEVICE)) {
961         /* Percentage to absolute */
962         g_object_set_data (spw, "update", GUINT_TO_POINTER (TRUE));
964         GtkAdjustment *a = GTK_ADJUSTMENT(g_object_get_data (spw, "width"));
966         gdouble average = stroke_average_width (objects);
968         gtk_adjustment_set_value (a, sp_pixels_get_units (0.01 * a->value * average, *new_units));
970         g_object_set_data (spw, "update", GUINT_TO_POINTER (FALSE));
971         return TRUE;
972     }
974     return FALSE;
978 /**
979  * \brief  Creates a new widget for the line stroke style.
980  *
981  */
982 GtkWidget *
983 sp_stroke_style_line_widget_new(void)
985     GtkWidget *spw, *f, *t, *hb, *sb, *us, *tb, *ds;
986     GtkObject *a;
988     GtkTooltips *tt = gtk_tooltips_new();
990     spw = sp_widget_new_global(INKSCAPE);
992     f = gtk_hbox_new (FALSE, 0);
993     gtk_widget_show(f);
994     gtk_container_add(GTK_CONTAINER(spw), f);
996     t = gtk_table_new(3, 6, FALSE);
997     gtk_widget_show(t);
998     gtk_container_set_border_width(GTK_CONTAINER(t), 4);
999     gtk_table_set_row_spacings(GTK_TABLE(t), 4);
1000     gtk_container_add(GTK_CONTAINER(f), t);
1001     gtk_object_set_data(GTK_OBJECT(spw), "stroke", t);
1003     gint i = 0;
1005     /* Stroke width */
1006     spw_label(t, _("Width:"), 0, i);
1008     hb = spw_hbox(t, 3, 1, i);
1010 // TODO: when this is gtkmmified, use an Inkscape::UI::Widget::ScalarUnit instead of the separate
1011 // spinbutton and unit selector for stroke width. In sp_stroke_style_line_update, use
1012 // setHundredPercent to remember the aeraged width corresponding to 100%. Then the
1013 // stroke_width_set_unit will be removed (because ScalarUnit takes care of conversions itself), and
1014 // with it, the two remaining calls of stroke_average_width, allowing us to get rid of that
1015 // function in desktop-style.
1017     a = gtk_adjustment_new(1.0, 0.0, 1000.0, 0.1, 10.0, 10.0);
1018     gtk_object_set_data(GTK_OBJECT(spw), "width", a);
1019     sb = gtk_spin_button_new(GTK_ADJUSTMENT(a), 0.1, 3);
1020     gtk_tooltips_set_tip(tt, sb, _("Stroke width"), NULL);
1021     gtk_widget_show(sb);
1023     sp_dialog_defocus_on_enter(sb);
1025     gtk_box_pack_start(GTK_BOX(hb), sb, FALSE, FALSE, 0);
1026     us = sp_unit_selector_new(SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE);
1027     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1028     if (desktop)
1029         sp_unit_selector_set_unit (SP_UNIT_SELECTOR(us), sp_desktop_namedview(desktop)->doc_units);
1030     sp_unit_selector_add_unit(SP_UNIT_SELECTOR(us), &sp_unit_get_by_id(SP_UNIT_PERCENT), 0);
1031     g_signal_connect ( G_OBJECT (us), "set_unit", G_CALLBACK (stroke_width_set_unit), spw );
1032     gtk_widget_show(us);
1033     sp_unit_selector_add_adjustment( SP_UNIT_SELECTOR(us), GTK_ADJUSTMENT(a) );
1034     gtk_box_pack_start(GTK_BOX(hb), us, FALSE, FALSE, 0);
1035     gtk_object_set_data(GTK_OBJECT(spw), "units", us);
1037     gtk_signal_connect( GTK_OBJECT(a), "value_changed", GTK_SIGNAL_FUNC(sp_stroke_style_width_changed), spw );
1038     i++;
1040     /* Join type */
1041     // TRANSLATORS: The line join style specifies the shape to be used at the
1042     //  corners of paths. It can be "miter", "round" or "bevel".
1043     spw_label(t, _("Join:"), 0, i);
1045     hb = spw_hbox(t, 3, 1, i);
1047     tb = NULL;
1049     tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_JOIN_MITER,
1050                                 hb, spw, "join", "miter");
1052     // TRANSLATORS: Miter join: joining lines with a sharp (pointed) corner.
1053     //  For an example, draw a triangle with a large stroke width and modify the
1054     //  "Join" option (in the Fill and Stroke dialog).
1055     gtk_tooltips_set_tip(tt, tb, _("Miter join"), NULL);
1057     tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_JOIN_ROUND,
1058                                 hb, spw, "join", "round");
1060     // TRANSLATORS: Round join: joining lines with a rounded corner.
1061     //  For an example, draw a triangle with a large stroke width and modify the
1062     //  "Join" option (in the Fill and Stroke dialog).
1063     gtk_tooltips_set_tip(tt, tb, _("Round join"), NULL);
1065     tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_JOIN_BEVEL,
1066                                 hb, spw, "join", "bevel");
1068     // TRANSLATORS: Bevel join: joining lines with a blunted (flattened) corner.
1069     //  For an example, draw a triangle with a large stroke width and modify the
1070     //  "Join" option (in the Fill and Stroke dialog).
1071     gtk_tooltips_set_tip(tt, tb, _("Bevel join"), NULL);
1073     i++;
1075     /* Miterlimit  */
1076     // TRANSLATORS: Miter limit: only for "miter join", this limits the length
1077     //  of the sharp "spike" when the lines connect at too sharp an angle.
1078     // When two line segments meet at a sharp angle, a miter join results in a
1079     //  spike that extends well beyond the connection point. The purpose of the
1080     //  miter limit is to cut off such spikes (i.e. convert them into bevels)
1081     //  when they become too long.
1082     spw_label(t, _("Miter limit:"), 0, i);
1084     hb = spw_hbox(t, 3, 1, i);
1086     a = gtk_adjustment_new(4.0, 0.0, 100.0, 0.1, 10.0, 10.0);
1087     gtk_object_set_data(GTK_OBJECT(spw), "miterlimit", a);
1089     sb = gtk_spin_button_new(GTK_ADJUSTMENT(a), 0.1, 2);
1090     gtk_tooltips_set_tip(tt, sb, _("Maximum length of the miter (in units of stroke width)"), NULL);
1091     gtk_widget_show(sb);
1092     gtk_object_set_data(GTK_OBJECT(spw), "miterlimit_sb", sb);
1093     sp_dialog_defocus_on_enter(sb);
1095     gtk_box_pack_start(GTK_BOX(hb), sb, FALSE, FALSE, 0);
1097     gtk_signal_connect( GTK_OBJECT(a), "value_changed",
1098                         GTK_SIGNAL_FUNC(sp_stroke_style_miterlimit_changed), spw );
1099     i++;
1101     /* Cap type */
1102     // TRANSLATORS: cap type specifies the shape for the ends of lines
1103     spw_label(t, _("Cap:"), 0, i);
1105     hb = spw_hbox(t, 3, 1, i);
1107     tb = NULL;
1109     tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_CAP_BUTT,
1110                                 hb, spw, "cap", "butt");
1112     // TRANSLATORS: Butt cap: the line shape does not extend beyond the end point
1113     //  of the line; the ends of the line are square
1114     gtk_tooltips_set_tip(tt, tb, _("Butt cap"), NULL);
1116     tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_CAP_ROUND,
1117                                 hb, spw, "cap", "round");
1119     // TRANSLATORS: Round cap: the line shape extends beyond the end point of the
1120     //  line; the ends of the line are rounded
1121     gtk_tooltips_set_tip(tt, tb, _("Round cap"), NULL);
1123     tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_CAP_SQUARE,
1124                                 hb, spw, "cap", "square");
1126     // TRANSLATORS: Square cap: the line shape extends beyond the end point of the
1127     //  line; the ends of the line are square
1128     gtk_tooltips_set_tip(tt, tb, _("Square cap"), NULL);
1130     i++;
1133     /* Dash */
1134     spw_label(t, _("Dashes:"), 0, i);
1135     ds = sp_dash_selector_new( inkscape_get_repr( INKSCAPE,
1136                                                   "palette.dashes") );
1138     gtk_widget_show(ds);
1139     gtk_table_attach( GTK_TABLE(t), ds, 1, 4, i, i+1,
1140                       (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ),
1141                       (GtkAttachOptions)0, 0, 0 );
1142     gtk_object_set_data(GTK_OBJECT(spw), "dash", ds);
1143     gtk_signal_connect( GTK_OBJECT(ds), "changed",
1144                         GTK_SIGNAL_FUNC(sp_stroke_style_line_dash_changed),
1145                         spw );
1146     i++;
1148     /* Drop down marker selectors*/
1150     // doing this here once, instead of for each preview, to speed things up
1151     SPDocument *sandbox = ink_markers_preview_doc ();
1153     // TRANSLATORS: Path markers are an SVG feature that allows you to attach arbitrary shapes
1154     // (arrowheads, bullets, faces, whatever) to the start, end, or middle nodes of a path.
1155     spw_label(t, _("Start Markers:"), 0, i);
1156     GtkWidget *mnu  = ink_marker_menu( spw ,"marker-start", sandbox);
1157     gtk_signal_connect( GTK_OBJECT(mnu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw );
1158     gtk_widget_show(mnu);
1159     gtk_table_attach( GTK_TABLE(t), mnu, 1, 4, i, i+1,
1160                       (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ),
1161                       (GtkAttachOptions)0, 0, 0 );
1162     gtk_object_set_data(GTK_OBJECT(spw), "start_mark_menu", mnu);
1164     i++;
1165     spw_label(t, _("Mid Markers:"), 0, i);
1166     mnu = NULL;
1167     mnu  = ink_marker_menu( spw ,"marker-mid", sandbox);
1168     gtk_signal_connect( GTK_OBJECT(mnu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw );
1169     gtk_widget_show(mnu);
1170     gtk_table_attach( GTK_TABLE(t), mnu, 1, 4, i, i+1,
1171                       (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ),
1172                       (GtkAttachOptions)0, 0, 0 );
1173     gtk_object_set_data(GTK_OBJECT(spw), "mid_mark_menu", mnu);
1175     i++;
1176     spw_label(t, _("End Markers:"), 0, i);
1177     mnu = NULL;
1178     mnu  = ink_marker_menu( spw ,"marker-end", sandbox);
1179     gtk_signal_connect( GTK_OBJECT(mnu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw );
1180     gtk_widget_show(mnu);
1181     gtk_table_attach( GTK_TABLE(t), mnu, 1, 4, i, i+1,
1182                       (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ),
1183                       (GtkAttachOptions)0, 0, 0 );
1184     gtk_object_set_data(GTK_OBJECT(spw), "end_mark_menu", mnu);
1186     i++;
1188     gtk_signal_connect( GTK_OBJECT(spw), "construct",
1189                         GTK_SIGNAL_FUNC(sp_stroke_style_line_construct),
1190                         NULL );
1191     gtk_signal_connect( GTK_OBJECT(spw), "modify_selection",
1192                         GTK_SIGNAL_FUNC(sp_stroke_style_line_selection_modified),
1193                         NULL );
1194     gtk_signal_connect( GTK_OBJECT(spw), "change_selection",
1195                         GTK_SIGNAL_FUNC(sp_stroke_style_line_selection_changed),
1196                         NULL );
1198     sp_stroke_style_line_update( SP_WIDGET(spw), desktop ? sp_desktop_selection(desktop) : NULL);
1200     return spw;
1204 /**
1205  * Callback for when the stroke style widget is called.  It causes
1206  * the stroke line style to be updated.
1207  */
1208 static void
1209 sp_stroke_style_line_construct(SPWidget *spw, gpointer data)
1212 #ifdef SP_SS_VERBOSE
1213     g_print( "Stroke style widget constructed: inkscape %p repr %p\n",
1214              spw->inkscape, spw->repr );
1215 #endif
1216     if (spw->inkscape) {
1217         sp_stroke_style_line_update(spw,
1218                                     ( SP_ACTIVE_DESKTOP
1219                                       ? sp_desktop_selection(SP_ACTIVE_DESKTOP)
1220                                       : NULL ));
1221     } 
1224 /**
1225  * Callback for when stroke style widget is modified.  
1226  * Triggers update action.
1227  */
1228 static void
1229 sp_stroke_style_line_selection_modified ( SPWidget *spw,
1230                                        Inkscape::Selection *selection,
1231                                        guint flags,
1232                                        gpointer data )
1234     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) {
1235         sp_stroke_style_line_update (spw, selection);
1236     }
1240 /**
1241  * Callback for when stroke style widget is changed.
1242  * Triggers update action.
1243  */
1244 static void
1245 sp_stroke_style_line_selection_changed ( SPWidget *spw,
1246                                        Inkscape::Selection *selection,
1247                                        gpointer data )
1249     sp_stroke_style_line_update (spw, selection);
1253 /**
1254  * Sets selector widgets' dash style from an SPStyle object.
1255  */
1256 static void
1257 sp_dash_selector_set_from_style (GtkWidget *dsel, SPStyle *style)
1259     if (style->stroke_dash.n_dash > 0) {
1260         double d[64];
1261         int len = MIN(style->stroke_dash.n_dash, 64);
1262         for (int i = 0; i < len; i++) {
1263             if (style->stroke_width.computed != 0)
1264                 d[i] = style->stroke_dash.dash[i] / style->stroke_width.computed;
1265             else
1266                 d[i] = style->stroke_dash.dash[i]; // is there a better thing to do for stroke_width==0?
1267         }
1268         sp_dash_selector_set_dash(SP_DASH_SELECTOR(dsel), len, d,
1269                style->stroke_width.computed != 0?
1270                     style->stroke_dash.offset / style->stroke_width.computed  :
1271                     style->stroke_dash.offset);
1272     } else {
1273         sp_dash_selector_set_dash(SP_DASH_SELECTOR(dsel), 0, NULL, 0.0);
1274     }
1277 /**
1278  * Sets the join type for a line, and updates the stroke style widget's buttons
1279  */
1280 static void
1281 sp_jointype_set (SPWidget *spw, unsigned const jointype)
1283     GtkWidget *tb = NULL;
1284     switch (jointype) {
1285         case SP_STROKE_LINEJOIN_MITER:
1286             tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_MITER));
1287             break;
1288         case SP_STROKE_LINEJOIN_ROUND:
1289             tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_ROUND));
1290             break;
1291         case SP_STROKE_LINEJOIN_BEVEL:
1292             tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_BEVEL));
1293             break;
1294         default:
1295             break;
1296     }
1297     sp_stroke_style_set_join_buttons (spw, tb);
1300 /**
1301  * Sets the cap type for a line, and updates the stroke style widget's buttons
1302  */
1303 static void
1304 sp_captype_set (SPWidget *spw, unsigned const captype)
1306     GtkWidget *tb = NULL;
1307     switch (captype) {
1308         case SP_STROKE_LINECAP_BUTT:
1309             tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_BUTT));
1310             break;
1311         case SP_STROKE_LINECAP_ROUND:
1312             tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_ROUND));
1313             break;
1314         case SP_STROKE_LINECAP_SQUARE:
1315             tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_SQUARE));
1316             break;
1317         default:
1318             break;
1319     }
1320     sp_stroke_style_set_cap_buttons (spw, tb);
1323 /**
1324  * Callback for when stroke style widget is updated, including markers, cap type,
1325  * join type, etc.
1326  */
1327 static void
1328 sp_stroke_style_line_update(SPWidget *spw, Inkscape::Selection *sel)
1330     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1331         return;
1332     }
1334     gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(TRUE));
1336     GtkWidget *sset = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "stroke"));
1337     GtkObject *width = GTK_OBJECT(gtk_object_get_data(GTK_OBJECT(spw), "width"));
1338     GtkObject *ml = GTK_OBJECT(gtk_object_get_data(GTK_OBJECT(spw), "miterlimit"));
1339     GtkWidget *us = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "units"));
1340     GtkWidget *dsel = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "dash"));
1342     // create temporary style
1343     SPStyle *query = sp_style_new ();
1344     // query into it
1345     int result_sw = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEWIDTH); 
1346     int result_ml = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEMITERLIMIT); 
1347     int result_cap = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKECAP); 
1348     int result_join = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEJOIN); 
1350     if (result_sw == QUERY_STYLE_NOTHING) {
1351         /* No objects stroked, set insensitive */
1352         gtk_widget_set_sensitive(sset, FALSE);
1354         gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(FALSE));
1355         return;
1356     } else {
1357         gtk_widget_set_sensitive(sset, TRUE);
1359         SPUnit const *unit = sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us));
1361         if (result_sw == QUERY_STYLE_MULTIPLE_AVERAGED) {
1362             sp_unit_selector_set_unit(SP_UNIT_SELECTOR(us), &sp_unit_get_by_id(SP_UNIT_PERCENT));
1363         } else {
1364             // same width, or only one object; no sense to keep percent, switch to absolute
1365             if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) {
1366                 sp_unit_selector_set_unit(SP_UNIT_SELECTOR(us), sp_desktop_namedview(SP_ACTIVE_DESKTOP)->doc_units);
1367             }
1368         }
1370         unit = sp_unit_selector_get_unit (SP_UNIT_SELECTOR (us));
1372         if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) {
1373             double avgwidth = sp_pixels_get_units (query->stroke_width.computed, *unit);
1374             gtk_adjustment_set_value(GTK_ADJUSTMENT(width), avgwidth);
1375         } else {
1376             gtk_adjustment_set_value(GTK_ADJUSTMENT(width), 100);
1377         }
1378     }
1380     if (result_ml != QUERY_STYLE_NOTHING)
1381         gtk_adjustment_set_value(GTK_ADJUSTMENT(ml), query->stroke_miterlimit.value); // TODO: reflect averagedness?
1383     if (result_join != QUERY_STYLE_MULTIPLE_DIFFERENT) {
1384         sp_jointype_set (spw, query->stroke_linejoin.value);
1385     } else {
1386         sp_stroke_style_set_join_buttons(spw, NULL);
1387     }
1389     if (result_cap != QUERY_STYLE_MULTIPLE_DIFFERENT) {
1390         sp_captype_set (spw, query->stroke_linecap.value);
1391     } else {
1392         sp_stroke_style_set_cap_buttons(spw, NULL);
1393     }
1395     g_free (query);
1397     if (!sel || sel->isEmpty())
1398         return;
1400     GSList const *objects = sel->itemList();
1401     SPObject * const object = SP_OBJECT(objects->data);
1402     SPStyle * const style = SP_OBJECT_STYLE(object);
1404     /* Markers */
1405     sp_stroke_style_update_marker_menus(spw, objects); // FIXME: make this desktop query too
1407     /* Dash */
1408     sp_dash_selector_set_from_style (dsel, style); // FIXME: make this desktop query too
1410     gtk_widget_set_sensitive(sset, TRUE);
1412     gtk_object_set_data(GTK_OBJECT(spw), "update",
1413                         GINT_TO_POINTER(FALSE));
1416 /**
1417  * Sets a line's dash properties in a CSS style object.
1418  */
1419 static void
1420 sp_stroke_style_set_scaled_dash(SPCSSAttr *css,
1421                                 int ndash, double *dash, double offset,
1422                                 double scale)
1424     if (ndash > 0) {
1425         Inkscape::CSSOStringStream osarray;
1426         for (int i = 0; i < ndash; i++) {
1427             osarray << dash[i] * scale;
1428             if (i < (ndash - 1)) {
1429                 osarray << ",";
1430             }
1431         }
1432         sp_repr_css_set_property(css, "stroke-dasharray", osarray.str().c_str());
1434         Inkscape::CSSOStringStream osoffset;
1435         osoffset << offset * scale;
1436         sp_repr_css_set_property(css, "stroke-dashoffset", osoffset.str().c_str());
1437     } else {
1438         sp_repr_css_set_property(css, "stroke-dasharray", "none");
1439         sp_repr_css_set_property(css, "stroke-dashoffset", NULL);
1440     }
1443 /**
1444  * Sets line properties like width, dashes, markers, etc. on all currently selected items.
1445  */
1446 static void
1447 sp_stroke_style_scale_line(SPWidget *spw)
1449     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1450         return;
1451     }
1453     gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(TRUE));
1455     GtkAdjustment *wadj = GTK_ADJUSTMENT(gtk_object_get_data(GTK_OBJECT(spw), "width"));
1456     SPUnitSelector *us = SP_UNIT_SELECTOR(gtk_object_get_data(GTK_OBJECT(spw), "units"));
1457     SPDashSelector *dsel = SP_DASH_SELECTOR(gtk_object_get_data(GTK_OBJECT(spw), "dash"));
1458     GtkAdjustment *ml = GTK_ADJUSTMENT(gtk_object_get_data(GTK_OBJECT(spw), "miterlimit"));
1460     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1461     SPDocument *document = sp_desktop_document (desktop);
1462     Inkscape::Selection *selection = sp_desktop_selection (desktop);
1464     GSList const *items = selection->itemList();
1466     /* TODO: Create some standardized method */
1467     SPCSSAttr *css = sp_repr_css_attr_new();
1469     if (items) {
1471         double width_typed = wadj->value;
1472         double const miterlimit = ml->value;
1474         SPUnit const *const unit = sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us));
1476         double *dash, offset;
1477         int ndash;
1478         sp_dash_selector_get_dash(dsel, &ndash, &dash, &offset);
1480         for (GSList const *i = items; i != NULL; i = i->next) {
1481             /* Set stroke width */
1482             double width;
1483             if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) {
1484                 width = sp_units_get_pixels (width_typed, *unit);
1485             } else { // percentage
1486                 gdouble old_w = SP_OBJECT_STYLE (i->data)->stroke_width.computed;
1487                 width = old_w * width_typed / 100;
1488             }
1490             {
1491                 Inkscape::CSSOStringStream os_width;
1492                 os_width << width;
1493                 sp_repr_css_set_property(css, "stroke-width", os_width.str().c_str());
1494             }
1496             {
1497                 Inkscape::CSSOStringStream os_ml;
1498                 os_ml << miterlimit;
1499                 sp_repr_css_set_property(css, "stroke-miterlimit", os_ml.str().c_str());
1500             }
1502             /* Set dash */
1503             sp_stroke_style_set_scaled_dash(css, ndash, dash, offset, width);
1505             sp_desktop_apply_css_recursive (SP_OBJECT(i->data), css, true);
1506         }
1508         g_free(dash);
1510         if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) {
1511             // reset to 100 percent
1512             gtk_adjustment_set_value (wadj, 100.0);
1513         }
1515     }
1517     // we have already changed the items, so set style without changing selection
1518     // FIXME: move the above stroke-setting stuff, including percentages, to desktop-style
1519     sp_desktop_set_style (desktop, css, false);
1521     sp_repr_css_attr_unref(css);
1523     sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE, 
1524                      _("Set stroke style"));
1526     gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(FALSE));
1530 /**
1531  * Callback for when the stroke style's width changes.  
1532  * Causes all line styles to be applied to all selected items.
1533  */
1534 static void
1535 sp_stroke_style_width_changed(GtkAdjustment *adj, SPWidget *spw)
1537     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1538         return;
1539     }
1541     sp_stroke_style_scale_line(spw);
1544 /**
1545  * Callback for when the stroke style's miterlimit changes.  
1546  * Causes all line styles to be applied to all selected items.
1547  */
1548 static void
1549 sp_stroke_style_miterlimit_changed(GtkAdjustment *adj, SPWidget *spw)
1551     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1552         return;
1553     }
1555     sp_stroke_style_scale_line(spw);
1558 /**
1559  * Callback for when the stroke style's dash changes.  
1560  * Causes all line styles to be applied to all selected items.
1561  */
1562 static void
1563 sp_stroke_style_line_dash_changed(SPDashSelector *dsel, SPWidget *spw)
1565     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1566         return;
1567     }
1569     sp_stroke_style_scale_line(spw);
1574 /**
1575  * \brief  This routine handles toggle events for buttons in the stroke style
1576  *         dialog.
1577  * When activated, this routine gets the data for the various widgets, and then
1578  * calls the respective routines to update css properties, etc.
1579  *
1580  */
1581 static void
1582 sp_stroke_style_any_toggled(GtkToggleButton *tb, SPWidget *spw)
1584     if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1585         return;
1586     }
1588     if (gtk_toggle_button_get_active(tb)) {
1590         gchar const *join
1591             = static_cast<gchar const *>(gtk_object_get_data(GTK_OBJECT(tb), "join"));
1592         gchar const *cap
1593             = static_cast<gchar const *>(gtk_object_get_data(GTK_OBJECT(tb), "cap"));
1595         if (join) {
1596             GtkWidget *ml = GTK_WIDGET(g_object_get_data(G_OBJECT(spw), "miterlimit_sb"));
1597             gtk_widget_set_sensitive (ml, !strcmp(join, "miter"));
1598         }
1600         SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1602         /* TODO: Create some standardized method */
1603         SPCSSAttr *css = sp_repr_css_attr_new();
1605         if (join) {
1606             sp_repr_css_set_property(css, "stroke-linejoin", join);
1608             sp_desktop_set_style (desktop, css);
1610             sp_stroke_style_set_join_buttons(spw, GTK_WIDGET(tb));
1611         } else if (cap) {
1612             sp_repr_css_set_property(css, "stroke-linecap", cap);
1614             sp_desktop_set_style (desktop, css);
1616             sp_stroke_style_set_cap_buttons(spw, GTK_WIDGET(tb));
1617         }
1619         sp_repr_css_attr_unref(css);
1621         sp_document_done(sp_desktop_document(desktop), SP_VERB_DIALOG_FILL_STROKE,
1622                          _("Set stroke style"));
1623     }
1627 /**
1628  * Updates the join style toggle buttons
1629  */
1630 static void
1631 sp_stroke_style_set_join_buttons(SPWidget *spw, GtkWidget *active)
1633     GtkWidget *tb;
1635     tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1636                                          INKSCAPE_STOCK_JOIN_MITER) );
1637     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1639     GtkWidget *ml = GTK_WIDGET(g_object_get_data(G_OBJECT(spw), "miterlimit_sb"));
1640     gtk_widget_set_sensitive(ml, (active == tb));
1642     tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1643                                          INKSCAPE_STOCK_JOIN_ROUND) );
1644     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1645     tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1646                                          INKSCAPE_STOCK_JOIN_BEVEL) );
1647     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1652 /**
1653  * Updates the cap style toggle buttons
1654  */
1655 static void
1656 sp_stroke_style_set_cap_buttons(SPWidget *spw, GtkWidget *active)
1658     GtkWidget *tb;
1660     tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1661                                          INKSCAPE_STOCK_CAP_BUTT));
1662     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1663     tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1664                                          INKSCAPE_STOCK_CAP_ROUND) );
1665     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1666     tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw),
1667                                          INKSCAPE_STOCK_CAP_SQUARE) );
1668     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb));
1671 /**
1672  * Sets the current marker in the marker menu.
1673  */
1674 static void
1675 ink_marker_menu_set_current(SPObject *marker, GtkOptionMenu *mnu)
1677     gtk_object_set_data(GTK_OBJECT(mnu), "update", GINT_TO_POINTER(TRUE));
1679     GtkMenu *m = GTK_MENU(gtk_option_menu_get_menu(mnu));
1680     if (marker != NULL) {
1681         bool mark_is_stock = false;
1682         if (SP_OBJECT_REPR(marker)->attribute("inkscape:stockid"))
1683             mark_is_stock = true;
1685         gchar *markname;
1686         if (mark_is_stock)
1687             markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("inkscape:stockid"));
1688         else
1689             markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("id"));
1691         int markpos = 0;
1692         GList *kids = GTK_MENU_SHELL(m)->children;
1693         int i = 0;
1694         for (; kids != NULL; kids = kids->next) {
1695             gchar *mark = (gchar *) g_object_get_data(G_OBJECT(kids->data), "marker");
1696             if ( mark && strcmp(mark, markname) == 0 ) {
1697                 if ( mark_is_stock && !strcmp((gchar *) g_object_get_data(G_OBJECT(kids->data), "stockid"), "true"))
1698                     markpos = i;
1699                 if ( !mark_is_stock && !strcmp((gchar *) g_object_get_data(G_OBJECT(kids->data), "stockid"), "false"))
1700                     markpos = i;
1701             }
1702             i++;
1703         }
1704         gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), markpos);
1706         g_free (markname);
1707     }
1708     else {
1709         gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 0);
1710     }
1711     gtk_object_set_data(GTK_OBJECT(mnu), "update", GINT_TO_POINTER(FALSE));
1714 /**
1715  * Updates the marker menus to highlight the appropriate marker and scroll to 
1716  * that marker.
1717  */
1718 static void
1719 sp_stroke_style_update_marker_menus( SPWidget *spw,
1720                                      GSList const *objects)
1722     struct { char const *key; int loc; } const keyloc[] = {
1723         { "start_mark_menu", SP_MARKER_LOC_START },
1724         { "mid_mark_menu", SP_MARKER_LOC_MID },
1725         { "end_mark_menu", SP_MARKER_LOC_END }
1726     };
1728     bool all_texts = true;
1729     for (GSList *i = (GSList *) objects; i != NULL; i = i->next) {
1730         if (!SP_IS_TEXT (i->data)) {
1731             all_texts = false;
1732         }
1733     }
1735     for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) {
1736         GtkOptionMenu *mnu = (GtkOptionMenu *) g_object_get_data(G_OBJECT(spw), keyloc[i].key);
1737         if (all_texts) {
1738             // Per SVG spec, text objects cannot have markers; disable menus if only texts are selected
1739             gtk_widget_set_sensitive (GTK_WIDGET(mnu), FALSE);
1740         } else {
1741             gtk_widget_set_sensitive (GTK_WIDGET(mnu), TRUE);
1742         }
1743     }
1745     // We show markers of the first object in the list only
1746     // FIXME: use the first in the list that has the marker of each type, if any
1747     SPObject *object = SP_OBJECT(objects->data);
1749     for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) {
1750         // For all three marker types,
1752         // find the corresponding menu
1753         GtkOptionMenu *mnu = (GtkOptionMenu *) g_object_get_data(G_OBJECT(spw), keyloc[i].key);
1755         // Quit if we're in update state
1756         if (gtk_object_get_data(GTK_OBJECT(mnu), "update")) {
1757             return;
1758         }
1760         if (object->style->marker[keyloc[i].loc].value != NULL && !all_texts) {
1761             // If the object has this type of markers,
1763             // Extract the name of the marker that the object uses
1764             SPObject *marker = ink_extract_marker_name(object->style->marker[keyloc[i].loc].value);
1765             // Scroll the menu to that marker
1766             ink_marker_menu_set_current (marker, mnu);
1768         } else {
1769             gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 0);
1770         }
1771     }
1775 /**
1776  * Extract the actual name of the link
1777  * e.g. get mTriangle from url(#mTriangle).
1778  * \return Buffer containing the actual name, allocated from GLib;
1779  * the caller should free the buffer when they no longer need it.
1780  */
1781 static SPObject*
1782 ink_extract_marker_name(gchar const *n)
1784     gchar const *p = n;
1785     while (*p != '\0' && *p != '#') {
1786         p++;
1787     }
1789     if (*p == '\0' || p[1] == '\0') {
1790         return NULL;
1791     }
1793     p++;
1794     int c = 0;
1795     while (p[c] != '\0' && p[c] != ')') {
1796         c++;
1797     }
1799     if (p[c] == '\0') {
1800         return NULL;
1801     }
1803     gchar* b = g_strdup(p);
1804     b[c] = '\0';
1807     SPDesktop *desktop = inkscape_active_desktop();
1808     SPDocument *doc = sp_desktop_document(desktop);
1809     SPObject *marker = doc->getObjectById(b);
1810     return marker;
1814 /*
1815   Local Variables:
1816   mode:c++
1817   c-file-style:"stroustrup"
1818   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1819   indent-tabs-mode:nil
1820   fill-column:99
1821   End:
1822 */
1823 // vim: filetype=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :