/** * @file * Symbols dialog. */ /* Authors: * Copyright (C) 2012 Tavmjong Bah * * Released under GNU GPL, read the file 'COPYING' for more information */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "path-prefix.h" #include "io/sys.h" #include "ui/cache/svg_preview_cache.h" #include "ui/clipboard.h" #include "symbols.h" #include "desktop.h" #include "desktop-handles.h" #include "document.h" #include "inkscape.h" #include "sp-root.h" #include "sp-use.h" #include "sp-symbol.h" #ifdef WITH_LIBVISIO #include #include #endif #include "verbs.h" #include "xml/repr.h" namespace Inkscape { namespace UI { static Cache::SvgPreview svg_preview_cache; namespace Dialog { // See: http://developer.gnome.org/gtkmm/stable/classGtk_1_1TreeModelColumnRecord.html class SymbolColumns : public Gtk::TreeModel::ColumnRecord { public: Gtk::TreeModelColumn symbol_id; Gtk::TreeModelColumn symbol_title; Gtk::TreeModelColumn< Glib::RefPtr > symbol_image; SymbolColumns() { add(symbol_id); add(symbol_title); add(symbol_image); } }; SymbolColumns* SymbolsDialog::getColumns() { SymbolColumns* columns = new SymbolColumns(); return columns; } /** * Constructor */ SymbolsDialog::SymbolsDialog( gchar const* prefsPath ) : UI::Widget::Panel("", prefsPath, SP_VERB_DIALOG_SYMBOLS), store(Gtk::ListStore::create(*getColumns())), iconView(0), previewScale(0), previewSize(0), currentDesktop(0), deskTrack(), currentDocument(0), previewDocument(0), instanceConns() { /******************** Table *************************/ // Replace by Grid for GTK 3.0 Gtk::Table *table = new Gtk::Table(2, 4, false); // panel is a cloked Gtk::VBox _getContents()->pack_start(*Gtk::manage(table), Gtk::PACK_EXPAND_WIDGET); guint row = 0; /******************** Symbol Sets *************************/ Gtk::Label* labelSet = new Gtk::Label(_("Symbol set: ")); table->attach(*Gtk::manage(labelSet),0,1,row,row+1,Gtk::SHRINK,Gtk::SHRINK); symbolSet = new Gtk::ComboBoxText(); // Fill in later symbolSet->append(_("Current Document")); symbolSet->set_active_text(_("Current Document")); table->attach(*Gtk::manage(symbolSet),1,2,row,row+1,Gtk::FILL|Gtk::EXPAND,Gtk::SHRINK); sigc::connection connSet = symbolSet->signal_changed().connect(sigc::mem_fun(*this, &SymbolsDialog::rebuild)); instanceConns.push_back(connSet); ++row; /********************* Icon View **************************/ SymbolColumns* columns = getColumns(); iconView = new Gtk::IconView(static_cast >(store)); //iconView->set_text_column( columns->symbol_id ); iconView->set_tooltip_column( 1 ); iconView->set_pixbuf_column( columns->symbol_image ); std::vector< Gtk::TargetEntry > targets; targets.push_back(Gtk::TargetEntry( "application/x-inkscape-paste")); iconView->enable_model_drag_source (targets, Gdk::BUTTON1_MASK, Gdk::ACTION_COPY); iconView->signal_drag_data_get().connect(sigc::mem_fun(*this, &SymbolsDialog::iconDragDataGet)); sigc::connection connIconChanged; connIconChanged = iconView->signal_selection_changed().connect(sigc::mem_fun(*this, &SymbolsDialog::iconChanged)); instanceConns.push_back(connIconChanged); Gtk::ScrolledWindow *scroller = new Gtk::ScrolledWindow(); scroller->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS); scroller->add(*Gtk::manage(iconView)); table->attach(*Gtk::manage(scroller),0,2,row,row+1,Gtk::EXPAND|Gtk::FILL,Gtk::EXPAND|Gtk::FILL); ++row; /******************** Preview Scale ***********************/ Gtk::Label* labelScale = new Gtk::Label(_("Preview scale: ")); table->attach(*Gtk::manage(labelScale),0,1,row,row+1,Gtk::SHRINK,Gtk::SHRINK); previewScale = new Gtk::ComboBoxText(); const gchar *scales[] = {_("Fit"), _("Fit to width"), _("Fit to height"), "0.1", "0.2", "0.5", "1.0", "2.0", "5.0", NULL}; for( int i = 0; scales[i]; ++i ) { previewScale->append(scales[i]); } previewScale->set_active_text(scales[0]); table->attach(*Gtk::manage(previewScale),1,2,row,row+1,Gtk::FILL|Gtk::EXPAND,Gtk::SHRINK); sigc::connection connScale = previewScale->signal_changed().connect(sigc::mem_fun(*this, &SymbolsDialog::rebuild)); instanceConns.push_back(connScale); ++row; /******************** Preview Size ************************/ Gtk::Label* labelSize = new Gtk::Label(_("Preview size: ")); table->attach(*Gtk::manage(labelSize),0,1,row,row+1,Gtk::SHRINK,Gtk::SHRINK); previewSize = new Gtk::ComboBoxText(); const gchar *sizes[] = {"16", "24", "32", "48", "64", NULL}; for( int i = 0; sizes[i]; ++i ) { previewSize->append(sizes[i]); } previewSize->set_active_text(sizes[2]); table->attach(*Gtk::manage(previewSize),1,2,row,row+1,Gtk::FILL|Gtk::EXPAND,Gtk::SHRINK); sigc::connection connSize = previewSize->signal_changed().connect(sigc::mem_fun(*this, &SymbolsDialog::rebuild)); instanceConns.push_back(connSize); ++row; /**********************************************************/ currentDesktop = inkscape_active_desktop(); currentDocument = sp_desktop_document(currentDesktop); previewDocument = symbols_preview_doc(); /* Template to render symbols in */ previewDocument->ensureUpToDate(); /* Necessary? */ key = SPItem::display_key_new(1); renderDrawing.setRoot(previewDocument->getRoot()->invoke_show(renderDrawing, key, SP_ITEM_SHOW_DISPLAY )); get_symbols(); draw_symbols( currentDocument ); /* Defaults to current document */ desktopChangeConn = deskTrack.connectDesktopChanged( sigc::mem_fun(*this, &SymbolsDialog::setTargetDesktop) ); instanceConns.push_back( desktopChangeConn ); deskTrack.connect(GTK_WIDGET(gobj())); } SymbolsDialog::~SymbolsDialog() { for (std::vector::iterator it = instanceConns.begin(); it != instanceConns.end(); ++it) { it->disconnect(); } instanceConns.clear(); deskTrack.disconnect(); } SymbolsDialog& SymbolsDialog::getInstance() { return *new SymbolsDialog(); } void SymbolsDialog::rebuild() { store->clear(); Glib::ustring symbolSetString = symbolSet->get_active_text(); SPDocument* symbolDocument = symbolSets[symbolSetString]; if( !symbolDocument ) { // Symbol must be from Current Document (this method of // checking should be language independent). symbolDocument = currentDocument; } draw_symbols( symbolDocument ); } void SymbolsDialog::iconDragDataGet(const Glib::RefPtr& /*context*/, Gtk::SelectionData& data, guint /*info*/, guint /*time*/) { #if WITH_GTKMM_3_0 std::vector iconArray = iconView->get_selected_items(); #else Gtk::IconView::ArrayHandle_TreePaths iconArray = iconView->get_selected_items(); #endif if( iconArray.empty() ) { //std::cout << " iconArray empty: huh? " << std::endl; } else { Gtk::TreeModel::Path const & path = *iconArray.begin(); Gtk::ListStore::iterator row = store->get_iter(path); Glib::ustring symbol_id = (*row)[getColumns()->symbol_id]; GdkAtom dataAtom = gdk_atom_intern( "application/x-inkscape-paste", FALSE ); gtk_selection_data_set( data.gobj(), dataAtom, 9, (guchar*)symbol_id.c_str(), symbol_id.length() ); } } void SymbolsDialog::iconChanged() { #if WITH_GTKMM_3_0 std::vector iconArray = iconView->get_selected_items(); #else Gtk::IconView::ArrayHandle_TreePaths iconArray = iconView->get_selected_items(); #endif if( iconArray.empty() ) { //std::cout << " iconArray empty: huh? " << std::endl; } else { Gtk::TreeModel::Path const & path = *iconArray.begin(); Gtk::ListStore::iterator row = store->get_iter(path); Glib::ustring symbol_id = (*row)[getColumns()->symbol_id]; /* OK, we know symbol name... now we need to copy it to clipboard, bon chance! */ Glib::ustring symbolSetString = symbolSet->get_active_text(); SPDocument* symbolDocument = symbolSets[symbolSetString]; if( !symbolDocument ) { // Symbol must be from Current Document (this method of // checking should be language independent). symbolDocument = currentDocument; } SPObject* symbol = symbolDocument->getObjectById(symbol_id); if( symbol ) { // Find style for use in // First look for default style stored in gchar const* style = symbol->getAttribute("inkscape:symbol-style"); if( !style ) { // If no default style in , look in documents. if( symbolDocument == currentDocument ) { style = style_from_use( symbol_id.c_str(), currentDocument ); } else { style = symbolDocument->getReprRoot()->attribute("style"); } } ClipboardManager *cm = ClipboardManager::get(); cm->copySymbol(symbol->getRepr(), style); } } } #ifdef WITH_LIBVISIO // Read Visio stencil files SPDocument* read_vss( gchar* fullname, gchar* filename ) { WPXFileStream input(fullname); if (!libvisio::VisioDocument::isSupported(&input)) { return NULL; } libvisio::VSDStringVector output; if (!libvisio::VisioDocument::generateSVGStencils(&input, output)) { return NULL; } if (output.empty()) { return NULL; } Glib::ustring tmpSVGOutput; tmpSVGOutput += "\n"; tmpSVGOutput += "\n"; tmpSVGOutput += " "; tmpSVGOutput += filename; tmpSVGOutput += "\n"; tmpSVGOutput += " \n"; // Create a string we can use for the symbol id (libvisio doesn't give us a name) std::string sanitized( filename ); sanitized.erase( sanitized.find_last_of(".vss")-3 ); sanitized.erase( std::remove_if( sanitized.begin(), sanitized.end(), ispunct ), sanitized.end() ); std::replace( sanitized.begin(), sanitized.end(), ' ', '_' ); // std::cout << filename << " |" << sanitized << "|" << std::endl; // Each "symbol" is in it's own SVG file, we wrap with and merge into one file. for (unsigned i=0; i\n"; std::istringstream iss( output[i].cstr() ); std::string line; while( std::getline( iss, line ) ) { // std::cout << line << std::endl; if( line.find( "svg:svg" ) == std::string::npos ) { tmpSVGOutput += line; tmpSVGOutput += "\n"; } } tmpSVGOutput += " \n"; } tmpSVGOutput += " \n"; tmpSVGOutput += "\n"; return SPDocument::createNewDocFromMem( tmpSVGOutput.c_str(), strlen( tmpSVGOutput.c_str()), 0 ); } #endif /* Hunts preference directories for symbol files */ void SymbolsDialog::get_symbols() { std::list directories; if( Inkscape::IO::file_test( INKSCAPE_SYMBOLSDIR, G_FILE_TEST_EXISTS ) && Inkscape::IO::file_test( INKSCAPE_SYMBOLSDIR, G_FILE_TEST_IS_DIR ) ) { directories.push_back( INKSCAPE_SYMBOLSDIR ); } if( Inkscape::IO::file_test( profile_path("symbols"), G_FILE_TEST_EXISTS ) && Inkscape::IO::file_test( profile_path("symbols"), G_FILE_TEST_IS_DIR ) ) { directories.push_back( profile_path("symbols") ); } std::list::iterator it; for( it = directories.begin(); it != directories.end(); ++it ) { GError *err = 0; GDir *dir = g_dir_open( (*it).c_str(), 0, &err ); if( dir ) { gchar *filename = 0; while( (filename = (gchar *)g_dir_read_name( dir ) ) != NULL) { gchar *fullname = g_build_filename((*it).c_str(), filename, NULL); if ( !Inkscape::IO::file_test( fullname, G_FILE_TEST_IS_DIR ) ) { Glib::ustring fn( filename ); Glib::ustring tag = fn.substr( fn.find_last_of(".") + 1 ); SPDocument* symbol_doc = NULL; #ifdef WITH_LIBVISIO if( tag.compare( "vss" ) == 0 ) { symbol_doc = read_vss( fullname, filename ); if( symbol_doc ) { symbolSets[Glib::ustring(filename)]= symbol_doc; symbolSet->append(filename); } } #endif // Try to read all remaining files as SVG if( !symbol_doc ) { symbol_doc = SPDocument::createNewDoc( fullname, FALSE ); if( symbol_doc ) { symbolSets[Glib::ustring(filename)]= symbol_doc; symbolSet->append(filename); } } } g_free( fullname ); } g_dir_close( dir ); } } } GSList* SymbolsDialog::symbols_in_doc_recursive (SPObject *r, GSList *l) { // Stop multiple counting of same symbol if( SP_IS_USE(r) ) { return l; } if( SP_IS_SYMBOL(r) ) { l = g_slist_prepend (l, r); } for (SPObject *child = r->firstChild(); child; child = child->getNext()) { l = symbols_in_doc_recursive( child, l ); } return l; } GSList* SymbolsDialog::symbols_in_doc( SPDocument* symbolDocument ) { GSList *l = NULL; l = symbols_in_doc_recursive (symbolDocument->getRoot(), l ); return l; } GSList* SymbolsDialog::use_in_doc_recursive (SPObject *r, GSList *l) { if( SP_IS_USE(r) ) { l = g_slist_prepend (l, r); } for (SPObject *child = r->firstChild(); child; child = child->getNext()) { l = use_in_doc_recursive( child, l ); } return l; } GSList* SymbolsDialog::use_in_doc( SPDocument* useDocument ) { GSList *l = NULL; l = use_in_doc_recursive (useDocument->getRoot(), l ); return l; } // Returns style from first element found that references id. // This is a last ditch effort to find a style. gchar const* SymbolsDialog::style_from_use( gchar const* id, SPDocument* document) { gchar const* style = 0; GSList* l = use_in_doc( document ); for( ; l != NULL; l = l->next ) { SPObject* use = SP_OBJECT(l->data); if( SP_IS_USE( use ) ) { gchar const *href = use->getRepr()->attribute("xlink:href"); if( href ) { Glib::ustring href2(href); Glib::ustring id2(id); id2 = "#" + id2; if( !href2.compare(id2) ) { style = use->getRepr()->attribute("style"); break; } } } } return style; } void SymbolsDialog::draw_symbols( SPDocument* symbolDocument ) { SymbolColumns* columns = getColumns(); GSList* l = symbols_in_doc( symbolDocument ); for( ; l != NULL; l = l->next ) { SPObject* symbol = SP_OBJECT(l->data); if (!SP_IS_SYMBOL(symbol)) { //std::cout << " Error: not symbol" << std::endl; continue; } gchar const *id = symbol->getRepr()->attribute("id"); gchar const *title = symbol->title(); // From title element if( !title ) { title = id; } Glib::RefPtr pixbuf = create_symbol_image(id, symbolDocument, &renderDrawing, key ); if( pixbuf ) { Gtk::ListStore::iterator row = store->append(); (*row)[columns->symbol_id] = Glib::ustring( id ); (*row)[columns->symbol_title] = Glib::ustring( title ); (*row)[columns->symbol_image] = pixbuf; } } delete columns; } /* * Returns image of symbol. * * Symbols normally are not visible. They must be referenced by a * element. A temporary document is created with a dummy * element and a element that references the symbol * element. Each real symbol is swapped in for the dummy symbol and * the temporary document is rendered. */ Glib::RefPtr SymbolsDialog::create_symbol_image(gchar const *symbol_id, SPDocument *source, Inkscape::Drawing* drawing, unsigned /*visionkey*/) { // Retrieve the symbol named 'symbol_id' from the source SVG document SPObject const* symbol = source->getObjectById(symbol_id); if (symbol == NULL) { //std::cout << " Failed to find symbol: " << symbol_id << std::endl; //return 0; } // Create a copy repr of the symbol with id="the_symbol" Inkscape::XML::Document *xml_doc = previewDocument->getReprDoc(); Inkscape::XML::Node *repr = symbol->getRepr()->duplicate(xml_doc); repr->setAttribute("id", "the_symbol"); // Replace old "the_symbol" in previewDocument by new. Inkscape::XML::Node *root = previewDocument->getReprRoot(); SPObject *symbol_old = previewDocument->getObjectById("the_symbol"); if (symbol_old) { symbol_old->deleteObject(false); } // First look for default style stored in gchar const* style = repr->attribute("inkscape:symbol-style"); if( !style ) { // If no default style in , look in documents. if( source == currentDocument ) { style = style_from_use( symbol_id, source ); } else { style = source->getReprRoot()->attribute("style"); } } // Last ditch effort to provide some default styling if( !style ) style = "fill:#bbbbbb;stroke:#808080"; // This is for display in Symbols dialog only if( style ) { repr->setAttribute( "style", style ); } // BUG: Symbols don't work if defined outside of . Causes Inkscape // crash when trying to read in such a file. root->appendChild(repr); //defsrepr->appendChild(repr); Inkscape::GC::release(repr); // Uncomment this to get the previewDocument documents saved (useful for debugging) // FILE *fp = fopen (g_strconcat(symbol_id, ".svg", NULL), "w"); // sp_repr_save_stream(previewDocument->getReprDoc(), fp); // fclose (fp); // Make sure previewDocument is up-to-date. previewDocument->getRoot()->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); previewDocument->ensureUpToDate(); // Make sure we have symbol in previewDocument SPObject *object_temp = previewDocument->getObjectById( "the_use" ); previewDocument->getRoot()->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); previewDocument->ensureUpToDate(); // if( object_temp == NULL || !SP_IS_ITEM(object_temp) ) { // //std::cout << " previewDocument broken?" << std::endl; // //return 0; // } SPItem *item = SP_ITEM(object_temp); Glib::ustring previewSizeString = previewSize->get_active_text(); unsigned psize = atol( previewSizeString.c_str() ); Glib::ustring previewScaleString = previewScale->get_active_text(); int previewScaleRow = previewScale->get_active_row_number(); /* Update to renderable state */ Glib::ustring key = svg_preview_cache.cache_key(previewDocument->getURI(), symbol_id, psize); //std::cout << " Key: " << key << std::endl; // FIX ME //Glib::RefPtr pixbuf = Glib::wrap(svg_preview_cache.get_preview_from_cache(key)); Glib::RefPtr pixbuf = Glib::RefPtr(0); // Find object's bbox in document. // Note symbols can have own viewport... ignore for now. //Geom::OptRect dbox = item->geometricBounds(); Geom::OptRect dbox = item->documentVisualBounds(); if (!dbox) { //std::cout << " No dbox" << std::endl; return pixbuf; } if (!pixbuf) { /* Scale symbols to fit */ double scale = 1.0; double width = dbox->width(); double height = dbox->height(); if( width == 0.0 ) { width = 1.0; } if( height == 0.0 ) { height = 1.0; } switch (previewScaleRow) { case 0: /* Fit */ scale = psize/std::max(width,height); break; case 1: /* Fit width */ scale = psize/width; break; case 2: /* Fit height */ scale = psize/height; break; default: scale = atof( previewScaleString.c_str() ); } pixbuf = Glib::wrap(render_pixbuf(*drawing, scale, *dbox, psize)); svg_preview_cache.set_preview_in_cache(key, pixbuf->gobj()); } return pixbuf; } /* * Return empty doc to render symbols in. * Symbols are by default not rendered so a element is * provided. */ SPDocument* SymbolsDialog::symbols_preview_doc() { // BUG: must be inside gchar const *buffer = "" " " " " " " " " ""; return SPDocument::createNewDocFromMem( buffer, strlen(buffer), FALSE ); } void SymbolsDialog::setTargetDesktop(SPDesktop *desktop) { if (this->currentDesktop != desktop) { this->currentDesktop = desktop; if( !symbolSets[symbolSet->get_active_text()] ) { // Symbol set is from Current document, update rebuild(); } } } } //namespace Dialogs } //namespace UI } //namespace Inkscape