1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** \file
3  *
4  * Inkscape::Extension::Extension:
5  * the ability to have features that are more modular so that they
6  * can be added and removed easily.  This is the basis for defining
7  * those actions.
8  */
9 
10 /*
11  * Authors:
12  *   Ted Gould <ted@gould.cx>
13  *
14  * Copyright (C) 2002-2005 Authors
15  *
16  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
17  */
18 
19 #include "extension.h"
20 #include "implementation/implementation.h"
21 #include "implementation/script.h"
22 #include "implementation/xslt.h"
23 
24 #include <glibmm/fileutils.h>
25 #include <glibmm/miscutils.h>
26 
27 #include <glib/gstdio.h>
28 #include <glib/gprintf.h>
29 
30 #include <glibmm/i18n.h>
31 #include <gtkmm/box.h>
32 #include <gtkmm/frame.h>
33 #include <gtkmm/grid.h>
34 #include <gtkmm/label.h>
35 
36 #include "db.h"
37 #include "dependency.h"
38 #include "inkscape.h"
39 #include "timer.h"
40 
41 #include "io/resource.h"
42 #include "io/sys.h"
43 
44 #include "prefdialog/parameter.h"
45 #include "prefdialog/widget.h"
46 
47 #include "xml/repr.h"
48 
49 
50 namespace Inkscape {
51 namespace Extension {
52 
53 /* Inkscape::Extension::Extension */
54 
55 FILE *Extension::error_file = nullptr;
56 
57 /**
58     \return  none
59     \brief   Constructs an Extension from a Inkscape::XML::Node
60     \param   in_repr        The repr that should be used to build it
61     \param   base_directory Base directory of extensions that were loaded from a file (.inx file's location)
62 
63     This function is the basis of building an extension for Inkscape.  It
64     currently extracts the fields from the Repr that are used in the
65     extension.  The Repr will likely include other children that are
66     not related to the module directly.  If the Repr does not include
67     a name and an ID the module will be left in an errored state.
68 */
Extension(Inkscape::XML::Node * in_repr,Implementation::Implementation * in_imp,std::string * base_directory)69 Extension::Extension(Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory)
70     : _gui(true)
71     , execution_env(nullptr)
72 {
73     g_return_if_fail(in_repr); // should be ensured in system.cpp
74     repr = in_repr;
75     Inkscape::GC::anchor(repr);
76 
77     if (in_imp == nullptr) {
78         imp = new Implementation::Implementation();
79     } else {
80         imp = in_imp;
81     }
82 
83     if (base_directory) {
84         _base_directory = *base_directory;
85     }
86 
87     // get name of the translation catalog ("gettext textdomain") that the extension wants to use for translations
88     // and lookup the locale directory for it
89     const char *translationdomain = repr->attribute("translationdomain");
90     if (translationdomain) {
91         _translationdomain = translationdomain;
92     } else {
93         _translationdomain = "inkscape"; // default to the Inkscape catalog
94     }
95     if (!strcmp(_translationdomain, "none")) {
96         // special keyword "none" means the extension author does not want translation of extension strings
97         _translation_enabled = false;
98         _translationdomain = nullptr;
99     } else if (!strcmp(_translationdomain, "inkscape")) {
100         // this is our default domain; we know the location already (also respects INKSCAPE_LOCALEDIR)
101         _gettext_catalog_dir = bindtextdomain("inkscape", nullptr);
102     } else {
103         lookup_translation_catalog();
104     }
105 
106     // Read XML tree and parse extension
107     Inkscape::XML::Node *child_repr = repr->firstChild();
108     while (child_repr) {
109         const char *chname = child_repr->name();
110         if (!strncmp(chname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) {
111             chname += strlen(INKSCAPE_EXTENSION_NS);
112         }
113         if (chname[0] == '_') { // allow leading underscore in tag names for backwards-compatibility
114             chname++;
115         }
116 
117         if (!strcmp(chname, "id")) {
118             const char *id = child_repr->firstChild() ? child_repr->firstChild()->content() : nullptr;
119             if (id) {
120                 _id = g_strdup(id);
121             } else {
122                 throw extension_no_id();
123             }
124         } else if (!strcmp(chname, "name")) {
125             const char *name = child_repr->firstChild() ? child_repr->firstChild()->content() : nullptr;
126             if (name) {
127                 _name = g_strdup(name);
128             } else {
129                 throw extension_no_name();
130             }
131         } else if (InxWidget::is_valid_widget_name(chname)) {
132             InxWidget *widget = InxWidget::make(child_repr, this);
133             if (widget) {
134                 _widgets.push_back(widget);
135             }
136         } else if (!strcmp(chname, "dependency")) {
137             _deps.push_back(new Dependency(child_repr, this));
138         } else if (!strcmp(chname, "script")) { // TODO: should these be parsed in their respective Implementation?
139             for (Inkscape::XML::Node *child = child_repr->firstChild(); child != nullptr; child = child->next()) {
140                 if (child->type() == Inkscape::XML::NodeType::ELEMENT_NODE) { // skip non-element nodes (see LP #1372200)
141                     const char *interpreted = child->attribute("interpreter");
142                     Dependency::type_t type = interpreted ? Dependency::TYPE_FILE : Dependency::TYPE_EXECUTABLE;
143                     _deps.push_back(new Dependency(child, this, type));
144                     break;
145                 }
146             }
147         } else if (!strcmp(chname, "xslt")) { // TODO: should these be parsed in their respective Implementation?
148             for (Inkscape::XML::Node *child = child_repr->firstChild(); child != nullptr; child = child->next()) {
149                 if (child->type() == Inkscape::XML::NodeType::ELEMENT_NODE) { // skip non-element nodes (see LP #1372200)
150                     _deps.push_back(new Dependency(child, this, Dependency::TYPE_FILE));
151                     break;
152                 }
153             }
154         } else {
155             // We could do some sanity checking here.
156             // However, we don't really know which additional elements Extension subclasses might need...
157         }
158 
159         child_repr = child_repr->next();
160     }
161 
162     // all extensions need an ID and a name
163     if (!_id) {
164         throw extension_no_id();
165     }
166     if (!_name) {
167         throw extension_no_name();
168     }
169 
170     // filter out extensions that are not compatible with the current platform
171 #ifndef _WIN32
172     if (strstr(_id, "win32")) {
173         throw extension_not_compatible();
174     }
175 #endif
176 
177     // finally register the extension if all checks passed
178     db.register_ext (this);
179 }
180 
181 /**
182     \return   none
183     \brief    Destroys the Extension
184 
185     This function frees all of the strings that could be attached
186     to the extension and also unreferences the repr.  This is better
187     than freeing it because it may (I wouldn't know why) be referenced
188     in another place.
189 */
~Extension()190 Extension::~Extension ()
191 {
192     set_state(STATE_UNLOADED);
193 
194     db.unregister_ext(this);
195 
196     Inkscape::GC::release(repr);
197 
198     g_free(_id);
199     g_free(_name);
200 
201     delete timer;
202     timer = nullptr;
203 
204     for (auto widget : _widgets) {
205         delete widget;
206     }
207 
208     for (auto & _dep : _deps) {
209         delete _dep;
210     }
211     _deps.clear();
212 }
213 
214 /**
215     \return   none
216     \brief    A function to set whether the extension should be loaded
217               or unloaded
218     \param    in_state  Which state should the extension be in?
219 
220     It checks to see if this is a state change or not.  If we're changing
221     states it will call the appropriate function in the implementation,
222     load or unload.  Currently, there is no error checking in this
223     function.  There should be.
224 */
225 void
set_state(state_t in_state)226 Extension::set_state (state_t in_state)
227 {
228     if (_state == STATE_DEACTIVATED) return;
229     if (in_state != _state) {
230         /** \todo Need some more error checking here! */
231         switch (in_state) {
232             case STATE_LOADED:
233                 if (imp->load(this))
234                     _state = STATE_LOADED;
235 
236                 if (timer != nullptr) {
237                     delete timer;
238                 }
239                 timer = new ExpirationTimer(this);
240 
241                 break;
242             case STATE_UNLOADED:
243                 // std::cout << "Unloading: " << name << std::endl;
244                 imp->unload(this);
245                 _state = STATE_UNLOADED;
246 
247                 if (timer != nullptr) {
248                     delete timer;
249                     timer = nullptr;
250                 }
251                 break;
252             case STATE_DEACTIVATED:
253                 _state = STATE_DEACTIVATED;
254 
255                 if (timer != nullptr) {
256                     delete timer;
257                     timer = nullptr;
258                 }
259                 break;
260             default:
261                 break;
262         }
263     }
264 
265     return;
266 }
267 
268 /**
269     \return   The state the extension is in
270     \brief    A getter for the state variable.
271 */
272 Extension::state_t
get_state()273 Extension::get_state ()
274 {
275     return _state;
276 }
277 
278 /**
279     \return  Whether the extension is loaded or not
280     \brief   A quick function to test the state of the extension
281 */
282 bool
loaded()283 Extension::loaded ()
284 {
285     return get_state() == STATE_LOADED;
286 }
287 
288 /**
289     \return  A boolean saying whether the extension passed the checks
290     \brief   A function to check the validity of the extension
291 
292     This function chekcs to make sure that there is an id, a name, a
293     repr and an implementation for this extension.  Then it checks all
294     of the dependencies to see if they pass.  Finally, it asks the
295     implementation to do a check of itself.
296 
297     On each check, if there is a failure, it will print a message to the
298     error log for that failure.  It is important to note that the function
299     keeps executing if it finds an error, to try and get as many of them
300     into the error log as possible.  This should help people debug
301     installations, and figure out what they need to get for the full
302     functionality of Inkscape to be available.
303 */
304 bool
check()305 Extension::check ()
306 {
307     const char * inx_failure = _("  This is caused by an improper .inx file for this extension."
308                                  "  An improper .inx file could have been caused by a faulty installation of Inkscape.");
309 
310     if (repr == nullptr) {
311         printFailure(Glib::ustring(_("the XML description of it got lost.")) + inx_failure);
312         return false;
313     }
314     if (imp == nullptr) {
315         printFailure(Glib::ustring(_("no implementation was defined for the extension.")) + inx_failure);
316         return false;
317     }
318 
319     bool retval = true;
320     for (auto _dep : _deps) {
321         if (_dep->check() == false) {
322             printFailure(Glib::ustring(_("a dependency was not met.")));
323             error_file_write(_dep->info_string());
324             retval = false;
325         }
326     }
327 
328     if (retval) {
329         return imp->check(this);
330     }
331 
332     error_file_write("");
333     return retval;
334 }
335 
336 /** \brief A quick function to print out a standard start of extension
337            errors in the log.
338     \param reason  A string explaining why this failed
339 
340     Real simple, just put everything into \c error_file.
341 */
342 void
printFailure(Glib::ustring reason)343 Extension::printFailure (Glib::ustring reason)
344 {
345     _error_reason = Glib::ustring::compose(_("Extension \"%1\" failed to load because %2"), _name, reason);
346     error_file_write(_error_reason);
347 }
348 
349 /**
350     \return  The XML tree that is used to define the extension
351     \brief   A getter for the internal Repr, does not add a reference.
352 */
353 Inkscape::XML::Node *
get_repr()354 Extension::get_repr ()
355 {
356     return repr;
357 }
358 
359 /**
360     \return  The textual id of this extension
361     \brief   Get the ID of this extension - not a copy don't delete!
362 */
363 gchar *
get_id() const364 Extension::get_id () const
365 {
366     return _id;
367 }
368 
369 /**
370     \return  The textual name of this extension
371     \brief   Get the name of this extension - not a copy don't delete!
372 */
373 const gchar *
get_name() const374 Extension::get_name () const
375 {
376     return get_translation(_name, nullptr);
377 }
378 
379 /**
380     \return  None
381     \brief   This function diactivates the extension (which makes it
382              unusable, but not deleted)
383 
384     This function is used to removed an extension from functioning, but
385     not delete it completely.  It sets the state to \c STATE_DEACTIVATED to
386     mark to the world that it has been deactivated.  It also removes
387     the current implementation and replaces it with a standard one.  This
388     makes it so that we don't have to continually check if there is an
389     implementation, but we are guaranteed to have a benign one.
390 
391     \warning It is important to note that there is no 'activate' function.
392     Running this function is irreversable.
393 */
394 void
deactivate()395 Extension::deactivate ()
396 {
397     set_state(STATE_DEACTIVATED);
398 
399     /* Removing the old implementation, and making this use the default. */
400     /* This should save some memory */
401     delete imp;
402     imp = new Implementation::Implementation();
403 
404     return;
405 }
406 
407 /**
408     \return  Whether the extension has been deactivated
409     \brief   Find out the status of the extension
410 */
411 bool
deactivated()412 Extension::deactivated ()
413 {
414     return get_state() == STATE_DEACTIVATED;
415 }
416 
417 /** Gets the location of the dependency file as an absolute path
418   *
419   * Iterates over all dependencies of this extension and finds the one with matching name,
420   * then returns the absolute path to this dependency file as determined previously.
421   *
422   * TODO: This function should not be necessary, but we parse script dependencies twice:
423   *       - Once here in the Extension::Extension() constructor
424   *       - A second time in Script::load() in "script.cpp" when determining the script location
425   *       Theoretically we could return the wrong path if an extension depends on two files with the same name
426   *       in different relative locations. In practice this risk should be close to zero, though.
427   *
428   * @return Absolute path of the dependency file
429   */
get_dependency_location(const char * name)430 std::string Extension::get_dependency_location(const char *name)
431 {
432     for (auto dep : _deps) {
433         if (!strcmp(name, dep->get_name())) {
434             return dep->get_path();
435         }
436     }
437 
438     return "";
439 }
440 
441 /** recursively searches directory for a file named filename; returns true if found */
_find_filename_recursive(std::string directory,std::string const & filename)442 static bool _find_filename_recursive(std::string directory, std::string const &filename) {
443     Glib::Dir dir(directory);
444 
445     std::string name = dir.read_name();
446     while (!name.empty()) {
447         std::string fullpath = Glib::build_filename(directory, name);
448         // g_message("%s", fullpath.c_str());
449 
450         if (Glib::file_test(fullpath, Glib::FILE_TEST_IS_DIR)) {
451             if (_find_filename_recursive(fullpath, filename)) {
452                 return true;
453             }
454         } else if (name == filename) {
455             return true;
456         }
457         name = dir.read_name();
458     }
459 
460     return false;
461 }
462 
463 /** Searches for a gettext catalog matching the extension's translationdomain
464   *
465   * This function will attempt to find the correct gettext catalog for the translationdomain
466   * requested by the extension.
467   *
468   * For this the following three locations are recursively searched for "${translationdomain}.mo":
469   *  - the 'locale' directory in the .inx file's folder
470   *  - the 'locale' directory in the "extensions" folder containing the .inx
471   *  - the system location for gettext catalogs, i.e. where Inkscape's own catalog is located
472   *
473   * If one matching file is found, the directory is assumed to be the correct location and registered with gettext
474   */
lookup_translation_catalog()475 void Extension::lookup_translation_catalog() {
476     g_assert(!_base_directory.empty());
477 
478     // get locale folder locations
479     std::string locale_dir_current_extension;
480     std::string locale_dir_extensions;
481     std::string locale_dir_system;
482 
483     locale_dir_current_extension = Glib::build_filename(_base_directory, "locale");
484 
485     size_t index = _base_directory.find_last_of("extensions");
486     if (index != std::string::npos) {
487         locale_dir_extensions = Glib::build_filename(_base_directory.substr(0, index+1), "locale");
488     }
489 
490     locale_dir_system = bindtextdomain("inkscape", nullptr);
491 
492     // collect unique locations into vector
493     std::vector<std::string> locale_dirs;
494     if (locale_dir_current_extension != locale_dir_extensions) {
495         locale_dirs.push_back(std::move(locale_dir_current_extension));
496     }
497     locale_dirs.push_back(std::move(locale_dir_extensions));
498     locale_dirs.push_back(std::move(locale_dir_system));
499 
500     // iterate over locations and look for the one that has the correct catalog
501     std::string search_name;
502     search_name += _translationdomain;
503     search_name += ".mo";
504     for (auto locale_dir : locale_dirs) {
505         if (!Glib::file_test(locale_dir, Glib::FILE_TEST_IS_DIR)) {
506             continue;
507         }
508 
509         if (_find_filename_recursive(locale_dir, search_name)) {
510             _gettext_catalog_dir = locale_dir;
511             break;
512         }
513     }
514 
515     // register catalog with gettext if found, disable translation for this extension otherwise
516     if (!_gettext_catalog_dir.empty()) {
517         const char *current_dir = bindtextdomain(_translationdomain, nullptr);
518         if (_gettext_catalog_dir != current_dir) {
519             g_info("Binding textdomain '%s' to '%s'.", _translationdomain, _gettext_catalog_dir.c_str());
520             bindtextdomain(_translationdomain, _gettext_catalog_dir.c_str());
521             bind_textdomain_codeset(_translationdomain, "UTF-8");
522         }
523     } else {
524         g_warning("Failed to locate message catalog for textdomain '%s'.", _translationdomain);
525         _translation_enabled = false;
526         _translationdomain = nullptr;
527     }
528 }
529 
530 /** Gets a translation within the context of the current extension
531   *
532   * Query gettext for the translated version of the input string,
533   * handling the preferred translation domain of the extension internally.
534   *
535   * @param   msgid   String to translate
536   * @param   msgctxt Context for the translation
537   *
538   * @return  Translated string (or original string if extension is not supposed to be translated)
539   */
get_translation(const char * msgid,const char * msgctxt) const540 const char *Extension::get_translation(const char *msgid, const char *msgctxt) const {
541     if (!_translation_enabled) {
542         return msgid;
543     }
544 
545     if (!strcmp(msgid, "")) {
546         g_warning("Attempting to translate an empty string in extension '%s', which is not supported.", _id);
547         return msgid;
548     }
549 
550     if (msgctxt) {
551         return g_dpgettext2(_translationdomain, msgctxt, msgid);
552     } else {
553         return g_dgettext(_translationdomain, msgid);
554     }
555 }
556 
557 /** Sets environment suitable for executing this Extension
558   *
559   * Currently sets the environment variables INKEX_GETTEXT_DOMAIN and INKEX_GETTEXT_DIRECTORY
560   * to make the "translationdomain" accessible to child processes spawned by this extension's Implementation.
561   *
562   * @param   doc   Optional document, if provided sets the DOCUMENT_PATH from the document's save location.
563   */
set_environment(const SPDocument * doc)564 void Extension::set_environment(const SPDocument *doc) {
565     Glib::unsetenv("INKEX_GETTEXT_DOMAIN");
566     Glib::unsetenv("INKEX_GETTEXT_DIRECTORY");
567 
568     // This is needed so extensions can interact with the user's profile, keep settings etc.
569     Glib::setenv("INKSCAPE_PROFILE_DIR", std::string(Inkscape::IO::Resource::profile_path()));
570 
571     // This is needed so files can be saved relative to their document location (see image-extract)
572     if (doc) {
573         auto path = doc->getDocumentURI();
574         if (!path) {
575             path = ""; // Set to blank string so extensions know the difference between old inkscape and not-saved document.
576         }
577         Glib::setenv("DOCUMENT_PATH", std::string(path));
578     }
579 
580     if (_translationdomain) {
581         Glib::setenv("INKEX_GETTEXT_DOMAIN", std::string(_translationdomain));
582     }
583     if (!_gettext_catalog_dir.empty()) {
584         Glib::setenv("INKEX_GETTEXT_DIRECTORY", _gettext_catalog_dir);
585     }
586 }
587 
588 /** Uses the object's type to figure out what the type is.
589   *
590   * @return  Returns the type of extension that this object is.
591   */
get_implementation_type()592 ModuleImpType Extension::get_implementation_type()
593 {
594     if (dynamic_cast<Implementation::Script *>(imp)) {
595         return MODULE_EXTENSION;
596     } else if (dynamic_cast<Implementation::XSLT *>(imp)) {
597         return MODULE_XSLT;
598     }
599     // MODULE_UNKNOWN_IMP is not required because it never results in an
600     // object being created. Thus this function wouldn't be available.
601     return MODULE_PLUGIN;
602 }
603 
604 /**
605     \brief  A function to get the parameters in a string form
606     \return An array with all the parameters in it.
607 
608 */
609 void
paramListString(std::list<std::string> & retlist)610 Extension::paramListString (std::list <std::string> &retlist)
611 {
612     // first collect all widgets in the current extension
613     std::vector<InxWidget *> widget_list;
614     for (auto widget : _widgets) {
615         widget->get_widgets(widget_list);
616     }
617 
618     // then build a list of parameter strings from parameter names and values, as '--name=value'
619     for (auto widget : widget_list) {
620         InxParameter *parameter = dynamic_cast<InxParameter *>(widget); // filter InxParameters from InxWidgets
621         if (parameter) {
622             const char *name = parameter->name();
623             std::string value = parameter->value_to_string();
624 
625             if (name && !value.empty()) { // TODO: Shouldn't empty string values be allowed?
626                 std::string parameter_string;
627                 parameter_string += "--";
628                 parameter_string += name;
629                 parameter_string += "=";
630                 parameter_string += value;
631                 retlist.push_back(parameter_string);
632             }
633         }
634     }
635 
636     return;
637 }
638 
get_param(const gchar * name)639 InxParameter *Extension::get_param(const gchar *name)
640 {
641     if (!name || _widgets.empty()) {
642         throw Extension::param_not_exist();
643     }
644 
645     // first collect all widgets in the current extension
646     std::vector<InxWidget *> widget_list;
647     for (auto widget : _widgets) {
648         widget->get_widgets(widget_list);
649     }
650 
651     // then search for a parameter with a matching name
652     for (auto widget : widget_list) {
653         InxParameter *parameter = dynamic_cast<InxParameter *>(widget); // filter InxParameters from InxWidgets
654         if (parameter && !strcmp(parameter->name(), name)) {
655             return parameter;
656         }
657     }
658 
659     // if execution reaches here, no parameter matching 'name' was found
660     throw Extension::param_not_exist();
661 }
662 
get_param(const gchar * name) const663 InxParameter const *Extension::get_param(const gchar *name) const
664 {
665     return const_cast<Extension *>(this)->get_param(name);
666 }
667 
668 
669 /**
670     \return   The value of the parameter identified by the name
671     \brief    Gets a parameter identified by name with the bool placed in value.
672     \param    name   The name of the parameter to get
673 
674     Look up in the parameters list, const then execute the function on that found parameter.
675 */
676 bool
get_param_bool(const gchar * name) const677 Extension::get_param_bool(const gchar *name) const
678 {
679     const InxParameter *param;
680     param = get_param(name);
681     return param->get_bool();
682 }
683 
684 /**
685     \return   The integer value for the parameter specified
686     \brief    Gets a parameter identified by name with the integer placed in value.
687     \param    name   The name of the parameter to get
688 
689     Look up in the parameters list, const then execute the function on that found parameter.
690 */
691 int
get_param_int(const gchar * name) const692 Extension::get_param_int(const gchar *name) const
693 {
694     const InxParameter *param;
695     param = get_param(name);
696     return param->get_int();
697 }
698 
699 /**
700     \return   The float value for the parameter specified
701     \brief    Gets a parameter identified by name with the float in value.
702     \param    name   The name of the parameter to get
703 
704     Look up in the parameters list, const then execute the function on that found parameter.
705 */
706 float
get_param_float(const gchar * name) const707 Extension::get_param_float(const gchar *name) const
708 {
709     const InxParameter *param;
710     param = get_param(name);
711     return param->get_float();
712 }
713 
714 /**
715     \return   The string value for the parameter specified
716     \brief    Gets a parameter identified by name with the string placed in value.
717     \param    name   The name of the parameter to get
718 
719     Look up in the parameters list, const then execute the function on that found parameter.
720 */
721 const char *
get_param_string(const gchar * name) const722 Extension::get_param_string(const gchar *name) const
723 {
724     const InxParameter *param;
725     param = get_param(name);
726     return param->get_string();
727 }
728 
729 /**
730     \return   The string value for the parameter specified
731     \brief    Gets a parameter identified by name with the string placed in value.
732     \param    name   The name of the parameter to get
733 
734     Look up in the parameters list, const then execute the function on that found parameter.
735 */
736 const char *
get_param_optiongroup(const gchar * name) const737 Extension::get_param_optiongroup(const gchar *name) const
738 {
739     const InxParameter *param;
740     param = get_param(name);
741     return param->get_optiongroup();
742 }
743 
744 /**
745  * This is useful to find out, if a given string \c value is selectable in a optiongroup named \cname.
746  *
747  * @param  name The name of the optiongroup parameter to get.
748  * @return true if value exists, false if not
749  */
750 bool
get_param_optiongroup_contains(const gchar * name,const char * value) const751 Extension::get_param_optiongroup_contains(const gchar *name, const char *value) const
752 {
753     const InxParameter *param;
754     param = get_param(name);
755     return param->get_optiongroup_contains(value);
756 }
757 
758 /**
759     \return   The unsigned integer RGBA value for the parameter specified
760     \brief    Gets a parameter identified by name with the unsigned int placed in value.
761     \param    name   The name of the parameter to get
762 
763     Look up in the parameters list, const then execute the function on that found parameter.
764 */
765 guint32
get_param_color(const gchar * name) const766 Extension::get_param_color(const gchar *name) const
767 {
768     const InxParameter *param;
769     param = get_param(name);
770     return param->get_color();
771 }
772 
773 /**
774     \return   The passed in value
775     \brief    Sets a parameter identified by name with the boolean in the parameter value.
776     \param    name   The name of the parameter to set
777     \param    value  The value to set the parameter to
778 
779     Look up in the parameters list, const then execute the function on that found parameter.
780 */
781 bool
set_param_bool(const gchar * name,const bool value)782 Extension::set_param_bool(const gchar *name, const bool value)
783 {
784     InxParameter *param;
785     param = get_param(name);
786     return param->set_bool(value);
787 }
788 
789 /**
790     \return   The passed in value
791     \brief    Sets a parameter identified by name with the integer in the parameter value.
792     \param    name   The name of the parameter to set
793     \param    value  The value to set the parameter to
794 
795     Look up in the parameters list, const then execute the function on that found parameter.
796 */
797 int
set_param_int(const gchar * name,const int value)798 Extension::set_param_int(const gchar *name, const int value)
799 {
800     InxParameter *param;
801     param = get_param(name);
802     return param->set_int(value);
803 }
804 
805 /**
806     \return   The passed in value
807     \brief    Sets a parameter identified by name with the float in the parameter value.
808     \param    name   The name of the parameter to set
809     \param    value  The value to set the parameter to
810 
811     Look up in the parameters list, const then execute the function on that found parameter.
812 */
813 float
set_param_float(const gchar * name,const float value)814 Extension::set_param_float(const gchar *name, const float value)
815 {
816     InxParameter *param;
817     param = get_param(name);
818     return param->set_float(value);
819 }
820 
821 /**
822     \return   The passed in value
823     \brief    Sets a parameter identified by name with the string in the parameter value.
824     \param    name   The name of the parameter to set
825     \param    value  The value to set the parameter to
826 
827     Look up in the parameters list, const then execute the function on that found parameter.
828 */
829 const char *
set_param_string(const gchar * name,const char * value)830 Extension::set_param_string(const gchar *name, const char *value)
831 {
832     InxParameter *param;
833     param = get_param(name);
834     return param->set_string(value);
835 }
836 
837 /**
838     \return   The passed in value
839     \brief    Sets a parameter identified by name with the string in the parameter value.
840     \param    name   The name of the parameter to set
841     \param    value  The value to set the parameter to
842 
843     Look up in the parameters list, const then execute the function on that found parameter.
844 */
845 const char *
set_param_optiongroup(const gchar * name,const char * value)846 Extension::set_param_optiongroup(const gchar *name, const char *value)
847 {
848     InxParameter *param;
849     param = get_param(name);
850     return param->set_optiongroup(value);
851 }
852 
853 /**
854     \return   The passed in value
855     \brief    Sets a parameter identified by name with the unsigned integer RGBA value in the parameter value.
856     \param    name   The name of the parameter to set
857     \param    value  The value to set the parameter to
858 
859 Look up in the parameters list, const then execute the function on that found parameter.
860 */
861 guint32
set_param_color(const gchar * name,const guint32 color)862 Extension::set_param_color(const gchar *name, const guint32 color)
863 {
864     InxParameter *param;
865     param = get_param(name);
866     return param->set_color(color);
867 }
868 
869 
870 /** \brief A function to open the error log file. */
871 void
error_file_open()872 Extension::error_file_open ()
873 {
874     gchar *ext_error_file = Inkscape::IO::Resource::log_path(EXTENSION_ERROR_LOG_FILENAME);
875     error_file = Inkscape::IO::fopen_utf8name(ext_error_file, "w+");
876     if (!error_file) {
877         g_warning(_("Could not create extension error log file '%s'"), ext_error_file);
878     }
879     g_free(ext_error_file);
880 };
881 
882 /** \brief A function to close the error log file. */
883 void
error_file_close()884 Extension::error_file_close ()
885 {
886     if (error_file) {
887         fclose(error_file);
888     }
889 };
890 
891 /** \brief A function to write to the error log file. */
892 void
error_file_write(Glib::ustring text)893 Extension::error_file_write (Glib::ustring text)
894 {
895     if (error_file) {
896         g_fprintf(error_file, "%s\n", text.c_str());
897     }
898 };
899 
900 /** \brief  A widget to represent the inside of an AutoGUI widget */
901 class AutoGUI : public Gtk::Box {
902 public:
903     /** \brief  Create an AutoGUI object */
AutoGUI()904     AutoGUI () : Gtk::Box(Gtk::ORIENTATION_VERTICAL) {};
905 
906     /**
907      * Adds a widget with a tool tip into the autogui.
908      *
909      * If there is no widget, nothing happens.  Otherwise it is just
910      * added into the VBox.  If there is a tooltip (non-NULL) then it
911      * is placed on the widget.
912      *
913      * @param widg Widget to add.
914      * @param tooltip Tooltip for the widget.
915      */
addWidget(Gtk::Widget * widg,gchar const * tooltip,int indent)916     void addWidget(Gtk::Widget *widg, gchar const *tooltip, int indent) {
917         if (widg) {
918             widg->set_margin_start(indent * InxParameter::GUI_INDENTATION);
919             this->pack_start(*widg, false, true, 0); // fill=true does not have an effect here, but allows the
920                                                      // child to choose to expand by setting hexpand/vexpand
921             if (tooltip) {
922                 widg->set_tooltip_text(tooltip);
923             } else {
924                 widg->set_tooltip_text("");
925                 widg->set_has_tooltip(false);
926             }
927         }
928     };
929 };
930 
931 /** \brief  A function to automatically generate a GUI from the extensions' widgets
932     \return Generated widget
933 
934     This function just goes through each widget, and calls it's 'get_widget'.
935     Then, each of those is placed into a Gtk::VBox, which is then returned to the calling function.
936 
937     If there are no visible parameters, this function just returns NULL.
938 */
939 Gtk::Widget *
autogui(SPDocument * doc,Inkscape::XML::Node * node,sigc::signal<void> * changeSignal)940 Extension::autogui (SPDocument *doc, Inkscape::XML::Node *node, sigc::signal<void> *changeSignal)
941 {
942     if (!_gui || widget_visible_count() == 0) {
943         return nullptr;
944     }
945 
946     AutoGUI * agui = Gtk::manage(new AutoGUI());
947     agui->set_border_width(InxParameter::GUI_BOX_MARGIN);
948     agui->set_spacing(InxParameter::GUI_BOX_SPACING);
949 
950     // go through the list of widgets and add the all non-hidden ones
951     for (auto widget : _widgets) {
952         if (widget->get_hidden()) {
953             continue;
954         }
955 
956         Gtk::Widget *widg = widget->get_widget(changeSignal);
957         gchar const *tip = widget->get_tooltip();
958         int indent = widget->get_indent();
959 
960         agui->addWidget(widg, tip, indent);
961     }
962 
963     agui->show();
964     return agui;
965 };
966 
967 /* Extension editor dialog stuff */
968 
969 Gtk::Box *
get_info_widget()970 Extension::get_info_widget()
971 {
972     Gtk::Box * retval = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
973     retval->set_border_width(4);
974 
975     Gtk::Frame * info = Gtk::manage(new Gtk::Frame("General Extension Information"));
976     retval->pack_start(*info, true, true, 4);
977 
978     auto table = Gtk::manage(new Gtk::Grid());
979     table->set_border_width(4);
980     table->set_column_spacing(4);
981 
982     info->add(*table);
983 
984     int row = 0;
985     add_val(_("Name:"), get_translation(_name), table, &row);
986     add_val(_("ID:"), _id, table, &row);
987     add_val(_("State:"), _state == STATE_LOADED ? _("Loaded") : _state == STATE_UNLOADED ? _("Unloaded") : _("Deactivated"), table, &row);
988 
989     retval->show_all();
990     return retval;
991 }
992 
add_val(Glib::ustring labelstr,Glib::ustring valuestr,Gtk::Grid * table,int * row)993 void Extension::add_val(Glib::ustring labelstr, Glib::ustring valuestr, Gtk::Grid * table, int * row)
994 {
995     Gtk::Label * label;
996     Gtk::Label * value;
997 
998     (*row)++;
999     label = Gtk::manage(new Gtk::Label(labelstr, Gtk::ALIGN_START));
1000     value = Gtk::manage(new Gtk::Label(valuestr, Gtk::ALIGN_START));
1001 
1002     table->attach(*label, 0, (*row) - 1, 1, 1);
1003     table->attach(*value, 1, (*row) - 1, 1, 1);
1004 
1005     label->show();
1006     value->show();
1007 
1008     return;
1009 }
1010 
1011 Gtk::Box *
get_params_widget()1012 Extension::get_params_widget()
1013 {
1014     Gtk::Box * retval = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
1015     Gtk::Widget * content = Gtk::manage(new Gtk::Label("Params"));
1016     retval->pack_start(*content, true, true, 4);
1017     content->show();
1018     retval->show();
1019     return retval;
1020 }
1021 
widget_visible_count()1022 unsigned int Extension::widget_visible_count ( )
1023 {
1024     unsigned int _visible_count = 0;
1025     for (auto widget : _widgets) {
1026         if (!widget->get_hidden()) {
1027             _visible_count++;
1028         }
1029     }
1030     return _visible_count;
1031 }
1032 
1033 }  /* namespace Extension */
1034 }  /* namespace Inkscape */
1035 
1036 
1037 /*
1038   Local Variables:
1039   mode:c++
1040   c-file-style:"stroustrup"
1041   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1042   indent-tabs-mode:nil
1043   fill-column:99
1044   End:
1045 */
1046 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
1047