#define __SP_STROKE_STYLE_C__ /** * \brief Stroke style dialog * * Authors: * Lauris Kaplinski * Bryce Harrington * bulia byak * * Copyright (C) 2001-2005 authors * Copyright (C) 2001 Ximian, Inc. * Copyright (C) 2004 John Cliff * * Released under GNU GPL, read the file 'COPYING' for more information */ #define noSP_SS_VERBOSE #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include "helper/unit-menu.h" #include "helper/units.h" #include "svg/css-ostringstream.h" #include "widgets/sp-widget.h" #include "widgets/spw-utilities.h" #include "sp-linear-gradient.h" #include "sp-radial-gradient.h" #include "marker.h" #include "sp-pattern.h" #include "widgets/paint-selector.h" #include "widgets/dash-selector.h" #include "style.h" #include "gradient-chemistry.h" #include "sp-namedview.h" #include "desktop-handles.h" #include "desktop-style.h" #include "selection.h" #include "inkscape.h" #include "inkscape-stock.h" #include "dialogs/dialog-events.h" #include "sp-text.h" #include "sp-rect.h" #include "document-private.h" #include "display/nr-arena.h" #include "display/nr-arena-item.h" #include "path-prefix.h" #include "widgets/icon.h" #include "helper/stock-items.h" #include "io/sys.h" #include "ui/cache/svg_preview_cache.h" #include "dialogs/stroke-style.h" /* Paint */ static void sp_stroke_style_paint_construct(SPWidget *spw, SPPaintSelector *psel); 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 ); /** * 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), "construct", GTK_SIGNAL_FUNC(sp_stroke_style_paint_construct), 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); 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 construction, simply does an update of the stroke style paint object. */ static void sp_stroke_style_paint_construct(SPWidget *spw, SPPaintSelector *psel) { #ifdef SP_SS_VERBOSE g_print( "Stroke style widget constructed: inkscape %p repr %p\n", spw->inkscape, spw->repr ); #endif if (spw->inkscape) { sp_stroke_style_paint_update (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 (); // 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.type == SP_PAINT_TYPE_COLOR) { gfloat d[3]; sp_color_get_rgb_floatv (&query->stroke.value.color, d); SPColor color; sp_color_set_rgb_float (&color, d[0], d[1], d[2]); sp_paint_selector_set_color_alpha (psel, &color, SP_SCALE24_TO_FLOAT (query->stroke_opacity.value)); } else if (query->stroke.set && query->stroke.type == SP_PAINT_TYPE_PAINTSERVER) { SPPaintServer *server = SP_STYLE_STROKE_SERVER (query); 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); } } break; } case QUERY_STYLE_MULTIPLE_DIFFERENT: { sp_paint_selector_set_mode (psel, SP_PAINT_SELECTOR_MODE_MULTIPLE); break; } } g_free (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 *undo_label_1 = "stroke:flatcolor:1"; static gchar *undo_label_2 = "stroke:flatcolor:2"; static gchar *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 (); int result = objects_query_fillstroke ((GSList *) items, query, false); guint32 common_rgb = 0; if (result == QUERY_STYLE_MULTIPLE_SAME) { if (query->fill.type != SP_PAINT_TYPE_COLOR) { common_rgb = sp_desktop_get_color(desktop, false); } else { common_rgb = sp_color_get_rgba32_ualpha(&query->stroke.value.color, 0xff); } vector = sp_document_default_gradient_vector(document, common_rgb); } g_free (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.type == SP_PAINT_TYPE_PAINTSERVER) { 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_UNSET: if (items) { SPCSSAttr *css = sp_repr_css_attr_new (); sp_repr_css_unset_property (css, "stroke"); 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_construct(SPWidget *spw, gpointer data); 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(SPWidget *spw, Inkscape::Selection *sel); static void sp_stroke_style_set_join_buttons(SPWidget *spw, GtkWidget *active); static void sp_stroke_style_set_cap_buttons(SPWidget *spw, GtkWidget *active); static void sp_stroke_style_width_changed(GtkAdjustment *adj, SPWidget *spw); static void sp_stroke_style_miterlimit_changed(GtkAdjustment *adj, SPWidget *spw); static void sp_stroke_style_any_toggled(GtkToggleButton *tb, SPWidget *spw); static void sp_stroke_style_line_dash_changed(SPDashSelector *dsel, SPWidget *spw); static void sp_stroke_style_update_marker_menus(SPWidget *spw, GSList const *objects); static SPObject *ink_extract_marker_name(gchar const *n); /** * Helper function for creating radio buttons. This should probably be re-thought out * when reimplementing this with Gtkmm. */ static GtkWidget * sp_stroke_radio_button(GtkWidget *tb, char const *icon, GtkWidget *hb, GtkWidget *spw, gchar const *key, gchar const *data) { g_assert(icon != NULL); g_assert(hb != NULL); g_assert(spw != NULL); if (tb == NULL) { tb = gtk_radio_button_new(NULL); } else { tb = gtk_radio_button_new(gtk_radio_button_group(GTK_RADIO_BUTTON(tb)) ); } gtk_widget_show(tb); gtk_toggle_button_set_mode(GTK_TOGGLE_BUTTON(tb), FALSE); gtk_box_pack_start(GTK_BOX(hb), tb, FALSE, FALSE, 0); gtk_object_set_data(GTK_OBJECT(spw), icon, tb); gtk_object_set_data(GTK_OBJECT(tb), key, (gpointer*)data); gtk_signal_connect(GTK_OBJECT(tb), "toggled", GTK_SIGNAL_FUNC(sp_stroke_style_any_toggled), spw); GtkWidget *px = sp_icon_new(Inkscape::ICON_SIZE_LARGE_TOOLBAR, icon); g_assert(px != NULL); gtk_widget_show(px); gtk_container_add(GTK_CONTAINER(tb), px); return tb; } /** * 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 GtkWidget * sp_marker_prev_new(unsigned psize, gchar const *mname, SPDocument *source, SPDocument *sandbox, gchar *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::Node *mrepr = SP_OBJECT_REPR (marker)->duplicate(); 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(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 NR::Matrix const i2doc(sp_item_i2doc_affine(SP_ITEM(object))); NR::Rect const dbox = SP_ITEM(object)->invokeBbox(i2doc); if (dbox.isEmpty()) { return NULL; } /* Update to renderable state */ double sf = 0.8; GdkPixbuf* pixbuf = render_pixbuf(root, sf, dbox, psize); // Create widget GtkWidget *pb = gtk_image_new_from_pixbuf(get_pixbuf(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 (GtkWidget *m, GSList *marker_list, SPDocument *source, SPDocument *sandbox, gchar *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); GtkWidget *i = gtk_menu_item_new(); gtk_widget_show(i); if (repr->attribute("inkscape:stockid")) g_object_set_data (G_OBJECT(i), "stockid", (void *) "true"); else g_object_set_data (G_OBJECT(i), "stockid", (void *) "false"); gchar const *markid = repr->attribute("id"); g_object_set_data (G_OBJECT(i), "marker", (void *) markid); GtkWidget *hb = gtk_hbox_new(FALSE, MARKER_ITEM_MARGIN); gtk_widget_show(hb); // generate preview GtkWidget *prv = sp_marker_prev_new (22, markid, source, sandbox, menu_id, arena, visionkey, root); gtk_widget_show(prv); gtk_box_pack_start(GTK_BOX(hb), prv, FALSE, FALSE, 6); // create label GtkWidget *l = gtk_label_new(repr->attribute("id")); gtk_widget_show(l); gtk_misc_set_alignment(GTK_MISC(l), 0.0, 0.5); gtk_box_pack_start(GTK_BOX(hb), l, TRUE, TRUE, 0); gtk_widget_show(hb); gtk_container_add(GTK_CONTAINER(i), hb); gtk_menu_append(GTK_MENU(m), i); } } /** * 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 (GtkWidget *m, SPDocument *current_doc, SPDocument *source, SPDocument *markers_doc, SPDocument *sandbox, gchar *menu_id) { GSList *ml = ink_marker_list_get(source); GSList *clean_ml = NULL; // Do this here, outside of loop, to speed up preview generation: /* Create new arena */ NRArena const *arena = NRArena::create(); /* Create ArenaItem and set transform */ 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 (; ml != NULL; ml = ml->next) { if (!SP_IS_MARKER(ml->data)) continue; /* Bug 980157 wants to have stock markers show up at the top of the dropdown menu Thus we can skip all of this code, which simply looks for duplicate stock markers Inkscape::XML::Node *repr = SP_OBJECT_REPR((SPItem *) ml->data); bool stock_dupe = false; if (repr->attribute("inkscape:stockid")) { GSList * markers_doc_ml = ink_marker_list_get(markers_doc); for (; markers_doc_ml != NULL; markers_doc_ml = markers_doc_ml->next) { const gchar* stockid = SP_OBJECT_REPR(markers_doc_ml->data)->attribute("inkscape:stockid"); if (stockid && !strcmp(repr->attribute("inkscape:stockid"), stockid)) stock_dupe = true; } } if (stock_dupe) // stock item, dont add to list from current doc 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); } /** * Creates a menu widget to display markers from markers.svg */ static GtkWidget * ink_marker_menu( GtkWidget *tbl, gchar *menu_id, SPDocument *sandbox) { SPDesktop *desktop = inkscape_active_desktop(); SPDocument *doc = sp_desktop_document(desktop); GtkWidget *mnu = gtk_option_menu_new(); /* Create new menu widget */ GtkWidget *m = gtk_menu_new(); gtk_widget_show(m); g_object_set_data(G_OBJECT(mnu), "updating", (gpointer) FALSE); if (!doc) { GtkWidget *i = gtk_menu_item_new_with_label(_("No document selected")); gtk_widget_show(i); gtk_menu_append(GTK_MENU(m), i); gtk_widget_set_sensitive(mnu, FALSE); } else { // add "None" { GtkWidget *i = gtk_menu_item_new(); gtk_widget_show(i); g_object_set_data(G_OBJECT(i), "marker", (void *) "none"); GtkWidget *hb = gtk_hbox_new(FALSE, MARKER_ITEM_MARGIN); gtk_widget_show(hb); GtkWidget *l = gtk_label_new( _("None") ); gtk_widget_show(l); gtk_misc_set_alignment(GTK_MISC(l), 0.0, 0.5); gtk_box_pack_start(GTK_BOX(hb), l, TRUE, TRUE, 0); gtk_widget_show(hb); gtk_container_add(GTK_CONTAINER(i), hb); gtk_menu_append(GTK_MENU(m), i); } // find and load markers.svg static SPDocument *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 { GtkWidget *i = gtk_separator_menu_item_new(); gtk_widget_show(i); gtk_menu_append(GTK_MENU(m), 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 ); } gtk_widget_set_sensitive(mnu, TRUE); } gtk_object_set_data(GTK_OBJECT(mnu), "menu_id", menu_id); gtk_option_menu_set_menu(GTK_OPTION_MENU(mnu), m); /* Set history */ gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 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(GtkOptionMenu *mnu, GtkWidget *spw) { if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { return; } SPDesktop *desktop = inkscape_active_desktop(); SPDocument *document = sp_desktop_document(desktop); if (!document) { return; } /* Get Marker */ if (!g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))), "marker")) { return; } gchar *markid = (gchar *) g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))), "marker"); gchar *marker = ""; if (strcmp(markid, "none")){ gchar *stockid = (gchar *) g_object_get_data(G_OBJECT(gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(mnu)))), "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 *menu_id = (gchar *) g_object_get_data(G_OBJECT(mnu), "menu_id"); sp_repr_css_set_property(css, menu_id, marker); 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")); } /** * 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, GObject *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 */ g_object_set_data (spw, "update", GUINT_TO_POINTER (TRUE)); GtkAdjustment *a = GTK_ADJUSTMENT(g_object_get_data (spw, "width")); float w = sp_units_get_pixels (a->value, *old); gdouble average = stroke_average_width (objects); if (average == NR_HUGE || average == 0) return FALSE; gtk_adjustment_set_value (a, 100.0 * w / average); g_object_set_data (spw, "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 */ g_object_set_data (spw, "update", GUINT_TO_POINTER (TRUE)); GtkAdjustment *a = GTK_ADJUSTMENT(g_object_get_data (spw, "width")); gdouble average = stroke_average_width (objects); gtk_adjustment_set_value (a, sp_pixels_get_units (0.01 * a->value * average, *new_units)); g_object_set_data (spw, "update", GUINT_TO_POINTER (FALSE)); return TRUE; } return FALSE; } /** * \brief Creates a new widget for the line stroke style. * */ GtkWidget * sp_stroke_style_line_widget_new(void) { GtkWidget *spw, *f, *t, *hb, *sb, *us, *tb, *ds; GtkObject *a; GtkTooltips *tt = gtk_tooltips_new(); spw = sp_widget_new_global(INKSCAPE); f = gtk_hbox_new (FALSE, 0); gtk_widget_show(f); gtk_container_add(GTK_CONTAINER(spw), f); t = gtk_table_new(3, 6, FALSE); gtk_widget_show(t); gtk_container_set_border_width(GTK_CONTAINER(t), 4); gtk_table_set_row_spacings(GTK_TABLE(t), 4); gtk_container_add(GTK_CONTAINER(f), t); gtk_object_set_data(GTK_OBJECT(spw), "stroke", t); gint i = 0; /* Stroke width */ spw_label(t, _("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 = gtk_adjustment_new(1.0, 0.0, 1000.0, 0.1, 10.0, 10.0); gtk_object_set_data(GTK_OBJECT(spw), "width", a); sb = gtk_spin_button_new(GTK_ADJUSTMENT(a), 0.1, 3); gtk_tooltips_set_tip(tt, sb, _("Stroke width"), NULL); gtk_widget_show(sb); sp_dialog_defocus_on_enter(sb); gtk_box_pack_start(GTK_BOX(hb), sb, FALSE, FALSE, 0); us = sp_unit_selector_new(SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE); SPDesktop *desktop = SP_ACTIVE_DESKTOP; if (desktop) sp_unit_selector_set_unit (SP_UNIT_SELECTOR(us), sp_desktop_namedview(desktop)->doc_units); sp_unit_selector_add_unit(SP_UNIT_SELECTOR(us), &sp_unit_get_by_id(SP_UNIT_PERCENT), 0); g_signal_connect ( G_OBJECT (us), "set_unit", G_CALLBACK (stroke_width_set_unit), spw ); gtk_widget_show(us); sp_unit_selector_add_adjustment( SP_UNIT_SELECTOR(us), GTK_ADJUSTMENT(a) ); gtk_box_pack_start(GTK_BOX(hb), us, FALSE, FALSE, 0); gtk_object_set_data(GTK_OBJECT(spw), "units", us); gtk_signal_connect( GTK_OBJECT(a), "value_changed", GTK_SIGNAL_FUNC(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_STOCK_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). gtk_tooltips_set_tip(tt, tb, _("Miter join"), NULL); tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_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). gtk_tooltips_set_tip(tt, tb, _("Round join"), NULL); tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_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). gtk_tooltips_set_tip(tt, tb, _("Bevel join"), NULL); 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 = gtk_adjustment_new(4.0, 0.0, 100.0, 0.1, 10.0, 10.0); gtk_object_set_data(GTK_OBJECT(spw), "miterlimit", a); sb = gtk_spin_button_new(GTK_ADJUSTMENT(a), 0.1, 2); gtk_tooltips_set_tip(tt, sb, _("Maximum length of the miter (in units of stroke width)"), NULL); gtk_widget_show(sb); gtk_object_set_data(GTK_OBJECT(spw), "miterlimit_sb", sb); sp_dialog_defocus_on_enter(sb); gtk_box_pack_start(GTK_BOX(hb), sb, FALSE, FALSE, 0); gtk_signal_connect( GTK_OBJECT(a), "value_changed", GTK_SIGNAL_FUNC(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_STOCK_CAP_BUTT, hb, spw, "cap", "butt"); // TRANSLATORS: Butt cap: the line shape does not extend beyond the end point // of the line; the ends of the line are square gtk_tooltips_set_tip(tt, tb, _("Butt cap"), NULL); tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_CAP_ROUND, hb, spw, "cap", "round"); // TRANSLATORS: Round cap: the line shape extends beyond the end point of the // line; the ends of the line are rounded gtk_tooltips_set_tip(tt, tb, _("Round cap"), NULL); tb = sp_stroke_radio_button(tb, INKSCAPE_STOCK_CAP_SQUARE, hb, spw, "cap", "square"); // TRANSLATORS: Square cap: the line shape extends beyond the end point of the // line; the ends of the line are square gtk_tooltips_set_tip(tt, tb, _("Square cap"), NULL); i++; /* Dash */ spw_label(t, _("Dashes:"), 0, i); ds = sp_dash_selector_new( inkscape_get_repr( INKSCAPE, "palette.dashes") ); gtk_widget_show(ds); gtk_table_attach( GTK_TABLE(t), ds, 1, 4, i, i+1, (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ), (GtkAttachOptions)0, 0, 0 ); gtk_object_set_data(GTK_OBJECT(spw), "dash", ds); gtk_signal_connect( GTK_OBJECT(ds), "changed", GTK_SIGNAL_FUNC(sp_stroke_style_line_dash_changed), spw ); i++; /* Drop down marker selectors*/ // 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); GtkWidget *mnu = ink_marker_menu( spw ,"marker-start", sandbox); gtk_signal_connect( GTK_OBJECT(mnu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw ); gtk_widget_show(mnu); gtk_table_attach( GTK_TABLE(t), mnu, 1, 4, i, i+1, (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ), (GtkAttachOptions)0, 0, 0 ); gtk_object_set_data(GTK_OBJECT(spw), "start_mark_menu", mnu); i++; spw_label(t, _("Mid Markers:"), 0, i); mnu = NULL; mnu = ink_marker_menu( spw ,"marker-mid", sandbox); gtk_signal_connect( GTK_OBJECT(mnu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw ); gtk_widget_show(mnu); gtk_table_attach( GTK_TABLE(t), mnu, 1, 4, i, i+1, (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ), (GtkAttachOptions)0, 0, 0 ); gtk_object_set_data(GTK_OBJECT(spw), "mid_mark_menu", mnu); i++; spw_label(t, _("End Markers:"), 0, i); mnu = NULL; mnu = ink_marker_menu( spw ,"marker-end", sandbox); gtk_signal_connect( GTK_OBJECT(mnu), "changed", GTK_SIGNAL_FUNC(sp_marker_select), spw ); gtk_widget_show(mnu); gtk_table_attach( GTK_TABLE(t), mnu, 1, 4, i, i+1, (GtkAttachOptions)( GTK_EXPAND | GTK_FILL ), (GtkAttachOptions)0, 0, 0 ); gtk_object_set_data(GTK_OBJECT(spw), "end_mark_menu", mnu); i++; gtk_signal_connect( GTK_OBJECT(spw), "construct", GTK_SIGNAL_FUNC(sp_stroke_style_line_construct), NULL ); gtk_signal_connect( GTK_OBJECT(spw), "modify_selection", GTK_SIGNAL_FUNC(sp_stroke_style_line_selection_modified), NULL ); gtk_signal_connect( GTK_OBJECT(spw), "change_selection", GTK_SIGNAL_FUNC(sp_stroke_style_line_selection_changed), NULL ); sp_stroke_style_line_update( SP_WIDGET(spw), desktop ? sp_desktop_selection(desktop) : NULL); return spw; } /** * Callback for when the stroke style widget is called. It causes * the stroke line style to be updated. */ static void sp_stroke_style_line_construct(SPWidget *spw, gpointer data) { #ifdef SP_SS_VERBOSE g_print( "Stroke style widget constructed: inkscape %p repr %p\n", spw->inkscape, spw->repr ); #endif if (spw->inkscape) { sp_stroke_style_line_update(spw, ( SP_ACTIVE_DESKTOP ? sp_desktop_selection(SP_ACTIVE_DESKTOP) : NULL )); } } /** * Callback for when stroke style widget is modified. * Triggers update action. */ static void sp_stroke_style_line_selection_modified ( SPWidget *spw, Inkscape::Selection *selection, guint flags, gpointer 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 *spw, Inkscape::Selection *selection, gpointer 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 (GtkWidget *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? } sp_dash_selector_set_dash(SP_DASH_SELECTOR(dsel), len, d, style->stroke_width.computed != 0? style->stroke_dash.offset / style->stroke_width.computed : style->stroke_dash.offset); } else { sp_dash_selector_set_dash(SP_DASH_SELECTOR(dsel), 0, NULL, 0.0); } } /** * Sets the join type for a line, and updates the stroke style widget's buttons */ static void sp_jointype_set (SPWidget *spw, unsigned const jointype) { GtkWidget *tb = NULL; switch (jointype) { case SP_STROKE_LINEJOIN_MITER: tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_MITER)); break; case SP_STROKE_LINEJOIN_ROUND: tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_ROUND)); break; case SP_STROKE_LINEJOIN_BEVEL: tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_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 (SPWidget *spw, unsigned const captype) { GtkWidget *tb = NULL; switch (captype) { case SP_STROKE_LINECAP_BUTT: tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_BUTT)); break; case SP_STROKE_LINECAP_ROUND: tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_ROUND)); break; case SP_STROKE_LINECAP_SQUARE: tb = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), INKSCAPE_STOCK_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(SPWidget *spw, Inkscape::Selection *sel) { if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { return; } gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(TRUE)); GtkWidget *sset = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "stroke")); GtkObject *width = GTK_OBJECT(gtk_object_get_data(GTK_OBJECT(spw), "width")); GtkObject *ml = GTK_OBJECT(gtk_object_get_data(GTK_OBJECT(spw), "miterlimit")); GtkWidget *us = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "units")); GtkWidget *dsel = GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(spw), "dash")); // create temporary style SPStyle *query = sp_style_new (); // 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 (result_sw == QUERY_STYLE_NOTHING) { /* No objects stroked, set insensitive */ gtk_widget_set_sensitive(sset, FALSE); gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(FALSE)); return; } else { gtk_widget_set_sensitive(sset, TRUE); SPUnit const *unit = sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us)); if (result_sw == QUERY_STYLE_MULTIPLE_AVERAGED) { sp_unit_selector_set_unit(SP_UNIT_SELECTOR(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(SP_UNIT_SELECTOR(us), sp_desktop_namedview(SP_ACTIVE_DESKTOP)->doc_units); } } unit = sp_unit_selector_get_unit (SP_UNIT_SELECTOR (us)); if (unit->base == SP_UNIT_ABSOLUTE || unit->base == SP_UNIT_DEVICE) { double avgwidth = sp_pixels_get_units (query->stroke_width.computed, *unit); gtk_adjustment_set_value(GTK_ADJUSTMENT(width), avgwidth); } else { gtk_adjustment_set_value(GTK_ADJUSTMENT(width), 100); } } if (result_ml != QUERY_STYLE_NOTHING) gtk_adjustment_set_value(GTK_ADJUSTMENT(ml), 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); } g_free (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 gtk_widget_set_sensitive(sset, TRUE); gtk_object_set_data(GTK_OBJECT(spw), "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(SPWidget *spw) { if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { return; } gtk_object_set_data(GTK_OBJECT(spw), "update", GINT_TO_POINTER(TRUE)); GtkAdjustment *wadj = GTK_ADJUSTMENT(gtk_object_get_data(GTK_OBJECT(spw), "width")); SPUnitSelector *us = SP_UNIT_SELECTOR(gtk_object_get_data(GTK_OBJECT(spw), "units")); SPDashSelector *dsel = SP_DASH_SELECTOR(gtk_object_get_data(GTK_OBJECT(spw), "dash")); GtkAdjustment *ml = GTK_ADJUSTMENT(gtk_object_get_data(GTK_OBJECT(spw), "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->value; double const miterlimit = ml->value; SPUnit const *const unit = sp_unit_selector_get_unit(SP_UNIT_SELECTOR(us)); double *dash, offset; int ndash; sp_dash_selector_get_dash(dsel, &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 gtk_adjustment_set_value (wadj, 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")); gtk_object_set_data(GTK_OBJECT(spw), "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(GtkAdjustment *adj, SPWidget *spw) { if (gtk_object_get_data(GTK_OBJECT(spw), "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(GtkAdjustment *adj, SPWidget *spw) { if (gtk_object_get_data(GTK_OBJECT(spw), "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(SPDashSelector *dsel, SPWidget *spw) { if (gtk_object_get_data(GTK_OBJECT(spw), "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(GtkToggleButton *tb, SPWidget *spw) { if (gtk_object_get_data(GTK_OBJECT(spw), "update")) { return; } if (gtk_toggle_button_get_active(tb)) { gchar const *join = static_cast(gtk_object_get_data(GTK_OBJECT(tb), "join")); gchar const *cap = static_cast(gtk_object_get_data(GTK_OBJECT(tb), "cap")); if (join) { GtkWidget *ml = GTK_WIDGET(g_object_get_data(G_OBJECT(spw), "miterlimit_sb")); gtk_widget_set_sensitive (ml, !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, GTK_WIDGET(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, GTK_WIDGET(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(SPWidget *spw, GtkWidget *active) { GtkWidget *tb; tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_MITER) ); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb)); GtkWidget *ml = GTK_WIDGET(g_object_get_data(G_OBJECT(spw), "miterlimit_sb")); gtk_widget_set_sensitive(ml, (active == tb)); tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_ROUND) ); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb)); tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw), INKSCAPE_STOCK_JOIN_BEVEL) ); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb)); } /** * Updates the cap style toggle buttons */ static void sp_stroke_style_set_cap_buttons(SPWidget *spw, GtkWidget *active) { GtkWidget *tb; tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_BUTT)); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb)); tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_ROUND) ); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb)); tb = GTK_WIDGET(gtk_object_get_data( GTK_OBJECT(spw), INKSCAPE_STOCK_CAP_SQUARE) ); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tb), (active == tb)); } /** * Sets the current marker in the marker menu. */ static void ink_marker_menu_set_current(SPObject *marker, GtkOptionMenu *mnu) { gtk_object_set_data(GTK_OBJECT(mnu), "update", GINT_TO_POINTER(TRUE)); GtkMenu *m = GTK_MENU(gtk_option_menu_get_menu(mnu)); 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 = 0; GList *kids = GTK_MENU_SHELL(m)->children; int i = 0; for (; kids != NULL; kids = kids->next) { gchar *mark = (gchar *) g_object_get_data(G_OBJECT(kids->data), "marker"); if ( mark && strcmp(mark, markname) == 0 ) { if ( mark_is_stock && !strcmp((gchar *) g_object_get_data(G_OBJECT(kids->data), "stockid"), "true")) markpos = i; if ( !mark_is_stock && !strcmp((gchar *) g_object_get_data(G_OBJECT(kids->data), "stockid"), "false")) markpos = i; } i++; } gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), markpos); g_free (markname); } else { gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 0); } gtk_object_set_data(GTK_OBJECT(mnu), "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( SPWidget *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) { GtkOptionMenu *mnu = (GtkOptionMenu *) g_object_get_data(G_OBJECT(spw), keyloc[i].key); if (all_texts) { // Per SVG spec, text objects cannot have markers; disable menus if only texts are selected gtk_widget_set_sensitive (GTK_WIDGET(mnu), FALSE); } else { gtk_widget_set_sensitive (GTK_WIDGET(mnu), TRUE); } } // 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 GtkOptionMenu *mnu = (GtkOptionMenu *) g_object_get_data(G_OBJECT(spw), keyloc[i].key); // Quit if we're in update state if (gtk_object_get_data(GTK_OBJECT(mnu), "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); // Scroll the menu to that marker ink_marker_menu_set_current (marker, mnu); } else { gtk_option_menu_set_history(GTK_OPTION_MENU(mnu), 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) { 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'; SPDesktop *desktop = inkscape_active_desktop(); SPDocument *doc = sp_desktop_document(desktop); 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=c++:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :