Code

Phase 2 - remove duplicated code and leave a single copy of each function.
[inkscape.git] / src / widgets / stroke-style.cpp
1 /** @file
2  * @brief  Stroke style dialog
3  */
4 /* Authors:
5  *   Lauris Kaplinski <lauris@kaplinski.com>
6  *   Bryce Harrington <brycehar@bryceharrington.org>
7  *   bulia byak <buliabyak@users.sf.net>
8  *   Maximilian Albert <maximilian.albert@gmail.com>
9  *   Josh Andler <scislac@users.sf.net>
10  *
11  * Copyright (C) 2001-2005 authors
12  * Copyright (C) 2001 Ximian, Inc.
13  * Copyright (C) 2004 John Cliff
14  * Copyright (C) 2008 Maximilian Albert (gtkmm-ification)
15  *
16  * Released under GNU GPL, read the file 'COPYING' for more information
17  */
19 #define noSP_SS_VERBOSE
21 #include <glib/gmem.h>
22 #include <gtk/gtk.h>
23 #include <glibmm/i18n.h>
25 #include "desktop-handles.h"
26 #include "desktop-style.h"
27 #include "dialogs/dialog-events.h"
28 #include "display/canvas-bpath.h" // for SP_STROKE_LINEJOIN_*
29 #include "display/nr-arena.h"
30 #include "display/nr-arena-item.h"
31 #include "document-private.h"
32 #include "gradient-chemistry.h"
33 #include "helper/stock-items.h"
34 #include "helper/unit-menu.h"
35 #include "helper/units.h"
36 #include "inkscape.h"
37 #include "io/sys.h"
38 #include "marker.h"
39 #include "path-prefix.h"
40 #include "selection.h"
41 #include "sp-linear-gradient.h"
42 #include "sp-namedview.h"
43 #include "sp-pattern.h"
44 #include "sp-radial-gradient.h"
45 #include "sp-rect.h"
46 #include "sp-text.h"
47 #include "style.h"
48 #include "svg/css-ostringstream.h"
49 #include "ui/cache/svg_preview_cache.h"
50 #include "ui/icon-names.h"
51 #include "widgets/dash-selector.h"
52 #include "widgets/icon.h"
53 #include "widgets/paint-selector.h"
54 #include "widgets/sp-widget.h"
55 #include "widgets/spw-utilities.h"
56 #include "xml/repr.h"
58 #include "stroke-style.h"
59 #include "fill-n-stroke-factory.h"
61 /** Marker selection option menus */
62 static Gtk::OptionMenu * marker_start_menu = NULL;
63 static Gtk::OptionMenu * marker_mid_menu = NULL;
64 static Gtk::OptionMenu * marker_end_menu = NULL;
66 sigc::connection marker_start_menu_connection;
67 sigc::connection marker_mid_menu_connection;
68 sigc::connection marker_end_menu_connection;
70 static SPObject *ink_extract_marker_name(gchar const *n, SPDocument *doc);
71 static void      ink_markers_menu_update(Gtk::Container* spw, SPMarkerLoc const which);
73 static Inkscape::UI::Cache::SvgPreview svg_preview_cache;
75 GtkWidget *sp_stroke_style_paint_widget_new(void)
76 {
77     return Inkscape::Widgets::createStyleWidget( STROKE );
78 }
81 /* Line */
83 static void sp_stroke_style_line_selection_modified(SPWidget *spw, Inkscape::Selection *selection, guint flags, gpointer data);
84 static void sp_stroke_style_line_selection_changed(SPWidget *spw, Inkscape::Selection *selection, gpointer data);
86 static void sp_stroke_style_line_update(Gtk::Container *spw, Inkscape::Selection *sel);
88 static void sp_stroke_style_set_join_buttons(Gtk::Container *spw, Gtk::ToggleButton *active);
90 static void sp_stroke_style_set_cap_buttons(Gtk::Container *spw, Gtk::ToggleButton *active);
92 static void sp_stroke_style_width_changed(Gtk::Container *spw);
93 static void sp_stroke_style_miterlimit_changed(Gtk::Container *spw);
94 static void sp_stroke_style_any_toggled(Gtk::ToggleButton *tb, Gtk::Container *spw);
95 static void sp_stroke_style_line_dash_changed(Gtk::Container *spw);
97 static void sp_stroke_style_update_marker_menus(Gtk::Container *spw, GSList const *objects);
100 /**
101  * Helper function for creating radio buttons.  This should probably be re-thought out
102  * when reimplementing this with Gtkmm.
103  */
104 static Gtk::RadioButton *
105 sp_stroke_radio_button(Gtk::RadioButton *tb, char const *icon,
106                        Gtk::HBox *hb, Gtk::Container *spw,
107                        gchar const *key, gchar const *data)
109     g_assert(icon != NULL);
110     g_assert(hb  != NULL);
111     g_assert(spw != NULL);
113     if (tb == NULL) {
114         tb = new Gtk::RadioButton();
115     } else {
116         Gtk::RadioButtonGroup grp = tb->get_group();
117         tb = new Gtk::RadioButton(grp);
118     }
120     tb->show();
121     tb->set_mode(false);
122     hb->pack_start(*tb, false, false, 0);
123     spw->set_data(icon, tb);
124     tb->set_data(key, (gpointer*)data);
125     tb->signal_toggled().connect(sigc::bind<Gtk::RadioButton *, Gtk::Container *>(
126                                      sigc::ptr_fun(&sp_stroke_style_any_toggled), tb, spw));
127     Gtk::Widget *px = manage(Glib::wrap(sp_icon_new(Inkscape::ICON_SIZE_LARGE_TOOLBAR, icon)));
128     g_assert(px != NULL);
129     px->show();
130     tb->add(*px);
132     return tb;
136 /**
137  * Create sa copy of the marker named mname, determines its visible and renderable
138  * area in menu_id's bounding box, and then renders it.  This allows us to fill in
139  * preview images of each marker in the marker menu.
140  */
141 static Gtk::Image *
142 sp_marker_prev_new(unsigned psize, gchar const *mname,
143                    SPDocument *source, SPDocument *sandbox,
144                    gchar const *menu_id, NRArena const * /*arena*/, unsigned /*visionkey*/, NRArenaItem *root)
146     // Retrieve the marker named 'mname' from the source SVG document
147     SPObject const *marker = source->getObjectById(mname);
148     if (marker == NULL)
149         return NULL;
151     // Create a copy repr of the marker with id="sample"
152     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(sandbox);
153     Inkscape::XML::Node *mrepr = SP_OBJECT_REPR (marker)->duplicate(xml_doc);
154     mrepr->setAttribute("id", "sample");
156     // Replace the old sample in the sandbox by the new one
157     Inkscape::XML::Node *defsrepr = SP_OBJECT_REPR (sandbox->getObjectById("defs"));
158     SPObject *oldmarker = sandbox->getObjectById("sample");
159     if (oldmarker)
160         oldmarker->deleteObject(false);
161     defsrepr->appendChild(mrepr);
162     Inkscape::GC::release(mrepr);
164 // Uncomment this to get the sandbox documents saved (useful for debugging)
165     //FILE *fp = fopen (g_strconcat(menu_id, mname, ".svg", NULL), "w");
166     //sp_repr_save_stream (sp_document_repr_doc (sandbox), fp);
167     //fclose (fp);
169     // object to render; note that the id is the same as that of the menu we're building
170     SPObject *object = sandbox->getObjectById(menu_id);
171     sp_document_root (sandbox)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
172     sp_document_ensure_up_to_date(sandbox);
174     if (object == NULL || !SP_IS_ITEM(object))
175         return NULL; // sandbox broken?
177     // Find object's bbox in document
178     Geom::Matrix const i2doc(sp_item_i2doc_affine(SP_ITEM(object)));
179     Geom::OptRect dbox = SP_ITEM(object)->getBounds(i2doc);
181     if (!dbox) {
182         return NULL;
183     }
185     /* Update to renderable state */
186     double sf = 0.8;
188     gchar *cache_name = g_strconcat(menu_id, mname, NULL);
189     Glib::ustring key = svg_preview_cache.cache_key(source->uri, cache_name, psize);
190     g_free (cache_name);
191     // TODO: is this correct?
192     Glib::RefPtr<Gdk::Pixbuf> pixbuf = Glib::wrap(svg_preview_cache.get_preview_from_cache(key));
194     if (!pixbuf) {
195         pixbuf = Glib::wrap(render_pixbuf(root, sf, *dbox, psize));
196         svg_preview_cache.set_preview_in_cache(key, pixbuf->gobj());
197     }
199     // Create widget
200     Gtk::Image *pb = new Gtk::Image(pixbuf);
202     return pb;
205 /**
206  *  Returns a list of markers in the defs of the given source document as a GSList object
207  *  Returns NULL if there are no markers in the document.
208  */
209 GSList *
210 ink_marker_list_get (SPDocument *source)
212     if (source == NULL)
213         return NULL;
215     GSList *ml   = NULL;
216     SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS (source);
217     for ( SPObject *child = sp_object_first_child(SP_OBJECT(defs));
218           child != NULL;
219           child = SP_OBJECT_NEXT (child) )
220     {
221         if (SP_IS_MARKER(child)) {
222             ml = g_slist_prepend (ml, child);
223         }
224     }
225     return ml;
228 #define MARKER_ITEM_MARGIN 0
230 /**
231  * Adds previews of markers in marker_list to the given menu widget
232  */
233 static void
234 sp_marker_menu_build (Gtk::Menu *m, GSList *marker_list, SPDocument *source, SPDocument *sandbox, gchar const *menu_id)
236     // Do this here, outside of loop, to speed up preview generation:
237     NRArena const *arena = NRArena::create();
238     unsigned const visionkey = sp_item_display_key_new(1);
239     NRArenaItem *root =  sp_item_invoke_show(SP_ITEM(SP_DOCUMENT_ROOT (sandbox)), (NRArena *) arena, visionkey, SP_ITEM_SHOW_DISPLAY);
241     for (; marker_list != NULL; marker_list = marker_list->next) {
242         Inkscape::XML::Node *repr = SP_OBJECT_REPR((SPItem *) marker_list->data);
243         Gtk::MenuItem *i = new Gtk::MenuItem();
244         i->show();
246         if (repr->attribute("inkscape:stockid"))
247             i->set_data("stockid", (void *) "true");
248         else
249             i->set_data("stockid", (void *) "false");
251         gchar const *markid = repr->attribute("id");
252         i->set_data("marker", (void *) markid);
254         Gtk::HBox *hb = new Gtk::HBox(false, MARKER_ITEM_MARGIN);
255         hb->show();
257         // generate preview
259         Gtk::Image *prv = sp_marker_prev_new (22, markid, source, sandbox, menu_id, arena, visionkey, root);
260         prv->show();
261         hb->pack_start(*prv, false, false, 6);
263         // create label
264         Gtk::Label *l = new Gtk::Label(repr->attribute("id"));
265         l->show();
266         l->set_alignment(0.0, 0.5);
268         hb->pack_start(*l, true, true, 0);
270         hb->show();
271         i->add(*hb);
273         m->append(*i);
274     }
276     sp_item_invoke_hide(SP_ITEM(sp_document_root(sandbox)), visionkey);
277     nr_object_unref((NRObject *) arena);
280 /**
281  * sp_marker_list_from_doc()
282  *
283  * \brief Pick up all markers from source, except those that are in
284  * current_doc (if non-NULL), and add items to the m menu
285  *
286  */
287 static void
288 sp_marker_list_from_doc (Gtk::Menu *m, SPDocument * /*current_doc*/, SPDocument *source, SPDocument * /*markers_doc*/, SPDocument *sandbox, gchar const *menu_id)
290     GSList *ml = ink_marker_list_get(source);
291     GSList *clean_ml = NULL;
293     for (; ml != NULL; ml = ml->next) {
294         if (!SP_IS_MARKER(ml->data))
295             continue;
297         // Add to the list of markers we really do wish to show
298         clean_ml = g_slist_prepend (clean_ml, ml->data);
299     }
300     sp_marker_menu_build(m, clean_ml, source, sandbox, menu_id);
302     g_slist_free (ml);
303     g_slist_free (clean_ml);
306 /**
307  * Returns a new document containing default start, mid, and end markers.
308  */
309 SPDocument *
310 ink_markers_preview_doc ()
312 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\">"
313 "  <defs id=\"defs\" />"
315 "  <g id=\"marker-start\">"
316 "    <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:url(#sample);marker-mid:none;marker-end:none\""
317 "       d=\"M 12.5,13 L 25,13\" id=\"path1\" />"
318 "    <rect style=\"fill:none;stroke:none\" id=\"rect2\""
319 "       width=\"25\" height=\"25\" x=\"0\" y=\"0\" />"
320 "  </g>"
322 "  <g id=\"marker-mid\">"
323 "    <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:none;marker-mid:url(#sample);marker-end:none\""
324 "       d=\"M 0,113 L 12.5,113 L 25,113\" id=\"path11\" />"
325 "    <rect style=\"fill:none;stroke:none\" id=\"rect22\""
326 "       width=\"25\" height=\"25\" x=\"0\" y=\"100\" />"
327 "  </g>"
329 "  <g id=\"marker-end\">"
330 "    <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:none;marker-mid:none;marker-end:url(#sample)\""
331 "       d=\"M 0,213 L 12.5,213\" id=\"path111\" />"
332 "    <rect style=\"fill:none;stroke:none\" id=\"rect222\""
333 "       width=\"25\" height=\"25\" x=\"0\" y=\"200\" />"
334 "  </g>"
336 "</svg>";
338     return sp_document_new_from_mem (buffer, strlen(buffer), FALSE);
341 static void
342 ink_marker_menu_create_menu(Gtk::Menu *m, gchar const *menu_id, SPDocument *doc, SPDocument *sandbox)
344     static SPDocument *markers_doc = NULL;
346     // add "None"
347     Gtk::MenuItem *i = new Gtk::MenuItem();
348     i->show();
350     i->set_data("marker", (void *) "none");
352     Gtk::HBox *hb = new Gtk::HBox(false,  MARKER_ITEM_MARGIN);
353     hb->show();
355     Gtk::Label *l = new Gtk::Label( _("None") );
356     l->show();
357     l->set_alignment(0.0, 0.5);
359     hb->pack_start(*l, true, true, 0);
361     hb->show();
362     i->add(*hb);
363     m->append(*i);
365     // find and load markers.svg
366     if (markers_doc == NULL) {
367         char *markers_source = g_build_filename(INKSCAPE_MARKERSDIR, "markers.svg", NULL);
368         if (Inkscape::IO::file_test(markers_source, G_FILE_TEST_IS_REGULAR)) {
369             markers_doc = sp_document_new(markers_source, FALSE);
370         }
371         g_free(markers_source);
372     }
374     // suck in from current doc
375     sp_marker_list_from_doc(m, NULL, doc, markers_doc, sandbox, menu_id);
377     // add separator
378     {
379         //Gtk::Separator *i = gtk_separator_menu_item_new();
380         Gtk::SeparatorMenuItem *i = new Gtk::SeparatorMenuItem();
381         i->show();
382         m->append(*i);
383     }
385     // suck in from markers.svg
386     if (markers_doc) {
387         sp_document_ensure_up_to_date(doc);
388         sp_marker_list_from_doc(m, doc, markers_doc, NULL, sandbox, menu_id);
389     }
393 /**
394  * Creates a menu widget to display markers from markers.svg
395  */
396 static Gtk::OptionMenu *
397 ink_marker_menu(Gtk::Widget * /*tbl*/, gchar const *menu_id, SPDocument *sandbox)
399     SPDesktop *desktop = inkscape_active_desktop();
400     SPDocument *doc = sp_desktop_document(desktop);
401     Gtk::OptionMenu *mnu = new Gtk::OptionMenu();
403     /* Create new menu widget */
404     Gtk::Menu *m = new Gtk::Menu();
405     m->show();
407     mnu->set_data("updating", (gpointer) FALSE);
409     if (!doc) {
410         Gtk::MenuItem *i = new Gtk::MenuItem(_("No document selected"));
411         i->show();
412         m->append(*i);
413         mnu->set_sensitive(false);
415     } else {
416         ink_marker_menu_create_menu(m, menu_id, doc, sandbox);
418         mnu->set_sensitive(true);
419     }
421     mnu->set_data("menu_id", const_cast<gchar *>(menu_id));
422     mnu->set_menu(*m);
424     /* Set history */
425     mnu->set_history(0);
427     return mnu;
430 /**
431  * Handles when user selects one of the markers from the marker menu.
432  * Defines a uri string to refer to it, then applies it to all selected
433  * items in the current desktop.
434  */
435 static void
436 sp_marker_select(Gtk::OptionMenu *mnu, Gtk::Container *spw, SPMarkerLoc const which)
438     if (spw->get_data("update")) {
439         return;
440     }
442     SPDesktop *desktop = inkscape_active_desktop();
443     SPDocument *document = sp_desktop_document(desktop);
444     if (!document) {
445         return;
446     }
448     /* Get Marker */
449     if (!mnu->get_menu()->get_active()->get_data("marker"))
450     {
451         return;
452     }
453     gchar *markid = static_cast<gchar *>(mnu->get_menu()->get_active()->get_data("marker"));
454     gchar const *marker = "";
455     if (strcmp(markid, "none")) {
456        gchar *stockid = static_cast<gchar *>(mnu->get_menu()->get_active()->get_data("stockid"));
458        gchar *markurn = markid;
459        if (!strcmp(stockid,"true")) markurn = g_strconcat("urn:inkscape:marker:",markid,NULL);
460        SPObject *mark = get_stock_item(markurn);
461        if (mark) {
462             Inkscape::XML::Node *repr = SP_OBJECT_REPR(mark);
463             marker = g_strconcat("url(#", repr->attribute("id"), ")", NULL);
464         }
465     } else {
466         marker = markid;
467     }
468     SPCSSAttr *css = sp_repr_css_attr_new();
469     gchar const *menu_id = static_cast<gchar const *>(mnu->get_data("menu_id"));
470     sp_repr_css_set_property(css, menu_id, marker);
472     // Also update the marker dropdown menus, so the document's markers
473     // show up at the top of the menu
474 //    sp_stroke_style_line_update( SP_WIDGET(spw), desktop ? sp_desktop_selection(desktop) : NULL);
475     ink_markers_menu_update(spw, which);
477     Inkscape::Selection *selection = sp_desktop_selection(desktop);
478     GSList const *items = selection->itemList();
479     for (; items != NULL; items = items->next) {
480          SPItem *item = (SPItem *) items->data;
481          if (!SP_IS_SHAPE(item) || SP_IS_RECT(item)) // can't set marker to rect, until it's converted to using <path>
482              continue;
483          Inkscape::XML::Node *selrepr = SP_OBJECT_REPR((SPItem *) items->data);
484          if (selrepr) {
485              sp_repr_css_change_recursive(selrepr, css, "style");
486          }
487          SP_OBJECT(items->data)->requestModified(SP_OBJECT_MODIFIED_FLAG);
488          SP_OBJECT(items->data)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
489      }
491     sp_repr_css_attr_unref(css);
492     css = 0;
494     sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE,
495                      _("Set markers"));
497 };
499 static unsigned int
500 ink_marker_menu_get_pos(Gtk::Menu *mnu, gchar const *markname)
502     if (markname == NULL)
503         markname = static_cast<gchar const *>(mnu->get_active()->get_data("marker"));
505     if (markname == NULL)
506         return 0;
508     std::vector<Gtk::Widget *> kids = mnu->get_children();
509     unsigned int i = 0;
510     for (; i < kids.size();) {
511         gchar const *mark = static_cast<gchar const *>(kids[i]->get_data("marker"));
512         if (mark && strcmp(mark, markname) == 0) {
513             break;
514         }
515         ++i;
516     }
518     return i;
521 static void
522 ink_markers_menu_update(Gtk::Container* /*spw*/, SPMarkerLoc const which) {
523     SPDesktop  *desktop = inkscape_active_desktop();
524     SPDocument *document = sp_desktop_document(desktop);
525     SPDocument *sandbox = ink_markers_preview_doc ();
526     Gtk::Menu  *m;
527     int        pos;
529     // TODO: this code can be shortened by abstracting out marker_(start|mid|end)_...
530     switch (which) {
531         case SP_MARKER_LOC_START:
532             marker_start_menu_connection.block();
533             pos = ink_marker_menu_get_pos(marker_start_menu->get_menu(), NULL);
534             m = new Gtk::Menu();
535             m->show();
536             ink_marker_menu_create_menu(m, "marker-start", document, sandbox);
537             marker_start_menu->remove_menu();
538             marker_start_menu->set_menu(*m);
539             marker_start_menu->set_history(pos);
540             marker_start_menu_connection.unblock();
541             break;
543         case SP_MARKER_LOC_MID:
544             marker_mid_menu_connection.block();
545             pos = ink_marker_menu_get_pos(marker_mid_menu->get_menu(), NULL);
546             m = new Gtk::Menu();
547             m->show();
548             ink_marker_menu_create_menu(m, "marker-mid", document, sandbox);
549             marker_mid_menu->remove_menu();
550             marker_mid_menu->set_menu(*m);
551             marker_mid_menu->set_history(pos);
552             marker_mid_menu_connection.unblock();
553             break;
555         case SP_MARKER_LOC_END:
556             marker_end_menu_connection.block();
557             pos = ink_marker_menu_get_pos(marker_end_menu->get_menu(), NULL);
558             m = new Gtk::Menu();
559             m->show();
560             ink_marker_menu_create_menu(m, "marker-end", document, sandbox);
561             marker_end_menu->remove_menu();
562             marker_end_menu->set_menu(*m);
563             marker_end_menu->set_history(pos);
564             marker_end_menu_connection.unblock();
565             break;
566         default:
567             g_assert_not_reached();
568     }
571 /**
572  * Sets the stroke width units for all selected items.
573  * Also handles absolute and dimensionless units.
574  */
575 static gboolean stroke_width_set_unit(SPUnitSelector *,
576                                       SPUnit const *old,
577                                       SPUnit const *new_units,
578                                       Gtk::Container *spw)
580     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
582     if (!desktop) {
583         return FALSE;
584     }
586     Inkscape::Selection *selection = sp_desktop_selection (desktop);
588     if (selection->isEmpty())
589         return FALSE;
591     GSList const *objects = selection->itemList();
593     if ((old->base == SP_UNIT_ABSOLUTE || old->base == SP_UNIT_DEVICE) &&
594        (new_units->base == SP_UNIT_DIMENSIONLESS)) {
596         /* Absolute to percentage */
597         spw->set_data ("update", GUINT_TO_POINTER (TRUE));
599         Gtk::Adjustment *a = static_cast<Gtk::Adjustment *>(spw->get_data("width"));
600         float w = sp_units_get_pixels (a->get_value(), *old);
602         gdouble average = stroke_average_width (objects);
604         if (average == NR_HUGE || average == 0)
605             return FALSE;
607         a->set_value (100.0 * w / average);
609         spw->set_data ("update", GUINT_TO_POINTER (FALSE));
610         return TRUE;
612     } else if ((old->base == SP_UNIT_DIMENSIONLESS) &&
613               (new_units->base == SP_UNIT_ABSOLUTE || new_units->base == SP_UNIT_DEVICE)) {
615         /* Percentage to absolute */
616         spw->set_data ("update", GUINT_TO_POINTER (TRUE));
618         Gtk::Adjustment *a = static_cast<Gtk::Adjustment *>(spw->get_data ("width"));
620         gdouble average = stroke_average_width (objects);
622         a->set_value (sp_pixels_get_units (0.01 * a->get_value() * average, *new_units));
624         spw->set_data ("update", GUINT_TO_POINTER (FALSE));
625         return TRUE;
626     }
628     return FALSE;
632 /**
633  * \brief  Creates a new widget for the line stroke style.
634  *
635  */
636 Gtk::Container *
637 sp_stroke_style_line_widget_new(void)
639     Gtk::Widget *us;
640     SPDashSelector *ds;
641     GtkWidget *us_old, *spw_old;
642     Gtk::Container *spw;
643     Gtk::Table *t;
644     Gtk::Adjustment *a;
645     Gtk::SpinButton *sb;
646     Gtk::RadioButton *tb;
647     Gtk::HBox *f, *hb;
649     Gtk::Tooltips *tt = new Gtk::Tooltips();
651     spw_old = sp_widget_new_global(INKSCAPE);
652     spw = dynamic_cast<Gtk::Container *>(manage(Glib::wrap(spw_old)));
654     f = new Gtk::HBox(false, 0);
655     f->show();
656     spw->add(*f);
658     t = new Gtk::Table(3, 6, false);
659     t->show();
660     t->set_border_width(4);
661     t->set_row_spacings(4);
662     f->add(*t);
663     spw->set_data("stroke", t);
665     gint i = 0;
667     //TRANSLATORS: only translate "string" in "context|string".
668     // For more details, see http://developer.gnome.org/doc/API/2.0/glib/glib-I18N.html#Q-:CAPS
669     /* Stroke width */
670     spw_label(t, Q_("StrokeWidth|Width:"), 0, i);
672     hb = spw_hbox(t, 3, 1, i);
674 // TODO: when this is gtkmmified, use an Inkscape::UI::Widget::ScalarUnit instead of the separate
675 // spinbutton and unit selector for stroke width. In sp_stroke_style_line_update, use
676 // setHundredPercent to remember the aeraged width corresponding to 100%. Then the
677 // stroke_width_set_unit will be removed (because ScalarUnit takes care of conversions itself), and
678 // with it, the two remaining calls of stroke_average_width, allowing us to get rid of that
679 // function in desktop-style.
681     a = new Gtk::Adjustment(1.0, 0.0, 1000.0, 0.1, 10.0, 0.0);
682     spw->set_data("width", a);
683     sb = new Gtk::SpinButton(*a, 0.1, 3);
684     tt->set_tip(*sb, _("Stroke width"));
685     sb->show();
687     sp_dialog_defocus_on_enter_cpp(sb);
689     hb->pack_start(*sb, false, false, 0);
690     us_old = sp_unit_selector_new(SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE);
691     us = manage(Glib::wrap(us_old));
692     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
693     if (desktop)
694         sp_unit_selector_set_unit (SP_UNIT_SELECTOR(us_old), sp_desktop_namedview(desktop)->doc_units);
695     sp_unit_selector_add_unit(SP_UNIT_SELECTOR(us_old), &sp_unit_get_by_id(SP_UNIT_PERCENT), 0);
696     g_signal_connect ( G_OBJECT (us_old), "set_unit", G_CALLBACK (stroke_width_set_unit), spw );
697     us->show();
698     sp_unit_selector_add_adjustment( SP_UNIT_SELECTOR(us_old), GTK_ADJUSTMENT(a->gobj()) );
699     hb->pack_start(*us, FALSE, FALSE, 0);
700     spw->set_data("units", us_old);
702     a->signal_value_changed().connect(sigc::bind(sigc::ptr_fun(&sp_stroke_style_width_changed), spw));
703     i++;
705     /* Join type */
706     // TRANSLATORS: The line join style specifies the shape to be used at the
707     //  corners of paths. It can be "miter", "round" or "bevel".
708     spw_label(t, _("Join:"), 0, i);
710     hb = spw_hbox(t, 3, 1, i);
712     tb = NULL;
714     tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_JOIN_MITER,
715                                 hb, spw, "join", "miter");
717     // TRANSLATORS: Miter join: joining lines with a sharp (pointed) corner.
718     //  For an example, draw a triangle with a large stroke width and modify the
719     //  "Join" option (in the Fill and Stroke dialog).
720     tt->set_tip(*tb, _("Miter join"));
721     spw->set_data("miter join", tb);
723     tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_JOIN_ROUND,
724                                 hb, spw, "join", "round");
727     // TRANSLATORS: Round join: joining lines with a rounded corner.
728     //  For an example, draw a triangle with a large stroke width and modify the
729     //  "Join" option (in the Fill and Stroke dialog).
730     tt->set_tip(*tb, _("Round join"));
731     spw->set_data("round join", tb);
733     tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_JOIN_BEVEL,
734                                 hb, spw, "join", "bevel");
737     // TRANSLATORS: Bevel join: joining lines with a blunted (flattened) corner.
738     //  For an example, draw a triangle with a large stroke width and modify the
739     //  "Join" option (in the Fill and Stroke dialog).
740     tt->set_tip(*tb, _("Bevel join"));
741     spw->set_data("bevel join", tb);
743     i++;
745     /* Miterlimit  */
746     // TRANSLATORS: Miter limit: only for "miter join", this limits the length
747     //  of the sharp "spike" when the lines connect at too sharp an angle.
748     // When two line segments meet at a sharp angle, a miter join results in a
749     //  spike that extends well beyond the connection point. The purpose of the
750     //  miter limit is to cut off such spikes (i.e. convert them into bevels)
751     //  when they become too long.
752     spw_label(t, _("Miter limit:"), 0, i);
754     hb = spw_hbox(t, 3, 1, i);
756     a = new Gtk::Adjustment(4.0, 0.0, 100.0, 0.1, 10.0, 0.0);
757     spw->set_data("miterlimit", a);
759     sb = new Gtk::SpinButton(*a, 0.1, 2);
760     tt->set_tip(*sb, _("Maximum length of the miter (in units of stroke width)"));
761     sb->show();
762     spw->set_data("miterlimit_sb", sb);
763     sp_dialog_defocus_on_enter_cpp(sb);
765     hb->pack_start(*sb, false, false, 0);
767     a->signal_value_changed().connect(sigc::bind(sigc::ptr_fun(&sp_stroke_style_miterlimit_changed), spw));
768     i++;
770     /* Cap type */
771     // TRANSLATORS: cap type specifies the shape for the ends of lines
772     spw_label(t, _("Cap:"), 0, i);
774     hb = spw_hbox(t, 3, 1, i);
776     tb = NULL;
778     tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_CAP_BUTT,
779                                 hb, spw, "cap", "butt");
780     spw->set_data("cap butt", tb);
782     // TRANSLATORS: Butt cap: the line shape does not extend beyond the end point
783     //  of the line; the ends of the line are square
784     tt->set_tip(*tb, _("Butt cap"));
786     tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_CAP_ROUND,
787                                 hb, spw, "cap", "round");
788     spw->set_data("cap round", tb);
790     // TRANSLATORS: Round cap: the line shape extends beyond the end point of the
791     //  line; the ends of the line are rounded
792     tt->set_tip(*tb, _("Round cap"));
794     tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_CAP_SQUARE,
795                                 hb, spw, "cap", "square");
796     spw->set_data("cap square", tb);
798     // TRANSLATORS: Square cap: the line shape extends beyond the end point of the
799     //  line; the ends of the line are square
800     tt->set_tip(*tb, _("Square cap"));
802     i++;
805     /* Dash */
806     spw_label(t, _("Dashes:"), 0, i);
807     ds = manage(new SPDashSelector);
809     ds->show();
810     t->attach(*ds, 1, 4, i, i+1, (Gtk::EXPAND | Gtk::FILL), static_cast<Gtk::AttachOptions>(0), 0, 0);
811     spw->set_data("dash", ds);
812     ds->changed_signal.connect(sigc::bind(sigc::ptr_fun(&sp_stroke_style_line_dash_changed), spw));
813     i++;
815     /* Drop down marker selectors*/
816     // TODO: this code can be shortened by iterating over the possible menus!
818     // doing this here once, instead of for each preview, to speed things up
819     SPDocument *sandbox = ink_markers_preview_doc ();
821     // TRANSLATORS: Path markers are an SVG feature that allows you to attach arbitrary shapes
822     // (arrowheads, bullets, faces, whatever) to the start, end, or middle nodes of a path.
823     spw_label(t, _("Start Markers:"), 0, i);
824     marker_start_menu = ink_marker_menu(spw ,"marker-start", sandbox);
825     tt->set_tip(*marker_start_menu, _("Start Markers are drawn on the first node of a path or shape"));
826     marker_start_menu_connection = marker_start_menu->signal_changed().connect(
827         sigc::bind<Gtk::OptionMenu *, Gtk::Container *, SPMarkerLoc>(
828             sigc::ptr_fun(&sp_marker_select), marker_start_menu, spw, SP_MARKER_LOC_START));
829     marker_start_menu->show();
830     t->attach(*marker_start_menu, 1, 4, i, i+1, (Gtk::EXPAND | Gtk::FILL), static_cast<Gtk::AttachOptions>(0), 0, 0);
831     spw->set_data("start_mark_menu", marker_start_menu);
833     i++;
834     spw_label(t, _("Mid Markers:"), 0, i);
835     marker_mid_menu = ink_marker_menu(spw ,"marker-mid", sandbox);
836     tt->set_tip(*marker_mid_menu, _("Mid Markers are drawn on every node of a path or shape except the first and last nodes"));
837     marker_mid_menu_connection = marker_mid_menu->signal_changed().connect(
838         sigc::bind<Gtk::OptionMenu *, Gtk::Container *, SPMarkerLoc>(
839             sigc::ptr_fun(&sp_marker_select), marker_mid_menu,spw, SP_MARKER_LOC_MID));
840     marker_mid_menu->show();
841     t->attach(*marker_mid_menu, 1, 4, i, i+1, (Gtk::EXPAND | Gtk::FILL), static_cast<Gtk::AttachOptions>(0), 0, 0);
842     spw->set_data("mid_mark_menu", marker_mid_menu);
844     i++;
845     spw_label(t, _("End Markers:"), 0, i);
846     marker_end_menu = ink_marker_menu(spw ,"marker-end", sandbox);
847     tt->set_tip(*marker_end_menu, _("End Markers are drawn on the last node of a path or shape"));
848     marker_end_menu_connection = marker_end_menu->signal_changed().connect(
849         sigc::bind<Gtk::OptionMenu *, Gtk::Container *, SPMarkerLoc>(
850             sigc::ptr_fun(&sp_marker_select), marker_end_menu, spw, SP_MARKER_LOC_END));
851     marker_end_menu->show();
852     t->attach(*marker_end_menu, 1, 4, i, i+1, (Gtk::EXPAND | Gtk::FILL), static_cast<Gtk::AttachOptions>(0), 0, 0);
853     spw->set_data("end_mark_menu", marker_end_menu);
855     i++;
857     // FIXME: we cheat and still use gtk+ signals
859     g_signal_connect(G_OBJECT(spw_old), "modify_selection",
860                      G_CALLBACK(sp_stroke_style_line_selection_modified),
861                      spw);
862     g_signal_connect(G_OBJECT(spw_old), "change_selection",
863                      G_CALLBACK(sp_stroke_style_line_selection_changed),
864                      spw);
866     sp_stroke_style_line_update(spw, desktop ? sp_desktop_selection(desktop) : NULL);
868     return spw;
871 /**
872  * Callback for when stroke style widget is modified.
873  * Triggers update action.
874  */
875 static void
876 sp_stroke_style_line_selection_modified(SPWidget *,
877                                         Inkscape::Selection *selection,
878                                         guint flags,
879                                         gpointer data)
881     Gtk::Container *spw = static_cast<Gtk::Container *>(data);
882     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) {
883         sp_stroke_style_line_update(spw, selection);
884     }
888 /**
889  * Callback for when stroke style widget is changed.
890  * Triggers update action.
891  */
892 static void
893 sp_stroke_style_line_selection_changed(SPWidget *,
894                                        Inkscape::Selection *selection,
895                                        gpointer data)
897     Gtk::Container *spw = static_cast<Gtk::Container *>(data);
898     sp_stroke_style_line_update(spw, selection);
901 /**
902  * Sets selector widgets' dash style from an SPStyle object.
903  */
904 static void
905 sp_dash_selector_set_from_style(SPDashSelector *dsel, SPStyle *style)
907     if (style->stroke_dash.n_dash > 0) {
908         double d[64];
909         int len = MIN(style->stroke_dash.n_dash, 64);
910         for (int i = 0; i < len; i++) {
911             if (style->stroke_width.computed != 0)
912                 d[i] = style->stroke_dash.dash[i] / style->stroke_width.computed;
913             else
914                 d[i] = style->stroke_dash.dash[i]; // is there a better thing to do for stroke_width==0?
915         }
916         dsel->set_dash(len, d, style->stroke_width.computed != 0 ?
917                        style->stroke_dash.offset / style->stroke_width.computed  :
918                        style->stroke_dash.offset);
919     } else {
920         dsel->set_dash(0, NULL, 0.0);
921     }
924 /**
925  * Sets the join type for a line, and updates the stroke style widget's buttons
926  */
927 static void
928 sp_jointype_set (Gtk::Container *spw, unsigned const jointype)
930     Gtk::RadioButton *tb = NULL;
931     switch (jointype) {
932         case SP_STROKE_LINEJOIN_MITER:
933             tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_MITER));
934             break;
935         case SP_STROKE_LINEJOIN_ROUND:
936             tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_ROUND));
937             break;
938         case SP_STROKE_LINEJOIN_BEVEL:
939             tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_BEVEL));
940             break;
941         default:
942             break;
943     }
944     sp_stroke_style_set_join_buttons(spw, tb);
947 /**
948  * Sets the cap type for a line, and updates the stroke style widget's buttons
949  */
950 static void
951 sp_captype_set (Gtk::Container *spw, unsigned const captype)
953     Gtk::RadioButton *tb = NULL;
954     switch (captype) {
955         case SP_STROKE_LINECAP_BUTT:
956             tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_BUTT));
957             break;
958         case SP_STROKE_LINECAP_ROUND:
959             tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_ROUND));
960             break;
961         case SP_STROKE_LINECAP_SQUARE:
962             tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_SQUARE));
963             break;
964         default:
965             break;
966     }
967     sp_stroke_style_set_cap_buttons(spw, tb);
970 /**
971  * Callback for when stroke style widget is updated, including markers, cap type,
972  * join type, etc.
973  */
974 static void
975 sp_stroke_style_line_update(Gtk::Container *spw, Inkscape::Selection *sel)
977     if (spw->get_data("update")) {
978         return;
979     }
981     spw->set_data("update", GINT_TO_POINTER(TRUE));
983     FillOrStroke kind = GPOINTER_TO_INT(spw->get_data("kind")) ? FILL : STROKE;
985     Gtk::Table *sset = static_cast<Gtk::Table *>(spw->get_data("stroke"));
986     Gtk::Adjustment *width = static_cast<Gtk::Adjustment *>(spw->get_data("width"));
987     Gtk::Adjustment *ml = static_cast<Gtk::Adjustment *>(spw->get_data("miterlimit"));
988     SPUnitSelector *us = SP_UNIT_SELECTOR(spw->get_data("units"));
989     SPDashSelector *dsel = static_cast<SPDashSelector *>(spw->get_data("dash"));
991     // create temporary style
992     SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT);
993     // query into it
994     int result_sw = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEWIDTH);
995     int result_ml = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEMITERLIMIT);
996     int result_cap = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKECAP);
997     int result_join = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEJOIN);
998     SPIPaint &targPaint = (kind == FILL) ? query->fill : query->stroke;
1000     if (!sel || sel->isEmpty()) {
1001         // Nothing selected, grey-out all controls in the stroke-style dialog
1002         sset->set_sensitive(false);
1004         spw->set_data("update", GINT_TO_POINTER(FALSE));
1006         return;
1007     } else {
1008         sset->set_sensitive(true);
1010         SPUnit const *unit = sp_unit_selector_get_unit(us);
1012         if (result_sw == QUERY_STYLE_MULTIPLE_AVERAGED) {
1013             sp_unit_selector_set_unit(us, &sp_unit_get_by_id(SP_UNIT_PERCENT));
1014         } else {
1015             // same width, or only one object; no sense to keep percent, switch to absolute
1016             if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) {
1017                 sp_unit_selector_set_unit(us, sp_desktop_namedview(SP_ACTIVE_DESKTOP)->doc_units);
1018             }
1019         }
1021         unit = sp_unit_selector_get_unit(us);
1023         if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) {
1024             double avgwidth = sp_pixels_get_units (query->stroke_width.computed, *unit);
1025             width->set_value(avgwidth);
1026         } else {
1027             width->set_value(100);
1028         }
1030         // if none of the selected objects has a stroke, than quite some controls should be disabled
1031         // The markers might still be shown though, so these will not be disabled
1032         bool enabled = (result_sw != QUERY_STYLE_NOTHING) && !targPaint.isNoneSet();
1033         /* No objects stroked, set insensitive */
1034         Gtk::RadioButton *tb = NULL;
1035         tb = static_cast<Gtk::RadioButton *>(spw->get_data("miter join"));
1036         tb->set_sensitive(enabled);
1037         tb = static_cast<Gtk::RadioButton *>(spw->get_data("round join"));
1038         tb->set_sensitive(enabled);
1039         tb = static_cast<Gtk::RadioButton *>(spw->get_data("bevel join"));
1040         tb->set_sensitive(enabled);
1042         Gtk::SpinButton* sb = NULL;
1043         sb = static_cast<Gtk::SpinButton *>(spw->get_data("miterlimit_sb"));
1044         sb->set_sensitive(enabled);
1046         tb = static_cast<Gtk::RadioButton *>(spw->get_data("cap butt"));
1047         tb->set_sensitive(enabled);
1048         tb = static_cast<Gtk::RadioButton *>(spw->get_data("cap round"));
1049         tb->set_sensitive(enabled);
1050         tb = static_cast<Gtk::RadioButton *>(spw->get_data("cap square"));
1051         tb->set_sensitive(enabled);
1053         dsel->set_sensitive(enabled);
1054     }
1056     if (result_ml != QUERY_STYLE_NOTHING)
1057         ml->set_value(query->stroke_miterlimit.value); // TODO: reflect averagedness?
1059     if (result_join != QUERY_STYLE_MULTIPLE_DIFFERENT) {
1060         sp_jointype_set(spw, query->stroke_linejoin.value);
1061     } else {
1062         sp_stroke_style_set_join_buttons(spw, NULL);
1063     }
1065     if (result_cap != QUERY_STYLE_MULTIPLE_DIFFERENT) {
1066         sp_captype_set (spw, query->stroke_linecap.value);
1067     } else {
1068         sp_stroke_style_set_cap_buttons(spw, NULL);
1069     }
1071     sp_style_unref(query);
1073     if (!sel || sel->isEmpty())
1074         return;
1076     GSList const *objects = sel->itemList();
1077     SPObject * const object = SP_OBJECT(objects->data);
1078     SPStyle * const style = SP_OBJECT_STYLE(object);
1080     /* Markers */
1081     sp_stroke_style_update_marker_menus(spw, objects); // FIXME: make this desktop query too
1083     /* Dash */
1084     sp_dash_selector_set_from_style(dsel, style); // FIXME: make this desktop query too
1086     sset->set_sensitive(true);
1088     spw->set_data("update", GINT_TO_POINTER(FALSE));
1091 /**
1092  * Sets a line's dash properties in a CSS style object.
1093  */
1094 static void
1095 sp_stroke_style_set_scaled_dash(SPCSSAttr *css,
1096                                 int ndash, double *dash, double offset,
1097                                 double scale)
1099     if (ndash > 0) {
1100         Inkscape::CSSOStringStream osarray;
1101         for (int i = 0; i < ndash; i++) {
1102             osarray << dash[i] * scale;
1103             if (i < (ndash - 1)) {
1104                 osarray << ",";
1105             }
1106         }
1107         sp_repr_css_set_property(css, "stroke-dasharray", osarray.str().c_str());
1109         Inkscape::CSSOStringStream osoffset;
1110         osoffset << offset * scale;
1111         sp_repr_css_set_property(css, "stroke-dashoffset", osoffset.str().c_str());
1112     } else {
1113         sp_repr_css_set_property(css, "stroke-dasharray", "none");
1114         sp_repr_css_set_property(css, "stroke-dashoffset", NULL);
1115     }
1118 /**
1119  * Sets line properties like width, dashes, markers, etc. on all currently selected items.
1120  */
1121 static void
1122 sp_stroke_style_scale_line(Gtk::Container *spw)
1124     if (spw->get_data("update")) {
1125         return;
1126     }
1128     spw->set_data("update", GINT_TO_POINTER(TRUE));
1130     Gtk::Adjustment *wadj = static_cast<Gtk::Adjustment *>(spw->get_data("width"));
1131     SPUnitSelector *us = SP_UNIT_SELECTOR(spw->get_data("units"));
1132     SPDashSelector *dsel = static_cast<SPDashSelector *>(spw->get_data("dash"));
1133     Gtk::Adjustment *ml = static_cast<Gtk::Adjustment *>(spw->get_data("miterlimit"));
1135     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1136     SPDocument *document = sp_desktop_document (desktop);
1137     Inkscape::Selection *selection = sp_desktop_selection (desktop);
1139     GSList const *items = selection->itemList();
1141     /* TODO: Create some standardized method */
1142     SPCSSAttr *css = sp_repr_css_attr_new();
1144     if (items) {
1146         double width_typed = wadj->get_value();
1147         double const miterlimit = ml->get_value();
1149         SPUnit const *const unit = sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us));
1151         double *dash, offset;
1152         int ndash;
1153         dsel->get_dash(&ndash, &dash, &offset);
1155         for (GSList const *i = items; i != NULL; i = i->next) {
1156             /* Set stroke width */
1157             double width;
1158             if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) {
1159                 width = sp_units_get_pixels (width_typed, *unit);
1160             } else { // percentage
1161                 gdouble old_w = SP_OBJECT_STYLE (i->data)->stroke_width.computed;
1162                 width = old_w * width_typed / 100;
1163             }
1165             {
1166                 Inkscape::CSSOStringStream os_width;
1167                 os_width << width;
1168                 sp_repr_css_set_property(css, "stroke-width", os_width.str().c_str());
1169             }
1171             {
1172                 Inkscape::CSSOStringStream os_ml;
1173                 os_ml << miterlimit;
1174                 sp_repr_css_set_property(css, "stroke-miterlimit", os_ml.str().c_str());
1175             }
1177             /* Set dash */
1178             sp_stroke_style_set_scaled_dash(css, ndash, dash, offset, width);
1180             sp_desktop_apply_css_recursive (SP_OBJECT(i->data), css, true);
1181         }
1183         g_free(dash);
1185         if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) {
1186             // reset to 100 percent
1187             wadj->set_value(100.0);
1188         }
1190     }
1192     // we have already changed the items, so set style without changing selection
1193     // FIXME: move the above stroke-setting stuff, including percentages, to desktop-style
1194     sp_desktop_set_style (desktop, css, false);
1196     sp_repr_css_attr_unref(css);
1197     css = 0;
1199     sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE,
1200                      _("Set stroke style"));
1202     spw->set_data("update", GINT_TO_POINTER(FALSE));
1205 /**
1206  * Callback for when the stroke style's width changes.
1207  * Causes all line styles to be applied to all selected items.
1208  */
1209 static void
1210 sp_stroke_style_width_changed(Gtk::Container *spw)
1212     if (spw->get_data("update")) {
1213         return;
1214     }
1216     sp_stroke_style_scale_line(spw);
1219 /**
1220  * Callback for when the stroke style's miterlimit changes.
1221  * Causes all line styles to be applied to all selected items.
1222  */
1223 static void
1224 sp_stroke_style_miterlimit_changed(Gtk::Container *spw)
1226     if (spw->get_data("update")) {
1227         return;
1228     }
1230     sp_stroke_style_scale_line(spw);
1233 /**
1234  * Callback for when the stroke style's dash changes.
1235  * Causes all line styles to be applied to all selected items.
1236  */
1238 static void
1239 sp_stroke_style_line_dash_changed(Gtk::Container *spw)
1241     if (spw->get_data("update")) {
1242         return;
1243     }
1245     sp_stroke_style_scale_line(spw);
1248 /**
1249  * \brief  This routine handles toggle events for buttons in the stroke style
1250  *         dialog.
1251  * When activated, this routine gets the data for the various widgets, and then
1252  * calls the respective routines to update css properties, etc.
1253  *
1254  */
1255 static void
1256 sp_stroke_style_any_toggled(Gtk::ToggleButton *tb, Gtk::Container *spw)
1258     if (spw->get_data("update")) {
1259         return;
1260     }
1262     if (tb->get_active()) {
1264         gchar const *join
1265             = static_cast<gchar const *>(tb->get_data("join"));
1266         gchar const *cap
1267             = static_cast<gchar const *>(tb->get_data("cap"));
1269         if (join) {
1270             Gtk::SpinButton *ml = static_cast<Gtk::SpinButton *>(spw->get_data("miterlimit_sb"));
1271             ml->set_sensitive(!strcmp(join, "miter"));
1272         }
1274         SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1276         /* TODO: Create some standardized method */
1277         SPCSSAttr *css = sp_repr_css_attr_new();
1279         if (join) {
1280             sp_repr_css_set_property(css, "stroke-linejoin", join);
1282             sp_desktop_set_style (desktop, css);
1284             sp_stroke_style_set_join_buttons(spw, tb);
1285         } else if (cap) {
1286             sp_repr_css_set_property(css, "stroke-linecap", cap);
1288             sp_desktop_set_style (desktop, css);
1290             sp_stroke_style_set_cap_buttons(spw, tb);
1291         }
1293         sp_repr_css_attr_unref(css);
1294         css = 0;
1296         sp_document_done(sp_desktop_document(desktop), SP_VERB_DIALOG_FILL_STROKE,
1297                          _("Set stroke style"));
1298     }
1301 /**
1302  * Updates the join style toggle buttons
1303  */
1304 static void
1305 sp_stroke_style_set_join_buttons(Gtk::Container *spw, Gtk::ToggleButton *active)
1307     Gtk::RadioButton *tb;
1309     tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_MITER));
1310     tb->set_active(active == tb);
1312     Gtk::SpinButton *ml = static_cast<Gtk::SpinButton *>(spw->get_data("miterlimit_sb"));
1313     ml->set_sensitive(active == tb);
1315     tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_ROUND));
1316     tb->set_active(active == tb);
1318     tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_BEVEL));
1319     tb->set_active(active == tb);
1322 /**
1323  * Updates the cap style toggle buttons
1324  */
1325 static void
1326 sp_stroke_style_set_cap_buttons(Gtk::Container *spw, Gtk::ToggleButton *active)
1328     Gtk::RadioButton *tb;
1330     tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_BUTT));
1331     tb->set_active(active == tb);
1332     tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_ROUND));
1333     tb->set_active(active == tb);
1334     tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_SQUARE));
1335     tb->set_active(active == tb);
1338 /**
1339  * Sets the current marker in the marker menu.
1340  */
1341 static void
1342 ink_marker_menu_set_current(SPObject *marker, Gtk::OptionMenu *mnu)
1344     mnu->set_data("update", GINT_TO_POINTER(TRUE));
1346     Gtk::Menu *m = mnu->get_menu();
1347     if (marker != NULL) {
1348         bool mark_is_stock = false;
1349         if (SP_OBJECT_REPR(marker)->attribute("inkscape:stockid"))
1350             mark_is_stock = true;
1352         gchar *markname;
1353         if (mark_is_stock)
1354             markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("inkscape:stockid"));
1355         else
1356             markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("id"));
1358         int markpos = ink_marker_menu_get_pos(m, markname);
1359         mnu->set_history(markpos);
1361         g_free (markname);
1362     }
1363     else {
1364         mnu->set_history(0);
1365     }
1366     mnu->set_data("update", GINT_TO_POINTER(FALSE));
1369 /**
1370  * Updates the marker menus to highlight the appropriate marker and scroll to
1371  * that marker.
1372  */
1373 static void
1374 sp_stroke_style_update_marker_menus(Gtk::Container *spw, GSList const *objects)
1376     struct { char const *key; int loc; } const keyloc[] = {
1377         { "start_mark_menu", SP_MARKER_LOC_START },
1378         { "mid_mark_menu", SP_MARKER_LOC_MID },
1379         { "end_mark_menu", SP_MARKER_LOC_END }
1380     };
1382     bool all_texts = true;
1383     for (GSList *i = (GSList *) objects; i != NULL; i = i->next) {
1384         if (!SP_IS_TEXT (i->data)) {
1385             all_texts = false;
1386         }
1387     }
1389     for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) {
1390         Gtk::OptionMenu *mnu = static_cast<Gtk::OptionMenu *>(spw->get_data(keyloc[i].key));
1391         // Per SVG spec, text objects cannot have markers; disable menus if only texts are selected
1392         mnu->set_sensitive(!all_texts);
1393     }
1395     // We show markers of the first object in the list only
1396     // FIXME: use the first in the list that has the marker of each type, if any
1397     SPObject *object = SP_OBJECT(objects->data);
1399     for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) {
1400         // For all three marker types,
1402         // find the corresponding menu
1403         Gtk::OptionMenu *mnu = static_cast<Gtk::OptionMenu *>(spw->get_data(keyloc[i].key));
1405         // Quit if we're in update state
1406         if (mnu->get_data("update")) {
1407             return;
1408         }
1410         if (object->style->marker[keyloc[i].loc].value != NULL && !all_texts) {
1411             // If the object has this type of markers,
1413             // Extract the name of the marker that the object uses
1414             SPObject *marker = ink_extract_marker_name(object->style->marker[keyloc[i].loc].value, SP_OBJECT_DOCUMENT(object));
1415             // Scroll the menu to that marker
1416             ink_marker_menu_set_current(marker, mnu);
1418         } else {
1419             mnu->set_history(0);
1420         }
1421     }
1425 /**
1426  * Extract the actual name of the link
1427  * e.g. get mTriangle from url(#mTriangle).
1428  * \return Buffer containing the actual name, allocated from GLib;
1429  * the caller should free the buffer when they no longer need it.
1430  */
1431 static SPObject*
1432 ink_extract_marker_name(gchar const *n, SPDocument *doc)
1434     gchar const *p = n;
1435     while (*p != '\0' && *p != '#') {
1436         p++;
1437     }
1439     if (*p == '\0' || p[1] == '\0') {
1440         return NULL;
1441     }
1443     p++;
1444     int c = 0;
1445     while (p[c] != '\0' && p[c] != ')') {
1446         c++;
1447     }
1449     if (p[c] == '\0') {
1450         return NULL;
1451     }
1453     gchar* b = g_strdup(p);
1454     b[c] = '\0';
1456     // FIXME: get the document from the object and let the caller pass it in
1457     SPObject *marker = doc->getObjectById(b);
1458     return marker;
1461 /*
1462   Local Variables:
1463   mode:c++
1464   c-file-style:"stroustrup"
1465   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1466   indent-tabs-mode:nil
1467   fill-column:99
1468   End:
1469 */
1470 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :