diff options
Diffstat (limited to 'src/extension/system.cpp')
-rw-r--r-- | src/extension/system.cpp | 710 |
1 files changed, 710 insertions, 0 deletions
diff --git a/src/extension/system.cpp b/src/extension/system.cpp new file mode 100644 index 0000000..f4ba2dc --- /dev/null +++ b/src/extension/system.cpp @@ -0,0 +1,710 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is file is kind of the junk file. Basically everything that + * didn't fit in one of the other well defined areas, well, it's now + * here. Which is good in someways, but this file really needs some + * definition. Hopefully that will come ASAP. + * + * Authors: + * Ted Gould <ted@gould.cx> + * Johan Engelen <johan@shouraizou.nl> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 2006-2007 Johan Engelen + * Copyright (C) 2002-2004 Ted Gould + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "ui/interface.h" + +#include "system.h" +#include "preferences.h" +#include "extension.h" +#include "db.h" +#include "input.h" +#include "output.h" +#include "effect.h" +#include "patheffect.h" +#include "print.h" +#include "implementation/script.h" +#include "implementation/xslt.h" +#include "xml/rebase-hrefs.h" +#include "io/sys.h" +#include "inkscape.h" +#include "document-undo.h" +#include "loader.h" + +#include <glibmm/miscutils.h> + +namespace Inkscape { +namespace Extension { + +static void open_internal(Inkscape::Extension::Extension *in_plug, gpointer in_data); +static void save_internal(Inkscape::Extension::Extension *in_plug, gpointer in_data); + +/** + * \return A new document created from the filename passed in + * \brief This is a generic function to use the open function of + * a module (including Autodetect) + * \param key Identifier of which module to use + * \param filename The file that should be opened + * + * First things first, are we looking at an autodetection? Well if that's the case then the module + * needs to be found, and that is done with a database lookup through the module DB. The foreach + * function is called, with the parameter being a gpointer array. It contains both the filename + * (to find its extension) and where to write the module when it is found. + * + * If there is no autodetection, then the module database is queried with the key given. + * + * If everything is cool at this point, the module is loaded, and there is possibility for + * preferences. If there is a function, then it is executed to get the dialog to be displayed. + * After it is finished the function continues. + * + * Lastly, the open function is called in the module itself. + */ +SPDocument *open(Extension *key, gchar const *filename) +{ + Input *imod = nullptr; + + if (key == nullptr) { + gpointer parray[2]; + parray[0] = (gpointer)filename; + parray[1] = (gpointer)&imod; + db.foreach(open_internal, (gpointer)&parray); + } else { + imod = dynamic_cast<Input *>(key); + } + + bool last_chance_svg = false; + if (key == nullptr && imod == nullptr) { + last_chance_svg = true; + imod = dynamic_cast<Input *>(db.get(SP_MODULE_KEY_INPUT_SVG)); + } + + if (imod == nullptr) { + throw Input::no_extension_found(); + } + + // Hide pixbuf extensions depending on user preferences. + //g_warning("Extension: %s", imod->get_id()); + + bool show = true; + if (strlen(imod->get_id()) > 21) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool ask = prefs->getBool("/dialogs/import/ask"); + bool ask_svg = prefs->getBool("/dialogs/import/ask_svg"); + Glib::ustring id = Glib::ustring(imod->get_id(), 22); + if (id.compare("org.inkscape.input.svg") == 0) { + if (ask_svg && prefs->getBool("/options/onimport", false)) { + show = true; + imod->set_gui(true); + } else { + show = false; + imod->set_gui(false); + } + } else if(strlen(imod->get_id()) > 27) { + id = Glib::ustring(imod->get_id(), 28); + if (!ask && id.compare( "org.inkscape.input.gdkpixbuf") == 0) { + show = false; + imod->set_gui(false); + } + } + } + imod->set_state(Extension::STATE_LOADED); + + if (!imod->loaded()) { + throw Input::open_failed(); + } + + if (!imod->prefs(filename)) { + throw Input::open_cancelled(); + } + + SPDocument *doc = imod->open(filename); + + if (!doc) { + if (last_chance_svg) { + if ( INKSCAPE.use_gui() ) { + sp_ui_error_dialog(_("Could not detect file format. Tried to open it as an SVG anyway but this also failed.")); + } else { + g_warning("%s", _("Could not detect file format. Tried to open it as an SVG anyway but this also failed.")); + } + } + throw Input::open_failed(); + } + // If last_chance_svg is true here, it means we successfully opened a file as an svg + // and there's no need to warn the user about it, just do it. + + doc->setDocumentFilename(filename); + if (!show) { + imod->set_gui(true); + } + + return doc; +} + +/** + * \return none + * \brief This is the function that searches each module to see + * if it matches the filename for autodetection. + * \param in_plug The module to be tested + * \param in_data An array of pointers containing the filename, and + * the place to put a successfully found module. + * + * Basically this function only looks at input modules as it is part of the open function. If the + * module is an input module, it then starts to take it apart, and the data that is passed in. + * Because the data being passed in is in such a weird format, there are a few casts to make it + * easier to use. While it looks like a lot of local variables, they'll all get removed by the + * compiler. + * + * First thing that is checked is if the filename is shorter than the extension itself. There is + * no way for a match in that case. If it's long enough then there is a string compare of the end + * of the filename (for the length of the extension), and the extension itself. If this passes + * then the pointer passed in is set to the current module. + */ +static void +open_internal(Extension *in_plug, gpointer in_data) +{ + auto imod = dynamic_cast<Input *>(in_plug); + if (imod && !imod->deactivated()) { + gpointer *parray = (gpointer *)in_data; + gchar const *filename = (gchar const *)parray[0]; + Input **pimod = (Input **)parray[1]; + + // skip all the rest if we already found a function to open it + // since they're ordered by preference now. + if (!*pimod) { + gchar const *ext = imod->get_extension(); + + gchar *filenamelower = g_utf8_strdown(filename, -1); + gchar *extensionlower = g_utf8_strdown(ext, -1); + + if (g_str_has_suffix(filenamelower, extensionlower)) { + *pimod = imod; + } + + g_free(filenamelower); + g_free(extensionlower); + } + } + + return; +} + +/** + * \return None + * \brief This is a generic function to use the save function of + * a module (including Autodetect) + * \param key Identifier of which module to use + * \param doc The document to be saved + * \param filename The file that the document should be saved to + * \param official (optional) whether to set :output_module and :modified in the + * document; is true for normal save, false for temporary saves + * + * First things first, are we looking at an autodetection? Well if that's the case then the module + * needs to be found, and that is done with a database lookup through the module DB. The foreach + * function is called, with the parameter being a gpointer array. It contains both the filename + * (to find its extension) and where to write the module when it is found. + * + * If there is no autodetection the module database is queried with the key given. + * + * If everything is cool at this point, the module is loaded, and there is possibility for + * preferences. If there is a function, then it is executed to get the dialog to be displayed. + * After it is finished the function continues. + * + * Lastly, the save function is called in the module itself. + */ +void +save(Extension *key, SPDocument *doc, gchar const *filename, bool check_overwrite, bool official, + Inkscape::Extension::FileSaveMethod save_method) +{ + Output *omod; + if (key == nullptr) { + gpointer parray[2]; + parray[0] = (gpointer)filename; + parray[1] = (gpointer)&omod; + omod = nullptr; + db.foreach(save_internal, (gpointer)&parray); + + /* This is a nasty hack, but it is required to ensure that + autodetect will always save with the Inkscape extensions + if they are available. */ + if (omod != nullptr && !strcmp(omod->get_id(), SP_MODULE_KEY_OUTPUT_SVG)) { + omod = dynamic_cast<Output *>(db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE)); + } + /* If autodetect fails, save as Inkscape SVG */ + if (omod == nullptr) { + // omod = dynamic_cast<Output *>(db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE)); use exception and let user choose + } + } else { + omod = dynamic_cast<Output *>(key); + } + + if (!dynamic_cast<Output *>(omod)) { + g_warning("Unable to find output module to handle file: %s\n", filename); + throw Output::no_extension_found(); + } + + omod->set_state(Extension::STATE_LOADED); + if (!omod->loaded()) { + throw Output::save_failed(); + } + + if (!omod->prefs()) { + throw Output::save_cancelled(); + } + + gchar *fileName = g_strdup(filename); + + if (check_overwrite && !sp_ui_overwrite_file(fileName)) { + g_free(fileName); + throw Output::no_overwrite(); + } + + // test if the file exists and is writable + // the test only checks the file attributes and might pass where ACL does not allow writes + if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS) && !Inkscape::IO::file_is_writable(filename)) { + g_free(fileName); + throw Output::file_read_only(); + } + + Inkscape::XML::Node *repr = doc->getReprRoot(); + + + // remember attributes in case this is an unofficial save and/or overwrite fails + gchar *saved_filename = g_strdup(doc->getDocumentFilename()); + gchar *saved_output_extension = nullptr; + gchar *saved_dataloss = nullptr; + bool saved_modified = doc->isModifiedSinceSave(); + saved_output_extension = g_strdup(get_file_save_extension(save_method).c_str()); + saved_dataloss = g_strdup(repr->attribute("inkscape:dataloss")); + if (official) { + // The document is changing name/uri. + doc->changeFilenameAndHrefs(fileName); + } + + // Update attributes: + { + { + DocumentUndo::ScopedInsensitive _no_undo(doc); + // also save the extension for next use + store_file_extension_in_prefs (omod->get_id(), save_method); + // set the "dataloss" attribute if the chosen extension is lossy + repr->removeAttribute("inkscape:dataloss"); + if (omod->causes_dataloss()) { + repr->setAttribute("inkscape:dataloss", "true"); + } + } + doc->setModifiedSinceSave(false); + } + + try { + omod->save(doc, fileName); + } + catch(...) { + // revert attributes in case of official and overwrite + if(check_overwrite && official) { + { + DocumentUndo::ScopedInsensitive _no_undo(doc); + store_file_extension_in_prefs (saved_output_extension, save_method); + repr->setAttribute("inkscape:dataloss", saved_dataloss); + } + doc->changeFilenameAndHrefs(saved_filename); + } + doc->setModifiedSinceSave(saved_modified); + // free used resources + g_free(saved_output_extension); + g_free(saved_dataloss); + g_free(saved_filename); + + g_free(fileName); + + throw; + } + + // If it is an unofficial save, set the modified attributes back to what they were. + if ( !official) { + { + DocumentUndo::ScopedInsensitive _no_undo(doc); + store_file_extension_in_prefs (saved_output_extension, save_method); + repr->setAttribute("inkscape:dataloss", saved_dataloss); + } + doc->setModifiedSinceSave(saved_modified); + + g_free(saved_output_extension); + g_free(saved_dataloss); + } + + g_free(fileName); + return; +} + +/** + * \return none + * \brief This is the function that searches each module to see + * if it matches the filename for autodetection. + * \param in_plug The module to be tested + * \param in_data An array of pointers containing the filename, and + * the place to put a successfully found module. + * + * Basically this function only looks at output modules as it is part of the open function. If the + * module is an output module, it then starts to take it apart, and the data that is passed in. + * Because the data being passed in is in such a weird format, there are a few casts to make it + * easier to use. While it looks like a lot of local variables, they'll all get removed by the + * compiler. + * + * First thing that is checked is if the filename is shorter than the extension itself. There is + * no way for a match in that case. If it's long enough then there is a string compare of the end + * of the filename (for the length of the extension), and the extension itself. If this passes + * then the pointer passed in is set to the current module. + */ +static void +save_internal(Extension *in_plug, gpointer in_data) +{ + auto omod = dynamic_cast<Output *>(in_plug); + if (omod && !omod->deactivated()) { + gpointer *parray = (gpointer *)in_data; + gchar const *filename = (gchar const *)parray[0]; + Output **pomod = (Output **)parray[1]; + + // skip all the rest if we already found someone to save it + // since they're ordered by preference now. + if (!*pomod) { + gchar const *ext = omod->get_extension(); + + gchar *filenamelower = g_utf8_strdown(filename, -1); + gchar *extensionlower = g_utf8_strdown(ext, -1); + + if (g_str_has_suffix(filenamelower, extensionlower)) { + *pomod = omod; + } + + g_free(filenamelower); + g_free(extensionlower); + } + } + + return; +} + +Print * +get_print(gchar const *key) +{ + return dynamic_cast<Print *>(db.get(key)); +} + +/** + * \return true if extension successfully parsed, false otherwise + * A true return value does not guarantee an extension was actually registered, + * but indicates no errors occurred while parsing the extension. + * \brief Creates a module from a Inkscape::XML::Document describing the module + * \param doc The XML description of the module + * + * This function basically has two segments. The first is that it goes through the Repr tree + * provided, and determines what kind of module this is, and what kind of implementation to use. + * All of these are then stored in two enums that are defined in this function. This makes it + * easier to add additional types (which will happen in the future, I'm sure). + * + * Second, there is case statements for these enums. The first one is the type of module. This is + * the one where the module is actually created. After that, then the implementation is applied to + * get the load and unload functions. If there is no implementation then these are not set. This + * case could apply to modules that are built in (like the SVG load/save functions). + */ +bool +build_from_reprdoc(Inkscape::XML::Document *doc, Implementation::Implementation *in_imp, std::string* baseDir) +{ + ModuleImpType module_implementation_type = MODULE_UNKNOWN_IMP; + ModuleFuncType module_functional_type = MODULE_UNKNOWN_FUNC; + + g_return_val_if_fail(doc != nullptr, false); + + Inkscape::XML::Node *repr = doc->root(); + + if (strcmp(repr->name(), INKSCAPE_EXTENSION_NS "inkscape-extension")) { + g_warning("Extension definition started with <%s> instead of <" INKSCAPE_EXTENSION_NS "inkscape-extension>. Extension will not be created. See http://wiki.inkscape.org/wiki/index.php/Extensions for reference.\n", repr->name()); + return false; + } + + Inkscape::XML::Node *child_repr = repr->firstChild(); + while (child_repr != nullptr) { + char const *element_name = child_repr->name(); + /* printf("Child: %s\n", child_repr->name()); */ + if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "input")) { + module_functional_type = MODULE_INPUT; + } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "output")) { + module_functional_type = MODULE_OUTPUT; + } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "effect")) { + module_functional_type = MODULE_FILTER; + } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "print")) { + module_functional_type = MODULE_PRINT; + } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "path-effect")) { + module_functional_type = MODULE_PATH_EFFECT; + } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "script")) { + module_implementation_type = MODULE_EXTENSION; + } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "xslt")) { + module_implementation_type = MODULE_XSLT; + } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "plugin")) { + module_implementation_type = MODULE_PLUGIN; + } + + //Inkscape::XML::Node *old_repr = child_repr; + child_repr = child_repr->next(); + //Inkscape::GC::release(old_repr); + } + + Implementation::Implementation *imp; + if (in_imp == nullptr) { + switch (module_implementation_type) { + case MODULE_EXTENSION: { + Implementation::Script *script = new Implementation::Script(); + imp = static_cast<Implementation::Implementation *>(script); + break; + } + case MODULE_XSLT: { + Implementation::XSLT *xslt = new Implementation::XSLT(); + imp = static_cast<Implementation::Implementation *>(xslt); + break; + } + case MODULE_PLUGIN: { + Inkscape::Extension::Loader loader = Inkscape::Extension::Loader(); + if( baseDir != nullptr){ + loader.set_base_directory ( *baseDir ); + } + imp = loader.load_implementation(doc); + break; + } + default: { + imp = nullptr; + break; + } + } + } else { + imp = in_imp; + } + + Extension *module = nullptr; + try { + switch (module_functional_type) { + case MODULE_INPUT: { + module = new Input(repr, imp, baseDir); + break; + } + case MODULE_OUTPUT: { + module = new Output(repr, imp, baseDir); + break; + } + case MODULE_FILTER: { + module = new Effect(repr, imp, baseDir); + break; + } + case MODULE_PRINT: { + module = new Print(repr, imp, baseDir); + break; + } + case MODULE_PATH_EFFECT: { + module = new PathEffect(repr, imp, baseDir); + break; + } + default: { + g_warning("Extension of unknown type!"); // TODO: Should not happen! Is this even useful? + module = new Extension(repr, imp, baseDir); + break; + } + } + } catch (const Extension::extension_no_id& e) { + g_warning("Building extension failed. Extension does not have a valid ID"); + } catch (const Extension::extension_no_name& e) { + g_warning("Building extension failed. Extension does not have a valid name"); + } catch (const Extension::extension_not_compatible& e) { + return true; // This is not an actual error; just silently ignore the extension + } + + if (module) { + return true; + } + + return false; +} + +/** + * \brief This function creates a module from a filename of an + * XML description. + * \param filename The file holding the XML description of the module. + * + * This function calls build_from_reprdoc with using sp_repr_read_file to create the reprdoc. + */ +void +build_from_file(gchar const *filename) +{ + std::string dir = Glib::path_get_dirname(filename); + + Inkscape::XML::Document *doc = sp_repr_read_file(filename, INKSCAPE_EXTENSION_URI); + if (!doc) { + g_critical("Inkscape::Extension::build_from_file() - XML description loaded from '%s' not valid.", filename); + return; + } + + if (!build_from_reprdoc(doc, nullptr, &dir)) { + g_warning("Inkscape::Extension::build_from_file() - Could not parse extension from '%s'.", filename); + } + + Inkscape::GC::release(doc); +} + +/** + * \brief This function creates a module from a buffer holding an + * XML description. + * \param buffer The buffer holding the XML description of the module. + * + * This function calls build_from_reprdoc with using sp_repr_read_mem to create the reprdoc. It + * finds the length of the buffer using strlen. + */ +void +build_from_mem(gchar const *buffer, Implementation::Implementation *in_imp) +{ + Inkscape::XML::Document *doc = sp_repr_read_mem(buffer, strlen(buffer), INKSCAPE_EXTENSION_URI); + if (!doc) { + g_critical("Inkscape::Extension::build_from_mem() - XML description loaded from memory buffer not valid."); + return; + } + + if (!build_from_reprdoc(doc, in_imp, nullptr)) { + g_critical("Inkscape::Extension::build_from_mem() - Could not parse extension from memory buffer."); + } + + Inkscape::GC::release(doc); +} + +/* + * TODO: Is it guaranteed that the returned extension is valid? If so, we can remove the check for + * filename_extension in sp_file_save_dialog(). + */ +Glib::ustring +get_file_save_extension (Inkscape::Extension::FileSaveMethod method) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring extension; + switch (method) { + case FILE_SAVE_METHOD_SAVE_AS: + case FILE_SAVE_METHOD_TEMPORARY: + extension = prefs->getString("/dialogs/save_as/default"); + break; + case FILE_SAVE_METHOD_SAVE_COPY: + extension = prefs->getString("/dialogs/save_copy/default"); + break; + case FILE_SAVE_METHOD_INKSCAPE_SVG: + extension = SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE; + break; + case FILE_SAVE_METHOD_EXPORT: + /// \todo no default extension set for Export? defaults to SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE is ok? + break; + } + + if(extension.empty()) { + extension = SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE; + } + + return extension; +} + +Glib::ustring +get_file_save_path (SPDocument *doc, FileSaveMethod method) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring path; + bool use_current_dir = true; + switch (method) { + case FILE_SAVE_METHOD_SAVE_AS: + { + use_current_dir = prefs->getBool("/dialogs/save_as/use_current_dir", true); + if (doc->getDocumentFilename() && use_current_dir) { + path = Glib::path_get_dirname(doc->getDocumentFilename()); + } else { + path = prefs->getString("/dialogs/save_as/path"); + } + break; + } + case FILE_SAVE_METHOD_TEMPORARY: + path = prefs->getString("/dialogs/save_as/path"); + break; + case FILE_SAVE_METHOD_SAVE_COPY: + use_current_dir = prefs->getBool("/dialogs/save_copy/use_current_dir", prefs->getBool("/dialogs/save_as/use_current_dir", true)); + if (doc->getDocumentFilename() && use_current_dir) { + path = Glib::path_get_dirname(doc->getDocumentFilename()); + } else { + path = prefs->getString("/dialogs/save_copy/path"); + } + break; + case FILE_SAVE_METHOD_INKSCAPE_SVG: + if (doc->getDocumentFilename()) { + path = Glib::path_get_dirname(doc->getDocumentFilename()); + } else { + // FIXME: should we use the save_as path here or something else? Maybe we should + // leave this as a choice to the user. + path = prefs->getString("/dialogs/save_as/path"); + } + break; + case FILE_SAVE_METHOD_EXPORT: + /// \todo no default path set for Export? + // defaults to g_get_home_dir() + break; + } + + if(path.empty()) { + path = g_get_home_dir(); // Is this the most sensible solution? Note that we should avoid + // g_get_current_dir because this leads to problems on OS X where + // Inkscape opens the dialog inside application bundle when it is + // invoked for the first teim. + } + + return path; +} + +void +store_file_extension_in_prefs (Glib::ustring extension, FileSaveMethod method) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + switch (method) { + case FILE_SAVE_METHOD_SAVE_AS: + case FILE_SAVE_METHOD_TEMPORARY: + prefs->setString("/dialogs/save_as/default", extension); + break; + case FILE_SAVE_METHOD_SAVE_COPY: + prefs->setString("/dialogs/save_copy/default", extension); + break; + case FILE_SAVE_METHOD_INKSCAPE_SVG: + case FILE_SAVE_METHOD_EXPORT: + // do nothing + break; + } +} + +void +store_save_path_in_prefs (Glib::ustring path, FileSaveMethod method) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + switch (method) { + case FILE_SAVE_METHOD_SAVE_AS: + case FILE_SAVE_METHOD_TEMPORARY: + prefs->setString("/dialogs/save_as/path", path); + break; + case FILE_SAVE_METHOD_SAVE_COPY: + prefs->setString("/dialogs/save_copy/path", path); + break; + case FILE_SAVE_METHOD_INKSCAPE_SVG: + case FILE_SAVE_METHOD_EXPORT: + // do nothing + break; + } +} + +} } /* namespace Inkscape::Extension */ + +/* + 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 : |