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