bab57b67906adeda000d66d8b3d06dc71894431a
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;
116 }
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)
123 {
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 }
131 }
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)
141 {
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 }
146 }
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 )
156 {
157 sp_stroke_style_paint_update (spw);
158 }
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 )
168 {
169 sp_stroke_style_paint_update (spw);
170 }
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)
178 {
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));
252 }
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 )
261 {
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);
271 }
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)
283 {
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 }
303 }
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)
311 {
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));
476 }
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)
521 {
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;
547 }
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)
558 {
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;
606 }
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)
615 {
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;
630 }
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)
639 {
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 }
678 }
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)
689 {
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);
731 }
734 /**
735 * Returns a new document containing default start, mid, and end markers.
736 */
737 SPDocument *
738 ink_markers_preview_doc ()
739 {
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);
767 }
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)
775 {
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;
849 }
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)
859 {
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"));
916 }
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)
926 {
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;
976 }
979 /**
980 * \brief Creates a new widget for the line stroke style.
981 *
982 */
983 GtkWidget *
984 sp_stroke_style_line_widget_new(void)
985 {
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;
1202 }
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)
1211 {
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 }
1223 }
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 )
1234 {
1235 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) {
1236 sp_stroke_style_line_update (spw, selection);
1237 }
1239 }
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 )
1249 {
1250 sp_stroke_style_line_update (spw, selection);
1251 }
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)
1259 {
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 }
1276 }
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)
1283 {
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);
1299 }
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)
1306 {
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);
1322 }
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)
1330 {
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));
1415 }
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)
1424 {
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 }
1442 }
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)
1449 {
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));
1528 }
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)
1537 {
1538 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1539 return;
1540 }
1542 sp_stroke_style_scale_line(spw);
1543 }
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)
1551 {
1552 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1553 return;
1554 }
1556 sp_stroke_style_scale_line(spw);
1557 }
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)
1565 {
1566 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1567 return;
1568 }
1570 sp_stroke_style_scale_line(spw);
1571 }
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)
1584 {
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 }
1625 }
1628 /**
1629 * Updates the join style toggle buttons
1630 */
1631 static void
1632 sp_stroke_style_set_join_buttons(SPWidget *spw, GtkWidget *active)
1633 {
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));
1649 }
1653 /**
1654 * Updates the cap style toggle buttons
1655 */
1656 static void
1657 sp_stroke_style_set_cap_buttons(SPWidget *spw, GtkWidget *active)
1658 {
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));
1670 }
1672 /**
1673 * Sets the current marker in the marker menu.
1674 */
1675 static void
1676 ink_marker_menu_set_current(SPObject *marker, GtkOptionMenu *mnu)
1677 {
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));
1713 }
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)
1722 {
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 }
1773 }
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)
1784 {
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;
1812 }
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 :