/** * @file * Combobox for selecting dash patterns - implementation. */ /* Author: * Lauris Kaplinski * bulia byak * Maximilian Albert * * Copyright (C) 2002 Lauris Kaplinski * * Released under GNU GPL, read the file 'COPYING' for more information */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "stroke-marker-selector.h" #include #include #include #include <2geom/coord.h> #include "style.h" #include "dialogs/dialog-events.h" #include "desktop-handles.h" #include "desktop-style.h" #include "preferences.h" #include "path-prefix.h" #include "io/sys.h" #include "marker.h" #include "sp-defs.h" #include "sp-root.h" #include "ui/cache/svg_preview_cache.h" #include "helper/stock-items.h" #include "gradient-vector.h" #include #include "ui/widget/spinbutton.h" static Inkscape::UI::Cache::SvgPreview svg_preview_cache; MarkerComboBox::MarkerComboBox(gchar const *id) : Gtk::ComboBox(), combo_id(id), updating(false) { marker_store = Gtk::ListStore::create(marker_columns); set_model(marker_store); pack_start(image_renderer, false); pack_end(label_renderer, true); label_renderer.set_padding(5, 0); image_renderer.set_padding(5, 0); set_cell_data_func(label_renderer, sigc::mem_fun(*this, &MarkerComboBox::prepareLabelRenderer)); set_cell_data_func(image_renderer, sigc::mem_fun(*this, &MarkerComboBox::prepareImageRenderer)); gtk_combo_box_set_row_separator_func(GTK_COMBO_BOX(gobj()), MarkerComboBox::separator_cb, NULL, NULL); empty_image = new Gtk::Image(); sandbox = ink_markers_preview_doc (); desktop = inkscape_active_desktop(); doc = sp_desktop_document(desktop); modified_connection = doc->getDefs()->connectModified( sigc::hide(sigc::hide(sigc::bind(sigc::ptr_fun(&MarkerComboBox::handleDefsModified), this))) ); init_combo(); show(); } MarkerComboBox::~MarkerComboBox() { delete combo_id; delete sandbox; if (doc) { modified_connection.disconnect(); } } void MarkerComboBox::setDesktop(SPDesktop *desktop) { if (this->desktop != desktop) { if (doc) { modified_connection.disconnect(); } this->desktop = desktop; doc = sp_desktop_document(desktop); if (doc) { modified_connection = doc->getDefs()->connectModified( sigc::hide(sigc::hide(sigc::bind(sigc::ptr_fun(&MarkerComboBox::handleDefsModified), this))) ); } refreshHistory(); } } void MarkerComboBox::handleDefsModified(MarkerComboBox *self) { self->refreshHistory(); } void MarkerComboBox::refreshHistory() { if (updating) return; updating = true; const char *active = get_active()->get_value(marker_columns.marker); sp_marker_list_from_doc(doc, true); set_selected(active); updating = false; } /** * Init the combobox widget to display markers from markers.svg */ void MarkerComboBox::init_combo() { if (updating) return; const gchar *active = NULL; if (get_active()) { active = get_active()->get_value(marker_columns.marker); } if (!doc) { Gtk::TreeModel::Row row = *(marker_store->append()); row[marker_columns.label] = _("No document selected"); row[marker_columns.marker] = g_strdup("None"); row[marker_columns.image] = NULL; row[marker_columns.stock] = false; row[marker_columns.history] = false; row[marker_columns.separator] = false; set_sensitive(false); set_current(NULL); return; } static SPDocument *markers_doc = NULL; // add separator Gtk::TreeModel::Row row_sep = *(marker_store->append()); row_sep[marker_columns.label] = "Separator"; row_sep[marker_columns.marker] = g_strdup("None"); row_sep[marker_columns.image] = NULL; row_sep[marker_columns.stock] = false; row_sep[marker_columns.history] = false; row_sep[marker_columns.separator] = true; // load markers from the current doc sp_marker_list_from_doc(doc, true); // 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 = SPDocument::createNewDoc(markers_source, FALSE); } g_free(markers_source); } // load markers from markers.svg if (markers_doc) { doc->ensureUpToDate(); sp_marker_list_from_doc(markers_doc, false); } set_sensitive(true); /* Set history */ set_selected(active); } /** * Sets the current marker in the marker combobox. */ void MarkerComboBox::set_current(SPObject *marker) { updating = true; if (marker != NULL) { bool mark_is_stock = false; if (marker->getRepr()->attribute("inkscape:stockid")) { mark_is_stock = true; } gchar *markname = 0; if (mark_is_stock) { markname = g_strdup(marker->getRepr()->attribute("inkscape:stockid")); } else { markname = g_strdup(marker->getRepr()->attribute("id")); } set_selected(markname); g_free (markname); } else { set_selected(NULL); } updating = false; } /** * Return a uri string representing the current selected marker used for setting the marker style in the document */ const gchar * MarkerComboBox::get_active_marker_uri() { /* Get Marker */ const gchar *markid = get_active()->get_value(marker_columns.marker); if (!markid) { return NULL; } gchar const *marker = ""; if (strcmp(markid, "none")) { bool stockid = get_active()->get_value(marker_columns.stock); gchar *markurn; if (stockid) { markurn = g_strconcat("urn:inkscape:marker:",markid,NULL); } else { markurn = g_strdup(markid); } SPObject *mark = get_stock_item(markurn); g_free(markurn); if (mark) { Inkscape::XML::Node *repr = mark->getRepr(); marker = g_strconcat("url(#", repr->attribute("id"), ")", NULL); } } else { marker = g_strdup(markid); } return marker; } void MarkerComboBox::set_active_history() { set_selected(get_active()->get_value(marker_columns.marker)); } void MarkerComboBox::set_selected(const gchar *name, gboolean retry/*=true*/) { if (!name) { set_active(0); return; } for(Gtk::TreeIter iter = marker_store->children().begin(); iter != marker_store->children().end(); ++iter) { Gtk::TreeModel::Row row = (*iter); if (row[marker_columns.marker] && !strcmp(row[marker_columns.marker], name)) { set_active(iter); return; } } // Didn't find it in the list, try refreshing from the doc if (retry) { sp_marker_list_from_doc(doc, true); set_selected(name, false); } } /** * Pick up all markers from source, except those that are in * current_doc (if non-NULL), and add items to the combo. */ void MarkerComboBox::sp_marker_list_from_doc(SPDocument *source, gboolean history) { GSList *ml = get_marker_list(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); } remove_markers(history); // Seem to need to remove 2x remove_markers(history); add_markers(clean_ml, source, history); g_slist_free (ml); g_slist_free (clean_ml); } /** * 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 *MarkerComboBox::get_marker_list (SPDocument *source) { if (source == NULL) return NULL; GSList *ml = NULL; SPDefs *defs = source->getDefs(); if (!defs) { return NULL; } for ( SPObject *child = defs->firstChild(); child; child = child->getNext() ) { if (SP_IS_MARKER(child)) { ml = g_slist_prepend (ml, child); } } return ml; } /** * Remove history or non-history markers from the combo */ void MarkerComboBox::remove_markers (gboolean history) { // Having the model set causes assertions when erasing rows, temporarily disconnect unset_model(); for(Gtk::TreeIter iter = marker_store->children().begin(); iter != marker_store->children().end(); ++iter) { Gtk::TreeModel::Row row = (*iter); if (row[marker_columns.history] == history && row[marker_columns.separator] == false) { marker_store->erase(iter); iter = marker_store->children().begin(); } } set_model(marker_store); } /** * Adds markers in marker_list to the combo */ void MarkerComboBox::add_markers (GSList *marker_list, SPDocument *source, gboolean history) { // Do this here, outside of loop, to speed up preview generation: Inkscape::Drawing drawing; unsigned const visionkey = SPItem::display_key_new(1); drawing.setRoot(sandbox->getRoot()->invoke_show(drawing, visionkey, SP_ITEM_SHOW_DISPLAY)); // Find the separator, Gtk::TreeIter sep_iter; for(Gtk::TreeIter iter = marker_store->children().begin(); iter != marker_store->children().end(); ++iter) { Gtk::TreeModel::Row row = (*iter); if (row[marker_columns.separator]) { sep_iter = iter; } } if (history) { // add "None" Gtk::TreeModel::Row row = *(marker_store->prepend()); row[marker_columns.label] = _("None"); row[marker_columns.stock] = false; row[marker_columns.marker] = g_strdup("None"); row[marker_columns.image] = NULL; row[marker_columns.history] = true; row[marker_columns.separator] = false; } for (; marker_list != NULL; marker_list = marker_list->next) { Inkscape::XML::Node *repr = reinterpret_cast(marker_list->data)->getRepr(); gchar const *markid = repr->attribute("id"); // generate preview Gtk::Image *prv = create_marker_image (22, markid, source, drawing, visionkey); prv->show(); // Add history before separator, others after Gtk::TreeModel::Row row; if (history) row = *(marker_store->insert(sep_iter)); else row = *(marker_store->append()); row[marker_columns.label] = gr_ellipsize_text(markid, 20); row[marker_columns.stock] = !history; row[marker_columns.marker] = g_strdup(markid); row[marker_columns.image] = prv; row[marker_columns.history] = history; row[marker_columns.separator] = false; } sandbox->getRoot()->invoke_hide(visionkey); } /** * Creates a copy of the marker named mname, determines its visible and renderable * area in the bounding box, and then renders it. This allows us to fill in * preview images of each marker in the marker combobox. */ Gtk::Image * MarkerComboBox::create_marker_image(unsigned psize, gchar const *mname, SPDocument *source, Inkscape::Drawing &drawing, unsigned /*visionkey*/) { // 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 = sandbox->getReprDoc(); Inkscape::XML::Node *mrepr = marker->getRepr()->duplicate(xml_doc); mrepr->setAttribute("id", "sample"); // Replace the old sample in the sandbox by the new one Inkscape::XML::Node *defsrepr = sandbox->getObjectById("defs")->getRepr(); SPObject *oldmarker = sandbox->getObjectById("sample"); if (oldmarker) { oldmarker->deleteObject(false); } // TODO - This causes a SIGTRAP on windows defsrepr->appendChild(mrepr); Inkscape::GC::release(mrepr); // Uncomment this to get the sandbox documents saved (useful for debugging) //FILE *fp = fopen (g_strconcat(combo_id, mname, ".svg", NULL), "w"); //sp_repr_save_stream(sandbox->getReprDoc(), fp); //fclose (fp); // object to render; note that the id is the same as that of the combo we're building SPObject *object = sandbox->getObjectById(combo_id); sandbox->getRoot()->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); sandbox->ensureUpToDate(); if (object == NULL || !SP_IS_ITEM(object)) { return NULL; // sandbox broken? } SPItem *item = SP_ITEM(object); // Find object's bbox in document Geom::OptRect dbox = item->documentVisualBounds(); if (!dbox) { return NULL; } /* Update to renderable state */ gchar *cache_name = g_strconcat(combo_id, mname, NULL); Glib::ustring key = svg_preview_cache.cache_key(source->getURI(), cache_name, psize); g_free (cache_name); Glib::RefPtr pixbuf = Glib::wrap(svg_preview_cache.get_preview_from_cache(key)); if (!pixbuf) { pixbuf = Glib::wrap(render_pixbuf(drawing, 0.8, *dbox, psize)); svg_preview_cache.set_preview_in_cache(key, pixbuf->gobj()); } // Create widget Gtk::Image *pb = new Gtk::Image(pixbuf); return pb; } void MarkerComboBox::prepareLabelRenderer( Gtk::TreeModel::const_iterator const &row ) { Glib::ustring name=(*row)[marker_columns.label]; label_renderer.property_markup() = name.c_str(); } void MarkerComboBox::prepareImageRenderer( Gtk::TreeModel::const_iterator const &row ) { Gtk::Image *image = (*row)[marker_columns.image]; if (image) image_renderer.property_pixbuf() = image->get_pixbuf(); else image_renderer.property_pixbuf() = empty_image->get_pixbuf(); } gboolean MarkerComboBox::separator_cb (GtkTreeModel *model, GtkTreeIter *iter, gpointer /*data*/) { gboolean sep = FALSE; gtk_tree_model_get(model, iter, 4, &sep, -1); return sep; } /** * Returns a new document containing default start, mid, and end markers. */ SPDocument *MarkerComboBox::ink_markers_preview_doc () { gchar const *buffer = "" " " " " " " " " " " " " " " " " " " " " " " " " " " ""; return SPDocument::createNewDocFromMem (buffer, strlen(buffer), FALSE); } /* 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:fileencoding=utf-8:textwidth=99 :