0dedcfe4cdc5cb31f594e51b8068103349e1067e
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)));
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;
605 }
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)
614 {
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;
629 }
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)
638 {
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 }
677 }
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)
688 {
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);
730 }
733 /**
734 * Returns a new document containing default start, mid, and end markers.
735 */
736 SPDocument *
737 ink_markers_preview_doc ()
738 {
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);
766 }
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)
774 {
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;
848 }
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)
858 {
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"));
915 }
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)
925 {
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;
975 }
978 /**
979 * \brief Creates a new widget for the line stroke style.
980 *
981 */
982 GtkWidget *
983 sp_stroke_style_line_widget_new(void)
984 {
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;
1201 }
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)
1210 {
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 }
1222 }
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 )
1233 {
1234 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) {
1235 sp_stroke_style_line_update (spw, selection);
1236 }
1238 }
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 )
1248 {
1249 sp_stroke_style_line_update (spw, selection);
1250 }
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)
1258 {
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 }
1275 }
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)
1282 {
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);
1298 }
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)
1305 {
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);
1321 }
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)
1329 {
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));
1414 }
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)
1423 {
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 }
1441 }
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)
1448 {
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));
1527 }
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)
1536 {
1537 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1538 return;
1539 }
1541 sp_stroke_style_scale_line(spw);
1542 }
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)
1550 {
1551 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1552 return;
1553 }
1555 sp_stroke_style_scale_line(spw);
1556 }
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)
1564 {
1565 if (gtk_object_get_data(GTK_OBJECT(spw), "update")) {
1566 return;
1567 }
1569 sp_stroke_style_scale_line(spw);
1570 }
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)
1583 {
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 }
1624 }
1627 /**
1628 * Updates the join style toggle buttons
1629 */
1630 static void
1631 sp_stroke_style_set_join_buttons(SPWidget *spw, GtkWidget *active)
1632 {
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));
1648 }
1652 /**
1653 * Updates the cap style toggle buttons
1654 */
1655 static void
1656 sp_stroke_style_set_cap_buttons(SPWidget *spw, GtkWidget *active)
1657 {
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));
1669 }
1671 /**
1672 * Sets the current marker in the marker menu.
1673 */
1674 static void
1675 ink_marker_menu_set_current(SPObject *marker, GtkOptionMenu *mnu)
1676 {
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));
1712 }
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)
1721 {
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 }
1772 }
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)
1783 {
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;
1811 }
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 :