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