Code

ffcbe48f88ed1e5b43579919bedba5816358c091
[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  *   Jon A. Cruz <jon@joncruz.org>
11  *   Abhishek Sharma
12  *
13  * Copyright (C) 2001-2005 authors
14  * Copyright (C) 2001 Ximian, Inc.
15  * Copyright (C) 2004 John Cliff
16  * Copyright (C) 2008 Maximilian Albert (gtkmm-ification)
17  *
18  * Released under GNU GPL, read the file 'COPYING' for more information
19  */
21 #define noSP_SS_VERBOSE
23 #include <glib/gmem.h>
24 #include <gtk/gtk.h>
25 #include <glibmm/i18n.h>
27 #include "desktop-handles.h"
28 #include "desktop-style.h"
29 #include "dialogs/dialog-events.h"
30 #include "display/canvas-bpath.h" // for SP_STROKE_LINEJOIN_*
31 #include "display/nr-arena.h"
32 #include "display/nr-arena-item.h"
33 #include "document-private.h"
34 #include "gradient-chemistry.h"
35 #include "helper/stock-items.h"
36 #include "helper/unit-menu.h"
37 #include "helper/units.h"
38 #include "inkscape.h"
39 #include "io/sys.h"
40 #include "marker.h"
41 #include "path-prefix.h"
42 #include "selection.h"
43 #include "sp-linear-gradient.h"
44 #include "sp-namedview.h"
45 #include "sp-pattern.h"
46 #include "sp-radial-gradient.h"
47 #include "sp-rect.h"
48 #include "sp-text.h"
49 #include "style.h"
50 #include "svg/css-ostringstream.h"
51 #include "ui/cache/svg_preview_cache.h"
52 #include "ui/icon-names.h"
53 #include "widgets/dash-selector.h"
54 #include "widgets/icon.h"
55 #include "widgets/paint-selector.h"
56 #include "widgets/sp-widget.h"
57 #include "widgets/spw-utilities.h"
58 #include "xml/repr.h"
60 #include "stroke-style.h"
61 #include "fill-style.h" // to get sp_fill_style_widget_set_desktop
62 #include "fill-n-stroke-factory.h"
64 using Inkscape::DocumentUndo;
66 /** Marker selection option menus */
67 static Gtk::OptionMenu * marker_start_menu = NULL;
68 static Gtk::OptionMenu * marker_mid_menu = NULL;
69 static Gtk::OptionMenu * marker_end_menu = NULL;
71 sigc::connection marker_start_menu_connection;
72 sigc::connection marker_mid_menu_connection;
73 sigc::connection marker_end_menu_connection;
75 static SPObject *ink_extract_marker_name(gchar const *n, SPDocument *doc);
76 static void      ink_markers_menu_update(Gtk::Container* spw, SPMarkerLoc const which);
78 static Inkscape::UI::Cache::SvgPreview svg_preview_cache;
80 Gtk::Widget *sp_stroke_style_paint_widget_new(void)
81 {
82     return Inkscape::Widgets::createStyleWidget( STROKE );
83 }
85 void sp_stroke_style_widget_set_desktop(Gtk::Widget *widget, SPDesktop *desktop)
86 {
87     sp_fill_style_widget_set_desktop(widget, desktop);
88 }
92 /* Line */
94 static void sp_stroke_style_line_selection_modified(SPWidget *spw, Inkscape::Selection *selection, guint flags, gpointer data);
95 static void sp_stroke_style_line_selection_changed(SPWidget *spw, Inkscape::Selection *selection, gpointer data);
97 static void sp_stroke_style_line_update(Gtk::Container *spw, Inkscape::Selection *sel);
99 static void sp_stroke_style_set_join_buttons(Gtk::Container *spw, Gtk::ToggleButton *active);
101 static void sp_stroke_style_set_cap_buttons(Gtk::Container *spw, Gtk::ToggleButton *active);
103 static void sp_stroke_style_width_changed(Gtk::Container *spw);
104 static void sp_stroke_style_miterlimit_changed(Gtk::Container *spw);
105 static void sp_stroke_style_any_toggled(Gtk::ToggleButton *tb, Gtk::Container *spw);
106 static void sp_stroke_style_line_dash_changed(Gtk::Container *spw);
108 static void sp_stroke_style_update_marker_menus(Gtk::Container *spw, GSList const *objects);
111 /**
112  * Helper function for creating radio buttons.  This should probably be re-thought out
113  * when reimplementing this with Gtkmm.
114  */
115 static Gtk::RadioButton *
116 sp_stroke_radio_button(Gtk::RadioButton *tb, char const *icon,
117                        Gtk::HBox *hb, Gtk::Container *spw,
118                        gchar const *key, gchar const *data)
120     g_assert(icon != NULL);
121     g_assert(hb  != NULL);
122     g_assert(spw != NULL);
124     if (tb == NULL) {
125         tb = new Gtk::RadioButton();
126     } else {
127         Gtk::RadioButtonGroup grp = tb->get_group();
128         tb = new Gtk::RadioButton(grp);
129     }
131     tb->show();
132     tb->set_mode(false);
133     hb->pack_start(*tb, false, false, 0);
134     spw->set_data(icon, tb);
135     tb->set_data(key, (gpointer*)data);
136     tb->signal_toggled().connect(sigc::bind<Gtk::RadioButton *, Gtk::Container *>(
137                                      sigc::ptr_fun(&sp_stroke_style_any_toggled), tb, spw));
138     Gtk::Widget *px = manage(Glib::wrap(sp_icon_new(Inkscape::ICON_SIZE_LARGE_TOOLBAR, icon)));
139     g_assert(px != NULL);
140     px->show();
141     tb->add(*px);
143     return tb;
147 /**
148  * Create sa copy of the marker named mname, determines its visible and renderable
149  * area in menu_id's bounding box, and then renders it.  This allows us to fill in
150  * preview images of each marker in the marker menu.
151  */
152 static Gtk::Image *
153 sp_marker_prev_new(unsigned psize, gchar const *mname,
154                    SPDocument *source, SPDocument *sandbox,
155                    gchar const *menu_id, NRArena const * /*arena*/, unsigned /*visionkey*/, NRArenaItem *root)
157     // Retrieve the marker named 'mname' from the source SVG document
158     SPObject const *marker = source->getObjectById(mname);
159     if (marker == NULL)
160         return NULL;
162     // Create a copy repr of the marker with id="sample"
163     Inkscape::XML::Document *xml_doc = sandbox->getReprDoc();
164     Inkscape::XML::Node *mrepr = SP_OBJECT_REPR (marker)->duplicate(xml_doc);
165     mrepr->setAttribute("id", "sample");
167     // Replace the old sample in the sandbox by the new one
168     Inkscape::XML::Node *defsrepr = SP_OBJECT_REPR (sandbox->getObjectById("defs"));
169     SPObject *oldmarker = sandbox->getObjectById("sample");
170     if (oldmarker)
171         oldmarker->deleteObject(false);
172     defsrepr->appendChild(mrepr);
173     Inkscape::GC::release(mrepr);
175 // Uncomment this to get the sandbox documents saved (useful for debugging)
176     //FILE *fp = fopen (g_strconcat(menu_id, mname, ".svg", NULL), "w");
177     //sp_repr_save_stream(sandbox->getReprDoc(), fp);
178     //fclose (fp);
180     // object to render; note that the id is the same as that of the menu we're building
181     SPObject *object = sandbox->getObjectById(menu_id);
182     sandbox->getRoot()->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
183     sandbox->ensureUpToDate();
185     if (object == NULL || !SP_IS_ITEM(object))
186         return NULL; // sandbox broken?
188     // Find object's bbox in document
189     Geom::Matrix const i2doc(SP_ITEM(object)->i2doc_affine());
190     Geom::OptRect dbox = SP_ITEM(object)->getBounds(i2doc);
192     if (!dbox) {
193         return NULL;
194     }
196     /* Update to renderable state */
197     double sf = 0.8;
199     gchar *cache_name = g_strconcat(menu_id, mname, NULL);
200     Glib::ustring key = svg_preview_cache.cache_key(source->getURI(), cache_name, psize);
201     g_free (cache_name);
202     // TODO: is this correct?
203     Glib::RefPtr<Gdk::Pixbuf> pixbuf = Glib::wrap(svg_preview_cache.get_preview_from_cache(key));
205     if (!pixbuf) {
206         pixbuf = Glib::wrap(render_pixbuf(root, sf, *dbox, psize));
207         svg_preview_cache.set_preview_in_cache(key, pixbuf->gobj());
208     }
210     // Create widget
211     Gtk::Image *pb = new Gtk::Image(pixbuf);
213     return pb;
216 /**
217  *  Returns a list of markers in the defs of the given source document as a GSList object
218  *  Returns NULL if there are no markers in the document.
219  */
220 GSList *
221 ink_marker_list_get (SPDocument *source)
223     if (source == NULL)
224         return NULL;
226     GSList *ml   = NULL;
227     SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS (source);
228     for ( SPObject *child = SP_OBJECT(defs)->firstChild(); child; child = child->getNext() )
229     {
230         if (SP_IS_MARKER(child)) {
231             ml = g_slist_prepend (ml, child);
232         }
233     }
234     return ml;
237 #define MARKER_ITEM_MARGIN 0
239 /**
240  * Adds previews of markers in marker_list to the given menu widget
241  */
242 static void
243 sp_marker_menu_build (Gtk::Menu *m, GSList *marker_list, SPDocument *source, SPDocument *sandbox, gchar const *menu_id)
245     // Do this here, outside of loop, to speed up preview generation:
246     NRArena const *arena = NRArena::create();
247     unsigned const visionkey = SPItem::display_key_new(1);
248     NRArenaItem *root =  SP_ITEM(sandbox->getRoot())->invoke_show((NRArena *) arena, visionkey, SP_ITEM_SHOW_DISPLAY);
250     for (; marker_list != NULL; marker_list = marker_list->next) {
251         Inkscape::XML::Node *repr = SP_OBJECT_REPR((SPItem *) marker_list->data);
252         Gtk::MenuItem *i = new Gtk::MenuItem();
253         i->show();
255         if (repr->attribute("inkscape:stockid"))
256             i->set_data("stockid", (void *) "true");
257         else
258             i->set_data("stockid", (void *) "false");
260         gchar const *markid = repr->attribute("id");
261         i->set_data("marker", (void *) markid);
263         Gtk::HBox *hb = new Gtk::HBox(false, MARKER_ITEM_MARGIN);
264         hb->show();
266         // generate preview
268         Gtk::Image *prv = sp_marker_prev_new (22, markid, source, sandbox, menu_id, arena, visionkey, root);
269         prv->show();
270         hb->pack_start(*prv, false, false, 6);
272         // create label
273         Gtk::Label *l = new Gtk::Label(repr->attribute("id"));
274         l->show();
275         l->set_alignment(0.0, 0.5);
277         hb->pack_start(*l, true, true, 0);
279         hb->show();
280         i->add(*hb);
282         m->append(*i);
283     }
285     SP_ITEM(sandbox->getRoot())->invoke_hide(visionkey);
286     nr_object_unref((NRObject *) arena);
289 /**
290  * sp_marker_list_from_doc()
291  *
292  * \brief Pick up all markers from source, except those that are in
293  * current_doc (if non-NULL), and add items to the m menu
294  *
295  */
296 static void
297 sp_marker_list_from_doc (Gtk::Menu *m, SPDocument * /*current_doc*/, SPDocument *source, SPDocument * /*markers_doc*/, SPDocument *sandbox, gchar const *menu_id)
299     GSList *ml = ink_marker_list_get(source);
300     GSList *clean_ml = NULL;
302     for (; ml != NULL; ml = ml->next) {
303         if (!SP_IS_MARKER(ml->data))
304             continue;
306         // Add to the list of markers we really do wish to show
307         clean_ml = g_slist_prepend (clean_ml, ml->data);
308     }
309     sp_marker_menu_build(m, clean_ml, source, sandbox, menu_id);
311     g_slist_free (ml);
312     g_slist_free (clean_ml);
315 /**
316  * Returns a new document containing default start, mid, and end markers.
317  */
318 SPDocument *
319 ink_markers_preview_doc ()
321 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\">"
322 "  <defs id=\"defs\" />"
324 "  <g id=\"marker-start\">"
325 "    <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:url(#sample);marker-mid:none;marker-end:none\""
326 "       d=\"M 12.5,13 L 25,13\" id=\"path1\" />"
327 "    <rect style=\"fill:none;stroke:none\" id=\"rect2\""
328 "       width=\"25\" height=\"25\" x=\"0\" y=\"0\" />"
329 "  </g>"
331 "  <g id=\"marker-mid\">"
332 "    <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:none;marker-mid:url(#sample);marker-end:none\""
333 "       d=\"M 0,113 L 12.5,113 L 25,113\" id=\"path11\" />"
334 "    <rect style=\"fill:none;stroke:none\" id=\"rect22\""
335 "       width=\"25\" height=\"25\" x=\"0\" y=\"100\" />"
336 "  </g>"
338 "  <g id=\"marker-end\">"
339 "    <path style=\"fill:none;stroke:black;stroke-width:1.7;marker-start:none;marker-mid:none;marker-end:url(#sample)\""
340 "       d=\"M 0,213 L 12.5,213\" id=\"path111\" />"
341 "    <rect style=\"fill:none;stroke:none\" id=\"rect222\""
342 "       width=\"25\" height=\"25\" x=\"0\" y=\"200\" />"
343 "  </g>"
345 "</svg>";
347     return SPDocument::createNewDocFromMem (buffer, strlen(buffer), FALSE);
350 static void
351 ink_marker_menu_create_menu(Gtk::Menu *m, gchar const *menu_id, SPDocument *doc, SPDocument *sandbox)
353     static SPDocument *markers_doc = NULL;
355     // add "None"
356     Gtk::MenuItem *i = new Gtk::MenuItem();
357     i->show();
359     i->set_data("marker", (void *) "none");
361     Gtk::HBox *hb = new Gtk::HBox(false,  MARKER_ITEM_MARGIN);
362     hb->show();
364     Gtk::Label *l = new Gtk::Label( _("None") );
365     l->show();
366     l->set_alignment(0.0, 0.5);
368     hb->pack_start(*l, true, true, 0);
370     hb->show();
371     i->add(*hb);
372     m->append(*i);
374     // find and load markers.svg
375     if (markers_doc == NULL) {
376         char *markers_source = g_build_filename(INKSCAPE_MARKERSDIR, "markers.svg", NULL);
377         if (Inkscape::IO::file_test(markers_source, G_FILE_TEST_IS_REGULAR)) {
378             markers_doc = SPDocument::createNewDoc(markers_source, FALSE);
379         }
380         g_free(markers_source);
381     }
383     // suck in from current doc
384     sp_marker_list_from_doc(m, NULL, doc, markers_doc, sandbox, menu_id);
386     // add separator
387     {
388         //Gtk::Separator *i = gtk_separator_menu_item_new();
389         Gtk::SeparatorMenuItem *i = new Gtk::SeparatorMenuItem();
390         i->show();
391         m->append(*i);
392     }
394     // suck in from markers.svg
395     if (markers_doc) {
396         doc->ensureUpToDate();
397         sp_marker_list_from_doc(m, doc, markers_doc, NULL, sandbox, menu_id);
398     }
402 /**
403  * Creates a menu widget to display markers from markers.svg
404  */
405 static Gtk::OptionMenu *
406 ink_marker_menu(Gtk::Widget * /*tbl*/, gchar const *menu_id, SPDocument *sandbox)
408     SPDesktop *desktop = inkscape_active_desktop();
409     SPDocument *doc = sp_desktop_document(desktop);
410     Gtk::OptionMenu *mnu = new Gtk::OptionMenu();
412     /* Create new menu widget */
413     Gtk::Menu *m = new Gtk::Menu();
414     m->show();
416     mnu->set_data("updating", (gpointer) FALSE);
418     if (!doc) {
419         Gtk::MenuItem *i = new Gtk::MenuItem(_("No document selected"));
420         i->show();
421         m->append(*i);
422         mnu->set_sensitive(false);
424     } else {
425         ink_marker_menu_create_menu(m, menu_id, doc, sandbox);
427         mnu->set_sensitive(true);
428     }
430     mnu->set_data("menu_id", const_cast<gchar *>(menu_id));
431     mnu->set_menu(*m);
433     /* Set history */
434     mnu->set_history(0);
436     return mnu;
439 /**
440  * Handles when user selects one of the markers from the marker menu.
441  * Defines a uri string to refer to it, then applies it to all selected
442  * items in the current desktop.
443  */
444 static void
445 sp_marker_select(Gtk::OptionMenu *mnu, Gtk::Container *spw, SPMarkerLoc const which)
447     if (spw->get_data("update")) {
448         return;
449     }
451     SPDesktop *desktop = inkscape_active_desktop();
452     SPDocument *document = sp_desktop_document(desktop);
453     if (!document) {
454         return;
455     }
457     /* Get Marker */
458     if (!mnu->get_menu()->get_active()->get_data("marker"))
459     {
460         return;
461     }
462     gchar *markid = static_cast<gchar *>(mnu->get_menu()->get_active()->get_data("marker"));
463     gchar const *marker = "";
464     if (strcmp(markid, "none")) {
465        gchar *stockid = static_cast<gchar *>(mnu->get_menu()->get_active()->get_data("stockid"));
467        gchar *markurn = markid;
468        if (!strcmp(stockid,"true")) markurn = g_strconcat("urn:inkscape:marker:",markid,NULL);
469        SPObject *mark = get_stock_item(markurn);
470        if (mark) {
471             Inkscape::XML::Node *repr = SP_OBJECT_REPR(mark);
472             marker = g_strconcat("url(#", repr->attribute("id"), ")", NULL);
473         }
474     } else {
475         marker = markid;
476     }
477     SPCSSAttr *css = sp_repr_css_attr_new();
478     gchar const *menu_id = static_cast<gchar const *>(mnu->get_data("menu_id"));
479     sp_repr_css_set_property(css, menu_id, marker);
481     // Also update the marker dropdown menus, so the document's markers
482     // show up at the top of the menu
483 //    sp_stroke_style_line_update( SP_WIDGET(spw), desktop ? sp_desktop_selection(desktop) : NULL);
484     ink_markers_menu_update(spw, which);
486     Inkscape::Selection *selection = sp_desktop_selection(desktop);
487     GSList const *items = selection->itemList();
488     for (; items != NULL; items = items->next) {
489          SPItem *item = (SPItem *) items->data;
490          if (!SP_IS_SHAPE(item) || SP_IS_RECT(item)) // can't set marker to rect, until it's converted to using <path>
491              continue;
492          Inkscape::XML::Node *selrepr = SP_OBJECT_REPR((SPItem *) items->data);
493          if (selrepr) {
494              sp_repr_css_change_recursive(selrepr, css, "style");
495          }
496          SP_OBJECT(items->data)->requestModified(SP_OBJECT_MODIFIED_FLAG);
497          SP_OBJECT(items->data)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
498      }
500     sp_repr_css_attr_unref(css);
501     css = 0;
503     DocumentUndo::done(document, SP_VERB_DIALOG_FILL_STROKE,
504                        _("Set markers"));
506 };
508 static unsigned int
509 ink_marker_menu_get_pos(Gtk::Menu *mnu, gchar const *markname)
511     if (markname == NULL)
512         markname = static_cast<gchar const *>(mnu->get_active()->get_data("marker"));
514     if (markname == NULL)
515         return 0;
517     std::vector<Gtk::Widget *> kids = mnu->get_children();
518     unsigned int i = 0;
519     for (; i < kids.size();) {
520         gchar const *mark = static_cast<gchar const *>(kids[i]->get_data("marker"));
521         if (mark && strcmp(mark, markname) == 0) {
522             break;
523         }
524         ++i;
525     }
527     return i;
530 static void
531 ink_markers_menu_update(Gtk::Container* /*spw*/, SPMarkerLoc const which) {
532     SPDesktop  *desktop = inkscape_active_desktop();
533     SPDocument *document = sp_desktop_document(desktop);
534     SPDocument *sandbox = ink_markers_preview_doc ();
535     Gtk::Menu  *m;
536     int        pos;
538     // TODO: this code can be shortened by abstracting out marker_(start|mid|end)_...
539     switch (which) {
540         case SP_MARKER_LOC_START:
541             marker_start_menu_connection.block();
542             pos = ink_marker_menu_get_pos(marker_start_menu->get_menu(), NULL);
543             m = new Gtk::Menu();
544             m->show();
545             ink_marker_menu_create_menu(m, "marker-start", document, sandbox);
546             marker_start_menu->remove_menu();
547             marker_start_menu->set_menu(*m);
548             marker_start_menu->set_history(pos);
549             marker_start_menu_connection.unblock();
550             break;
552         case SP_MARKER_LOC_MID:
553             marker_mid_menu_connection.block();
554             pos = ink_marker_menu_get_pos(marker_mid_menu->get_menu(), NULL);
555             m = new Gtk::Menu();
556             m->show();
557             ink_marker_menu_create_menu(m, "marker-mid", document, sandbox);
558             marker_mid_menu->remove_menu();
559             marker_mid_menu->set_menu(*m);
560             marker_mid_menu->set_history(pos);
561             marker_mid_menu_connection.unblock();
562             break;
564         case SP_MARKER_LOC_END:
565             marker_end_menu_connection.block();
566             pos = ink_marker_menu_get_pos(marker_end_menu->get_menu(), NULL);
567             m = new Gtk::Menu();
568             m->show();
569             ink_marker_menu_create_menu(m, "marker-end", document, sandbox);
570             marker_end_menu->remove_menu();
571             marker_end_menu->set_menu(*m);
572             marker_end_menu->set_history(pos);
573             marker_end_menu_connection.unblock();
574             break;
575         default:
576             g_assert_not_reached();
577     }
580 /**
581  * Sets the stroke width units for all selected items.
582  * Also handles absolute and dimensionless units.
583  */
584 static gboolean stroke_width_set_unit(SPUnitSelector *,
585                                       SPUnit const *old,
586                                       SPUnit const *new_units,
587                                       Gtk::Container *spw)
589     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
591     if (!desktop) {
592         return FALSE;
593     }
595     Inkscape::Selection *selection = sp_desktop_selection (desktop);
597     if (selection->isEmpty())
598         return FALSE;
600     GSList const *objects = selection->itemList();
602     if ((old->base == SP_UNIT_ABSOLUTE || old->base == SP_UNIT_DEVICE) &&
603        (new_units->base == SP_UNIT_DIMENSIONLESS)) {
605         /* Absolute to percentage */
606         spw->set_data ("update", GUINT_TO_POINTER (TRUE));
608         Gtk::Adjustment *a = static_cast<Gtk::Adjustment *>(spw->get_data("width"));
609         float w = sp_units_get_pixels (a->get_value(), *old);
611         gdouble average = stroke_average_width (objects);
613         if (average == NR_HUGE || average == 0)
614             return FALSE;
616         a->set_value (100.0 * w / average);
618         spw->set_data ("update", GUINT_TO_POINTER (FALSE));
619         return TRUE;
621     } else if ((old->base == SP_UNIT_DIMENSIONLESS) &&
622               (new_units->base == SP_UNIT_ABSOLUTE || new_units->base == SP_UNIT_DEVICE)) {
624         /* Percentage to absolute */
625         spw->set_data ("update", GUINT_TO_POINTER (TRUE));
627         Gtk::Adjustment *a = static_cast<Gtk::Adjustment *>(spw->get_data ("width"));
629         gdouble average = stroke_average_width (objects);
631         a->set_value (sp_pixels_get_units (0.01 * a->get_value() * average, *new_units));
633         spw->set_data ("update", GUINT_TO_POINTER (FALSE));
634         return TRUE;
635     }
637     return FALSE;
641 /**
642  * \brief  Creates a new widget for the line stroke style.
643  *
644  */
645 Gtk::Container *
646 sp_stroke_style_line_widget_new(void)
648     Gtk::Widget *us;
649     SPDashSelector *ds;
650     GtkWidget *us_old, *spw_old;
651     Gtk::Container *spw;
652     Gtk::Table *t;
653     Gtk::Adjustment *a;
654     Gtk::SpinButton *sb;
655     Gtk::RadioButton *tb;
656     Gtk::HBox *f, *hb;
658     Gtk::Tooltips *tt = new Gtk::Tooltips();
660     spw_old = sp_widget_new_global(INKSCAPE);
661     spw = dynamic_cast<Gtk::Container *>(manage(Glib::wrap(spw_old)));
663     f = new Gtk::HBox(false, 0);
664     f->show();
665     spw->add(*f);
667     t = new Gtk::Table(3, 6, false);
668     t->show();
669     t->set_border_width(4);
670     t->set_row_spacings(4);
671     f->add(*t);
672     spw->set_data("stroke", t);
674     gint i = 0;
676     spw_label(t, C_("Stroke width", "Width:"), 0, i);
678     hb = spw_hbox(t, 3, 1, i);
680 // TODO: when this is gtkmmified, use an Inkscape::UI::Widget::ScalarUnit instead of the separate
681 // spinbutton and unit selector for stroke width. In sp_stroke_style_line_update, use
682 // setHundredPercent to remember the aeraged width corresponding to 100%. Then the
683 // stroke_width_set_unit will be removed (because ScalarUnit takes care of conversions itself), and
684 // with it, the two remaining calls of stroke_average_width, allowing us to get rid of that
685 // function in desktop-style.
687     a = new Gtk::Adjustment(1.0, 0.0, 1000.0, 0.1, 10.0, 0.0);
688     spw->set_data("width", a);
689     sb = new Gtk::SpinButton(*a, 0.1, 3);
690     tt->set_tip(*sb, _("Stroke width"));
691     sb->show();
693     sp_dialog_defocus_on_enter_cpp(sb);
695     hb->pack_start(*sb, false, false, 0);
696     us_old = sp_unit_selector_new(SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE);
697     us = manage(Glib::wrap(us_old));
698     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
699     if (desktop)
700         sp_unit_selector_set_unit (SP_UNIT_SELECTOR(us_old), sp_desktop_namedview(desktop)->doc_units);
701     sp_unit_selector_add_unit(SP_UNIT_SELECTOR(us_old), &sp_unit_get_by_id(SP_UNIT_PERCENT), 0);
702     g_signal_connect ( G_OBJECT (us_old), "set_unit", G_CALLBACK (stroke_width_set_unit), spw );
703     us->show();
704     sp_unit_selector_add_adjustment( SP_UNIT_SELECTOR(us_old), GTK_ADJUSTMENT(a->gobj()) );
705     hb->pack_start(*us, FALSE, FALSE, 0);
706     spw->set_data("units", us_old);
708     a->signal_value_changed().connect(sigc::bind(sigc::ptr_fun(&sp_stroke_style_width_changed), spw));
709     i++;
711     /* Join type */
712     // TRANSLATORS: The line join style specifies the shape to be used at the
713     //  corners of paths. It can be "miter", "round" or "bevel".
714     spw_label(t, _("Join:"), 0, i);
716     hb = spw_hbox(t, 3, 1, i);
718     tb = NULL;
720     tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_JOIN_MITER,
721                                 hb, spw, "join", "miter");
723     // TRANSLATORS: Miter join: joining lines with a sharp (pointed) corner.
724     //  For an example, draw a triangle with a large stroke width and modify the
725     //  "Join" option (in the Fill and Stroke dialog).
726     tt->set_tip(*tb, _("Miter join"));
727     spw->set_data("miter join", tb);
729     tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_JOIN_ROUND,
730                                 hb, spw, "join", "round");
733     // TRANSLATORS: Round join: joining lines with a rounded corner.
734     //  For an example, draw a triangle with a large stroke width and modify the
735     //  "Join" option (in the Fill and Stroke dialog).
736     tt->set_tip(*tb, _("Round join"));
737     spw->set_data("round join", tb);
739     tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_JOIN_BEVEL,
740                                 hb, spw, "join", "bevel");
743     // TRANSLATORS: Bevel join: joining lines with a blunted (flattened) corner.
744     //  For an example, draw a triangle with a large stroke width and modify the
745     //  "Join" option (in the Fill and Stroke dialog).
746     tt->set_tip(*tb, _("Bevel join"));
747     spw->set_data("bevel join", tb);
749     i++;
751     /* Miterlimit  */
752     // TRANSLATORS: Miter limit: only for "miter join", this limits the length
753     //  of the sharp "spike" when the lines connect at too sharp an angle.
754     // When two line segments meet at a sharp angle, a miter join results in a
755     //  spike that extends well beyond the connection point. The purpose of the
756     //  miter limit is to cut off such spikes (i.e. convert them into bevels)
757     //  when they become too long.
758     spw_label(t, _("Miter limit:"), 0, i);
760     hb = spw_hbox(t, 3, 1, i);
762     a = new Gtk::Adjustment(4.0, 0.0, 100.0, 0.1, 10.0, 0.0);
763     spw->set_data("miterlimit", a);
765     sb = new Gtk::SpinButton(*a, 0.1, 2);
766     tt->set_tip(*sb, _("Maximum length of the miter (in units of stroke width)"));
767     sb->show();
768     spw->set_data("miterlimit_sb", sb);
769     sp_dialog_defocus_on_enter_cpp(sb);
771     hb->pack_start(*sb, false, false, 0);
773     a->signal_value_changed().connect(sigc::bind(sigc::ptr_fun(&sp_stroke_style_miterlimit_changed), spw));
774     i++;
776     /* Cap type */
777     // TRANSLATORS: cap type specifies the shape for the ends of lines
778     spw_label(t, _("Cap:"), 0, i);
780     hb = spw_hbox(t, 3, 1, i);
782     tb = NULL;
784     tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_CAP_BUTT,
785                                 hb, spw, "cap", "butt");
786     spw->set_data("cap butt", tb);
788     // TRANSLATORS: Butt cap: the line shape does not extend beyond the end point
789     //  of the line; the ends of the line are square
790     tt->set_tip(*tb, _("Butt cap"));
792     tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_CAP_ROUND,
793                                 hb, spw, "cap", "round");
794     spw->set_data("cap round", tb);
796     // TRANSLATORS: Round cap: the line shape extends beyond the end point of the
797     //  line; the ends of the line are rounded
798     tt->set_tip(*tb, _("Round cap"));
800     tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_CAP_SQUARE,
801                                 hb, spw, "cap", "square");
802     spw->set_data("cap square", tb);
804     // TRANSLATORS: Square cap: the line shape extends beyond the end point of the
805     //  line; the ends of the line are square
806     tt->set_tip(*tb, _("Square cap"));
808     i++;
811     /* Dash */
812     spw_label(t, _("Dashes:"), 0, i);
813     ds = manage(new SPDashSelector);
815     ds->show();
816     t->attach(*ds, 1, 4, i, i+1, (Gtk::EXPAND | Gtk::FILL), static_cast<Gtk::AttachOptions>(0), 0, 0);
817     spw->set_data("dash", ds);
818     ds->changed_signal.connect(sigc::bind(sigc::ptr_fun(&sp_stroke_style_line_dash_changed), spw));
819     i++;
821     /* Drop down marker selectors*/
822     // TODO: this code can be shortened by iterating over the possible menus!
824     // doing this here once, instead of for each preview, to speed things up
825     SPDocument *sandbox = ink_markers_preview_doc ();
827     // TRANSLATORS: Path markers are an SVG feature that allows you to attach arbitrary shapes
828     // (arrowheads, bullets, faces, whatever) to the start, end, or middle nodes of a path.
829     spw_label(t, _("Start Markers:"), 0, i);
830     marker_start_menu = ink_marker_menu(spw ,"marker-start", sandbox);
831     tt->set_tip(*marker_start_menu, _("Start Markers are drawn on the first node of a path or shape"));
832     marker_start_menu_connection = marker_start_menu->signal_changed().connect(
833         sigc::bind<Gtk::OptionMenu *, Gtk::Container *, SPMarkerLoc>(
834             sigc::ptr_fun(&sp_marker_select), marker_start_menu, spw, SP_MARKER_LOC_START));
835     marker_start_menu->show();
836     t->attach(*marker_start_menu, 1, 4, i, i+1, (Gtk::EXPAND | Gtk::FILL), static_cast<Gtk::AttachOptions>(0), 0, 0);
837     spw->set_data("start_mark_menu", marker_start_menu);
839     i++;
840     spw_label(t, _("Mid Markers:"), 0, i);
841     marker_mid_menu = ink_marker_menu(spw ,"marker-mid", sandbox);
842     tt->set_tip(*marker_mid_menu, _("Mid Markers are drawn on every node of a path or shape except the first and last nodes"));
843     marker_mid_menu_connection = marker_mid_menu->signal_changed().connect(
844         sigc::bind<Gtk::OptionMenu *, Gtk::Container *, SPMarkerLoc>(
845             sigc::ptr_fun(&sp_marker_select), marker_mid_menu,spw, SP_MARKER_LOC_MID));
846     marker_mid_menu->show();
847     t->attach(*marker_mid_menu, 1, 4, i, i+1, (Gtk::EXPAND | Gtk::FILL), static_cast<Gtk::AttachOptions>(0), 0, 0);
848     spw->set_data("mid_mark_menu", marker_mid_menu);
850     i++;
851     spw_label(t, _("End Markers:"), 0, i);
852     marker_end_menu = ink_marker_menu(spw ,"marker-end", sandbox);
853     tt->set_tip(*marker_end_menu, _("End Markers are drawn on the last node of a path or shape"));
854     marker_end_menu_connection = marker_end_menu->signal_changed().connect(
855         sigc::bind<Gtk::OptionMenu *, Gtk::Container *, SPMarkerLoc>(
856             sigc::ptr_fun(&sp_marker_select), marker_end_menu, spw, SP_MARKER_LOC_END));
857     marker_end_menu->show();
858     t->attach(*marker_end_menu, 1, 4, i, i+1, (Gtk::EXPAND | Gtk::FILL), static_cast<Gtk::AttachOptions>(0), 0, 0);
859     spw->set_data("end_mark_menu", marker_end_menu);
861     i++;
863     // FIXME: we cheat and still use gtk+ signals
865     g_signal_connect(G_OBJECT(spw_old), "modify_selection",
866                      G_CALLBACK(sp_stroke_style_line_selection_modified),
867                      spw);
868     g_signal_connect(G_OBJECT(spw_old), "change_selection",
869                      G_CALLBACK(sp_stroke_style_line_selection_changed),
870                      spw);
872     sp_stroke_style_line_update(spw, desktop ? sp_desktop_selection(desktop) : NULL);
874     return spw;
877 /**
878  * Callback for when stroke style widget is modified.
879  * Triggers update action.
880  */
881 static void
882 sp_stroke_style_line_selection_modified(SPWidget *,
883                                         Inkscape::Selection *selection,
884                                         guint flags,
885                                         gpointer data)
887     Gtk::Container *spw = static_cast<Gtk::Container *>(data);
888     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) {
889         sp_stroke_style_line_update(spw, selection);
890     }
894 /**
895  * Callback for when stroke style widget is changed.
896  * Triggers update action.
897  */
898 static void
899 sp_stroke_style_line_selection_changed(SPWidget *,
900                                        Inkscape::Selection *selection,
901                                        gpointer data)
903     Gtk::Container *spw = static_cast<Gtk::Container *>(data);
904     sp_stroke_style_line_update(spw, selection);
907 /**
908  * Sets selector widgets' dash style from an SPStyle object.
909  */
910 static void
911 sp_dash_selector_set_from_style(SPDashSelector *dsel, SPStyle *style)
913     if (style->stroke_dash.n_dash > 0) {
914         double d[64];
915         int len = MIN(style->stroke_dash.n_dash, 64);
916         for (int i = 0; i < len; i++) {
917             if (style->stroke_width.computed != 0)
918                 d[i] = style->stroke_dash.dash[i] / style->stroke_width.computed;
919             else
920                 d[i] = style->stroke_dash.dash[i]; // is there a better thing to do for stroke_width==0?
921         }
922         dsel->set_dash(len, d, style->stroke_width.computed != 0 ?
923                        style->stroke_dash.offset / style->stroke_width.computed  :
924                        style->stroke_dash.offset);
925     } else {
926         dsel->set_dash(0, NULL, 0.0);
927     }
930 /**
931  * Sets the join type for a line, and updates the stroke style widget's buttons
932  */
933 static void
934 sp_jointype_set (Gtk::Container *spw, unsigned const jointype)
936     Gtk::RadioButton *tb = NULL;
937     switch (jointype) {
938         case SP_STROKE_LINEJOIN_MITER:
939             tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_MITER));
940             break;
941         case SP_STROKE_LINEJOIN_ROUND:
942             tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_ROUND));
943             break;
944         case SP_STROKE_LINEJOIN_BEVEL:
945             tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_BEVEL));
946             break;
947         default:
948             break;
949     }
950     sp_stroke_style_set_join_buttons(spw, tb);
953 /**
954  * Sets the cap type for a line, and updates the stroke style widget's buttons
955  */
956 static void
957 sp_captype_set (Gtk::Container *spw, unsigned const captype)
959     Gtk::RadioButton *tb = NULL;
960     switch (captype) {
961         case SP_STROKE_LINECAP_BUTT:
962             tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_BUTT));
963             break;
964         case SP_STROKE_LINECAP_ROUND:
965             tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_ROUND));
966             break;
967         case SP_STROKE_LINECAP_SQUARE:
968             tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_SQUARE));
969             break;
970         default:
971             break;
972     }
973     sp_stroke_style_set_cap_buttons(spw, tb);
976 /**
977  * Callback for when stroke style widget is updated, including markers, cap type,
978  * join type, etc.
979  */
980 static void
981 sp_stroke_style_line_update(Gtk::Container *spw, Inkscape::Selection *sel)
983     if (spw->get_data("update")) {
984         return;
985     }
987     spw->set_data("update", GINT_TO_POINTER(TRUE));
989     FillOrStroke kind = GPOINTER_TO_INT(spw->get_data("kind")) ? FILL : STROKE;
991     Gtk::Table *sset = static_cast<Gtk::Table *>(spw->get_data("stroke"));
992     Gtk::Adjustment *width = static_cast<Gtk::Adjustment *>(spw->get_data("width"));
993     Gtk::Adjustment *ml = static_cast<Gtk::Adjustment *>(spw->get_data("miterlimit"));
994     SPUnitSelector *us = SP_UNIT_SELECTOR(spw->get_data("units"));
995     SPDashSelector *dsel = static_cast<SPDashSelector *>(spw->get_data("dash"));
997     // create temporary style
998     SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT);
999     // query into it
1000     int result_sw = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEWIDTH);
1001     int result_ml = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEMITERLIMIT);
1002     int result_cap = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKECAP);
1003     int result_join = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEJOIN);
1004     SPIPaint &targPaint = (kind == FILL) ? query->fill : query->stroke;
1006     if (!sel || sel->isEmpty()) {
1007         // Nothing selected, grey-out all controls in the stroke-style dialog
1008         sset->set_sensitive(false);
1010         spw->set_data("update", GINT_TO_POINTER(FALSE));
1012         return;
1013     } else {
1014         sset->set_sensitive(true);
1016         SPUnit const *unit = sp_unit_selector_get_unit(us);
1018         if (result_sw == QUERY_STYLE_MULTIPLE_AVERAGED) {
1019             sp_unit_selector_set_unit(us, &sp_unit_get_by_id(SP_UNIT_PERCENT));
1020         } else {
1021             // same width, or only one object; no sense to keep percent, switch to absolute
1022             if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) {
1023                 sp_unit_selector_set_unit(us, sp_desktop_namedview(SP_ACTIVE_DESKTOP)->doc_units);
1024             }
1025         }
1027         unit = sp_unit_selector_get_unit(us);
1029         if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) {
1030             double avgwidth = sp_pixels_get_units (query->stroke_width.computed, *unit);
1031             width->set_value(avgwidth);
1032         } else {
1033             width->set_value(100);
1034         }
1036         // if none of the selected objects has a stroke, than quite some controls should be disabled
1037         // The markers might still be shown though, so these will not be disabled
1038         bool enabled = (result_sw != QUERY_STYLE_NOTHING) && !targPaint.isNoneSet();
1039         /* No objects stroked, set insensitive */
1040         Gtk::RadioButton *tb = NULL;
1041         tb = static_cast<Gtk::RadioButton *>(spw->get_data("miter join"));
1042         tb->set_sensitive(enabled);
1043         tb = static_cast<Gtk::RadioButton *>(spw->get_data("round join"));
1044         tb->set_sensitive(enabled);
1045         tb = static_cast<Gtk::RadioButton *>(spw->get_data("bevel join"));
1046         tb->set_sensitive(enabled);
1048         Gtk::SpinButton* sb = NULL;
1049         sb = static_cast<Gtk::SpinButton *>(spw->get_data("miterlimit_sb"));
1050         sb->set_sensitive(enabled);
1052         tb = static_cast<Gtk::RadioButton *>(spw->get_data("cap butt"));
1053         tb->set_sensitive(enabled);
1054         tb = static_cast<Gtk::RadioButton *>(spw->get_data("cap round"));
1055         tb->set_sensitive(enabled);
1056         tb = static_cast<Gtk::RadioButton *>(spw->get_data("cap square"));
1057         tb->set_sensitive(enabled);
1059         dsel->set_sensitive(enabled);
1060     }
1062     if (result_ml != QUERY_STYLE_NOTHING)
1063         ml->set_value(query->stroke_miterlimit.value); // TODO: reflect averagedness?
1065     if (result_join != QUERY_STYLE_MULTIPLE_DIFFERENT) {
1066         sp_jointype_set(spw, query->stroke_linejoin.value);
1067     } else {
1068         sp_stroke_style_set_join_buttons(spw, NULL);
1069     }
1071     if (result_cap != QUERY_STYLE_MULTIPLE_DIFFERENT) {
1072         sp_captype_set (spw, query->stroke_linecap.value);
1073     } else {
1074         sp_stroke_style_set_cap_buttons(spw, NULL);
1075     }
1077     sp_style_unref(query);
1079     if (!sel || sel->isEmpty())
1080         return;
1082     GSList const *objects = sel->itemList();
1083     SPObject * const object = SP_OBJECT(objects->data);
1084     SPStyle * const style = SP_OBJECT_STYLE(object);
1086     /* Markers */
1087     sp_stroke_style_update_marker_menus(spw, objects); // FIXME: make this desktop query too
1089     /* Dash */
1090     sp_dash_selector_set_from_style(dsel, style); // FIXME: make this desktop query too
1092     sset->set_sensitive(true);
1094     spw->set_data("update", GINT_TO_POINTER(FALSE));
1097 /**
1098  * Sets a line's dash properties in a CSS style object.
1099  */
1100 static void
1101 sp_stroke_style_set_scaled_dash(SPCSSAttr *css,
1102                                 int ndash, double *dash, double offset,
1103                                 double scale)
1105     if (ndash > 0) {
1106         Inkscape::CSSOStringStream osarray;
1107         for (int i = 0; i < ndash; i++) {
1108             osarray << dash[i] * scale;
1109             if (i < (ndash - 1)) {
1110                 osarray << ",";
1111             }
1112         }
1113         sp_repr_css_set_property(css, "stroke-dasharray", osarray.str().c_str());
1115         Inkscape::CSSOStringStream osoffset;
1116         osoffset << offset * scale;
1117         sp_repr_css_set_property(css, "stroke-dashoffset", osoffset.str().c_str());
1118     } else {
1119         sp_repr_css_set_property(css, "stroke-dasharray", "none");
1120         sp_repr_css_set_property(css, "stroke-dashoffset", NULL);
1121     }
1124 /**
1125  * Sets line properties like width, dashes, markers, etc. on all currently selected items.
1126  */
1127 static void
1128 sp_stroke_style_scale_line(Gtk::Container *spw)
1130     if (spw->get_data("update")) {
1131         return;
1132     }
1134     spw->set_data("update", GINT_TO_POINTER(TRUE));
1136     Gtk::Adjustment *wadj = static_cast<Gtk::Adjustment *>(spw->get_data("width"));
1137     SPUnitSelector *us = SP_UNIT_SELECTOR(spw->get_data("units"));
1138     SPDashSelector *dsel = static_cast<SPDashSelector *>(spw->get_data("dash"));
1139     Gtk::Adjustment *ml = static_cast<Gtk::Adjustment *>(spw->get_data("miterlimit"));
1141     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1142     SPDocument *document = sp_desktop_document (desktop);
1143     Inkscape::Selection *selection = sp_desktop_selection (desktop);
1145     GSList const *items = selection->itemList();
1147     /* TODO: Create some standardized method */
1148     SPCSSAttr *css = sp_repr_css_attr_new();
1150     if (items) {
1152         double width_typed = wadj->get_value();
1153         double const miterlimit = ml->get_value();
1155         SPUnit const *const unit = sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us));
1157         double *dash, offset;
1158         int ndash;
1159         dsel->get_dash(&ndash, &dash, &offset);
1161         for (GSList const *i = items; i != NULL; i = i->next) {
1162             /* Set stroke width */
1163             double width;
1164             if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) {
1165                 width = sp_units_get_pixels (width_typed, *unit);
1166             } else { // percentage
1167                 gdouble old_w = SP_OBJECT_STYLE (i->data)->stroke_width.computed;
1168                 width = old_w * width_typed / 100;
1169             }
1171             {
1172                 Inkscape::CSSOStringStream os_width;
1173                 os_width << width;
1174                 sp_repr_css_set_property(css, "stroke-width", os_width.str().c_str());
1175             }
1177             {
1178                 Inkscape::CSSOStringStream os_ml;
1179                 os_ml << miterlimit;
1180                 sp_repr_css_set_property(css, "stroke-miterlimit", os_ml.str().c_str());
1181             }
1183             /* Set dash */
1184             sp_stroke_style_set_scaled_dash(css, ndash, dash, offset, width);
1186             sp_desktop_apply_css_recursive (SP_OBJECT(i->data), css, true);
1187         }
1189         g_free(dash);
1191         if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) {
1192             // reset to 100 percent
1193             wadj->set_value(100.0);
1194         }
1196     }
1198     // we have already changed the items, so set style without changing selection
1199     // FIXME: move the above stroke-setting stuff, including percentages, to desktop-style
1200     sp_desktop_set_style (desktop, css, false);
1202     sp_repr_css_attr_unref(css);
1203     css = 0;
1205     DocumentUndo::done(document, SP_VERB_DIALOG_FILL_STROKE,
1206                        _("Set stroke style"));
1208     spw->set_data("update", GINT_TO_POINTER(FALSE));
1211 /**
1212  * Callback for when the stroke style's width changes.
1213  * Causes all line styles to be applied to all selected items.
1214  */
1215 static void
1216 sp_stroke_style_width_changed(Gtk::Container *spw)
1218     if (spw->get_data("update")) {
1219         return;
1220     }
1222     sp_stroke_style_scale_line(spw);
1225 /**
1226  * Callback for when the stroke style's miterlimit changes.
1227  * Causes all line styles to be applied to all selected items.
1228  */
1229 static void
1230 sp_stroke_style_miterlimit_changed(Gtk::Container *spw)
1232     if (spw->get_data("update")) {
1233         return;
1234     }
1236     sp_stroke_style_scale_line(spw);
1239 /**
1240  * Callback for when the stroke style's dash changes.
1241  * Causes all line styles to be applied to all selected items.
1242  */
1244 static void
1245 sp_stroke_style_line_dash_changed(Gtk::Container *spw)
1247     if (spw->get_data("update")) {
1248         return;
1249     }
1251     sp_stroke_style_scale_line(spw);
1254 /**
1255  * \brief  This routine handles toggle events for buttons in the stroke style
1256  *         dialog.
1257  * When activated, this routine gets the data for the various widgets, and then
1258  * calls the respective routines to update css properties, etc.
1259  *
1260  */
1261 static void
1262 sp_stroke_style_any_toggled(Gtk::ToggleButton *tb, Gtk::Container *spw)
1264     if (spw->get_data("update")) {
1265         return;
1266     }
1268     if (tb->get_active()) {
1270         gchar const *join
1271             = static_cast<gchar const *>(tb->get_data("join"));
1272         gchar const *cap
1273             = static_cast<gchar const *>(tb->get_data("cap"));
1275         if (join) {
1276             Gtk::SpinButton *ml = static_cast<Gtk::SpinButton *>(spw->get_data("miterlimit_sb"));
1277             ml->set_sensitive(!strcmp(join, "miter"));
1278         }
1280         SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1282         /* TODO: Create some standardized method */
1283         SPCSSAttr *css = sp_repr_css_attr_new();
1285         if (join) {
1286             sp_repr_css_set_property(css, "stroke-linejoin", join);
1288             sp_desktop_set_style (desktop, css);
1290             sp_stroke_style_set_join_buttons(spw, tb);
1291         } else if (cap) {
1292             sp_repr_css_set_property(css, "stroke-linecap", cap);
1294             sp_desktop_set_style (desktop, css);
1296             sp_stroke_style_set_cap_buttons(spw, tb);
1297         }
1299         sp_repr_css_attr_unref(css);
1300         css = 0;
1302         DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_DIALOG_FILL_STROKE,
1303                            _("Set stroke style"));
1304     }
1307 /**
1308  * Updates the join style toggle buttons
1309  */
1310 static void
1311 sp_stroke_style_set_join_buttons(Gtk::Container *spw, Gtk::ToggleButton *active)
1313     Gtk::RadioButton *tb;
1315     tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_MITER));
1316     tb->set_active(active == tb);
1318     Gtk::SpinButton *ml = static_cast<Gtk::SpinButton *>(spw->get_data("miterlimit_sb"));
1319     ml->set_sensitive(active == tb);
1321     tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_ROUND));
1322     tb->set_active(active == tb);
1324     tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_BEVEL));
1325     tb->set_active(active == tb);
1328 /**
1329  * Updates the cap style toggle buttons
1330  */
1331 static void
1332 sp_stroke_style_set_cap_buttons(Gtk::Container *spw, Gtk::ToggleButton *active)
1334     Gtk::RadioButton *tb;
1336     tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_BUTT));
1337     tb->set_active(active == tb);
1338     tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_ROUND));
1339     tb->set_active(active == tb);
1340     tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_SQUARE));
1341     tb->set_active(active == tb);
1344 /**
1345  * Sets the current marker in the marker menu.
1346  */
1347 static void
1348 ink_marker_menu_set_current(SPObject *marker, Gtk::OptionMenu *mnu)
1350     mnu->set_data("update", GINT_TO_POINTER(TRUE));
1352     Gtk::Menu *m = mnu->get_menu();
1353     if (marker != NULL) {
1354         bool mark_is_stock = false;
1355         if (SP_OBJECT_REPR(marker)->attribute("inkscape:stockid"))
1356             mark_is_stock = true;
1358         gchar *markname;
1359         if (mark_is_stock)
1360             markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("inkscape:stockid"));
1361         else
1362             markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("id"));
1364         int markpos = ink_marker_menu_get_pos(m, markname);
1365         mnu->set_history(markpos);
1367         g_free (markname);
1368     }
1369     else {
1370         mnu->set_history(0);
1371     }
1372     mnu->set_data("update", GINT_TO_POINTER(FALSE));
1375 /**
1376  * Updates the marker menus to highlight the appropriate marker and scroll to
1377  * that marker.
1378  */
1379 static void
1380 sp_stroke_style_update_marker_menus(Gtk::Container *spw, GSList const *objects)
1382     struct { char const *key; int loc; } const keyloc[] = {
1383         { "start_mark_menu", SP_MARKER_LOC_START },
1384         { "mid_mark_menu", SP_MARKER_LOC_MID },
1385         { "end_mark_menu", SP_MARKER_LOC_END }
1386     };
1388     bool all_texts = true;
1389     for (GSList *i = (GSList *) objects; i != NULL; i = i->next) {
1390         if (!SP_IS_TEXT (i->data)) {
1391             all_texts = false;
1392         }
1393     }
1395     for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) {
1396         Gtk::OptionMenu *mnu = static_cast<Gtk::OptionMenu *>(spw->get_data(keyloc[i].key));
1397         // Per SVG spec, text objects cannot have markers; disable menus if only texts are selected
1398         mnu->set_sensitive(!all_texts);
1399     }
1401     // We show markers of the first object in the list only
1402     // FIXME: use the first in the list that has the marker of each type, if any
1403     SPObject *object = SP_OBJECT(objects->data);
1405     for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) {
1406         // For all three marker types,
1408         // find the corresponding menu
1409         Gtk::OptionMenu *mnu = static_cast<Gtk::OptionMenu *>(spw->get_data(keyloc[i].key));
1411         // Quit if we're in update state
1412         if (mnu->get_data("update")) {
1413             return;
1414         }
1416         if (object->style->marker[keyloc[i].loc].value != NULL && !all_texts) {
1417             // If the object has this type of markers,
1419             // Extract the name of the marker that the object uses
1420             SPObject *marker = ink_extract_marker_name(object->style->marker[keyloc[i].loc].value, SP_OBJECT_DOCUMENT(object));
1421             // Scroll the menu to that marker
1422             ink_marker_menu_set_current(marker, mnu);
1424         } else {
1425             mnu->set_history(0);
1426         }
1427     }
1431 /**
1432  * Extract the actual name of the link
1433  * e.g. get mTriangle from url(#mTriangle).
1434  * \return Buffer containing the actual name, allocated from GLib;
1435  * the caller should free the buffer when they no longer need it.
1436  */
1437 static SPObject*
1438 ink_extract_marker_name(gchar const *n, SPDocument *doc)
1440     gchar const *p = n;
1441     while (*p != '\0' && *p != '#') {
1442         p++;
1443     }
1445     if (*p == '\0' || p[1] == '\0') {
1446         return NULL;
1447     }
1449     p++;
1450     int c = 0;
1451     while (p[c] != '\0' && p[c] != ')') {
1452         c++;
1453     }
1455     if (p[c] == '\0') {
1456         return NULL;
1457     }
1459     gchar* b = g_strdup(p);
1460     b[c] = '\0';
1462     // FIXME: get the document from the object and let the caller pass it in
1463     SPObject *marker = doc->getObjectById(b);
1464     return marker;
1467 /*
1468   Local Variables:
1469   mode:c++
1470   c-file-style:"stroustrup"
1471   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1472   indent-tabs-mode:nil
1473   fill-column:99
1474   End:
1475 */
1476 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :