// SPDX-License-Identifier: GPL-2.0-or-later /** * @file * Symbols dialog. */ /* Authors: * Copyright (C) 2012 Tavmjong Bah * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #ifdef HAVE_CONFIG_H # include "config.h" // only include where actually required! #endif #include "symbols.h" #include #include #include #include #include #include #include #include #include #include #include "document.h" #include "inkscape.h" #include "path-prefix.h" #include "selection.h" #include "display/cairo-utils.h" #include "include/gtkmm_version.h" #include "io/resource.h" #include "io/sys.h" #include "object/sp-defs.h" #include "object/sp-root.h" #include "object/sp-symbol.h" #include "object/sp-use.h" #include "ui/cache/svg_preview_cache.h" #include "ui/clipboard.h" #include "ui/icon-loader.h" #include "ui/icon-names.h" #include "ui/widget/scrollprotected.h" #ifdef WITH_LIBVISIO #include #include using librevenge::RVNGFileStream; using librevenge::RVNGString; using librevenge::RVNGStringVector; using librevenge::RVNGPropertyList; using librevenge::RVNGSVGDrawingGenerator; #endif namespace Inkscape { namespace UI { 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 symbol_doc_title; Gtk::TreeModelColumn< Glib::RefPtr > symbol_image; SymbolColumns() { add(symbol_id); add(symbol_title); add(symbol_doc_title); add(symbol_image); } }; SymbolColumns* SymbolsDialog::getColumns() { SymbolColumns* columns = new SymbolColumns(); return columns; } /** * Constructor */ SymbolsDialog::SymbolsDialog(gchar const *prefsPath) : DialogBase(prefsPath, "Symbols") , store(Gtk::ListStore::create(*getColumns())) , all_docs_processed(false) , icon_view(nullptr) , preview_document(nullptr) , gtk_connections() , CURRENTDOC(_("Current document")) , ALLDOCS(_("All symbol sets")) { /******************** Table *************************/ auto table = new Gtk::Grid(); table->set_margin_start(3); table->set_margin_end(3); table->set_margin_top(4); // panel is a locked Gtk::VBox pack_start(*Gtk::manage(table), Gtk::PACK_EXPAND_WIDGET); guint row = 0; /******************** Symbol Sets *************************/ Gtk::Label* label_set = new Gtk::Label(Glib::ustring(_("Symbol set")) + ": "); table->attach(*Gtk::manage(label_set),0,row,1,1); symbol_set = new Inkscape::UI::Widget::ScrollProtected(); // Fill in later symbol_set->append(CURRENTDOC); symbol_set->append(ALLDOCS); symbol_set->set_active_text(CURRENTDOC); symbol_set->set_hexpand(); gtk_connections.emplace_back( symbol_set->signal_changed().connect(sigc::mem_fun(*this, &SymbolsDialog::rebuild))); table->attach(*Gtk::manage(symbol_set),1,row,1,1); ++row; /******************** Separator *************************/ Gtk::Separator* separator = Gtk::manage(new Gtk::Separator()); // Search separator->set_margin_top(10); separator->set_margin_bottom(10); table->attach(*Gtk::manage(separator),0,row,2,1); ++row; /******************** Search *************************/ search = Gtk::manage(new Gtk::SearchEntry()); // Search search->set_tooltip_text(_("Press 'Return' to start search.")); search->signal_key_press_event().connect_notify( sigc::mem_fun(*this, &SymbolsDialog::beforeSearch)); search->signal_key_release_event().connect_notify(sigc::mem_fun(*this, &SymbolsDialog::unsensitive)); search->set_margin_bottom(6); search->signal_search_changed().connect(sigc::mem_fun(*this, &SymbolsDialog::clearSearch)); table->attach(*Gtk::manage(search),0,row,2,1); search_str = ""; ++row; /********************* Icon View **************************/ SymbolColumns* columns = getColumns(); icon_view = new Gtk::IconView(static_cast >(store)); //icon_view->set_text_column( columns->symbol_id ); icon_view->set_tooltip_column( 1 ); icon_view->set_pixbuf_column( columns->symbol_image ); // Giving the iconview a small minimum size will help users understand // What the dialog does. icon_view->set_size_request( 100, 250 ); icon_view->set_vexpand(true); std::vector< Gtk::TargetEntry > targets; targets.emplace_back( "application/x-inkscape-paste"); icon_view->enable_model_drag_source (targets, Gdk::BUTTON1_MASK, Gdk::ACTION_COPY); gtk_connections.emplace_back( icon_view->signal_drag_data_get().connect(sigc::mem_fun(*this, &SymbolsDialog::iconDragDataGet))); gtk_connections.emplace_back( icon_view->signal_selection_changed().connect(sigc::mem_fun(*this, &SymbolsDialog::iconChanged))); scroller = new Gtk::ScrolledWindow(); scroller->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); scroller->add(*Gtk::manage(icon_view)); scroller->set_hexpand(); scroller->set_vexpand(); scroller->set_overlay_scrolling(false); // here we fix scoller to allow pass the scroll to parent scroll when reach upper or lower limit // this must be added to al scrolleing window in dialogs. We dont do auto because dialogs can be recreated // in the dialog code so think is safer call inside fix_inner_scroll(scroller); overlay = new Gtk::Overlay(); overlay->set_hexpand(); overlay->set_vexpand(); overlay->add(* scroller); overlay->get_style_context()->add_class("forcebright"); scroller->set_size_request(100, -1); table->attach(*Gtk::manage(overlay), 0, row, 2, 1); /*************************Overlays******************************/ overlay_opacity = new Gtk::Image(); overlay_opacity->set_halign(Gtk::ALIGN_START); overlay_opacity->set_valign(Gtk::ALIGN_START); overlay_opacity->get_style_context()->add_class("rawstyle"); overlay_opacity->set_no_show_all(true); // No results overlay_icon = sp_get_icon_image("searching", Gtk::ICON_SIZE_DIALOG); overlay_icon->set_pixel_size(110); overlay_icon->set_halign(Gtk::ALIGN_CENTER); overlay_icon->set_valign(Gtk::ALIGN_START); overlay_icon->set_margin_top(25); overlay_icon->set_no_show_all(true); overlay_title = new Gtk::Label(); overlay_title->set_halign(Gtk::ALIGN_CENTER ); overlay_title->set_valign(Gtk::ALIGN_START ); overlay_title->set_justify(Gtk::JUSTIFY_CENTER); overlay_title->set_margin_top(135); overlay_title->set_no_show_all(true); overlay_desc = new Gtk::Label(); overlay_desc->set_halign(Gtk::ALIGN_CENTER); overlay_desc->set_valign(Gtk::ALIGN_START); overlay_desc->set_margin_top(160); overlay_desc->set_justify(Gtk::JUSTIFY_CENTER); overlay_desc->set_no_show_all(true); overlay->add_overlay(*overlay_opacity); overlay->add_overlay(*overlay_icon); overlay->add_overlay(*overlay_title); overlay->add_overlay(*overlay_desc); previous_height = 0; previous_width = 0; ++row; /******************** Progress *******************************/ progress = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL); progress_bar = Gtk::manage(new Gtk::ProgressBar()); table->attach(*Gtk::manage(progress),0,row, 2, 1); progress->pack_start(* progress_bar, Gtk::PACK_EXPAND_WIDGET); progress->set_margin_top(15); progress->set_margin_bottom(15); progress->set_margin_start(20); progress->set_margin_end(20); ++row; /******************** Tools *******************************/ tools = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL); //tools->set_layout( Gtk::BUTTONBOX_END ); scroller->set_hexpand(); table->attach(*Gtk::manage(tools),0,row,2,1); auto add_symbol_image = Gtk::manage(sp_get_icon_image("symbol-add", Gtk::ICON_SIZE_SMALL_TOOLBAR)); add_symbol = Gtk::manage(new Gtk::Button()); add_symbol->add(*add_symbol_image); add_symbol->set_tooltip_text(_("Add Symbol from the current document.")); add_symbol->set_relief( Gtk::RELIEF_NONE ); add_symbol->set_focus_on_click( false ); add_symbol->signal_clicked().connect(sigc::mem_fun(*this, &SymbolsDialog::insertSymbol)); tools->pack_start(* add_symbol, Gtk::PACK_SHRINK); auto remove_symbolImage = Gtk::manage(sp_get_icon_image("symbol-remove", Gtk::ICON_SIZE_SMALL_TOOLBAR)); remove_symbol = Gtk::manage(new Gtk::Button()); remove_symbol->add(*remove_symbolImage); remove_symbol->set_tooltip_text(_("Remove Symbol from the current document.")); remove_symbol->set_relief( Gtk::RELIEF_NONE ); remove_symbol->set_focus_on_click( false ); remove_symbol->signal_clicked().connect(sigc::mem_fun(*this, &SymbolsDialog::revertSymbol)); tools->pack_start(* remove_symbol, Gtk::PACK_SHRINK); Gtk::Label* spacer = Gtk::manage(new Gtk::Label("")); tools->pack_start(* Gtk::manage(spacer)); // Pack size (controls display area) pack_size = 2; // Default 32px auto packMoreImage = Gtk::manage(sp_get_icon_image("pack-more", Gtk::ICON_SIZE_SMALL_TOOLBAR)); more = Gtk::manage(new Gtk::Button()); more->add(*packMoreImage); more->set_tooltip_text(_("Display more icons in row.")); more->set_relief( Gtk::RELIEF_NONE ); more->set_focus_on_click( false ); more->signal_clicked().connect(sigc::mem_fun(*this, &SymbolsDialog::packmore)); tools->pack_start(* more, Gtk::PACK_SHRINK); auto packLessImage = Gtk::manage(sp_get_icon_image("pack-less", Gtk::ICON_SIZE_SMALL_TOOLBAR)); fewer = Gtk::manage(new Gtk::Button()); fewer->add(*packLessImage); fewer->set_tooltip_text(_("Display fewer icons in row.")); fewer->set_relief( Gtk::RELIEF_NONE ); fewer->set_focus_on_click( false ); fewer->signal_clicked().connect(sigc::mem_fun(*this, &SymbolsDialog::packless)); tools->pack_start(* fewer, Gtk::PACK_SHRINK); // Toggle scale to fit on/off auto fit_symbolImage = Gtk::manage(sp_get_icon_image("symbol-fit", Gtk::ICON_SIZE_SMALL_TOOLBAR)); fit_symbol = Gtk::manage(new Gtk::ToggleButton()); fit_symbol->add(*fit_symbolImage); fit_symbol->set_tooltip_text(_("Toggle 'fit' symbols in icon space.")); fit_symbol->set_relief( Gtk::RELIEF_NONE ); fit_symbol->set_focus_on_click( false ); fit_symbol->set_active( true ); fit_symbol->signal_clicked().connect(sigc::mem_fun(*this, &SymbolsDialog::rebuild)); tools->pack_start(* fit_symbol, Gtk::PACK_SHRINK); // Render size (scales symbols within display area) scale_factor = 0; // Default 1:1 * pack_size/pack_size default auto zoom_outImage = Gtk::manage(sp_get_icon_image("symbol-smaller", Gtk::ICON_SIZE_SMALL_TOOLBAR)); zoom_out = Gtk::manage(new Gtk::Button()); zoom_out->add(*zoom_outImage); zoom_out->set_tooltip_text(_("Make symbols smaller by zooming out.")); zoom_out->set_relief( Gtk::RELIEF_NONE ); zoom_out->set_focus_on_click( false ); zoom_out->set_sensitive( false ); zoom_out->signal_clicked().connect(sigc::mem_fun(*this, &SymbolsDialog::zoomout)); tools->pack_start(* zoom_out, Gtk::PACK_SHRINK); auto zoom_inImage = Gtk::manage(sp_get_icon_image("symbol-bigger", Gtk::ICON_SIZE_SMALL_TOOLBAR)); zoom_in = Gtk::manage(new Gtk::Button()); zoom_in->add(*zoom_inImage); zoom_in->set_tooltip_text(_("Make symbols bigger by zooming in.")); zoom_in->set_relief( Gtk::RELIEF_NONE ); zoom_in->set_focus_on_click( false ); zoom_in->set_sensitive( false ); zoom_in->signal_clicked().connect(sigc::mem_fun(*this, &SymbolsDialog::zoomin)); tools->pack_start(* zoom_in, Gtk::PACK_SHRINK); ++row; sensitive = true; preview_document = symbolsPreviewDoc(); /* Template to render symbols in */ preview_document->ensureUpToDate(); /* Necessary? */ key = SPItem::display_key_new(1); renderDrawing.setRoot(preview_document->getRoot()->invoke_show(renderDrawing, key, SP_ITEM_SHOW_DISPLAY )); getSymbolsTitle(); icons_found = false; } SymbolsDialog::~SymbolsDialog() { for (auto &connection : gtk_connections) { connection.disconnect(); } gtk_connections.clear(); idleconn.disconnect(); Inkscape::GC::release(preview_document); assert(preview_document->_anchored_refcount() == 0); delete preview_document; } SymbolsDialog& SymbolsDialog::getInstance() { return *new SymbolsDialog(); } void SymbolsDialog::packless() { if(pack_size < 4) { pack_size++; rebuild(); } } void SymbolsDialog::packmore() { if(pack_size > 0) { pack_size--; rebuild(); } } void SymbolsDialog::zoomin() { if(scale_factor < 4) { scale_factor++; rebuild(); } } void SymbolsDialog::zoomout() { if(scale_factor > -8) { scale_factor--; rebuild(); } } void SymbolsDialog::rebuild() { if (!sensitive) { return; } if( fit_symbol->get_active() ) { zoom_in->set_sensitive( false ); zoom_out->set_sensitive( false ); } else { zoom_in->set_sensitive( true); zoom_out->set_sensitive( true ); } store->clear(); SPDocument* symbol_document = selectedSymbols(); icons_found = false; //We are not in search all docs if (search->get_text() != _("Searching...") && search->get_text() != _("Loading all symbols...")) { Glib::ustring current = Glib::Markup::escape_text(symbol_set->get_active_text()); if (current == ALLDOCS && search->get_text() != "") { searchsymbols(); return; } } if (symbol_document) { addSymbolsInDoc(symbol_document); } else { showOverlay(); } } void SymbolsDialog::showOverlay() { Glib::ustring current = Glib::Markup::escape_text(symbol_set->get_active_text()); if (current == ALLDOCS && !l.size()) { overlay_icon->hide(); if (!all_docs_processed ) { overlay_icon->show(); overlay_title->set_markup(Glib::ustring("") + Glib::ustring(_("Search in all symbol sets...")) + Glib::ustring("")); overlay_desc->set_markup(Glib::ustring("") + Glib::ustring(_("The first search can be slow.")) + Glib::ustring("")); } else if (!icons_found && !search_str.empty()) { overlay_title->set_markup(Glib::ustring("") + Glib::ustring(_("No symbols found.")) + Glib::ustring("")); overlay_desc->set_markup(Glib::ustring("") + Glib::ustring(_("Try a different search term.")) + Glib::ustring("")); } else { overlay_icon->show(); overlay_title->set_markup(Glib::ustring("") + Glib::ustring(_("Search in all symbol sets...")) + Glib::ustring("")); overlay_desc->set_markup(Glib::ustring("") + Glib::ustring("")); } } else if (!number_symbols && (current != CURRENTDOC || !search_str.empty())) { overlay_title->set_markup(Glib::ustring("") + Glib::ustring(_("No symbols found.")) + Glib::ustring("")); overlay_desc->set_markup(Glib::ustring("") + Glib::ustring(_("Try a different search term,\nor switch to a different symbol set.")) + Glib::ustring("")); } else if (!number_symbols && current == CURRENTDOC) { overlay_title->set_markup(Glib::ustring("") + Glib::ustring(_("No symbols found.")) + Glib::ustring("")); overlay_desc->set_markup( Glib::ustring("") + Glib::ustring(_("No symbols in current document.\nChoose a different symbol set\nor add a new symbol.")) + Glib::ustring("")); } else if (!icons_found && !search_str.empty()) { overlay_title->set_markup(Glib::ustring("") + Glib::ustring(_("No symbols found.")) + Glib::ustring("")); overlay_desc->set_markup(Glib::ustring("") + Glib::ustring(_("Try a different search term,\nor switch to a different symbol set.")) + Glib::ustring("")); } gint width = scroller->get_allocated_width(); gint height = scroller->get_allocated_height(); if (previous_height != height || previous_width != width) { previous_height = height; previous_width = width; overlay_opacity->set_size_request(width, height); overlay_opacity->set(getOverlay(width, height)); } overlay_opacity->hide(); overlay_icon->show(); overlay_title->show(); overlay_desc->show(); if (l.size()) { overlay_opacity->show(); overlay_icon->hide(); overlay_title->hide(); overlay_desc->hide(); } } void SymbolsDialog::hideOverlay() { overlay_opacity->hide(); overlay_icon->hide(); overlay_title->hide(); overlay_desc->hide(); } void SymbolsDialog::insertSymbol() { getDesktop()->selection->toSymbol(); } void SymbolsDialog::revertSymbol() { if (auto document = getDocument()) { if (auto symbol = dynamic_cast (document->getObjectById(selectedSymbolId()))) { symbol->unSymbol(); } Inkscape::DocumentUndo::done(document, _("Group from symbol"), ""); } } void SymbolsDialog::iconDragDataGet(const Glib::RefPtr& /*context*/, Gtk::SelectionData& data, guint /*info*/, guint /*time*/) { auto iconArray = icon_view->get_selected_items(); 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::defsModified(SPObject * /*object*/, guint /*flags*/) { Glib::ustring doc_title = symbol_set->get_active_text(); if (doc_title != ALLDOCS && !symbol_sets[doc_title] ) { rebuild(); } } void SymbolsDialog::selectionChanged(Inkscape::Selection *selection) { Glib::ustring symbol_id = selectedSymbolId(); Glib::ustring doc_title = selectedSymbolDocTitle(); if (!doc_title.empty()) { SPDocument* symbol_document = symbol_sets[doc_title]; if (!symbol_document) { //we are in global search so get the original symbol document by title symbol_document = selectedSymbols(); } if (symbol_document) { SPObject* symbol = symbol_document->getObjectById(symbol_id); if(symbol && !selection->includes(symbol)) { icon_view->unselect_all(); } } } } void SymbolsDialog::documentReplaced() { defs_modified.disconnect(); if (auto document = getDocument()) { defs_modified = document->getDefs()->connectModified(sigc::mem_fun(*this, &SymbolsDialog::defsModified)); if (!symbol_sets[symbol_set->get_active_text()]) { // Symbol set is from Current document, need to rebuild rebuild(); } } } SPDocument* SymbolsDialog::selectedSymbols() { /* OK, we know symbol name... now we need to copy it to clipboard, bon chance! */ Glib::ustring doc_title = symbol_set->get_active_text(); if (doc_title == ALLDOCS) { return nullptr; } SPDocument* symbol_document = symbol_sets[doc_title]; if( !symbol_document ) { symbol_document = getSymbolsSet(doc_title).second; // Symbol must be from Current Document (this method of checking should be language independent). if( !symbol_document ) { // Symbol must be from Current Document (this method of // checking should be language independent). symbol_document = getDocument(); add_symbol->set_sensitive( true ); remove_symbol->set_sensitive( true ); } else { add_symbol->set_sensitive( false ); remove_symbol->set_sensitive( false ); } } return symbol_document; } Glib::ustring SymbolsDialog::selectedSymbolId() { auto iconArray = icon_view->get_selected_items(); if( !iconArray.empty() ) { Gtk::TreeModel::Path const & path = *iconArray.begin(); Gtk::ListStore::iterator row = store->get_iter(path); return (*row)[getColumns()->symbol_id]; } return Glib::ustring(""); } Glib::ustring SymbolsDialog::selectedSymbolDocTitle() { auto iconArray = icon_view->get_selected_items(); if( !iconArray.empty() ) { Gtk::TreeModel::Path const & path = *iconArray.begin(); Gtk::ListStore::iterator row = store->get_iter(path); return (*row)[getColumns()->symbol_doc_title]; } return Glib::ustring(""); } Glib::ustring SymbolsDialog::documentTitle(SPDocument* symbol_doc) { if (symbol_doc) { SPRoot * root = symbol_doc->getRoot(); gchar * title = root->title(); if (title) { return ellipsize(Glib::ustring(title), 33); } g_free(title); } Glib::ustring current = symbol_set->get_active_text(); if (current == CURRENTDOC) { return current; } return _("Untitled document"); } void SymbolsDialog::iconChanged() { Glib::ustring symbol_id = selectedSymbolId(); SPDocument* symbol_document = selectedSymbols(); if (!symbol_document) { //we are in global search so get the original symbol document by title Glib::ustring doc_title = selectedSymbolDocTitle(); if (!doc_title.empty()) { symbol_document = symbol_sets[doc_title]; } } if (symbol_document) { SPObject* symbol = symbol_document->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(symbol_document == getDocument()) { style = styleFromUse(symbol_id.c_str(), symbol_document); } else { style = symbol_document->getReprRoot()->attribute("style"); } } ClipboardManager *cm = ClipboardManager::get(); cm->copySymbol(symbol->getRepr(), style, symbol_document); } } } #ifdef WITH_LIBVISIO // Extend libvisio's native RVNGSVGDrawingGenerator with support for extracting stencil names (to be used as ID/title) class REVENGE_API RVNGSVGDrawingGenerator_WithTitle : public RVNGSVGDrawingGenerator { public: RVNGSVGDrawingGenerator_WithTitle(RVNGStringVector &output, RVNGStringVector &titles, const RVNGString &nmSpace) : RVNGSVGDrawingGenerator(output, nmSpace) , _titles(titles) {} void startPage(const RVNGPropertyList &propList) override { RVNGSVGDrawingGenerator::startPage(propList); if (propList["draw:name"]) { _titles.append(propList["draw:name"]->getStr()); } else { _titles.append(""); } } private: RVNGStringVector &_titles; }; // Read Visio stencil files SPDocument* read_vss(Glib::ustring filename, Glib::ustring name ) { gchar *fullname; #ifdef _WIN32 // RVNGFileStream uses fopen() internally which unfortunately only uses ANSI encoding on Windows // therefore attempt to convert uri to the system codepage // even if this is not possible the alternate short (8.3) file name will be used if available fullname = g_win32_locale_filename_from_utf8(filename.c_str()); #else fullname = strdup(filename.c_str()); #endif RVNGFileStream input(fullname); g_free(fullname); if (!libvisio::VisioDocument::isSupported(&input)) { return nullptr; } RVNGStringVector output; RVNGStringVector titles; RVNGSVGDrawingGenerator_WithTitle generator(output, titles, "svg"); if (!libvisio::VisioDocument::parseStencils(&input, &generator)) { return nullptr; } if (output.empty()) { return nullptr; } // prepare a valid title for the symbol file Glib::ustring title = Glib::Markup::escape_text(name); // prepare a valid id prefix for symbols libvisio doesn't give us a name for Glib::RefPtr regex1 = Glib::Regex::create("[^a-zA-Z0-9_-]"); Glib::ustring id = regex1->replace(name, 0, "_", Glib::REGEX_MATCH_PARTIAL); Glib::ustring tmpSVGOutput; tmpSVGOutput += "\n"; tmpSVGOutput += "\n"; tmpSVGOutput += " "; tmpSVGOutput += title; tmpSVGOutput += "\n"; tmpSVGOutput += " \n"; // Each "symbol" is in its own SVG file, we wrap with and merge into one file. for (unsigned i=0; ireplace(titles[i].cstr(), 0, "_", Glib::REGEX_MATCH_PARTIAL); } else { ss << id << "_" << i; } tmpSVGOutput += " \n"; if (titles.size() == output.size() && titles[i] != "") { tmpSVGOutput += " " + Glib::ustring(RVNGString::escapeXML(titles[i].cstr()).cstr()) + "\n"; } std::istringstream iss( output[i].cstr() ); std::string line; while( std::getline( iss, line ) ) { if( line.find( "svg:svg" ) == std::string::npos ) { tmpSVGOutput += " " + line + "\n"; } } tmpSVGOutput += " \n"; } tmpSVGOutput += " \n"; tmpSVGOutput += "\n"; return SPDocument::createNewDocFromMem( tmpSVGOutput.c_str(), strlen( tmpSVGOutput.c_str()), false ); } #endif /* Hunts preference directories for symbol files */ void SymbolsDialog::getSymbolsTitle() { using namespace Inkscape::IO::Resource; Glib::ustring title; number_docs = 0; std::regex matchtitle (".*?(.*?)<(/| /)"); for(auto &filename: get_filenames(SYMBOLS, {".svg", ".vss"})) { if(Glib::str_has_suffix(filename, ".vss")) { std::size_t found = filename.find_last_of("/\\"); filename = filename.substr(found+1); title = filename.erase(filename.rfind('.')); if(title.empty()) { title = _("Unnamed Symbols"); } symbol_sets[title]= nullptr; ++number_docs; } else { std::ifstream infile(filename); std::string line; while (std::getline(infile, line)) { std::string title_res = std::regex_replace (line, matchtitle,"$1",std::regex_constants::format_no_copy); if (!title_res.empty()) { title_res = g_dpgettext2(nullptr, "Symbol", title_res.c_str()); symbol_sets[ellipsize(Glib::ustring(title_res), 33)]= nullptr; ++number_docs; break; } std::string::size_type position_exit = line.find ("append(symbol_document_map.first); } } /* Hunts preference directories for symbol files */ std::pair SymbolsDialog::getSymbolsSet(Glib::ustring title) { SPDocument* symbol_doc = nullptr; Glib::ustring current = symbol_set->get_active_text(); if (current == CURRENTDOC) { return std::make_pair(CURRENTDOC, symbol_doc); } if (symbol_sets[title]) { sensitive = false; symbol_set->remove_all(); symbol_set->append(CURRENTDOC); symbol_set->append(ALLDOCS); for(auto const &symbol_document_map : symbol_sets) { if (CURRENTDOC != symbol_document_map.first) { symbol_set->append(symbol_document_map.first); } } symbol_set->set_active_text(title); sensitive = true; return std::make_pair(title, symbol_sets[title]); } using namespace Inkscape::IO::Resource; Glib::ustring new_title; std::regex matchtitle (".*?(.*?)<(/| /)"); for(auto &filename: get_filenames(SYMBOLS, {".svg", ".vss"})) { if(Glib::str_has_suffix(filename, ".vss")) { #ifdef WITH_LIBVISIO std::size_t pos = filename.find_last_of("/\\"); Glib::ustring filename_short = ""; if (pos != std::string::npos) { filename_short = filename.substr(pos+1); } if (filename_short == title + ".vss") { new_title = title; symbol_doc = read_vss(Glib::ustring(filename), title); } #endif } else { std::ifstream infile(filename); std::string line; while (std::getline(infile, line)) { std::string title_res = std::regex_replace (line, matchtitle,"$1",std::regex_constants::format_no_copy); if (!title_res.empty()) { title_res = g_dpgettext2(nullptr, "Symbol", title_res.c_str()); new_title = ellipsize(Glib::ustring(title_res), 33); } std::size_t pos = filename.find_last_of("/\\"); Glib::ustring filename_short = ""; if (pos != std::string::npos) { filename_short = filename.substr(pos+1); } if (title == new_title || filename_short == title + ".svg") { new_title = title; if(Glib::str_has_suffix(filename, ".svg")) { symbol_doc = SPDocument::createNewDoc(filename.c_str(), FALSE); } } if (symbol_doc) { break; } std::string::size_type position_exit = line.find ("remove_all(); symbol_set->append(CURRENTDOC); symbol_set->append(ALLDOCS); for(auto const &symbol_document_map : symbol_sets) { if (CURRENTDOC != symbol_document_map.first) { symbol_set->append(symbol_document_map.first); } } symbol_set->set_active_text(new_title); sensitive = true; } return std::make_pair(new_title, symbol_doc); } void SymbolsDialog::symbolsInDocRecursive (SPObject *r, std::map > &l, Glib::ustring doc_title) { if(!r) return; // Stop multiple counting of same symbol if ( dynamic_cast(r) ) { return; } if ( dynamic_cast(r)) { Glib::ustring id = r->getAttribute("id"); gchar * title = r->title(); if(title) { l[doc_title + title + id] = std::make_pair(doc_title,dynamic_cast(r)); } else { l[Glib::ustring(_("notitle_")) + id] = std::make_pair(doc_title,dynamic_cast(r)); } g_free(title); } for (auto& child: r->children) { symbolsInDocRecursive(&child, l, doc_title); } } std::map > SymbolsDialog::symbolsInDoc( SPDocument* symbol_document, Glib::ustring doc_title) { std::map > l; if (symbol_document) { symbolsInDocRecursive (symbol_document->getRoot(), l , doc_title); } return l; } void SymbolsDialog::useInDoc (SPObject *r, std::vector &l) { if ( dynamic_cast(r) ) { l.push_back(dynamic_cast(r)); } for (auto& child: r->children) { useInDoc( &child, l ); } } std::vector SymbolsDialog::useInDoc( SPDocument* useDocument) { std::vector l; useInDoc (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::styleFromUse( gchar const* id, SPDocument* document) { gchar const* style = nullptr; std::vector l = useInDoc( document ); for( auto use:l ) { if ( 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::clearSearch() { if(search->get_text().empty() && sensitive) { enableWidgets(false); search_str = ""; store->clear(); SPDocument* symbol_document = selectedSymbols(); if (symbol_document) { //We are not in search all docs icons_found = false; addSymbolsInDoc(symbol_document); } else { showOverlay(); enableWidgets(true); } } } void SymbolsDialog::enableWidgets(bool enable) { symbol_set->set_sensitive(enable); search->set_sensitive(enable); tools ->set_sensitive(enable); } void SymbolsDialog::beforeSearch(GdkEventKey* evt) { sensitive = false; search_str = search->get_text().lowercase(); if (evt->keyval != GDK_KEY_Return) { return; } searchsymbols(); } void SymbolsDialog::searchsymbols() { progress_bar->set_fraction(0.0); enableWidgets(false); SPDocument *symbol_document = selectedSymbols(); if (symbol_document) { // We are not in search all docs search->set_text(_("Searching...")); store->clear(); icons_found = false; addSymbolsInDoc(symbol_document); } else { idleconn.disconnect(); idleconn = Glib::signal_idle().connect(sigc::mem_fun(*this, &SymbolsDialog::callbackAllSymbols)); search->set_text(_("Loading all symbols...")); } } void SymbolsDialog::unsensitive(GdkEventKey* evt) { sensitive = true; } bool SymbolsDialog::callbackSymbols(){ if (l.size()) { showOverlay(); for (auto symbol_data = l.begin(); symbol_data != l.end();) { Glib::ustring doc_title = symbol_data->second.first; SPSymbol * symbol = symbol_data->second.second; counter_symbols ++; gchar *symbol_title_char = symbol->title(); gchar *symbol_desc_char = symbol->description(); bool found = false; if (symbol_title_char) { Glib::ustring symbol_title = Glib::ustring(symbol_title_char).lowercase(); auto pos = symbol_title.rfind(search_str); auto pos_translated = Glib::ustring(g_dpgettext2(nullptr, "Symbol", symbol_title_char)).lowercase().rfind(search_str); if ((pos != std::string::npos) || (pos_translated != std::string::npos)) { found = true; } if (!found && symbol_desc_char) { Glib::ustring symbol_desc = Glib::ustring(symbol_desc_char).lowercase(); auto pos = symbol_desc.rfind(search_str); auto pos_translated = Glib::ustring(g_dpgettext2(nullptr, "Symbol", symbol_desc_char)).lowercase().rfind(search_str); if ((pos != std::string::npos) || (pos_translated != std::string::npos)) { found = true; } } } if (symbol && (search_str.empty() || found)) { addSymbol( symbol, doc_title); icons_found = true; } progress_bar->set_fraction(((100.0/number_symbols) * counter_symbols)/100.0); symbol_data = l.erase(l.begin()); //to get more items and best performance int modulus = number_symbols > 200 ? 50 : (number_symbols/4); g_free(symbol_title_char); g_free(symbol_desc_char); if (modulus && counter_symbols % modulus == 0 && !l.empty()) { return true; } } if (!icons_found && !search_str.empty()) { showOverlay(); } else { hideOverlay(); } progress_bar->set_fraction(0); sensitive = false; search->set_text(search_str); sensitive = true; enableWidgets(true); return false; } return true; } bool SymbolsDialog::callbackAllSymbols(){ Glib::ustring current = symbol_set->get_active_text(); if (current == ALLDOCS && search->get_text() == _("Loading all symbols...")) { size_t counter = 0; std::map symbol_sets_tmp = symbol_sets; for(auto const &symbol_document_map : symbol_sets_tmp) { ++counter; SPDocument* symbol_document = symbol_document_map.second; if (symbol_document) { continue; } symbol_document = getSymbolsSet(symbol_document_map.first).second; symbol_set->set_active_text(ALLDOCS); if (!symbol_document) { continue; } progress_bar->set_fraction(((100.0/number_docs) * counter)/100.0); return true; } symbol_sets_tmp.clear(); hideOverlay(); all_docs_processed = true; addSymbols(); progress_bar->set_fraction(0); search->set_text("Searching..."); return false; } return true; } Glib::ustring SymbolsDialog::ellipsize(Glib::ustring data, size_t limit) { if (data.length() > limit) { data = data.substr(0, limit-3); return data + "..."; } return data; } void SymbolsDialog::addSymbolsInDoc(SPDocument* symbol_document) { if (!symbol_document) { return; //Search all } Glib::ustring doc_title = documentTitle(symbol_document); progress_bar->set_fraction(0.0); counter_symbols = 0; l = symbolsInDoc(symbol_document, doc_title); number_symbols = l.size(); if (!number_symbols) { sensitive = false; search->set_text(search_str); sensitive = true; enableWidgets(true); idleconn.disconnect(); showOverlay(); } else { idleconn.disconnect(); idleconn = Glib::signal_idle().connect( sigc::mem_fun(*this, &SymbolsDialog::callbackSymbols)); } } void SymbolsDialog::addSymbols() { store->clear(); icons_found = false; for(auto const &symbol_document_map : symbol_sets) { SPDocument* symbol_document = symbol_document_map.second; if (!symbol_document) { continue; } Glib::ustring doc_title = documentTitle(symbol_document); std::map > l_tmp = symbolsInDoc(symbol_document, doc_title); for(auto &p : l_tmp ) { l[p.first] = p.second; } l_tmp.clear(); } counter_symbols = 0; progress_bar->set_fraction(0.0); number_symbols = l.size(); if (!number_symbols) { showOverlay(); idleconn.disconnect(); sensitive = false; search->set_text(search_str); sensitive = true; enableWidgets(true); } else { idleconn.disconnect(); idleconn = Glib::signal_idle().connect( sigc::mem_fun(*this, &SymbolsDialog::callbackSymbols)); } } void SymbolsDialog::addSymbol( SPObject* symbol, Glib::ustring doc_title) { gchar const *id = symbol->getRepr()->attribute("id"); if (doc_title.empty()) { doc_title = CURRENTDOC; } else { doc_title = g_dpgettext2(nullptr, "Symbol", doc_title.c_str()); } Glib::ustring symbol_title; gchar *title = symbol->title(); // From title element if (title) { symbol_title = Glib::ustring::compose("%1 (%2)", g_dpgettext2(nullptr, "Symbol", title), doc_title.c_str()); } else { symbol_title = Glib::ustring::compose("%1 %2 (%3)", _("Symbol without title"), Glib::ustring(id), doc_title); } g_free(title); Glib::RefPtr pixbuf = drawSymbol( symbol ); if( pixbuf ) { Gtk::ListStore::iterator row = store->append(); SymbolColumns* columns = getColumns(); (*row)[columns->symbol_id] = Glib::ustring( id ); (*row)[columns->symbol_title] = Glib::Markup::escape_text(symbol_title); (*row)[columns->symbol_doc_title] = Glib::Markup::escape_text(doc_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::drawSymbol(SPObject *symbol) { // Create a copy repr of the symbol with id="the_symbol" Inkscape::XML::Document *xml_doc = preview_document->getReprDoc(); Inkscape::XML::Node *repr = symbol->getRepr()->duplicate(xml_doc); repr->setAttribute("id", "the_symbol"); // Replace old "the_symbol" in preview_document by new. Inkscape::XML::Node *root = preview_document->getReprRoot(); SPObject *symbol_old = preview_document->getObjectById("the_symbol"); if (symbol_old) { symbol_old->deleteObject(false); } SPDocument::install_reference_document scoped(preview_document, getDocument()); // 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(symbol->document == getDocument()) { gchar const *id = symbol->getRepr()->attribute("id"); style = styleFromUse( id, symbol->document ); } else { style = symbol->document->getReprRoot()->attribute("style"); } } // This is for display in Symbols dialog only if( style ) repr->setAttribute( "style", style ); root->appendChild(repr); Inkscape::GC::release(repr); // Uncomment this to get the preview_document documents saved (useful for debugging) // FILE *fp = fopen (g_strconcat(id, ".svg", NULL), "w"); // sp_repr_save_stream(preview_document->getReprDoc(), fp); // fclose (fp); // Make sure preview_document is up-to-date. preview_document->getRoot()->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); preview_document->ensureUpToDate(); // Make sure we have symbol in preview_document SPObject *object_temp = preview_document->getObjectById( "the_use" ); SPItem *item = dynamic_cast(object_temp); g_assert(item != nullptr); unsigned psize = SYMBOL_ICON_SIZES[pack_size]; Glib::RefPtr pixbuf(nullptr); // We could use cache here, but it doesn't really work with the structure // of this user interface and we've already cached the pixbuf in the gtklist // 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) { /* 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; if( fit_symbol->get_active() ) scale = psize / ceil(std::max(width, height)); else scale = pow( 2.0, scale_factor/2.0 ) * psize / 32.0; pixbuf = Glib::wrap(render_pixbuf(renderDrawing, scale, *dbox, psize)); } return pixbuf; } /* * Return empty doc to render symbols in. * Symbols are by default not rendered so a element is * provided. */ SPDocument* SymbolsDialog::symbolsPreviewDoc() { // BUG: must be inside gchar const *buffer = "" " " " " " " " " ""; return SPDocument::createNewDocFromMem( buffer, strlen(buffer), FALSE ); } /* * Update image widgets */ Glib::RefPtr SymbolsDialog::getOverlay(gint width, gint height) { cairo_surface_t *surface; cairo_t *cr; surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); cr = cairo_create (surface); cairo_set_source_rgba(cr, 1, 1, 1, 0.75); cairo_rectangle (cr, 0, 0, width, height); cairo_fill (cr); GdkPixbuf* pixbuf = ink_pixbuf_create_from_cairo_surface(surface); cairo_destroy (cr); return Glib::wrap(pixbuf); } } //namespace Dialogs } //namespace UI } //namespace Inkscape /* Local Variables: mode:c++ c-file-style:"stroustrup" c-basic-offset:2 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=2:tabstop=8:softtabstop=2:fileencoding=utf-8:textwidth=99 :