/** @file * @brief Stroke style dialog */ /* Authors: * Lauris Kaplinski * Bryce Harrington * bulia byak * Maximilian Albert * Josh Andler * * Copyright (C) 2001-2005 authors * Copyright (C) 2001 Ximian, Inc. * Copyright (C) 2004 John Cliff * Copyright (C) 2008 Maximilian Albert (gtkmm-ification) * * Released under GNU GPL, read the file 'COPYING' for more information */ #define noSP_SS_VERBOSE #include #include #include #include "desktop-handles.h" #include "desktop-style.h" #include "dialogs/dialog-events.h" #include "display/nr-arena.h" #include "display/nr-arena-item.h" #include "document-private.h" #include "gradient-chemistry.h" #include "helper/stock-items.h" #include "helper/unit-menu.h" #include "helper/units.h" #include "inkscape.h" #include "io/sys.h" #include "marker.h" #include "path-prefix.h" #include "selection.h" #include "sp-linear-gradient.h" #include "sp-namedview.h" #include "sp-pattern.h" #include "sp-radial-gradient.h" #include "sp-rect.h" #include "sp-text.h" #include "style.h" #include "svg/css-ostringstream.h" #include "ui/cache/svg_preview_cache.h" #include "ui/icon-names.h" #include "widgets/dash-selector.h" #include "widgets/icon.h" #include "widgets/paint-selector.h" #include "widgets/sp-widget.h" #include "widgets/spw-utilities.h" #include "xml/repr.h" #include "widgets/stroke-style.h" /* Paint */ static void sp_stroke_style_paint_selection_modified (SPWidget *spw, Inkscape::Selection *selection, guint flags, SPPaintSelector *psel); static void sp_stroke_style_paint_selection_changed (SPWidget *spw, Inkscape::Selection *selection, SPPaintSelector *psel); static void sp_stroke_style_paint_update(SPWidget *spw); static void sp_stroke_style_paint_mode_changed(SPPaintSelector *psel, SPPaintSelectorMode mode, SPWidget *spw); static void sp_stroke_style_paint_dragged(SPPaintSelector *psel, SPWidget *spw); static void sp_stroke_style_paint_changed(SPPaintSelector *psel, SPWidget *spw); static void sp_stroke_style_widget_change_subselection ( Inkscape::Application *inkscape, SPDesktop *desktop, SPWidget *spw ); static void sp_stroke_style_widget_transientize_callback(Inkscape::Application *inkscape, SPDesktop *desktop, SPWidget *spw ); /** Marker selection option menus */ static Gtk::OptionMenu * marker_start_menu = NULL; static Gtk::OptionMenu * marker_mid_menu = NULL; static Gtk::OptionMenu * marker_end_menu = NULL; sigc::connection marker_start_menu_connection; sigc::connection marker_mid_menu_connection; sigc::connection marker_end_menu_connection; static SPObject *ink_extract_marker_name(gchar const *n, SPDocument *doc); static void ink_markers_menu_update(Gtk::Container* spw, SPMarkerLoc const which); static Inkscape::UI::Cache::SvgPreview svg_preview_cache; /** * Create the stroke style widget, and hook up all the signals. */ GtkWidget * sp_stroke_style_paint_widget_new(void) { GtkWidget *spw, *psel; spw = sp_widget_new_global(INKSCAPE); psel = sp_paint_selector_new(false); // without fillrule selector gtk_widget_show(psel); gtk_container_add(GTK_CONTAINER(spw), psel); gtk_object_set_data(GTK_OBJECT(spw), "paint-selector", psel); gtk_signal_connect(GTK_OBJECT(spw), "modify_selection", GTK_SIGNAL_FUNC(sp_stroke_style_paint_selection_modified), psel); gtk_signal_connect(GTK_OBJECT(spw), "change_selection", GTK_SIGNAL_FUNC(sp_stroke_style_paint_selection_changed), psel); g_signal_connect (INKSCAPE, "change_subselection", G_CALLBACK (sp_stroke_style_widget_change_subselection), spw); g_signal_connect (G_OBJECT(INKSCAPE), "activate_desktop", G_CALLBACK (sp_stroke_style_widget_transientize_callback), spw ); gtk_signal_connect(GTK_OBJECT(psel), "mode_changed", GTK_SIGNAL_FUNC(sp_stroke_style_paint_mode_changed), spw); gtk_signal_connect(GTK_OBJECT(psel), "dragged", GTK_SIGNAL_FUNC(sp_stroke_style_paint_dragged), spw); gtk_signal_connect(GTK_OBJECT(psel), "changed", GTK_SIGNAL_FUNC(sp_stroke_style_paint_changed), spw); sp_stroke_style_paint_update (SP_WIDGET(spw)); return spw; } /** * On signal modified, invokes an update of the stroke style paint object. */ static void sp_stroke_style_paint_selection_modified( SPWidget *spw, Inkscape::Selection */*selection*/, guint flags, SPPaintSelector */*psel*/ ) { if (flags & ( SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG) ) { sp_stroke_style_paint_update(spw); } } /** * On signal selection changed, invokes an update of the stroke style paint object. */ static void sp_stroke_style_paint_selection_changed( SPWidget *spw, Inkscape::Selection */*selection*/, SPPaintSelector */*psel*/ ) { sp_stroke_style_paint_update (spw); } /** * On signal change subselection, invoke an update of the stroke style widget. */ static void sp_stroke_style_widget_change_subselection( Inkscape::Application */*inkscape*/, SPDesktop */*desktop*/, SPWidget *spw ) { sp_stroke_style_paint_update (spw); } /** * Gets the active stroke style property, then sets the appropriate color, alpha, gradient, * pattern, etc. for the paint-selector. */ static void sp_stroke_style_paint_update (SPWidget *spw) { if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { return; } gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(TRUE)); SPPaintSelector *psel = SP_PAINT_SELECTOR(gtk_object_get_data(GTK_OBJECT(spw), "paint-selector")); // create temporary style SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT); // query into it int result = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKE); switch (result) { case QUERY_STYLE_NOTHING: { /* No paint at all */ sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_EMPTY); break; } case QUERY_STYLE_SINGLE: case QUERY_STYLE_MULTIPLE_AVERAGED: // TODO: treat this slightly differently, e.g. display "averaged" somewhere in paint selector case QUERY_STYLE_MULTIPLE_SAME: { SPPaintSelectorMode pselmode = sp_style_determine_paint_selector_mode (query, false); sp_paint_selector_set_mode (psel, pselmode); if (query->stroke.set && query->stroke.isPaintserver()) { SPPaintServer *server = SP_STYLE_STROKE_SERVER (query); if (server && server->isSwatch()) { sp_paint_selector_set_swatch( psel, server ); } else if (SP_IS_LINEARGRADIENT (server)) { SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE); sp_paint_selector_set_gradient_linear (psel, vector); SPLinearGradient *lg = SP_LINEARGRADIENT (server); sp_paint_selector_set_gradient_properties (psel, SP_GRADIENT_UNITS (lg), SP_GRADIENT_SPREAD (lg)); } else if (SP_IS_RADIALGRADIENT (server)) { SPGradient *vector = sp_gradient_get_vector (SP_GRADIENT (server), FALSE); sp_paint_selector_set_gradient_radial (psel, vector); SPRadialGradient *rg = SP_RADIALGRADIENT (server); sp_paint_selector_set_gradient_properties (psel, SP_GRADIENT_UNITS (rg), SP_GRADIENT_SPREAD (rg)); } else if (SP_IS_PATTERN (server)) { SPPattern *pat = pattern_getroot (SP_PATTERN (server)); sp_update_pattern_list (psel, pat); } } else if (query->stroke.set && query->stroke.isColor()) { sp_paint_selector_set_color_alpha (psel, &query->stroke.value.color, SP_SCALE24_TO_FLOAT (query->stroke_opacity.value)); } break; } case QUERY_STYLE_MULTIPLE_DIFFERENT: { sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_MULTIPLE); break; } } sp_style_unref(query); gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(FALSE)); } /** * When the mode is changed, invoke a regular changed handler. */ static void sp_stroke_style_paint_mode_changed( SPPaintSelector *psel, SPPaintSelectorMode /*mode*/, SPWidget *spw ) { if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { return; } /* TODO: Does this work? * Not really, here we have to get old color back from object * Instead of relying on paint widget having meaningful colors set */ sp_stroke_style_paint_changed(psel, spw); } static gchar const *const undo_label_1 = "stroke:flatcolor:1"; static gchar const *const undo_label_2 = "stroke:flatcolor:2"; static gchar const *undo_label = undo_label_1; /** * When a drag callback occurs on a paint selector object, if it is a RGB or CMYK * color mode, then set the stroke opacity to psel's flat color. */ static void sp_stroke_style_paint_dragged(SPPaintSelector *psel, SPWidget *spw) { if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { return; } switch (psel->mode) { case SP_PAINT_SELECTOR_MODE_COLOR_RGB: case SP_PAINT_SELECTOR_MODE_COLOR_CMYK: { sp_paint_selector_set_flat_color (psel, SP_ACTIVE_DESKTOP, "stroke", "stroke-opacity"); sp_document_maybe_done (sp_desktop_document(SP_ACTIVE_DESKTOP), undo_label, SP_VERB_DIALOG_FILL_STROKE, _("Set stroke color")); break; } default: g_warning( "file %s: line %d: Paint %d should not emit 'dragged'", __FILE__, __LINE__, psel->mode); break; } } /** * When the stroke style's paint settings change, this handler updates the * repr's stroke css style and applies the style to relevant drawing items. */ static void sp_stroke_style_paint_changed(SPPaintSelector *psel, SPWidget *spw) { if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { return; } g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (TRUE)); SPDesktop *desktop = SP_ACTIVE_DESKTOP; SPDocument *document = sp_desktop_document (desktop); Inkscape::Selection *selection = sp_desktop_selection (desktop); GSList const *items = selection->itemList(); switch (psel->mode) { case SP_PAINT_SELECTOR_MODE_EMPTY: // This should not happen. g_warning ( "file %s: line %d: Paint %d should not emit 'changed'", __FILE__, __LINE__, psel->mode); break; case SP_PAINT_SELECTOR_MODE_MULTIPLE: // This happens when you switch multiple objects with different gradients to flat color; // nothing to do here. break; case SP_PAINT_SELECTOR_MODE_NONE: { SPCSSAttr *css = sp_repr_css_attr_new(); sp_repr_css_set_property(css, "stroke", "none"); sp_desktop_set_style (desktop, css); sp_repr_css_attr_unref(css); sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE, _("Remove stroke")); break; } case SP_PAINT_SELECTOR_MODE_COLOR_RGB: case SP_PAINT_SELECTOR_MODE_COLOR_CMYK: { sp_paint_selector_set_flat_color (psel, desktop, "stroke", "stroke-opacity"); sp_document_maybe_done (sp_desktop_document(desktop), undo_label, SP_VERB_DIALOG_FILL_STROKE, _("Set stroke color")); // on release, toggle undo_label so that the next drag will not be lumped with this one if (undo_label == undo_label_1) undo_label = undo_label_2; else undo_label = undo_label_1; break; } case SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR: case SP_PAINT_SELECTOR_MODE_GRADIENT_RADIAL: if (items) { SPGradientType const gradient_type = ( psel->mode == SP_PAINT_SELECTOR_MODE_GRADIENT_LINEAR ? SP_GRADIENT_TYPE_LINEAR : SP_GRADIENT_TYPE_RADIAL ); SPGradient *vector = sp_paint_selector_get_gradient_vector(psel); if (!vector) { /* No vector in paint selector should mean that we just changed mode */ SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT); int result = objects_query_fillstroke ((GSList *) items, query, false); guint32 common_rgb = 0; if (result == QUERY_STYLE_MULTIPLE_SAME) { if (!query->fill.isColor()) { common_rgb = sp_desktop_get_color(desktop, false); } else { common_rgb = query->stroke.value.color.toRGBA32( 0xff ); } vector = sp_document_default_gradient_vector(document, common_rgb); } sp_style_unref(query); for (GSList const *i = items; i != NULL; i = i->next) { if (!vector) { sp_item_set_gradient(SP_ITEM(i->data), sp_gradient_vector_for_object(document, desktop, SP_OBJECT(i->data), false), gradient_type, false); } else { sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, false); } } } else { vector = sp_gradient_ensure_vector_normalized(vector); for (GSList const *i = items; i != NULL; i = i->next) { SPGradient *gr = sp_item_set_gradient(SP_ITEM(i->data), vector, gradient_type, false); sp_gradient_selector_attrs_to_gradient(gr, psel); } } sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE, _("Set gradient on stroke")); } break; case SP_PAINT_SELECTOR_MODE_PATTERN: if (items) { SPPattern *pattern = sp_paint_selector_get_pattern (psel); if (!pattern) { /* No Pattern in paint selector should mean that we just * changed mode - dont do jack. */ } else { Inkscape::XML::Node *patrepr = SP_OBJECT_REPR(pattern); SPCSSAttr *css = sp_repr_css_attr_new (); gchar *urltext = g_strdup_printf ("url(#%s)", patrepr->attribute("id")); sp_repr_css_set_property (css, "stroke", urltext); for (GSList const *i = items; i != NULL; i = i->next) { Inkscape::XML::Node *selrepr = SP_OBJECT_REPR (i->data); SPObject *selobj = SP_OBJECT (i->data); if (!selrepr) continue; SPStyle *style = SP_OBJECT_STYLE (selobj); if (style && style->stroke.isPaintserver()) { SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (selobj); if (SP_IS_PATTERN (server) && pattern_getroot (SP_PATTERN(server)) == pattern) // only if this object's pattern is not rooted in our selected pattern, apply continue; } sp_repr_css_change_recursive (selrepr, css, "style"); } sp_repr_css_attr_unref (css); g_free (urltext); } // end if sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE, _("Set pattern on stroke")); } // end if break; case SP_PAINT_SELECTOR_MODE_SWATCH: // TODO break; case SP_PAINT_SELECTOR_MODE_UNSET: if (items) { SPCSSAttr *css = sp_repr_css_attr_new (); sp_repr_css_unset_property (css, "stroke"); sp_repr_css_unset_property (css, "stroke-opacity"); sp_repr_css_unset_property (css, "stroke-width"); sp_repr_css_unset_property (css, "stroke-miterlimit"); sp_repr_css_unset_property (css, "stroke-linejoin"); sp_repr_css_unset_property (css, "stroke-linecap"); sp_repr_css_unset_property (css, "stroke-dashoffset"); sp_repr_css_unset_property (css, "stroke-dasharray"); sp_desktop_set_style (desktop, css); sp_repr_css_attr_unref (css); sp_document_done (document, SP_VERB_DIALOG_FILL_STROKE, _("Unset stroke")); } break; default: g_warning( "file %s: line %d: Paint selector should not be in " "mode %d", __FILE__, __LINE__, psel->mode ); break; } g_object_set_data (G_OBJECT (spw), "update", GINT_TO_POINTER (FALSE)); } /* Line */ static void sp_stroke_style_line_selection_modified(SPWidget *spw, Inkscape::Selection *selection, guint flags, gpointer data); static void sp_stroke_style_line_selection_changed(SPWidget *spw, Inkscape::Selection *selection, gpointer data); static void sp_stroke_style_line_update(Gtk::Container *spw, Inkscape::Selection *sel); static void sp_stroke_style_set_join_buttons(Gtk::Container *spw, Gtk::ToggleButton *active); static void sp_stroke_style_set_cap_buttons(Gtk::Container *spw, Gtk::ToggleButton *active); static void sp_stroke_style_width_changed(Gtk::Container *spw); static void sp_stroke_style_miterlimit_changed(Gtk::Container *spw); static void sp_stroke_style_any_toggled(Gtk::ToggleButton *tb, Gtk::Container *spw); static void sp_stroke_style_line_dash_changed(Gtk::Container *spw); static void sp_stroke_style_update_marker_menus(Gtk::Container *spw, GSList const *objects); /** * Helper function for creating radio buttons. This should probably be re-thought out * when reimplementing this with Gtkmm. */ static Gtk::RadioButton * sp_stroke_radio_button(Gtk::RadioButton *tb, char const *icon, Gtk::HBox *hb, Gtk::Container *spw, gchar const *key, gchar const *data) { g_assert(icon != NULL); g_assert(hb != NULL); g_assert(spw != NULL); if (tb == NULL) { tb = new Gtk::RadioButton(); } else { Gtk::RadioButtonGroup grp = tb->get_group(); tb = new Gtk::RadioButton(grp); } tb->show(); tb->set_mode(false); hb->pack_start(*tb, false, false, 0); spw->set_data(icon, tb); tb->set_data(key, (gpointer*)data); tb->signal_toggled().connect(sigc::bind( sigc::ptr_fun(&sp_stroke_style_any_toggled), tb, spw)); Gtk::Widget *px = manage(Glib::wrap(sp_icon_new(Inkscape::ICON_SIZE_LARGE_TOOLBAR, icon))); g_assert(px != NULL); px->show(); tb->add(*px); return tb; } static void sp_stroke_style_widget_transientize_callback(Inkscape::Application */*inkscape*/, SPDesktop */*desktop*/, SPWidget */*spw*/ ) { // TODO: Either of these will cause crashes sometimes // sp_stroke_style_line_update( SP_WIDGET(spw), desktop ? sp_desktop_selection(desktop) : NULL); // ink_markers_menu_update(spw); } /** * Creates a copy of the marker named mname, determines its visible and renderable * area in menu_id's bounding box, and then renders it. This allows us to fill in * preview images of each marker in the marker menu. */ static Gtk::Image * sp_marker_prev_new(unsigned psize, gchar const *mname, SPDocument *source, SPDocument *sandbox, gchar const *menu_id, NRArena const */*arena*/, unsigned /*visionkey*/, NRArenaItem *root) { // Retrieve the marker named 'mname' from the source SVG document SPObject const *marker = source->getObjectById(mname); if (marker == NULL) return NULL; // Create a copy repr of the marker with id="sample" Inkscape::XML::Document *xml_doc = sp_document_repr_doc(sandbox); Inkscape::XML::Node *mrepr = SP_OBJECT_REPR (marker)->duplicate(xml_doc); mrepr->setAttribute("id", "sample"); // Replace the old sample in the sandbox by the new one Inkscape::XML::Node *defsrepr = SP_OBJECT_REPR (sandbox->getObjectById("defs")); SPObject *oldmarker = sandbox->getObjectById("sample"); if (oldmarker) oldmarker->deleteObject(false); defsrepr->appendChild(mrepr); Inkscape::GC::release(mrepr); // Uncomment this to get the sandbox documents saved (useful for debugging) //FILE *fp = fopen (g_strconcat(menu_id, mname, ".svg", NULL), "w"); //sp_repr_save_stream (sp_document_repr_doc (sandbox), fp); //fclose (fp); // object to render; note that the id is the same as that of the menu we're building SPObject *object = sandbox->getObjectById(menu_id); sp_document_root (sandbox)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); sp_document_ensure_up_to_date(sandbox); if (object == NULL || !SP_IS_ITEM(object)) return NULL; // sandbox broken? // Find object's bbox in document Geom::Matrix const i2doc(sp_item_i2doc_affine(SP_ITEM(object))); Geom::OptRect dbox = SP_ITEM(object)->getBounds(i2doc); if (!dbox) { return NULL; } /* Update to renderable state */ double sf = 0.8; gchar *cache_name = g_strconcat(menu_id, mname, NULL); Glib::ustring key = svg_preview_cache.cache_key(source->uri, cache_name, psize); g_free (cache_name); // TODO: is this correct? Glib::RefPtr pixbuf = Glib::wrap(svg_preview_cache.get_preview_from_cache(key)); if (!pixbuf) { pixbuf = Glib::wrap(render_pixbuf(root, sf, *dbox, psize)); svg_preview_cache.set_preview_in_cache(key, pixbuf->gobj()); } // Create widget Gtk::Image *pb = new Gtk::Image(pixbuf); return pb; } /** * Returns a list of markers in the defs of the given source document as a GSList object * Returns NULL if there are no markers in the document. */ GSList * ink_marker_list_get (SPDocument *source) { if (source == NULL) return NULL; GSList *ml = NULL; SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS (source); for ( SPObject *child = sp_object_first_child(SP_OBJECT(defs)); child != NULL; child = SP_OBJECT_NEXT (child) ) { if (SP_IS_MARKER(child)) { ml = g_slist_prepend (ml, child); } } return ml; } #define MARKER_ITEM_MARGIN 0 /** * Adds previews of markers in marker_list to the given menu widget */ static void sp_marker_menu_build (Gtk::Menu *m, GSList *marker_list, SPDocument *source, SPDocument *sandbox, gchar const *menu_id) { // Do this here, outside of loop, to speed up preview generation: NRArena const *arena = NRArena::create(); unsigned const visionkey = sp_item_display_key_new(1); NRArenaItem *root = sp_item_invoke_show(SP_ITEM(SP_DOCUMENT_ROOT (sandbox)), (NRArena *) arena, visionkey, SP_ITEM_SHOW_DISPLAY); for (; marker_list != NULL; marker_list = marker_list->next) { Inkscape::XML::Node *repr = SP_OBJECT_REPR((SPItem *) marker_list->data); Gtk::MenuItem *i = new Gtk::MenuItem(); i->show(); if (repr->attribute("inkscape:stockid")) i->set_data("stockid", (void *) "true"); else i->set_data("stockid", (void *) "false"); gchar const *markid = repr->attribute("id"); i->set_data("marker", (void *) markid); Gtk::HBox *hb = new Gtk::HBox(false, MARKER_ITEM_MARGIN); hb->show(); // generate preview Gtk::Image *prv = sp_marker_prev_new (22, markid, source, sandbox, menu_id, arena, visionkey, root); prv->show(); hb->pack_start(*prv, false, false, 6); // create label Gtk::Label *l = new Gtk::Label(repr->attribute("id")); l->show(); l->set_alignment(0.0, 0.5); hb->pack_start(*l, true, true, 0); hb->show(); i->add(*hb); m->append(*i); } sp_item_invoke_hide(SP_ITEM(sp_document_root(sandbox)), visionkey); nr_object_unref((NRObject *) arena); } /** * sp_marker_list_from_doc() * * \brief Pick up all markers from source, except those that are in * current_doc (if non-NULL), and add items to the m menu * */ static void sp_marker_list_from_doc (Gtk::Menu *m, SPDocument */*current_doc*/, SPDocument *source, SPDocument */*markers_doc*/, SPDocument *sandbox, gchar const *menu_id) { GSList *ml = ink_marker_list_get(source); GSList *clean_ml = NULL; for (; ml != NULL; ml = ml->next) { if (!SP_IS_MARKER(ml->data)) continue; // Add to the list of markers we really do wish to show clean_ml = g_slist_prepend (clean_ml, ml->data); } sp_marker_menu_build(m, clean_ml, source, sandbox, menu_id); g_slist_free (ml); g_slist_free (clean_ml); } /** * Returns a new document containing default start, mid, and end markers. */ SPDocument * ink_markers_preview_doc () { gchar const *buffer = "" " " " " " " " " " " " " " " " " " " " " " " " " " " ""; return sp_document_new_from_mem (buffer, strlen(buffer), FALSE); } static void ink_marker_menu_create_menu(Gtk::Menu *m, gchar const *menu_id, SPDocument *doc, SPDocument *sandbox) { static SPDocument *markers_doc = NULL; // add "None" Gtk::MenuItem *i = new Gtk::MenuItem(); i->show(); i->set_data("marker", (void *) "none"); Gtk::HBox *hb = new Gtk::HBox(false, MARKER_ITEM_MARGIN); hb->show(); Gtk::Label *l = new Gtk::Label( _("None") ); l->show(); l->set_alignment(0.0, 0.5); hb->pack_start(*l, true, true, 0); hb->show(); i->add(*hb); m->append(*i); // find and load markers.svg if (markers_doc == NULL) { char *markers_source = g_build_filename(INKSCAPE_MARKERSDIR, "markers.svg", NULL); if (Inkscape::IO::file_test(markers_source, G_FILE_TEST_IS_REGULAR)) { markers_doc = sp_document_new(markers_source, FALSE); } g_free(markers_source); } // suck in from current doc sp_marker_list_from_doc(m, NULL, doc, markers_doc, sandbox, menu_id); // add separator { //Gtk::Separator *i = gtk_separator_menu_item_new(); Gtk::SeparatorMenuItem *i = new Gtk::SeparatorMenuItem(); i->show(); m->append(*i); } // suck in from markers.svg if (markers_doc) { sp_document_ensure_up_to_date(doc); sp_marker_list_from_doc(m, doc, markers_doc, NULL, sandbox, menu_id); } } /** * Creates a menu widget to display markers from markers.svg */ static Gtk::OptionMenu * ink_marker_menu(Gtk::Widget */*tbl*/, gchar const *menu_id, SPDocument *sandbox) { SPDesktop *desktop = inkscape_active_desktop(); SPDocument *doc = sp_desktop_document(desktop); Gtk::OptionMenu *mnu = new Gtk::OptionMenu(); /* Create new menu widget */ Gtk::Menu *m = new Gtk::Menu(); m->show(); mnu->set_data("updating", (gpointer) FALSE); if (!doc) { Gtk::MenuItem *i = new Gtk::MenuItem(_("No document selected")); i->show(); m->append(*i); mnu->set_sensitive(false); } else { ink_marker_menu_create_menu(m, menu_id, doc, sandbox); mnu->set_sensitive(true); } mnu->set_data("menu_id", const_cast(menu_id)); mnu->set_menu(*m); /* Set history */ mnu->set_history(0); return mnu; } /** * Handles when user selects one of the markers from the marker menu. * Defines a uri string to refer to it, then applies it to all selected * items in the current desktop. */ static void sp_marker_select(Gtk::OptionMenu *mnu, Gtk::Container *spw, SPMarkerLoc const which) { if (spw->get_data("update")) { return; } SPDesktop *desktop = inkscape_active_desktop(); SPDocument *document = sp_desktop_document(desktop); if (!document) { return; } /* Get Marker */ if (!mnu->get_menu()->get_active()->get_data("marker")) { return; } gchar *markid = static_cast(mnu->get_menu()->get_active()->get_data("marker")); gchar const *marker = ""; if (strcmp(markid, "none")) { gchar *stockid = static_cast(mnu->get_menu()->get_active()->get_data("stockid")); gchar *markurn = markid; if (!strcmp(stockid,"true")) markurn = g_strconcat("urn:inkscape:marker:",markid,NULL); SPObject *mark = get_stock_item(markurn); if (mark) { Inkscape::XML::Node *repr = SP_OBJECT_REPR(mark); marker = g_strconcat("url(#", repr->attribute("id"), ")", NULL); } } else { marker = markid; } SPCSSAttr *css = sp_repr_css_attr_new(); gchar const *menu_id = static_cast(mnu->get_data("menu_id")); sp_repr_css_set_property(css, menu_id, marker); // Also update the marker dropdown menus, so the document's markers // show up at the top of the menu // sp_stroke_style_line_update( SP_WIDGET(spw), desktop ? sp_desktop_selection(desktop) : NULL); ink_markers_menu_update(spw, which); Inkscape::Selection *selection = sp_desktop_selection(desktop); GSList const *items = selection->itemList(); for (; items != NULL; items = items->next) { SPItem *item = (SPItem *) items->data; if (!SP_IS_SHAPE(item) || SP_IS_RECT(item)) // can't set marker to rect, until it's converted to using continue; Inkscape::XML::Node *selrepr = SP_OBJECT_REPR((SPItem *) items->data); if (selrepr) { sp_repr_css_change_recursive(selrepr, css, "style"); } SP_OBJECT(items->data)->requestModified(SP_OBJECT_MODIFIED_FLAG); SP_OBJECT(items->data)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); } sp_repr_css_attr_unref(css); sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE, _("Set markers")); }; static unsigned int ink_marker_menu_get_pos(Gtk::Menu *mnu, gchar const *markname) { if (markname == NULL) markname = static_cast(mnu->get_active()->get_data("marker")); if (markname == NULL) return 0; std::vector kids = mnu->get_children(); unsigned int i = 0; for (; i < kids.size();) { gchar const *mark = static_cast(kids[i]->get_data("marker")); if (mark && strcmp(mark, markname) == 0) { break; } ++i; } return i; } static void ink_markers_menu_update(Gtk::Container* /*spw*/, SPMarkerLoc const which) { SPDesktop *desktop = inkscape_active_desktop(); SPDocument *document = sp_desktop_document(desktop); SPDocument *sandbox = ink_markers_preview_doc (); Gtk::Menu *m; int pos; // TODO: this code can be shortened by abstracting out marker_(start|mid|end)_... switch (which) { case SP_MARKER_LOC_START: marker_start_menu_connection.block(); pos = ink_marker_menu_get_pos(marker_start_menu->get_menu(), NULL); m = new Gtk::Menu(); m->show(); ink_marker_menu_create_menu(m, "marker-start", document, sandbox); marker_start_menu->remove_menu(); marker_start_menu->set_menu(*m); marker_start_menu->set_history(pos); marker_start_menu_connection.unblock(); break; case SP_MARKER_LOC_MID: marker_mid_menu_connection.block(); pos = ink_marker_menu_get_pos(marker_mid_menu->get_menu(), NULL); m = new Gtk::Menu(); m->show(); ink_marker_menu_create_menu(m, "marker-mid", document, sandbox); marker_mid_menu->remove_menu(); marker_mid_menu->set_menu(*m); marker_mid_menu->set_history(pos); marker_mid_menu_connection.unblock(); break; case SP_MARKER_LOC_END: marker_end_menu_connection.block(); pos = ink_marker_menu_get_pos(marker_end_menu->get_menu(), NULL); m = new Gtk::Menu(); m->show(); ink_marker_menu_create_menu(m, "marker-end", document, sandbox); marker_end_menu->remove_menu(); marker_end_menu->set_menu(*m); marker_end_menu->set_history(pos); marker_end_menu_connection.unblock(); break; default: g_assert_not_reached(); } } /** * Sets the stroke width units for all selected items. * Also handles absolute and dimensionless units. */ static gboolean stroke_width_set_unit(SPUnitSelector *, SPUnit const *old, SPUnit const *new_units, Gtk::Container *spw) { SPDesktop *desktop = SP_ACTIVE_DESKTOP; if (!desktop) { return FALSE; } Inkscape::Selection *selection = sp_desktop_selection (desktop); if (selection->isEmpty()) return FALSE; GSList const *objects = selection->itemList(); if ((old->base == SP_UNIT_ABSOLUTE || old->base == SP_UNIT_DEVICE) && (new_units->base == SP_UNIT_DIMENSIONLESS)) { /* Absolute to percentage */ spw->set_data ("update", GUINT_TO_POINTER (TRUE)); Gtk::Adjustment *a = static_cast(spw->get_data("width")); float w = sp_units_get_pixels (a->get_value(), *old); gdouble average = stroke_average_width (objects); if (average == NR_HUGE || average == 0) return FALSE; a->set_value (100.0 * w / average); spw->set_data ("update", GUINT_TO_POINTER (FALSE)); return TRUE; } else if ((old->base == SP_UNIT_DIMENSIONLESS) && (new_units->base == SP_UNIT_ABSOLUTE || new_units->base == SP_UNIT_DEVICE)) { /* Percentage to absolute */ spw->set_data ("update", GUINT_TO_POINTER (TRUE)); Gtk::Adjustment *a = static_cast(spw->get_data ("width")); gdouble average = stroke_average_width (objects); a->set_value (sp_pixels_get_units (0.01 * a->get_value() * average, *new_units)); spw->set_data ("update", GUINT_TO_POINTER (FALSE)); return TRUE; } return FALSE; } /** * \brief Creates a new widget for the line stroke style. * */ Gtk::Container * sp_stroke_style_line_widget_new(void) { Gtk::Widget *us; SPDashSelector *ds; GtkWidget *us_old, *spw_old; Gtk::Container *spw; Gtk::Table *t; Gtk::Adjustment *a; Gtk::SpinButton *sb; Gtk::RadioButton *tb; Gtk::HBox *f, *hb; Gtk::Tooltips *tt = new Gtk::Tooltips(); spw_old = sp_widget_new_global(INKSCAPE); spw = dynamic_cast(manage(Glib::wrap(spw_old))); f = new Gtk::HBox(false, 0); f->show(); spw->add(*f); t = new Gtk::Table(3, 6, false); t->show(); t->set_border_width(4); t->set_row_spacings(4); f->add(*t); spw->set_data("stroke", t); gint i = 0; //TRANSLATORS: only translate "string" in "context|string". // For more details, see http://developer.gnome.org/doc/API/2.0/glib/glib-I18N.html#Q-:CAPS /* Stroke width */ spw_label(t, Q_("StrokeWidth|Width:"), 0, i); hb = spw_hbox(t, 3, 1, i); // TODO: when this is gtkmmified, use an Inkscape::UI::Widget::ScalarUnit instead of the separate // spinbutton and unit selector for stroke width. In sp_stroke_style_line_update, use // setHundredPercent to remember the aeraged width corresponding to 100%. Then the // stroke_width_set_unit will be removed (because ScalarUnit takes care of conversions itself), and // with it, the two remaining calls of stroke_average_width, allowing us to get rid of that // function in desktop-style. a = new Gtk::Adjustment(1.0, 0.0, 1000.0, 0.1, 10.0, 0.0); spw->set_data("width", a); sb = new Gtk::SpinButton(*a, 0.1, 3); tt->set_tip(*sb, _("Stroke width")); sb->show(); sp_dialog_defocus_on_enter_cpp(sb); hb->pack_start(*sb, false, false, 0); us_old = sp_unit_selector_new(SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE); us = manage(Glib::wrap(us_old)); SPDesktop *desktop = SP_ACTIVE_DESKTOP; if (desktop) sp_unit_selector_set_unit (SP_UNIT_SELECTOR(us_old), sp_desktop_namedview(desktop)->doc_units); sp_unit_selector_add_unit(SP_UNIT_SELECTOR(us_old), &sp_unit_get_by_id(SP_UNIT_PERCENT), 0); g_signal_connect ( G_OBJECT (us_old), "set_unit", G_CALLBACK (stroke_width_set_unit), spw ); us->show(); sp_unit_selector_add_adjustment( SP_UNIT_SELECTOR(us_old), GTK_ADJUSTMENT(a->gobj()) ); hb->pack_start(*us, FALSE, FALSE, 0); spw->set_data("units", us_old); a->signal_value_changed().connect(sigc::bind(sigc::ptr_fun(&sp_stroke_style_width_changed), spw)); i++; /* Join type */ // TRANSLATORS: The line join style specifies the shape to be used at the // corners of paths. It can be "miter", "round" or "bevel". spw_label(t, _("Join:"), 0, i); hb = spw_hbox(t, 3, 1, i); tb = NULL; tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_JOIN_MITER, hb, spw, "join", "miter"); // TRANSLATORS: Miter join: joining lines with a sharp (pointed) corner. // For an example, draw a triangle with a large stroke width and modify the // "Join" option (in the Fill and Stroke dialog). tt->set_tip(*tb, _("Miter join")); spw->set_data("miter join", tb); tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_JOIN_ROUND, hb, spw, "join", "round"); // TRANSLATORS: Round join: joining lines with a rounded corner. // For an example, draw a triangle with a large stroke width and modify the // "Join" option (in the Fill and Stroke dialog). tt->set_tip(*tb, _("Round join")); spw->set_data("round join", tb); tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_JOIN_BEVEL, hb, spw, "join", "bevel"); // TRANSLATORS: Bevel join: joining lines with a blunted (flattened) corner. // For an example, draw a triangle with a large stroke width and modify the // "Join" option (in the Fill and Stroke dialog). tt->set_tip(*tb, _("Bevel join")); spw->set_data("bevel join", tb); i++; /* Miterlimit */ // TRANSLATORS: Miter limit: only for "miter join", this limits the length // of the sharp "spike" when the lines connect at too sharp an angle. // When two line segments meet at a sharp angle, a miter join results in a // spike that extends well beyond the connection point. The purpose of the // miter limit is to cut off such spikes (i.e. convert them into bevels) // when they become too long. spw_label(t, _("Miter limit:"), 0, i); hb = spw_hbox(t, 3, 1, i); a = new Gtk::Adjustment(4.0, 0.0, 100.0, 0.1, 10.0, 0.0); spw->set_data("miterlimit", a); sb = new Gtk::SpinButton(*a, 0.1, 2); tt->set_tip(*sb, _("Maximum length of the miter (in units of stroke width)")); sb->show(); spw->set_data("miterlimit_sb", sb); sp_dialog_defocus_on_enter_cpp(sb); hb->pack_start(*sb, false, false, 0); a->signal_value_changed().connect(sigc::bind(sigc::ptr_fun(&sp_stroke_style_miterlimit_changed), spw)); i++; /* Cap type */ // TRANSLATORS: cap type specifies the shape for the ends of lines spw_label(t, _("Cap:"), 0, i); hb = spw_hbox(t, 3, 1, i); tb = NULL; tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_CAP_BUTT, hb, spw, "cap", "butt"); spw->set_data("cap butt", tb); // TRANSLATORS: Butt cap: the line shape does not extend beyond the end point // of the line; the ends of the line are square tt->set_tip(*tb, _("Butt cap")); tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_CAP_ROUND, hb, spw, "cap", "round"); spw->set_data("cap round", tb); // TRANSLATORS: Round cap: the line shape extends beyond the end point of the // line; the ends of the line are rounded tt->set_tip(*tb, _("Round cap")); tb = sp_stroke_radio_button(tb, INKSCAPE_ICON_STROKE_CAP_SQUARE, hb, spw, "cap", "square"); spw->set_data("cap square", tb); // TRANSLATORS: Square cap: the line shape extends beyond the end point of the // line; the ends of the line are square tt->set_tip(*tb, _("Square cap")); i++; /* Dash */ spw_label(t, _("Dashes:"), 0, i); ds = manage(new SPDashSelector); ds->show(); t->attach(*ds, 1, 4, i, i+1, (Gtk::EXPAND | Gtk::FILL), static_cast(0), 0, 0); spw->set_data("dash", ds); ds->changed_signal.connect(sigc::bind(sigc::ptr_fun(&sp_stroke_style_line_dash_changed), spw)); i++; /* Drop down marker selectors*/ // TODO: this code can be shortened by iterating over the possible menus! // doing this here once, instead of for each preview, to speed things up SPDocument *sandbox = ink_markers_preview_doc (); // TRANSLATORS: Path markers are an SVG feature that allows you to attach arbitrary shapes // (arrowheads, bullets, faces, whatever) to the start, end, or middle nodes of a path. spw_label(t, _("Start Markers:"), 0, i); marker_start_menu = ink_marker_menu(spw ,"marker-start", sandbox); tt->set_tip(*marker_start_menu, _("Start Markers are drawn on the first node of a path or shape")); marker_start_menu_connection = marker_start_menu->signal_changed().connect( sigc::bind( sigc::ptr_fun(&sp_marker_select), marker_start_menu, spw, SP_MARKER_LOC_START)); marker_start_menu->show(); t->attach(*marker_start_menu, 1, 4, i, i+1, (Gtk::EXPAND | Gtk::FILL), static_cast(0), 0, 0); spw->set_data("start_mark_menu", marker_start_menu); i++; spw_label(t, _("Mid Markers:"), 0, i); marker_mid_menu = ink_marker_menu(spw ,"marker-mid", sandbox); tt->set_tip(*marker_mid_menu, _("Mid Markers are drawn on every node of a path or shape except the first and last nodes")); marker_mid_menu_connection = marker_mid_menu->signal_changed().connect( sigc::bind( sigc::ptr_fun(&sp_marker_select), marker_mid_menu,spw, SP_MARKER_LOC_MID)); marker_mid_menu->show(); t->attach(*marker_mid_menu, 1, 4, i, i+1, (Gtk::EXPAND | Gtk::FILL), static_cast(0), 0, 0); spw->set_data("mid_mark_menu", marker_mid_menu); i++; spw_label(t, _("End Markers:"), 0, i); marker_end_menu = ink_marker_menu(spw ,"marker-end", sandbox); tt->set_tip(*marker_end_menu, _("End Markers are drawn on the last node of a path or shape")); marker_end_menu_connection = marker_end_menu->signal_changed().connect( sigc::bind( sigc::ptr_fun(&sp_marker_select), marker_end_menu, spw, SP_MARKER_LOC_END)); marker_end_menu->show(); t->attach(*marker_end_menu, 1, 4, i, i+1, (Gtk::EXPAND | Gtk::FILL), static_cast(0), 0, 0); spw->set_data("end_mark_menu", marker_end_menu); i++; // FIXME: we cheat and still use gtk+ signals gtk_signal_connect(GTK_OBJECT(spw_old), "modify_selection", GTK_SIGNAL_FUNC(sp_stroke_style_line_selection_modified), spw); gtk_signal_connect(GTK_OBJECT(spw_old), "change_selection", GTK_SIGNAL_FUNC(sp_stroke_style_line_selection_changed), spw); sp_stroke_style_line_update(spw, desktop ? sp_desktop_selection(desktop) : NULL); return spw; } /** * Callback for when stroke style widget is modified. * Triggers update action. */ static void sp_stroke_style_line_selection_modified(SPWidget *, Inkscape::Selection *selection, guint flags, gpointer data) { Gtk::Container *spw = static_cast(data); if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) { sp_stroke_style_line_update(spw, selection); } } /** * Callback for when stroke style widget is changed. * Triggers update action. */ static void sp_stroke_style_line_selection_changed(SPWidget *, Inkscape::Selection *selection, gpointer data) { Gtk::Container *spw = static_cast(data); sp_stroke_style_line_update(spw, selection); } /** * Sets selector widgets' dash style from an SPStyle object. */ static void sp_dash_selector_set_from_style(SPDashSelector *dsel, SPStyle *style) { if (style->stroke_dash.n_dash > 0) { double d[64]; int len = MIN(style->stroke_dash.n_dash, 64); for (int i = 0; i < len; i++) { if (style->stroke_width.computed != 0) d[i] = style->stroke_dash.dash[i] / style->stroke_width.computed; else d[i] = style->stroke_dash.dash[i]; // is there a better thing to do for stroke_width==0? } dsel->set_dash(len, d, style->stroke_width.computed != 0 ? style->stroke_dash.offset / style->stroke_width.computed : style->stroke_dash.offset); } else { dsel->set_dash(0, NULL, 0.0); } } /** * Sets the join type for a line, and updates the stroke style widget's buttons */ static void sp_jointype_set (Gtk::Container *spw, unsigned const jointype) { Gtk::RadioButton *tb = NULL; switch (jointype) { case SP_STROKE_LINEJOIN_MITER: tb = static_cast(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_MITER)); break; case SP_STROKE_LINEJOIN_ROUND: tb = static_cast(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_ROUND)); break; case SP_STROKE_LINEJOIN_BEVEL: tb = static_cast(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_BEVEL)); break; default: break; } sp_stroke_style_set_join_buttons(spw, tb); } /** * Sets the cap type for a line, and updates the stroke style widget's buttons */ static void sp_captype_set (Gtk::Container *spw, unsigned const captype) { Gtk::RadioButton *tb = NULL; switch (captype) { case SP_STROKE_LINECAP_BUTT: tb = static_cast(spw->get_data(INKSCAPE_ICON_STROKE_CAP_BUTT)); break; case SP_STROKE_LINECAP_ROUND: tb = static_cast(spw->get_data(INKSCAPE_ICON_STROKE_CAP_ROUND)); break; case SP_STROKE_LINECAP_SQUARE: tb = static_cast(spw->get_data(INKSCAPE_ICON_STROKE_CAP_SQUARE)); break; default: break; } sp_stroke_style_set_cap_buttons(spw, tb); } /** * Callback for when stroke style widget is updated, including markers, cap type, * join type, etc. */ static void sp_stroke_style_line_update(Gtk::Container *spw, Inkscape::Selection *sel) { if (spw->get_data("update")) { return; } spw->set_data("update", GINT_TO_POINTER(TRUE)); Gtk::Table *sset = static_cast(spw->get_data("stroke")); Gtk::Adjustment *width = static_cast(spw->get_data("width")); Gtk::Adjustment *ml = static_cast(spw->get_data("miterlimit")); SPUnitSelector *us = SP_UNIT_SELECTOR(spw->get_data("units")); SPDashSelector *dsel = static_cast(spw->get_data("dash")); // create temporary style SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT); // query into it int result_sw = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEWIDTH); int result_ml = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEMITERLIMIT); int result_cap = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKECAP); int result_join = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_STROKEJOIN); if (!sel || sel->isEmpty()) { // Nothing selected, grey-out all controls in the stroke-style dialog sset->set_sensitive(false); spw->set_data("update", GINT_TO_POINTER(FALSE)); return; } else { sset->set_sensitive(true); SPUnit const *unit = sp_unit_selector_get_unit(us); if (result_sw == QUERY_STYLE_MULTIPLE_AVERAGED) { sp_unit_selector_set_unit(us, &sp_unit_get_by_id(SP_UNIT_PERCENT)); } else { // same width, or only one object; no sense to keep percent, switch to absolute if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) { sp_unit_selector_set_unit(us, sp_desktop_namedview(SP_ACTIVE_DESKTOP)->doc_units); } } unit = sp_unit_selector_get_unit(us); if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) { double avgwidth = sp_pixels_get_units (query->stroke_width.computed, *unit); width->set_value(avgwidth); } else { width->set_value(100); } // if none of the selected objects has a stroke, than quite some controls should be disabled // The markers might still be shown though, so these will not be disabled bool enabled = (result_sw != QUERY_STYLE_NOTHING) && !query->stroke.isNoneSet(); /* No objects stroked, set insensitive */ Gtk::RadioButton *tb = NULL; tb = static_cast(spw->get_data("miter join")); tb->set_sensitive(enabled); tb = static_cast(spw->get_data("round join")); tb->set_sensitive(enabled); tb = static_cast(spw->get_data("bevel join")); tb->set_sensitive(enabled); Gtk::SpinButton* sb = NULL; sb = static_cast(spw->get_data("miterlimit_sb")); sb->set_sensitive(enabled); tb = static_cast(spw->get_data("cap butt")); tb->set_sensitive(enabled); tb = static_cast(spw->get_data("cap round")); tb->set_sensitive(enabled); tb = static_cast(spw->get_data("cap square")); tb->set_sensitive(enabled); dsel->set_sensitive(enabled); } if (result_ml != QUERY_STYLE_NOTHING) ml->set_value(query->stroke_miterlimit.value); // TODO: reflect averagedness? if (result_join != QUERY_STYLE_MULTIPLE_DIFFERENT) { sp_jointype_set(spw, query->stroke_linejoin.value); } else { sp_stroke_style_set_join_buttons(spw, NULL); } if (result_cap != QUERY_STYLE_MULTIPLE_DIFFERENT) { sp_captype_set (spw, query->stroke_linecap.value); } else { sp_stroke_style_set_cap_buttons(spw, NULL); } sp_style_unref(query); if (!sel || sel->isEmpty()) return; GSList const *objects = sel->itemList(); SPObject * const object = SP_OBJECT(objects->data); SPStyle * const style = SP_OBJECT_STYLE(object); /* Markers */ sp_stroke_style_update_marker_menus(spw, objects); // FIXME: make this desktop query too /* Dash */ sp_dash_selector_set_from_style(dsel, style); // FIXME: make this desktop query too sset->set_sensitive(true); spw->set_data("update", GINT_TO_POINTER(FALSE)); } /** * Sets a line's dash properties in a CSS style object. */ static void sp_stroke_style_set_scaled_dash(SPCSSAttr *css, int ndash, double *dash, double offset, double scale) { if (ndash > 0) { Inkscape::CSSOStringStream osarray; for (int i = 0; i < ndash; i++) { osarray << dash[i] * scale; if (i < (ndash - 1)) { osarray << ","; } } sp_repr_css_set_property(css, "stroke-dasharray", osarray.str().c_str()); Inkscape::CSSOStringStream osoffset; osoffset << offset * scale; sp_repr_css_set_property(css, "stroke-dashoffset", osoffset.str().c_str()); } else { sp_repr_css_set_property(css, "stroke-dasharray", "none"); sp_repr_css_set_property(css, "stroke-dashoffset", NULL); } } /** * Sets line properties like width, dashes, markers, etc. on all currently selected items. */ static void sp_stroke_style_scale_line(Gtk::Container *spw) { if (spw->get_data("update")) { return; } spw->set_data("update", GINT_TO_POINTER(TRUE)); Gtk::Adjustment *wadj = static_cast(spw->get_data("width")); SPUnitSelector *us = SP_UNIT_SELECTOR(spw->get_data("units")); SPDashSelector *dsel = static_cast(spw->get_data("dash")); Gtk::Adjustment *ml = static_cast(spw->get_data("miterlimit")); SPDesktop *desktop = SP_ACTIVE_DESKTOP; SPDocument *document = sp_desktop_document (desktop); Inkscape::Selection *selection = sp_desktop_selection (desktop); GSList const *items = selection->itemList(); /* TODO: Create some standardized method */ SPCSSAttr *css = sp_repr_css_attr_new(); if (items) { double width_typed = wadj->get_value(); double const miterlimit = ml->get_value(); SPUnit const *const unit = sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us)); double *dash, offset; int ndash; dsel->get_dash(&ndash, &dash, &offset); for (GSList const *i = items; i != NULL; i = i->next) { /* Set stroke width */ double width; if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) { width = sp_units_get_pixels (width_typed, *unit); } else { // percentage gdouble old_w = SP_OBJECT_STYLE (i->data)->stroke_width.computed; width = old_w * width_typed / 100; } { Inkscape::CSSOStringStream os_width; os_width << width; sp_repr_css_set_property(css, "stroke-width", os_width.str().c_str()); } { Inkscape::CSSOStringStream os_ml; os_ml << miterlimit; sp_repr_css_set_property(css, "stroke-miterlimit", os_ml.str().c_str()); } /* Set dash */ sp_stroke_style_set_scaled_dash(css, ndash, dash, offset, width); sp_desktop_apply_css_recursive (SP_OBJECT(i->data), css, true); } g_free(dash); if (unit->base != SP_UNIT_ABSOLUTE && unit->base != SP_UNIT_DEVICE) { // reset to 100 percent wadj->set_value(100.0); } } // we have already changed the items, so set style without changing selection // FIXME: move the above stroke-setting stuff, including percentages, to desktop-style sp_desktop_set_style (desktop, css, false); sp_repr_css_attr_unref(css); sp_document_done(document, SP_VERB_DIALOG_FILL_STROKE, _("Set stroke style")); spw->set_data("update", GINT_TO_POINTER(FALSE)); } /** * Callback for when the stroke style's width changes. * Causes all line styles to be applied to all selected items. */ static void sp_stroke_style_width_changed(Gtk::Container *spw) { if (spw->get_data("update")) { return; } sp_stroke_style_scale_line(spw); } /** * Callback for when the stroke style's miterlimit changes. * Causes all line styles to be applied to all selected items. */ static void sp_stroke_style_miterlimit_changed(Gtk::Container *spw) { if (spw->get_data("update")) { return; } sp_stroke_style_scale_line(spw); } /** * Callback for when the stroke style's dash changes. * Causes all line styles to be applied to all selected items. */ static void sp_stroke_style_line_dash_changed(Gtk::Container *spw) { if (spw->get_data("update")) { return; } sp_stroke_style_scale_line(spw); } /** * \brief This routine handles toggle events for buttons in the stroke style * dialog. * When activated, this routine gets the data for the various widgets, and then * calls the respective routines to update css properties, etc. * */ static void sp_stroke_style_any_toggled(Gtk::ToggleButton *tb, Gtk::Container *spw) { if (spw->get_data("update")) { return; } if (tb->get_active()) { gchar const *join = static_cast(tb->get_data("join")); gchar const *cap = static_cast(tb->get_data("cap")); if (join) { Gtk::SpinButton *ml = static_cast(spw->get_data("miterlimit_sb")); ml->set_sensitive(!strcmp(join, "miter")); } SPDesktop *desktop = SP_ACTIVE_DESKTOP; /* TODO: Create some standardized method */ SPCSSAttr *css = sp_repr_css_attr_new(); if (join) { sp_repr_css_set_property(css, "stroke-linejoin", join); sp_desktop_set_style (desktop, css); sp_stroke_style_set_join_buttons(spw, tb); } else if (cap) { sp_repr_css_set_property(css, "stroke-linecap", cap); sp_desktop_set_style (desktop, css); sp_stroke_style_set_cap_buttons(spw, tb); } sp_repr_css_attr_unref(css); sp_document_done(sp_desktop_document(desktop), SP_VERB_DIALOG_FILL_STROKE, _("Set stroke style")); } } /** * Updates the join style toggle buttons */ static void sp_stroke_style_set_join_buttons(Gtk::Container *spw, Gtk::ToggleButton *active) { Gtk::RadioButton *tb; tb = static_cast(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_MITER)); tb->set_active(active == tb); Gtk::SpinButton *ml = static_cast(spw->get_data("miterlimit_sb")); ml->set_sensitive(active == tb); tb = static_cast(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_ROUND)); tb->set_active(active == tb); tb = static_cast(spw->get_data(INKSCAPE_ICON_STROKE_JOIN_BEVEL)); tb->set_active(active == tb); } /** * Updates the cap style toggle buttons */ static void sp_stroke_style_set_cap_buttons(Gtk::Container *spw, Gtk::ToggleButton *active) { Gtk::RadioButton *tb; tb = static_cast(spw->get_data(INKSCAPE_ICON_STROKE_CAP_BUTT)); tb->set_active(active == tb); tb = static_cast(spw->get_data(INKSCAPE_ICON_STROKE_CAP_ROUND)); tb->set_active(active == tb); tb = static_cast(spw->get_data(INKSCAPE_ICON_STROKE_CAP_SQUARE)); tb->set_active(active == tb); } /** * Sets the current marker in the marker menu. */ static void ink_marker_menu_set_current(SPObject *marker, Gtk::OptionMenu *mnu) { mnu->set_data("update", GINT_TO_POINTER(TRUE)); Gtk::Menu *m = mnu->get_menu(); if (marker != NULL) { bool mark_is_stock = false; if (SP_OBJECT_REPR(marker)->attribute("inkscape:stockid")) mark_is_stock = true; gchar *markname; if (mark_is_stock) markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("inkscape:stockid")); else markname = g_strdup(SP_OBJECT_REPR(marker)->attribute("id")); int markpos = ink_marker_menu_get_pos(m, markname); mnu->set_history(markpos); g_free (markname); } else { mnu->set_history(0); } mnu->set_data("update", GINT_TO_POINTER(FALSE)); } /** * Updates the marker menus to highlight the appropriate marker and scroll to * that marker. */ static void sp_stroke_style_update_marker_menus(Gtk::Container *spw, GSList const *objects) { struct { char const *key; int loc; } const keyloc[] = { { "start_mark_menu", SP_MARKER_LOC_START }, { "mid_mark_menu", SP_MARKER_LOC_MID }, { "end_mark_menu", SP_MARKER_LOC_END } }; bool all_texts = true; for (GSList *i = (GSList *) objects; i != NULL; i = i->next) { if (!SP_IS_TEXT (i->data)) { all_texts = false; } } for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) { Gtk::OptionMenu *mnu = static_cast(spw->get_data(keyloc[i].key)); // Per SVG spec, text objects cannot have markers; disable menus if only texts are selected mnu->set_sensitive(!all_texts); } // We show markers of the first object in the list only // FIXME: use the first in the list that has the marker of each type, if any SPObject *object = SP_OBJECT(objects->data); for (unsigned i = 0; i < G_N_ELEMENTS(keyloc); ++i) { // For all three marker types, // find the corresponding menu Gtk::OptionMenu *mnu = static_cast(spw->get_data(keyloc[i].key)); // Quit if we're in update state if (mnu->get_data("update")) { return; } if (object->style->marker[keyloc[i].loc].value != NULL && !all_texts) { // If the object has this type of markers, // Extract the name of the marker that the object uses SPObject *marker = ink_extract_marker_name(object->style->marker[keyloc[i].loc].value, SP_OBJECT_DOCUMENT(object)); // Scroll the menu to that marker ink_marker_menu_set_current(marker, mnu); } else { mnu->set_history(0); } } } /** * Extract the actual name of the link * e.g. get mTriangle from url(#mTriangle). * \return Buffer containing the actual name, allocated from GLib; * the caller should free the buffer when they no longer need it. */ static SPObject* ink_extract_marker_name(gchar const *n, SPDocument *doc) { gchar const *p = n; while (*p != '\0' && *p != '#') { p++; } if (*p == '\0' || p[1] == '\0') { return NULL; } p++; int c = 0; while (p[c] != '\0' && p[c] != ')') { c++; } if (p[c] == '\0') { return NULL; } gchar* b = g_strdup(p); b[c] = '\0'; // FIXME: get the document from the object and let the caller pass it in SPObject *marker = doc->getObjectById(b); return marker; } /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :