diff options
Diffstat (limited to '')
-rw-r--r-- | src/extension/extension.cpp | 1111 |
1 files changed, 1111 insertions, 0 deletions
diff --git a/src/extension/extension.cpp b/src/extension/extension.cpp new file mode 100644 index 0000000..b95607f --- /dev/null +++ b/src/extension/extension.cpp @@ -0,0 +1,1111 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** \file + * + * Inkscape::Extension::Extension: + * the ability to have features that are more modular so that they + * can be added and removed easily. This is the basis for defining + * those actions. + */ + +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2002-2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension.h" +#include "implementation/implementation.h" +#include "implementation/script.h" +#include "implementation/xslt.h" + +#include <glibmm/fileutils.h> +#include <glibmm/miscutils.h> + +#include <glib/gstdio.h> +#include <glib/gprintf.h> + +#include <glibmm/i18n.h> +#include <gtkmm/box.h> +#include <gtkmm/frame.h> +#include <gtkmm/grid.h> +#include <gtkmm/label.h> + +#include "db.h" +#include "dependency.h" +#include "inkscape.h" +#include "timer.h" + +#include "io/resource.h" +#include "io/sys.h" + +#include "prefdialog/parameter.h" +#include "prefdialog/widget.h" + +#include "xml/repr.h" + + +namespace Inkscape { +namespace Extension { + +/* Inkscape::Extension::Extension */ + +FILE *Extension::error_file = nullptr; + +/** + \return none + \brief Constructs an Extension from a Inkscape::XML::Node + \param in_repr The repr that should be used to build it + \param base_directory Base directory of extensions that were loaded from a file (.inx file's location) + + This function is the basis of building an extension for Inkscape. It + currently extracts the fields from the Repr that are used in the + extension. The Repr will likely include other children that are + not related to the module directly. If the Repr does not include + a name and an ID the module will be left in an errored state. +*/ +Extension::Extension(Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory) + : _gui(true) + , execution_env(nullptr) +{ + g_return_if_fail(in_repr); // should be ensured in system.cpp + repr = in_repr; + Inkscape::GC::anchor(repr); + + if (in_imp == nullptr) { + imp = new Implementation::Implementation(); + } else { + imp = in_imp; + } + + if (base_directory) { + _base_directory = *base_directory; + } + + // get name of the translation catalog ("gettext textdomain") that the extension wants to use for translations + // and lookup the locale directory for it + const char *translationdomain = repr->attribute("translationdomain"); + if (translationdomain) { + _translationdomain = translationdomain; + } else { + _translationdomain = "inkscape"; // default to the Inkscape catalog + } + if (!strcmp(_translationdomain, "none")) { + // special keyword "none" means the extension author does not want translation of extension strings + _translation_enabled = false; + _translationdomain = nullptr; + } else if (!strcmp(_translationdomain, "inkscape")) { + // this is our default domain; we know the location already (also respects INKSCAPE_LOCALEDIR) + _gettext_catalog_dir = bindtextdomain("inkscape", nullptr); + } else { + lookup_translation_catalog(); + } + + // Read XML tree and parse extension + Inkscape::XML::Node *child_repr = repr->firstChild(); + while (child_repr) { + const char *chname = child_repr->name(); + if (!strncmp(chname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) { + chname += strlen(INKSCAPE_EXTENSION_NS); + } + if (chname[0] == '_') { // allow leading underscore in tag names for backwards-compatibility + chname++; + } + + if (!strcmp(chname, "id")) { + const char *id = child_repr->firstChild() ? child_repr->firstChild()->content() : nullptr; + if (id) { + _id = g_strdup(id); + } else { + throw extension_no_id(); + } + } else if (!strcmp(chname, "name")) { + const char *name = child_repr->firstChild() ? child_repr->firstChild()->content() : nullptr; + if (name) { + _name = g_strdup(name); + } else { + throw extension_no_name(); + } + } else if (InxWidget::is_valid_widget_name(chname)) { + InxWidget *widget = InxWidget::make(child_repr, this); + if (widget) { + _widgets.push_back(widget); + } + } else if (!strcmp(chname, "dependency")) { + _deps.push_back(new Dependency(child_repr, this)); + } else if (!strcmp(chname, "script")) { // TODO: should these be parsed in their respective Implementation? + for (Inkscape::XML::Node *child = child_repr->firstChild(); child != nullptr; child = child->next()) { + if (child->type() == Inkscape::XML::NodeType::ELEMENT_NODE) { // skip non-element nodes (see LP #1372200) + const char *interpreted = child->attribute("interpreter"); + Dependency::type_t type = interpreted ? Dependency::TYPE_FILE : Dependency::TYPE_EXECUTABLE; + _deps.push_back(new Dependency(child, this, type)); + break; + } + } + } else if (!strcmp(chname, "xslt")) { // TODO: should these be parsed in their respective Implementation? + for (Inkscape::XML::Node *child = child_repr->firstChild(); child != nullptr; child = child->next()) { + if (child->type() == Inkscape::XML::NodeType::ELEMENT_NODE) { // skip non-element nodes (see LP #1372200) + _deps.push_back(new Dependency(child, this, Dependency::TYPE_FILE)); + break; + } + } + } else { + // We could do some sanity checking here. + // However, we don't really know which additional elements Extension subclasses might need... + } + + child_repr = child_repr->next(); + } + + // all extensions need an ID and a name + if (!_id) { + throw extension_no_id(); + } + if (!_name) { + throw extension_no_name(); + } + + // filter out extensions that are not compatible with the current platform +#ifndef _WIN32 + if (strstr(_id, "win32")) { + throw extension_not_compatible(); + } +#endif + + // finally register the extension if all checks passed + db.register_ext (this); +} + +/** + \return none + \brief Destroys the Extension + + This function frees all of the strings that could be attached + to the extension and also unreferences the repr. This is better + than freeing it because it may (I wouldn't know why) be referenced + in another place. +*/ +Extension::~Extension () +{ + set_state(STATE_UNLOADED); + + db.unregister_ext(this); + + Inkscape::GC::release(repr); + + g_free(_id); + g_free(_name); + + delete timer; + timer = nullptr; + + for (auto widget : _widgets) { + delete widget; + } + + for (auto & _dep : _deps) { + delete _dep; + } + _deps.clear(); +} + +/** + \return none + \brief A function to set whether the extension should be loaded + or unloaded + \param in_state Which state should the extension be in? + + It checks to see if this is a state change or not. If we're changing + states it will call the appropriate function in the implementation, + load or unload. Currently, there is no error checking in this + function. There should be. +*/ +void +Extension::set_state (state_t in_state) +{ + if (_state == STATE_DEACTIVATED) return; + if (in_state != _state) { + /** \todo Need some more error checking here! */ + switch (in_state) { + case STATE_LOADED: + if (imp->load(this)) + _state = STATE_LOADED; + + if (timer != nullptr) { + delete timer; + } + timer = new ExpirationTimer(this); + + break; + case STATE_UNLOADED: + imp->unload(this); + _state = STATE_UNLOADED; + + if (timer != nullptr) { + delete timer; + timer = nullptr; + } + break; + case STATE_DEACTIVATED: + _state = STATE_DEACTIVATED; + + if (timer != nullptr) { + delete timer; + timer = nullptr; + } + break; + default: + break; + } + } + + return; +} + +/** + \return The state the extension is in + \brief A getter for the state variable. +*/ +Extension::state_t +Extension::get_state () +{ + return _state; +} + +/** + \return Whether the extension is loaded or not + \brief A quick function to test the state of the extension +*/ +bool +Extension::loaded () +{ + return get_state() == STATE_LOADED; +} + +/** + \return A boolean saying whether the extension passed the checks + \brief A function to check the validity of the extension + + This function chekcs to make sure that there is an id, a name, a + repr and an implementation for this extension. Then it checks all + of the dependencies to see if they pass. Finally, it asks the + implementation to do a check of itself. + + On each check, if there is a failure, it will print a message to the + error log for that failure. It is important to note that the function + keeps executing if it finds an error, to try and get as many of them + into the error log as possible. This should help people debug + installations, and figure out what they need to get for the full + functionality of Inkscape to be available. +*/ +bool +Extension::check () +{ + const char * inx_failure = _(" This is caused by an improper .inx file for this extension." + " An improper .inx file could have been caused by a faulty installation of Inkscape."); + + if (repr == nullptr) { + printFailure(Glib::ustring(_("the XML description of it got lost.")) + inx_failure); + return false; + } + if (imp == nullptr) { + printFailure(Glib::ustring(_("no implementation was defined for the extension.")) + inx_failure); + return false; + } + + bool retval = true; + for (auto _dep : _deps) { + if (_dep->check() == false) { + printFailure(Glib::ustring(_("a dependency was not met."))); + error_file_write(_dep->info_string()); + retval = false; + } + } + + if (retval) { + return imp->check(this); + } + + error_file_write(""); + return retval; +} + +/** \brief A quick function to print out a standard start of extension + errors in the log. + \param reason A string explaining why this failed + + Real simple, just put everything into \c error_file. +*/ +void +Extension::printFailure (Glib::ustring reason) +{ + _error_reason = Glib::ustring::compose(_("Extension \"%1\" failed to load because %2"), _name, reason); + error_file_write(_error_reason); +} + +/** + \return The XML tree that is used to define the extension + \brief A getter for the internal Repr, does not add a reference. +*/ +Inkscape::XML::Node * +Extension::get_repr () +{ + return repr; +} + +/** + \return The textual id of this extension + \brief Get the ID of this extension - not a copy don't delete! +*/ +gchar * +Extension::get_id () const +{ + return _id; +} + +/** + \return The textual name of this extension + \brief Get the name of this extension - not a copy don't delete! +*/ +const gchar * +Extension::get_name () const +{ + return get_translation(_name, nullptr); +} + +/** + \return None + \brief This function diactivates the extension (which makes it + unusable, but not deleted) + + This function is used to removed an extension from functioning, but + not delete it completely. It sets the state to \c STATE_DEACTIVATED to + mark to the world that it has been deactivated. It also removes + the current implementation and replaces it with a standard one. This + makes it so that we don't have to continually check if there is an + implementation, but we are guaranteed to have a benign one. + + \warning It is important to note that there is no 'activate' function. + Running this function is irreversible. +*/ +void +Extension::deactivate () +{ + set_state(STATE_DEACTIVATED); + + /* Removing the old implementation, and making this use the default. */ + /* This should save some memory */ + delete imp; + imp = new Implementation::Implementation(); + + return; +} + +/** + \return Whether the extension has been deactivated + \brief Find out the status of the extension +*/ +bool +Extension::deactivated () +{ + return get_state() == STATE_DEACTIVATED; +} + +/** Gets the location of the dependency file as an absolute path + * + * Iterates over all dependencies of this extension and finds the one with matching name, + * then returns the absolute path to this dependency file as determined previously. + * + * TODO: This function should not be necessary, but we parse script dependencies twice: + * - Once here in the Extension::Extension() constructor + * - A second time in Script::load() in "script.cpp" when determining the script location + * Theoretically we could return the wrong path if an extension depends on two files with the same name + * in different relative locations. In practice this risk should be close to zero, though. + * + * @return Absolute path of the dependency file + */ +std::string Extension::get_dependency_location(const char *name) +{ + for (auto dep : _deps) { + if (!strcmp(name, dep->get_name())) { + return dep->get_path(); + } + } + + return ""; +} + +/** recursively searches directory for a file named filename; returns true if found */ +static bool _find_filename_recursive(std::string directory, std::string const &filename) { + Glib::Dir dir(directory); + + std::string name = dir.read_name(); + while (!name.empty()) { + std::string fullpath = Glib::build_filename(directory, name); + // g_message("%s", fullpath.c_str()); + + if (Glib::file_test(fullpath, Glib::FILE_TEST_IS_DIR)) { + if (_find_filename_recursive(fullpath, filename)) { + return true; + } + } else if (name == filename) { + return true; + } + name = dir.read_name(); + } + + return false; +} + +/** Searches for a gettext catalog matching the extension's translationdomain + * + * This function will attempt to find the correct gettext catalog for the translationdomain + * requested by the extension. + * + * For this the following three locations are recursively searched for "${translationdomain}.mo": + * - the 'locale' directory in the .inx file's folder + * - the 'locale' directory in the "extensions" folder containing the .inx + * - the system location for gettext catalogs, i.e. where Inkscape's own catalog is located + * + * If one matching file is found, the directory is assumed to be the correct location and registered with gettext + */ +void Extension::lookup_translation_catalog() { + g_assert(!_base_directory.empty()); + + // get locale folder locations + std::string locale_dir_current_extension; + std::string locale_dir_extensions; + std::string locale_dir_system; + + locale_dir_current_extension = Glib::build_filename(_base_directory, "locale"); + + size_t index = _base_directory.find_last_of("extensions"); + if (index != std::string::npos) { + locale_dir_extensions = Glib::build_filename(_base_directory.substr(0, index+1), "locale"); + } + + locale_dir_system = bindtextdomain("inkscape", nullptr); + + // collect unique locations into vector + std::vector<std::string> locale_dirs; + if (locale_dir_current_extension != locale_dir_extensions) { + locale_dirs.push_back(std::move(locale_dir_current_extension)); + } + locale_dirs.push_back(std::move(locale_dir_extensions)); + locale_dirs.push_back(std::move(locale_dir_system)); + + // iterate over locations and look for the one that has the correct catalog + std::string search_name; + search_name += _translationdomain; + search_name += ".mo"; + for (auto locale_dir : locale_dirs) { + if (!Glib::file_test(locale_dir, Glib::FILE_TEST_IS_DIR)) { + continue; + } + + if (_find_filename_recursive(locale_dir, search_name)) { + _gettext_catalog_dir = locale_dir; + break; + } + } + + // register catalog with gettext if found, disable translation for this extension otherwise + if (!_gettext_catalog_dir.empty()) { + const char *current_dir = bindtextdomain(_translationdomain, nullptr); + if (_gettext_catalog_dir != current_dir) { + g_info("Binding textdomain '%s' to '%s'.", _translationdomain, _gettext_catalog_dir.c_str()); + bindtextdomain(_translationdomain, _gettext_catalog_dir.c_str()); + bind_textdomain_codeset(_translationdomain, "UTF-8"); + } + } else { + g_warning("Failed to locate message catalog for textdomain '%s'.", _translationdomain); + _translation_enabled = false; + _translationdomain = nullptr; + } +} + +/** Gets a translation within the context of the current extension + * + * Query gettext for the translated version of the input string, + * handling the preferred translation domain of the extension internally. + * + * @param msgid String to translate + * @param msgctxt Context for the translation + * + * @return Translated string (or original string if extension is not supposed to be translated) + */ +const char *Extension::get_translation(const char *msgid, const char *msgctxt) const { + if (!_translation_enabled) { + return msgid; + } + + if (!strcmp(msgid, "")) { + g_warning("Attempting to translate an empty string in extension '%s', which is not supported.", _id); + return msgid; + } + + if (msgctxt) { + return g_dpgettext2(_translationdomain, msgctxt, msgid); + } else { + return g_dgettext(_translationdomain, msgid); + } +} + +/** Sets environment suitable for executing this Extension + * + * Currently sets the environment variables INKEX_GETTEXT_DOMAIN and INKEX_GETTEXT_DIRECTORY + * to make the "translationdomain" accessible to child processes spawned by this extension's Implementation. + * + * @param doc Optional document, if provided sets the DOCUMENT_PATH from the document's save location. + */ +void Extension::set_environment(const SPDocument *doc) { + Glib::unsetenv("INKEX_GETTEXT_DOMAIN"); + Glib::unsetenv("INKEX_GETTEXT_DIRECTORY"); + + // This is needed so extensions can interact with the user's profile, keep settings etc. + Glib::setenv("INKSCAPE_PROFILE_DIR", std::string(Inkscape::IO::Resource::profile_path())); + + // This is needed so files can be saved relative to their document location (see image-extract) + if (doc) { + auto path = doc->getDocumentFilename(); + if (!path) { + path = ""; // Set to blank string so extensions know the difference between old inkscape and not-saved document. + } + Glib::setenv("DOCUMENT_PATH", std::string(path)); + } + + if (_translationdomain) { + Glib::setenv("INKEX_GETTEXT_DOMAIN", std::string(_translationdomain)); + } + if (!_gettext_catalog_dir.empty()) { + Glib::setenv("INKEX_GETTEXT_DIRECTORY", _gettext_catalog_dir); + } +} + +/** Uses the object's type to figure out what the type is. + * + * @return Returns the type of extension that this object is. + */ +ModuleImpType Extension::get_implementation_type() +{ + if (dynamic_cast<Implementation::Script *>(imp)) { + return MODULE_EXTENSION; + } else if (dynamic_cast<Implementation::XSLT *>(imp)) { + return MODULE_XSLT; + } + // MODULE_UNKNOWN_IMP is not required because it never results in an + // object being created. Thus this function wouldn't be available. + return MODULE_PLUGIN; +} + +/** + \brief A function to get the parameters in a string form + \return An array with all the parameters in it. + +*/ +void +Extension::paramListString (std::list <std::string> &retlist) +{ + // first collect all widgets in the current extension + std::vector<InxWidget *> widget_list; + for (auto widget : _widgets) { + widget->get_widgets(widget_list); + } + + // then build a list of parameter strings from parameter names and values, as '--name=value' + for (auto widget : widget_list) { + InxParameter *parameter = dynamic_cast<InxParameter *>(widget); // filter InxParameters from InxWidgets + if (parameter) { + const char *name = parameter->name(); + std::string value = parameter->value_to_string(); + + if (name && !value.empty()) { // TODO: Shouldn't empty string values be allowed? + std::string parameter_string; + parameter_string += "--"; + parameter_string += name; + parameter_string += "="; + parameter_string += value; + retlist.push_back(parameter_string); + } + } + } + + return; +} + +InxParameter *Extension::get_param(const gchar *name) +{ + if (!name || _widgets.empty()) { + throw Extension::param_not_exist(); + } + + // first collect all widgets in the current extension + std::vector<InxWidget *> widget_list; + for (auto widget : _widgets) { + widget->get_widgets(widget_list); + } + + // then search for a parameter with a matching name + for (auto widget : widget_list) { + InxParameter *parameter = dynamic_cast<InxParameter *>(widget); // filter InxParameters from InxWidgets + if (parameter && !strcmp(parameter->name(), name)) { + return parameter; + } + } + + // if execution reaches here, no parameter matching 'name' was found + throw Extension::param_not_exist(); +} + +InxParameter const *Extension::get_param(const gchar *name) const +{ + return const_cast<Extension *>(this)->get_param(name); +} + + +/** + \return The value of the parameter identified by the name + \brief Gets a parameter identified by name with the bool placed in value. + \param name The name of the parameter to get + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +bool +Extension::get_param_bool(const gchar *name) const +{ + const InxParameter *param; + param = get_param(name); + return param->get_bool(); +} + +/** + * \return The value of the param or the alternate if the param doesn't exist. + * \brief Like get_param_bool but with a default on param_not_exist error. + */ +bool Extension::get_param_bool(const gchar *name, bool alt) const +{ + try { + return get_param_bool(name); + } catch (Extension::param_not_exist) { + return alt; + } +} + +/** + \return The integer value for the parameter specified + \brief Gets a parameter identified by name with the integer placed in value. + \param name The name of the parameter to get + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +int +Extension::get_param_int(const gchar *name) const +{ + const InxParameter *param; + param = get_param(name); + return param->get_int(); +} + +/** + * \return The value of the param or the alternate if the param doesn't exist. + * \brief Like get_param_int but with a default on param_not_exist error. + */ +int Extension::get_param_int(const gchar *name, int alt) const +{ + try { + return get_param_int(name); + } catch (Extension::param_not_exist) { + return alt; + } +} + + +/** + \return The double value for the float parameter specified + \brief Gets a float parameter identified by name with the double placed in value. + \param name The name of the parameter to get + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +double +Extension::get_param_float(const gchar *name) const +{ + const InxParameter *param; + param = get_param(name); + return param->get_float(); +} + +/** + * \return The value of the param or the alternate if the param doesn't exist. + * \brief Like get_param_float but with a default on param_not_exist error. + */ +double Extension::get_param_float(const gchar *name, double alt) const +{ + try { + return get_param_float(name); + } catch (Extension::param_not_exist) { + return alt; + } +} + +/** + \return The string value for the parameter specified + \brief Gets a parameter identified by name with the string placed in value. + \param name The name of the parameter to get + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +const char * +Extension::get_param_string(const gchar *name) const +{ + const InxParameter *param; + param = get_param(name); + return param->get_string(); +} + +/** + * \return The value of the param or the alternate if the param doesn't exist. + * \brief Like get_param_string but with a default on param_not_exist error. + */ +const char *Extension::get_param_string(const gchar *name, const char *alt) const +{ + try { + return get_param_string(name); + } catch (Extension::param_not_exist) { + return alt; + } +} + +/** + \return The string value for the parameter specified + \brief Gets a parameter identified by name with the string placed in value. + \param name The name of the parameter to get + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +const char * +Extension::get_param_optiongroup(const gchar *name) const +{ + const InxParameter *param; + param = get_param(name); + return param->get_optiongroup(); +} + +/** + * \return The value of the param or the alternate if the param doesn't exist. + * \brief Like get_param_optiongroup but with a default on param_not_exist error. + */ +const char *Extension::get_param_optiongroup(const gchar *name, const char *alt) const +{ + try { + return get_param_optiongroup(name); + } catch (Extension::param_not_exist) { + return alt; + } +} + +/** + * This is useful to find out, if a given string \c value is selectable in a optiongroup named \cname. + * + * @param name The name of the optiongroup parameter to get. + * @return true if value exists, false if not + */ +bool +Extension::get_param_optiongroup_contains(const gchar *name, const char *value) const +{ + const InxParameter *param; + param = get_param(name); + return param->get_optiongroup_contains(value); +} + +/** + \return The unsigned integer RGBA value for the parameter specified + \brief Gets a parameter identified by name with the unsigned int placed in value. + \param name The name of the parameter to get + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +guint32 +Extension::get_param_color(const gchar *name) const +{ + const InxParameter *param; + param = get_param(name); + return param->get_color(); +} + +/** + \return The passed in value + \brief Sets a parameter identified by name with the boolean in the parameter value. + \param name The name of the parameter to set + \param value The value to set the parameter to + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +bool +Extension::set_param_bool(const gchar *name, const bool value) +{ + InxParameter *param; + param = get_param(name); + return param->set_bool(value); +} + +/** + \return The passed in value + \brief Sets a parameter identified by name with the integer in the parameter value. + \param name The name of the parameter to set + \param value The value to set the parameter to + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +int +Extension::set_param_int(const gchar *name, const int value) +{ + InxParameter *param; + param = get_param(name); + return param->set_int(value); +} + +/** + \return The passed in value + \brief Sets a parameter identified by name with the double in the parameter value. + \param name The name of the parameter to set + \param value The value to set the parameter to + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +double +Extension::set_param_float(const gchar *name, const double value) +{ + InxParameter *param; + param = get_param(name); + return param->set_float(value); +} + +/** + \return The passed in value + \brief Sets a parameter identified by name with the string in the parameter value. + \param name The name of the parameter to set + \param value The value to set the parameter to + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +const char * +Extension::set_param_string(const gchar *name, const char *value) +{ + InxParameter *param; + param = get_param(name); + return param->set_string(value); +} + +/** + \return The passed in value + \brief Sets a parameter identified by name with the string in the parameter value. + \param name The name of the parameter to set + \param value The value to set the parameter to + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +const char * +Extension::set_param_optiongroup(const gchar *name, const char *value) +{ + InxParameter *param; + param = get_param(name); + return param->set_optiongroup(value); +} + +/** + \return The passed in value + \brief Sets a parameter identified by name with the unsigned integer RGBA value in the parameter value. + \param name The name of the parameter to set + \param value The value to set the parameter to + +Look up in the parameters list, const then execute the function on that found parameter. +*/ +guint32 +Extension::set_param_color(const gchar *name, const guint32 color) +{ + InxParameter *param; + param = get_param(name); + return param->set_color(color); +} + + +/** \brief A function to open the error log file. */ +void +Extension::error_file_open () +{ + gchar *ext_error_file = Inkscape::IO::Resource::log_path(EXTENSION_ERROR_LOG_FILENAME); + error_file = Inkscape::IO::fopen_utf8name(ext_error_file, "w+"); + if (!error_file) { + g_warning(_("Could not create extension error log file '%s'"), ext_error_file); + } + g_free(ext_error_file); +}; + +/** \brief A function to close the error log file. */ +void +Extension::error_file_close () +{ + if (error_file) { + fclose(error_file); + } +}; + +/** \brief A function to write to the error log file. */ +void +Extension::error_file_write (Glib::ustring text) +{ + if (error_file) { + g_fprintf(error_file, "%s\n", text.c_str()); + } +}; + +/** \brief A widget to represent the inside of an AutoGUI widget */ +class AutoGUI : public Gtk::Box { +public: + /** \brief Create an AutoGUI object */ + AutoGUI () : Gtk::Box(Gtk::ORIENTATION_VERTICAL) {}; + + /** + * Adds a widget with a tool tip into the autogui. + * + * If there is no widget, nothing happens. Otherwise it is just + * added into the VBox. If there is a tooltip (non-NULL) then it + * is placed on the widget. + * + * @param widg Widget to add. + * @param tooltip Tooltip for the widget. + */ + void addWidget(Gtk::Widget *widg, gchar const *tooltip, int indent) { + if (widg) { + widg->set_margin_start(indent * InxParameter::GUI_INDENTATION); + this->pack_start(*widg, false, true, 0); // fill=true does not have an effect here, but allows the + // child to choose to expand by setting hexpand/vexpand + if (tooltip) { + widg->set_tooltip_text(tooltip); + } else { + widg->set_tooltip_text(""); + widg->set_has_tooltip(false); + } + } + }; +}; + +/** \brief A function to automatically generate a GUI from the extensions' widgets + \return Generated widget + + This function just goes through each widget, and calls it's 'get_widget'. + Then, each of those is placed into a Gtk::VBox, which is then returned to the calling function. + + If there are no visible parameters, this function just returns NULL. +*/ +Gtk::Widget * +Extension::autogui (SPDocument *doc, Inkscape::XML::Node *node, sigc::signal<void> *changeSignal) +{ + if (!_gui || widget_visible_count() == 0) { + return nullptr; + } + + AutoGUI * agui = Gtk::manage(new AutoGUI()); + agui->set_border_width(InxParameter::GUI_BOX_MARGIN); + agui->set_spacing(InxParameter::GUI_BOX_SPACING); + + // go through the list of widgets and add the all non-hidden ones + for (auto widget : _widgets) { + if (widget->get_hidden()) { + continue; + } + + Gtk::Widget *widg = widget->get_widget(changeSignal); + gchar const *tip = widget->get_tooltip(); + int indent = widget->get_indent(); + + agui->addWidget(widg, tip, indent); + } + + agui->show(); + return agui; +}; + +/* Extension editor dialog stuff */ + +Gtk::Box * +Extension::get_info_widget() +{ + Gtk::Box * retval = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + retval->set_border_width(4); + + Gtk::Frame * info = Gtk::manage(new Gtk::Frame("General Extension Information")); + retval->pack_start(*info, true, true, 4); + + auto table = Gtk::manage(new Gtk::Grid()); + table->set_border_width(4); + table->set_column_spacing(4); + + info->add(*table); + + int row = 0; + add_val(_("Name:"), get_translation(_name), table, &row); + add_val(_("ID:"), _id, table, &row); + add_val(_("State:"), _state == STATE_LOADED ? _("Loaded") : _state == STATE_UNLOADED ? _("Unloaded") : _("Deactivated"), table, &row); + + retval->show_all(); + return retval; +} + +void Extension::add_val(Glib::ustring labelstr, Glib::ustring valuestr, Gtk::Grid * table, int * row) +{ + Gtk::Label * label; + Gtk::Label * value; + + (*row)++; + label = Gtk::manage(new Gtk::Label(labelstr, Gtk::ALIGN_START)); + value = Gtk::manage(new Gtk::Label(valuestr, Gtk::ALIGN_START)); + + table->attach(*label, 0, (*row) - 1, 1, 1); + table->attach(*value, 1, (*row) - 1, 1, 1); + + label->show(); + value->show(); + + return; +} + +Gtk::Box * +Extension::get_params_widget() +{ + Gtk::Box * retval = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + Gtk::Widget * content = Gtk::manage(new Gtk::Label("Params")); + retval->pack_start(*content, true, true, 4); + content->show(); + retval->show(); + return retval; +} + +unsigned int Extension::widget_visible_count ( ) +{ + unsigned int _visible_count = 0; + for (auto widget : _widgets) { + if (!widget->get_hidden()) { + _visible_count++; + } + } + return _visible_count; +} + +} /* namespace Extension */ +} /* namespace Inkscape */ + + +/* + 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 : |