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)
119 {
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;
145 }
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)
156 {
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;
214 }
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)
222 {
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;
235 }
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)
244 {
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);
287 }
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)
298 {
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);
313 }
315 /**
316 * Returns a new document containing default start, mid, and end markers.
317 */
318 SPDocument *
319 ink_markers_preview_doc ()
320 {
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);
348 }
350 static void
351 ink_marker_menu_create_menu(Gtk::Menu *m, gchar const *menu_id, SPDocument *doc, SPDocument *sandbox)
352 {
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 }
400 }
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)
407 {
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;
437 }
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)
446 {
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)
510 {
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;
528 }
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 }
578 }
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)
588 {
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;
638 }
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)
647 {
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();
692 spw_label(t, C_("Stroke width", "_Width:"), 0, i, sb);
694 sp_dialog_defocus_on_enter_cpp(sb);
696 hb->pack_start(*sb, false, false, 0);
697 us_old = sp_unit_selector_new(SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE);
698 us = manage(Glib::wrap(us_old));
699 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
700 if (desktop)
701 sp_unit_selector_set_unit (SP_UNIT_SELECTOR(us_old), sp_desktop_namedview(desktop)->doc_units);
702 sp_unit_selector_add_unit(SP_UNIT_SELECTOR(us_old), &sp_unit_get_by_id(SP_UNIT_PERCENT), 0);
703 g_signal_connect ( G_OBJECT (us_old), "set_unit", G_CALLBACK (stroke_width_set_unit), spw );
704 us->show();
705 sp_unit_selector_add_adjustment( SP_UNIT_SELECTOR(us_old), GTK_ADJUSTMENT(a->gobj()) );
706 hb->pack_start(*us, FALSE, FALSE, 0);
707 spw->set_data("units", us_old);
709 a->signal_value_changed().connect(sigc::bind(sigc::ptr_fun(&sp_stroke_style_width_changed), spw));
710 i++;
712 /* Join type */
713 // TRANSLATORS: The line join style specifies the shape to be used at the
714 // corners of paths. It can be "miter", "round" or "bevel".
715 spw_label(t, _("Join:"), 0, i, NULL);
717 hb = spw_hbox(t, 3, 1, i);
719 tb = NULL;
721 tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_JOIN_MITER,
722 hb, spw, "join", "miter");
724 // TRANSLATORS: Miter join: joining lines with a sharp (pointed) corner.
725 // For an example, draw a triangle with a large stroke width and modify the
726 // "Join" option (in the Fill and Stroke dialog).
727 tt->set_tip(*tb, _("Miter join"));
728 spw->set_data("miter join", tb);
730 tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_JOIN_ROUND,
731 hb, spw, "join", "round");
734 // TRANSLATORS: Round join: joining lines with a rounded corner.
735 // For an example, draw a triangle with a large stroke width and modify the
736 // "Join" option (in the Fill and Stroke dialog).
737 tt->set_tip(*tb, _("Round join"));
738 spw->set_data("round join", tb);
740 tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_JOIN_BEVEL,
741 hb, spw, "join", "bevel");
744 // TRANSLATORS: Bevel join: joining lines with a blunted (flattened) corner.
745 // For an example, draw a triangle with a large stroke width and modify the
746 // "Join" option (in the Fill and Stroke dialog).
747 tt->set_tip(*tb, _("Bevel join"));
748 spw->set_data("bevel join", tb);
750 i++;
752 /* Miterlimit */
753 // TRANSLATORS: Miter limit: only for "miter join", this limits the length
754 // of the sharp "spike" when the lines connect at too sharp an angle.
755 // When two line segments meet at a sharp angle, a miter join results in a
756 // spike that extends well beyond the connection point. The purpose of the
757 // miter limit is to cut off such spikes (i.e. convert them into bevels)
758 // when they become too long.
759 //spw_label(t, _("Miter _limit:"), 0, i);
761 hb = spw_hbox(t, 3, 1, i);
763 a = new Gtk::Adjustment(4.0, 0.0, 100.0, 0.1, 10.0, 0.0);
764 spw->set_data("miterlimit", a);
766 sb = new Gtk::SpinButton(*a, 0.1, 2);
767 tt->set_tip(*sb, _("Maximum length of the miter (in units of stroke width)"));
768 sb->show();
769 spw_label(t, _("Miter _limit:"), 0, i, sb);
770 spw->set_data("miterlimit_sb", sb);
771 sp_dialog_defocus_on_enter_cpp(sb);
773 hb->pack_start(*sb, false, false, 0);
775 a->signal_value_changed().connect(sigc::bind(sigc::ptr_fun(&sp_stroke_style_miterlimit_changed), spw));
776 i++;
778 /* Cap type */
779 // TRANSLATORS: cap type specifies the shape for the ends of lines
780 //spw_label(t, _("_Cap:"), 0, i);
781 spw_label(t, _("Cap:"), 0, i, NULL);
783 hb = spw_hbox(t, 3, 1, i);
785 tb = NULL;
787 tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_CAP_BUTT,
788 hb, spw, "cap", "butt");
789 spw->set_data("cap butt", tb);
791 // TRANSLATORS: Butt cap: the line shape does not extend beyond the end point
792 // of the line; the ends of the line are square
793 tt->set_tip(*tb, _("Butt cap"));
795 tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_CAP_ROUND,
796 hb, spw, "cap", "round");
797 spw->set_data("cap round", tb);
799 // TRANSLATORS: Round cap: the line shape extends beyond the end point of the
800 // line; the ends of the line are rounded
801 tt->set_tip(*tb, _("Round cap"));
803 tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_CAP_SQUARE,
804 hb, spw, "cap", "square");
805 spw->set_data("cap square", tb);
807 // TRANSLATORS: Square cap: the line shape extends beyond the end point of the
808 // line; the ends of the line are square
809 tt->set_tip(*tb, _("Square cap"));
811 i++;
814 /* Dash */
815 spw_label(t, _("Dashes:"), 0, i, NULL); //no mnemonic for now
816 //decide what to do:
817 // implement a set_mnemonic_source function in the
818 // SPDashSelector class, so that we do not have to
819 // expose any of the underlying widgets?
820 ds = manage(new SPDashSelector);
822 ds->show();
823 t->attach(*ds, 1, 4, i, i+1, (Gtk::EXPAND | Gtk::FILL), static_cast<Gtk::AttachOptions>(0), 0, 0);
824 spw->set_data("dash", ds);
825 ds->changed_signal.connect(sigc::bind(sigc::ptr_fun(&sp_stroke_style_line_dash_changed), spw));
826 i++;
828 /* Drop down marker selectors*/
829 // TODO: this code can be shortened by iterating over the possible menus!
831 // doing this here once, instead of for each preview, to speed things up
832 SPDocument *sandbox = ink_markers_preview_doc ();
834 // TRANSLATORS: Path markers are an SVG feature that allows you to attach arbitrary shapes
835 // (arrowheads, bullets, faces, whatever) to the start, end, or middle nodes of a path.
836 //spw_label(t, _("_Start Markers:"), 0, i);
837 marker_start_menu = ink_marker_menu(spw ,"marker-start", sandbox);
838 spw_label(t, _("_Start Markers:"), 0, i, marker_start_menu);
839 tt->set_tip(*marker_start_menu, _("Start Markers are drawn on the first node of a path or shape"));
840 marker_start_menu_connection = marker_start_menu->signal_changed().connect(
841 sigc::bind<Gtk::OptionMenu *, Gtk::Container *, SPMarkerLoc>(
842 sigc::ptr_fun(&sp_marker_select), marker_start_menu, spw, SP_MARKER_LOC_START));
843 marker_start_menu->show();
844 t->attach(*marker_start_menu, 1, 4, i, i+1, (Gtk::EXPAND | Gtk::FILL), static_cast<Gtk::AttachOptions>(0), 0, 0);
845 spw->set_data("start_mark_menu", marker_start_menu);
847 i++;
848 //spw_label(t, _("_Mid Markers:"), 0, i);
849 marker_mid_menu = ink_marker_menu(spw ,"marker-mid", sandbox);
850 spw_label(t, _("_Mid Markers:"), 0, i, marker_mid_menu);
851 tt->set_tip(*marker_mid_menu, _("Mid Markers are drawn on every node of a path or shape except the first and last nodes"));
852 marker_mid_menu_connection = marker_mid_menu->signal_changed().connect(
853 sigc::bind<Gtk::OptionMenu *, Gtk::Container *, SPMarkerLoc>(
854 sigc::ptr_fun(&sp_marker_select), marker_mid_menu,spw, SP_MARKER_LOC_MID));
855 marker_mid_menu->show();
856 t->attach(*marker_mid_menu, 1, 4, i, i+1, (Gtk::EXPAND | Gtk::FILL), static_cast<Gtk::AttachOptions>(0), 0, 0);
857 spw->set_data("mid_mark_menu", marker_mid_menu);
859 i++;
860 //spw_label(t, _("_End Markers:"), 0, i);
861 marker_end_menu = ink_marker_menu(spw ,"marker-end", sandbox);
862 spw_label(t, _("_End Markers:"), 0, i, marker_end_menu);
863 tt->set_tip(*marker_end_menu, _("End Markers are drawn on the last node of a path or shape"));
864 marker_end_menu_connection = marker_end_menu->signal_changed().connect(
865 sigc::bind<Gtk::OptionMenu *, Gtk::Container *, SPMarkerLoc>(
866 sigc::ptr_fun(&sp_marker_select), marker_end_menu, spw, SP_MARKER_LOC_END));
867 marker_end_menu->show();
868 t->attach(*marker_end_menu, 1, 4, i, i+1, (Gtk::EXPAND | Gtk::FILL), static_cast<Gtk::AttachOptions>(0), 0, 0);
869 spw->set_data("end_mark_menu", marker_end_menu);
871 i++;
873 // FIXME: we cheat and still use gtk+ signals
875 g_signal_connect(G_OBJECT(spw_old), "modify_selection",
876 G_CALLBACK(sp_stroke_style_line_selection_modified),
877 spw);
878 g_signal_connect(G_OBJECT(spw_old), "change_selection",
879 G_CALLBACK(sp_stroke_style_line_selection_changed),
880 spw);
882 sp_stroke_style_line_update(spw, desktop ? sp_desktop_selection(desktop) : NULL);
884 return spw;
885 }
887 /**
888 * Callback for when stroke style widget is modified.
889 * Triggers update action.
890 */
891 static void
892 sp_stroke_style_line_selection_modified(SPWidget *,
893 Inkscape::Selection *selection,
894 guint flags,
895 gpointer data)
896 {
897 Gtk::Container *spw = static_cast<Gtk::Container *>(data);
898 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) {
899 sp_stroke_style_line_update(spw, selection);
900 }
902 }
904 /**
905 * Callback for when stroke style widget is changed.
906 * Triggers update action.
907 */
908 static void
909 sp_stroke_style_line_selection_changed(SPWidget *,
910 Inkscape::Selection *selection,
911 gpointer data)
912 {
913 Gtk::Container *spw = static_cast<Gtk::Container *>(data);
914 sp_stroke_style_line_update(spw, selection);
915 }
917 /**
918 * Sets selector widgets' dash style from an SPStyle object.
919 */
920 static void
921 sp_dash_selector_set_from_style(SPDashSelector *dsel, SPStyle *style)
922 {
923 if (style->stroke_dash.n_dash > 0) {
924 double d[64];
925 int len = MIN(style->stroke_dash.n_dash, 64);
926 for (int i = 0; i < len; i++) {
927 if (style->stroke_width.computed != 0)
928 d[i] = style->stroke_dash.dash[i] / style->stroke_width.computed;
929 else
930 d[i] = style->stroke_dash.dash[i]; // is there a better thing to do for stroke_width==0?
931 }
932 dsel->set_dash(len, d, style->stroke_width.computed != 0 ?
933 style->stroke_dash.offset / style->stroke_width.computed :
934 style->stroke_dash.offset);
935 } else {
936 dsel->set_dash(0, NULL, 0.0);
937 }
938 }
940 /**
941 * Sets the join type for a line, and updates the stroke style widget's buttons
942 */
943 static void
944 sp_jointype_set (Gtk::Container *spw, unsigned const jointype)
945 {
946 Gtk::RadioButton *tb = NULL;
947 switch (jointype) {
948 case SP_STROKE_LINEJOIN_MITER:
949 tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_MITER));
950 break;
951 case SP_STROKE_LINEJOIN_ROUND:
952 tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_ROUND));
953 break;
954 case SP_STROKE_LINEJOIN_BEVEL:
955 tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_BEVEL));
956 break;
957 default:
958 break;
959 }
960 sp_stroke_style_set_join_buttons(spw, tb);
961 }
963 /**
964 * Sets the cap type for a line, and updates the stroke style widget's buttons
965 */
966 static void
967 sp_captype_set (Gtk::Container *spw, unsigned const captype)
968 {
969 Gtk::RadioButton *tb = NULL;
970 switch (captype) {
971 case SP_STROKE_LINECAP_BUTT:
972 tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_BUTT));
973 break;
974 case SP_STROKE_LINECAP_ROUND:
975 tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_ROUND));
976 break;
977 case SP_STROKE_LINECAP_SQUARE:
978 tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_SQUARE));
979 break;
980 default:
981 break;
982 }
983 sp_stroke_style_set_cap_buttons(spw, tb);
984 }
986 /**
987 * Callback for when stroke style widget is updated, including markers, cap type,
988 * join type, etc.
989 */
990 static void
991 sp_stroke_style_line_update(Gtk::Container *spw, Inkscape::Selection *sel)
992 {
993 if (spw->get_data("update")) {
994 return;
995 }
997 spw->set_data("update", GINT_TO_POINTER(TRUE));
999 FillOrStroke kind = GPOINTER_TO_INT(spw->get_data("kind")) ? FILL : STROKE;
1001 Gtk::Table *sset = static_cast<Gtk::Table *>(spw->get_data("stroke"));
1002 Gtk::Adjustment *width = static_cast<Gtk::Adjustment *>(spw->get_data("width"));
1003 Gtk::Adjustment *ml = static_cast<Gtk::Adjustment *>(spw->get_data("miterlimit"));
1004 SPUnitSelector *us = SP_UNIT_SELECTOR(spw->get_data("units"));
1005 SPDashSelector *dsel = static_cast<SPDashSelector *>(spw->get_data("dash"));
1007 // create temporary style
1008 SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT);
1009 // query into it
1010 int result_sw = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEWIDTH);
1011 int result_ml = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEMITERLIMIT);
1012 int result_cap = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKECAP);
1013 int result_join = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEJOIN);
1014 SPIPaint &targPaint = (kind == FILL) ? query->fill : query->stroke;
1016 if (!sel || sel->isEmpty()) {
1017 // Nothing selected, grey-out all controls in the stroke-style dialog
1018 sset->set_sensitive(false);
1020 spw->set_data("update", GINT_TO_POINTER(FALSE));
1022 return;
1023 } else {
1024 sset->set_sensitive(true);
1026 SPUnit const *unit = sp_unit_selector_get_unit(us);
1028 if (result_sw == QUERY_STYLE_MULTIPLE_AVERAGED) {
1029 sp_unit_selector_set_unit(us, &sp_unit_get_by_id(SP_UNIT_PERCENT));
1030 } else {
1031 // same width, or only one object; no sense to keep percent, switch to absolute
1032 if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) {
1033 sp_unit_selector_set_unit(us, sp_desktop_namedview(SP_ACTIVE_DESKTOP)->doc_units);
1034 }
1035 }
1037 unit = sp_unit_selector_get_unit(us);
1039 if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) {
1040 double avgwidth = sp_pixels_get_units (query->stroke_width.computed, *unit);
1041 width->set_value(avgwidth);
1042 } else {
1043 width->set_value(100);
1044 }
1046 // if none of the selected objects has a stroke, than quite some controls should be disabled
1047 // The markers might still be shown though, so these will not be disabled
1048 bool enabled = (result_sw != QUERY_STYLE_NOTHING) && !targPaint.isNoneSet();
1049 /* No objects stroked, set insensitive */
1050 Gtk::RadioButton *tb = NULL;
1051 tb = static_cast<Gtk::RadioButton *>(spw->get_data("miter join"));
1052 tb->set_sensitive(enabled);
1053 tb = static_cast<Gtk::RadioButton *>(spw->get_data("round join"));
1054 tb->set_sensitive(enabled);
1055 tb = static_cast<Gtk::RadioButton *>(spw->get_data("bevel join"));
1056 tb->set_sensitive(enabled);
1058 Gtk::SpinButton* sb = NULL;
1059 sb = static_cast<Gtk::SpinButton *>(spw->get_data("miterlimit_sb"));
1060 sb->set_sensitive(enabled);
1062 tb = static_cast<Gtk::RadioButton *>(spw->get_data("cap butt"));
1063 tb->set_sensitive(enabled);
1064 tb = static_cast<Gtk::RadioButton *>(spw->get_data("cap round"));
1065 tb->set_sensitive(enabled);
1066 tb = static_cast<Gtk::RadioButton *>(spw->get_data("cap square"));
1067 tb->set_sensitive(enabled);
1069 dsel->set_sensitive(enabled);
1070 }
1072 if (result_ml != QUERY_STYLE_NOTHING)
1073 ml->set_value(query->stroke_miterlimit.value); // TODO: reflect averagedness?
1075 if (result_join != QUERY_STYLE_MULTIPLE_DIFFERENT) {
1076 sp_jointype_set(spw, query->stroke_linejoin.value);
1077 } else {
1078 sp_stroke_style_set_join_buttons(spw, NULL);
1079 }
1081 if (result_cap != QUERY_STYLE_MULTIPLE_DIFFERENT) {
1082 sp_captype_set (spw, query->stroke_linecap.value);
1083 } else {
1084 sp_stroke_style_set_cap_buttons(spw, NULL);
1085 }
1087 sp_style_unref(query);
1089 if (!sel || sel->isEmpty())
1090 return;
1092 GSList const *objects = sel->itemList();
1093 SPObject * const object = SP_OBJECT(objects->data);
1094 SPStyle * const style = SP_OBJECT_STYLE(object);
1096 /* Markers */
1097 sp_stroke_style_update_marker_menus(spw, objects); // FIXME: make this desktop query too
1099 /* Dash */
1100 sp_dash_selector_set_from_style(dsel, style); // FIXME: make this desktop query too
1102 sset->set_sensitive(true);
1104 spw->set_data("update", GINT_TO_POINTER(FALSE));
1105 }
1107 /**
1108 * Sets a line's dash properties in a CSS style object.
1109 */
1110 static void
1111 sp_stroke_style_set_scaled_dash(SPCSSAttr *css,
1112 int ndash, double *dash, double offset,
1113 double scale)
1114 {
1115 if (ndash > 0) {
1116 Inkscape::CSSOStringStream osarray;
1117 for (int i = 0; i < ndash; i++) {
1118 osarray << dash[i] * scale;
1119 if (i < (ndash - 1)) {
1120 osarray << ",";
1121 }
1122 }
1123 sp_repr_css_set_property(css, "stroke-dasharray", osarray.str().c_str());
1125 Inkscape::CSSOStringStream osoffset;
1126 osoffset << offset * scale;
1127 sp_repr_css_set_property(css, "stroke-dashoffset", osoffset.str().c_str());
1128 } else {
1129 sp_repr_css_set_property(css, "stroke-dasharray", "none");
1130 sp_repr_css_set_property(css, "stroke-dashoffset", NULL);
1131 }
1132 }
1134 /**
1135 * Sets line properties like width, dashes, markers, etc. on all currently selected items.
1136 */
1137 static void
1138 sp_stroke_style_scale_line(Gtk::Container *spw)
1139 {
1140 if (spw->get_data("update")) {
1141 return;
1142 }
1144 spw->set_data("update", GINT_TO_POINTER(TRUE));
1146 Gtk::Adjustment *wadj = static_cast<Gtk::Adjustment *>(spw->get_data("width"));
1147 SPUnitSelector *us = SP_UNIT_SELECTOR(spw->get_data("units"));
1148 SPDashSelector *dsel = static_cast<SPDashSelector *>(spw->get_data("dash"));
1149 Gtk::Adjustment *ml = static_cast<Gtk::Adjustment *>(spw->get_data("miterlimit"));
1151 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1152 SPDocument *document = sp_desktop_document (desktop);
1153 Inkscape::Selection *selection = sp_desktop_selection (desktop);
1155 GSList const *items = selection->itemList();
1157 /* TODO: Create some standardized method */
1158 SPCSSAttr *css = sp_repr_css_attr_new();
1160 if (items) {
1162 double width_typed = wadj->get_value();
1163 double const miterlimit = ml->get_value();
1165 SPUnit const *const unit = sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us));
1167 double *dash, offset;
1168 int ndash;
1169 dsel->get_dash(&ndash, &dash, &offset);
1171 for (GSList const *i = items; i != NULL; i = i->next) {
1172 /* Set stroke width */
1173 double width;
1174 if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) {
1175 width = sp_units_get_pixels (width_typed, *unit);
1176 } else { // percentage
1177 gdouble old_w = SP_OBJECT_STYLE (i->data)->stroke_width.computed;
1178 width = old_w * width_typed / 100;
1179 }
1181 {
1182 Inkscape::CSSOStringStream os_width;
1183 os_width << width;
1184 sp_repr_css_set_property(css, "stroke-width", os_width.str().c_str());
1185 }
1187 {
1188 Inkscape::CSSOStringStream os_ml;
1189 os_ml << miterlimit;
1190 sp_repr_css_set_property(css, "stroke-miterlimit", os_ml.str().c_str());
1191 }
1193 /* Set dash */
1194 sp_stroke_style_set_scaled_dash(css, ndash, dash, offset, width);
1196 sp_desktop_apply_css_recursive (SP_OBJECT(i->data), css, true);
1197 }
1199 g_free(dash);
1201 if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) {
1202 // reset to 100 percent
1203 wadj->set_value(100.0);
1204 }
1206 }
1208 // we have already changed the items, so set style without changing selection
1209 // FIXME: move the above stroke-setting stuff, including percentages, to desktop-style
1210 sp_desktop_set_style (desktop, css, false);
1212 sp_repr_css_attr_unref(css);
1213 css = 0;
1215 DocumentUndo::done(document, SP_VERB_DIALOG_FILL_STROKE,
1216 _("Set stroke style"));
1218 spw->set_data("update", GINT_TO_POINTER(FALSE));
1219 }
1221 /**
1222 * Callback for when the stroke style's width changes.
1223 * Causes all line styles to be applied to all selected items.
1224 */
1225 static void
1226 sp_stroke_style_width_changed(Gtk::Container *spw)
1227 {
1228 if (spw->get_data("update")) {
1229 return;
1230 }
1232 sp_stroke_style_scale_line(spw);
1233 }
1235 /**
1236 * Callback for when the stroke style's miterlimit changes.
1237 * Causes all line styles to be applied to all selected items.
1238 */
1239 static void
1240 sp_stroke_style_miterlimit_changed(Gtk::Container *spw)
1241 {
1242 if (spw->get_data("update")) {
1243 return;
1244 }
1246 sp_stroke_style_scale_line(spw);
1247 }
1249 /**
1250 * Callback for when the stroke style's dash changes.
1251 * Causes all line styles to be applied to all selected items.
1252 */
1254 static void
1255 sp_stroke_style_line_dash_changed(Gtk::Container *spw)
1256 {
1257 if (spw->get_data("update")) {
1258 return;
1259 }
1261 sp_stroke_style_scale_line(spw);
1262 }
1264 /**
1265 * \brief This routine handles toggle events for buttons in the stroke style
1266 * dialog.
1267 * When activated, this routine gets the data for the various widgets, and then
1268 * calls the respective routines to update css properties, etc.
1269 *
1270 */
1271 static void
1272 sp_stroke_style_any_toggled(Gtk::ToggleButton *tb, Gtk::Container *spw)
1273 {
1274 if (spw->get_data("update")) {
1275 return;
1276 }
1278 if (tb->get_active()) {
1280 gchar const *join
1281 = static_cast<gchar const *>(tb->get_data("join"));
1282 gchar const *cap
1283 = static_cast<gchar const *>(tb->get_data("cap"));
1285 if (join) {
1286 Gtk::SpinButton *ml = static_cast<Gtk::SpinButton *>(spw->get_data("miterlimit_sb"));
1287 ml->set_sensitive(!strcmp(join, "miter"));
1288 }
1290 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1292 /* TODO: Create some standardized method */
1293 SPCSSAttr *css = sp_repr_css_attr_new();
1295 if (join) {
1296 sp_repr_css_set_property(css, "stroke-linejoin", join);
1298 sp_desktop_set_style (desktop, css);
1300 sp_stroke_style_set_join_buttons(spw, tb);
1301 } else if (cap) {
1302 sp_repr_css_set_property(css, "stroke-linecap", cap);
1304 sp_desktop_set_style (desktop, css);
1306 sp_stroke_style_set_cap_buttons(spw, tb);
1307 }
1309 sp_repr_css_attr_unref(css);
1310 css = 0;
1312 DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_DIALOG_FILL_STROKE,
1313 _("Set stroke style"));
1314 }
1315 }
1317 /**
1318 * Updates the join style toggle buttons
1319 */
1320 static void
1321 sp_stroke_style_set_join_buttons(Gtk::Container *spw, Gtk::ToggleButton *active)
1322 {
1323 Gtk::RadioButton *tb;
1325 tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_MITER));
1326 tb->set_active(active == tb);
1328 Gtk::SpinButton *ml = static_cast<Gtk::SpinButton *>(spw->get_data("miterlimit_sb"));
1329 ml->set_sensitive(active == tb);
1331 tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_ROUND));
1332 tb->set_active(active == tb);
1334 tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_BEVEL));
1335 tb->set_active(active == tb);
1336 }
1338 /**
1339 * Updates the cap style toggle buttons
1340 */
1341 static void
1342 sp_stroke_style_set_cap_buttons(Gtk::Container *spw, Gtk::ToggleButton *active)
1343 {
1344 Gtk::RadioButton *tb;
1346 tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_BUTT));
1347 tb->set_active(active == tb);
1348 tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_ROUND));
1349 tb->set_active(active == tb);
1350 tb = static_cast<Gtk::RadioButton *>(spw->get_data(INKSCAPE_ICON_STROKE_CAP_SQUARE));
1351 tb->set_active(active == tb);
1352 }
1354 /**
1355 * Sets the current marker in the marker menu.
1356 */
1357 static void
1358 ink_marker_menu_set_current(SPObject *marker, Gtk::OptionMenu *mnu)
1359 {
1360 mnu->set_data("update", GINT_TO_POINTER(TRUE));
1362 Gtk::Menu *m = mnu->get_menu();
1363 if (marker != NULL) {
1364 bool mark_is_stock = false;
1365 if (SP_OBJECT_REPR(marker)->attribute("inkscape:stockid"))
1366 mark_is_stock = true;
1368 gchar *markname;
1369 if (mark_is_stock)
1370 markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("inkscape:stockid"));
1371 else
1372 markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("id"));
1374 int markpos = ink_marker_menu_get_pos(m, markname);
1375 mnu->set_history(markpos);
1377 g_free (markname);
1378 }
1379 else {
1380 mnu->set_history(0);
1381 }
1382 mnu->set_data("update", GINT_TO_POINTER(FALSE));
1383 }
1385 /**
1386 * Updates the marker menus to highlight the appropriate marker and scroll to
1387 * that marker.
1388 */
1389 static void
1390 sp_stroke_style_update_marker_menus(Gtk::Container *spw, GSList const *objects)
1391 {
1392 struct { char const *key; int loc; } const keyloc[] = {
1393 { "start_mark_menu", SP_MARKER_LOC_START },
1394 { "mid_mark_menu", SP_MARKER_LOC_MID },
1395 { "end_mark_menu", SP_MARKER_LOC_END }
1396 };
1398 bool all_texts = true;
1399 for (GSList *i = (GSList *) objects; i != NULL; i = i->next) {
1400 if (!SP_IS_TEXT (i->data)) {
1401 all_texts = false;
1402 }
1403 }
1405 for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) {
1406 Gtk::OptionMenu *mnu = static_cast<Gtk::OptionMenu *>(spw->get_data(keyloc[i].key));
1407 // Per SVG spec, text objects cannot have markers; disable menus if only texts are selected
1408 mnu->set_sensitive(!all_texts);
1409 }
1411 // We show markers of the first object in the list only
1412 // FIXME: use the first in the list that has the marker of each type, if any
1413 SPObject *object = SP_OBJECT(objects->data);
1415 for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) {
1416 // For all three marker types,
1418 // find the corresponding menu
1419 Gtk::OptionMenu *mnu = static_cast<Gtk::OptionMenu *>(spw->get_data(keyloc[i].key));
1421 // Quit if we're in update state
1422 if (mnu->get_data("update")) {
1423 return;
1424 }
1426 if (object->style->marker[keyloc[i].loc].value != NULL && !all_texts) {
1427 // If the object has this type of markers,
1429 // Extract the name of the marker that the object uses
1430 SPObject *marker = ink_extract_marker_name(object->style->marker[keyloc[i].loc].value, SP_OBJECT_DOCUMENT(object));
1431 // Scroll the menu to that marker
1432 ink_marker_menu_set_current(marker, mnu);
1434 } else {
1435 mnu->set_history(0);
1436 }
1437 }
1438 }
1441 /**
1442 * Extract the actual name of the link
1443 * e.g. get mTriangle from url(#mTriangle).
1444 * \return Buffer containing the actual name, allocated from GLib;
1445 * the caller should free the buffer when they no longer need it.
1446 */
1447 static SPObject*
1448 ink_extract_marker_name(gchar const *n, SPDocument *doc)
1449 {
1450 gchar const *p = n;
1451 while (*p != '\0' && *p != '#') {
1452 p++;
1453 }
1455 if (*p == '\0' || p[1] == '\0') {
1456 return NULL;
1457 }
1459 p++;
1460 int c = 0;
1461 while (p[c] != '\0' && p[c] != ')') {
1462 c++;
1463 }
1465 if (p[c] == '\0') {
1466 return NULL;
1467 }
1469 gchar* b = g_strdup(p);
1470 b[c] = '\0';
1472 // FIXME: get the document from the object and let the caller pass it in
1473 SPObject *marker = doc->getObjectById(b);
1474 return marker;
1475 }
1477 /*
1478 Local Variables:
1479 mode:c++
1480 c-file-style:"stroustrup"
1481 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1482 indent-tabs-mode:nil
1483 fill-column:99
1484 End:
1485 */
1486 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :