Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
preferences.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
5/* Authors:
6 * Krzysztof KosiƄski <tweenk.pl@gmail.com>
7 * Jon A. Cruz <jon@joncruz.org>
8 *
9 * Copyright (C) 2008,2009 Authors
10 *
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
14#include "preferences.h"
15
16#include <cstring>
17#include <ctime>
18#include <glib/gstdio.h>
19#include <glibmm/convert.h>
20#include <glibmm/i18n.h>
21#include <glibmm/stringutils.h>
22#include <iomanip>
23#include <sstream>
24#include <utility>
25
26#include "attribute-rel-util.h"
27#include "colors/color.h"
28#include "colors/manager.h"
29#include "io/resource.h"
32#include "util/units.h"
34#include "xml/node-iterators.h"
35#include "xml/node-observer.h"
36
37#define PREFERENCES_FILE_NAME "preferences.xml"
38
39namespace Inkscape {
40
41static Inkscape::XML::Document *loadImpl( std::string const& prefsFilename, Glib::ustring & errMsg );
43
45
46// private inner class definition
47
54class Preferences::PrefNodeObserver : public XML::NodeObserver {
55public:
56 PrefNodeObserver(Observer &o, Glib::ustring filter) :
57 _observer(o),
58 _filter(std::move(filter))
59 {}
60 ~PrefNodeObserver() override = default;
61 void notifyAttributeChanged(XML::Node &node, GQuark name, Util::ptr_shared, Util::ptr_shared) override;
62private:
63 Observer &_observer;
64 Glib::ustring const _filter;
65};
66
68{
70
72 _load();
73
74 _initialized = true;
75}
76
78{
79 // unref XML document
81}
82
90{
91 _prefs_doc = sp_repr_read_mem(preferences_skeleton, PREFERENCES_SKELETON_SIZE, nullptr);
92#ifdef _WIN32
93 setBool("/options/desktopintegration/value", 1);
94#endif
95#if defined(GDK_WINDOWING_QUARTZ)
96 // No maximise for Quartz, see lp:1302627
97 setInt("/options/defaultwindowsize/value", -1);
98#endif
99
100}
101
108{
109 Glib::ustring const not_saved = _("Inkscape will run with default settings, "
110 "and new settings will not be saved. ");
111
112 // NOTE: After we upgrade to Glib 2.16, use Glib::ustring::compose
113
114 // 1. Does the file exist?
115 if (!g_file_test(_prefs_filename.c_str(), G_FILE_TEST_EXISTS)) {
116 auto _prefs_dir = Inkscape::IO::Resource::profile_path();
117 // No - we need to create one.
118 // Does the profile directory exist?
119 if (!g_file_test(_prefs_dir.c_str(), G_FILE_TEST_EXISTS)) {
120 // No - create the profile directory
121 if (g_mkdir_with_parents(_prefs_dir.c_str(), 0755)) {
122 // the creation failed
123 //_reportError(Glib::ustring::compose(_("Cannot create profile directory %1."),
124 // Glib::filename_to_utf8(_prefs_dir)), not_saved);
125 gchar *msg = g_strdup_printf(_("Cannot create profile directory %s."), _prefs_dir.c_str());
126 _reportError(msg, not_saved);
127 g_free(msg);
128 return;
129 }
130 } else if (!g_file_test(_prefs_dir.c_str(), G_FILE_TEST_IS_DIR)) {
131 // The profile dir is not actually a directory
132 //_reportError(Glib::ustring::compose(_("%1 is not a valid directory."),
133 // Glib::filename_to_utf8(_prefs_dir)), not_saved);
134 gchar *msg = g_strdup_printf(_("%s is not a valid directory."), _prefs_dir.c_str());
135 _reportError(msg, not_saved);
136 g_free(msg);
137 return;
138 }
139 // create some subdirectories for user stuff
140 char const *user_dirs[] = {"extensions", "fonts", "icons", "keys", "palettes", "templates", nullptr};
141 for (int i=0; user_dirs[i]; ++i) {
142 // XXX Why are we doing this here? shouldn't this be an IO load item?
143 auto dir = Inkscape::IO::Resource::profile_path(user_dirs[i]);
144 if (!g_file_test(dir.c_str(), G_FILE_TEST_EXISTS))
145 g_mkdir(dir.c_str(), 0755);
146 }
147 // The profile dir exists and is valid.
148 if (!g_file_set_contents(_prefs_filename.c_str(), preferences_skeleton, PREFERENCES_SKELETON_SIZE, nullptr)) {
149 // The write failed.
150 //_reportError(Glib::ustring::compose(_("Failed to create the preferences file %1."),
151 // Glib::filename_to_utf8(_prefs_filename)), not_saved);
152 gchar *msg = g_strdup_printf(_("Failed to create the preferences file %s."),
153 Glib::filename_to_utf8(_prefs_filename).c_str());
154 _reportError(msg, not_saved);
155 g_free(msg);
156 return;
157 }
158
159 if ( migrateFromDoc ) {
161 }
162
163 // The prefs file was just created.
164 // We can return now and skip the rest of the load process.
165 _writable = true;
166 return;
167 }
168
169 // Yes, the pref file exists.
170 Glib::ustring errMsg;
171 Inkscape::XML::Document *prefs_read = loadImpl( _prefs_filename, errMsg );
172
173 if ( prefs_read ) {
174 // Merge the loaded prefs with defaults.
175 _prefs_doc->root()->mergeFrom(prefs_read->root(), "id");
176 Inkscape::GC::release(prefs_read);
177 _writable = true;
178 } else {
179 _reportError(errMsg, not_saved);
180 }
181}
182
183//_reportError(msg, not_saved);
184static Inkscape::XML::Document *loadImpl( std::string const& prefsFilename, Glib::ustring & errMsg )
185{
186 // 2. Is it a regular file?
187 if (!g_file_test(prefsFilename.c_str(), G_FILE_TEST_IS_REGULAR)) {
188 gchar *msg = g_strdup_printf(_("The preferences file %s is not a regular file."),
189 Glib::filename_to_utf8(prefsFilename).c_str());
190 errMsg = msg;
191 g_free(msg);
192 return nullptr;
193 }
194
195 // 3. Is the file readable?
196 gchar *prefs_xml = nullptr; gsize len = 0;
197 if (!g_file_get_contents(prefsFilename.c_str(), &prefs_xml, &len, nullptr)) {
198 gchar *msg = g_strdup_printf(_("The preferences file %s could not be read."),
199 Glib::filename_to_utf8(prefsFilename).c_str());
200 errMsg = msg;
201 g_free(msg);
202 return nullptr;
203 }
204
205 // 4. Is it valid XML?
206 Inkscape::XML::Document *prefs_read = sp_repr_read_mem(prefs_xml, len, nullptr);
207 g_free(prefs_xml);
208 if (!prefs_read) {
209 gchar *msg = g_strdup_printf(_("The preferences file %s is not a valid XML document."),
210 Glib::filename_to_utf8(prefsFilename).c_str());
211 errMsg = msg;
212 g_free(msg);
213 return nullptr;
214 }
215
216 // 5. Basic sanity check: does the root element have a correct name?
217 if (strcmp(prefs_read->root()->name(), "inkscape")) {
218 gchar *msg = g_strdup_printf(_("The file %s is not a valid Inkscape preferences file."),
219 Glib::filename_to_utf8(prefsFilename).c_str());
220 errMsg = msg;
221 g_free(msg);
222 Inkscape::GC::release(prefs_read);
223 return nullptr;
224 }
225
226 return prefs_read;
227}
228
230{
231 // TODO pull in additional prefs with more granularity
232 to->root()->mergeFrom(from->root(), "id");
233}
234
239{
240 // no-op if the prefs file is not writable
241 if (_writable) {
242 // sp_repr_save_file uses utf-8 instead of the glib filename encoding.
243 // I don't know why filenames are kept in utf-8 in Inkscape and then
244 // converted to filename encoding when necessary through special functions
245 // - wouldn't it be easier to keep things in the encoding they are supposed
246 // to be in?
247
248 // No, it would not. There are many reasons, one key reason being that the
249 // rest of GTK+ is explicitly UTF-8. From an engineering standpoint, keeping
250 // the filesystem encoding would change things from a one-to-many problem to
251 // instead be a many-to-many problem. Also filesystem encoding can change
252 // from one run of the program to the next, so can not be stored.
253 // There are many other factors, so ask if you would like to learn them. - JAC
254 Glib::ustring utf8name = Glib::filename_to_utf8(_prefs_filename);
255 if (!utf8name.empty()) {
256 sp_repr_save_file(_prefs_doc, utf8name.c_str());
257 }
258 }
259}
260
265{
266 time_t sptime = time (nullptr);
267 struct tm *sptm = localtime (&sptime);
268 gchar sptstr[256];
269 strftime(sptstr, 256, "%Y_%m_%d_%H_%M_%S", sptm);
270
271 char *new_name = g_strdup_printf("%s_%s.xml", _prefs_filename.c_str(), sptstr);
272
273
274 if (g_file_test(_prefs_filename.c_str(), G_FILE_TEST_EXISTS)) {
275 //int retcode = g_unlink (_prefs_filename.c_str());
276 int retcode = g_rename (_prefs_filename.c_str(), new_name );
277 if (retcode == 0) g_warning("%s %s.", _("Preferences file was backed up to"), new_name);
278 else g_warning("%s", _("There was an error trying to reset the preferences file."));
279 }
280
281 g_free(new_name);
282 _observer_map.clear();
284 _prefs_doc = nullptr;
286 _load();
287 save();
288}
289
290bool Preferences::getLastError( Glib::ustring& primary, Glib::ustring& secondary )
291{
292 bool result = _hasError;
293 if ( _hasError ) {
294 primary = _lastErrPrimary;
295 secondary = _lastErrSecondary;
296 _hasError = false;
297 _lastErrPrimary.clear();
298 _lastErrSecondary.clear();
299 } else {
300 primary.clear();
301 secondary.clear();
302 }
303 return result;
304}
305
306// Now for the meat.
307
314std::vector<Preferences::Entry> Preferences::getAllEntries(Glib::ustring const &path)
315{
316 std::vector<Entry> temp;
317 Inkscape::XML::Node *node = _getNode(path, false);
318 if (node) {
319 for (const auto & iter : node->attributeList()) {
320 temp.emplace_back(path + '/' + g_quark_to_string(iter.key), iter.value.pointer());
321 }
322 }
323 return temp;
324}
325
332std::vector<Glib::ustring> Preferences::getAllDirs(Glib::ustring const &path)
333{
334 std::vector<Glib::ustring> temp;
335 Inkscape::XML::Node *node = _getNode(path, false);
336 if (node) {
338 if (i->attribute("id") == nullptr) {
339 continue;
340 }
341 temp.push_back(path + '/' + i->attribute("id"));
342 }
343 }
344 return temp;
345}
346
347// getter methods
348
349Preferences::Entry const Preferences::getEntry(Glib::ustring const &pref_path)
350{
351 // This function uses caching because it is called very often.
352 // We implement caching also for the negative case, where no preference exists.
353 // For standard GUI preferences, the negative case is usually not needed
354 // because they should have their default value in preferences_skeleton.h.
355 // However, for preferences of extensions (i.e., the options that the user can select in the
356 // Extension dialog), this case is quite common (ca. 1000 times on Inkscape startup).
357
358 // Negative caching is done "implicitly" here by storing std::nullopt in the cache
359 // and passing it to Entry().
360
361 if (_initialized) {
362 // get cached value, if it exists
363 auto it = cachedEntry.find(pref_path.raw());
364 if (it != cachedEntry.end()) {
365 auto const &cacheResult = it->second;
366 return cacheResult;
367 }
368 }
369
370 auto const entry = Entry(pref_path, _getRawValue(pref_path));
371
372 if (_initialized) {
373 // write to cache
374 // Note: Several other functions in this class also write to `cachedEntry`.
375 cachedEntry[pref_path.raw()] = entry;
376 }
377 return entry;
378}
379
380// setter methods
381
388void Preferences::setBool(Glib::ustring const &pref_path, bool value)
389{
393 _setRawValue(pref_path, ( value ? "1" : "0" ));
394}
395
402void Preferences::setPoint(Glib::ustring const &pref_path, Geom::Point value)
403{
404 setDouble(pref_path + "/x", value[Geom::X]);
405 setDouble(pref_path + "/y", value[Geom::Y]);
406}
407
414void Preferences::setInt(Glib::ustring const &pref_path, int value)
415{
417}
418
425void Preferences::setUInt(Glib::ustring const &pref_path, unsigned int value)
426{
428}
429
436void Preferences::setDouble(Glib::ustring const &pref_path, double value)
437{
438 static constexpr auto digits10 = std::numeric_limits<double>::digits10; // number of decimal digits that are ensured to be precise
439 _setRawValue(pref_path, Inkscape::ustring::format_classic(std::setprecision(digits10), value));
440}
441
449void Preferences::setDoubleUnit(Glib::ustring const &pref_path, double value, Glib::ustring const &unit_abbr)
450{
451 static constexpr auto digits10 = std::numeric_limits<double>::digits10; // number of decimal digits that are ensured to be precise
452 Glib::ustring str = Glib::ustring::compose("%1%2", Inkscape::ustring::format_classic(std::setprecision(digits10), value), unit_abbr);
453 _setRawValue(pref_path, str);
454}
455
456void Preferences::setColor(Glib::ustring const &pref_path, Colors::Color const &color)
457{
458 _setRawValue(pref_path, color.toString());
459}
460
467void Preferences::setString(Glib::ustring const &pref_path, Glib::ustring const &value)
468{
469 _setRawValue(pref_path, value);
470}
471
472void Preferences::setStyle(Glib::ustring const &pref_path, SPCSSAttr *style)
473{
474 Glib::ustring css_str;
475 sp_repr_css_write_string(style, css_str);
476 _setRawValue(pref_path, css_str);
477}
478
479void Preferences::mergeStyle(Glib::ustring const &pref_path, SPCSSAttr *style)
480{
481 SPCSSAttr *current = getStyle(pref_path);
484 Glib::ustring css_str;
486 _setRawValue(pref_path, css_str);
488}
489
494void Preferences::remove(Glib::ustring const &pref_path)
495{
496 cachedEntry.erase(pref_path);
497
498 Inkscape::XML::Node *node = _getNode(pref_path, false);
499 if (node && node->parent()) {
501 } else { //Handle to remove also attributes in path not only the container node
502 // verify path
503 g_assert( pref_path.at(0) == '/' );
504 if (_prefs_doc == nullptr){
505 return;
506 }
507 node = _prefs_doc->root();
508 Inkscape::XML::Node *child = nullptr;
509 gchar **splits = g_strsplit(pref_path.c_str(), "/", 0);
510 if ( splits ) {
511 for (int part_i = 0; splits[part_i]; ++part_i) {
512 // skip empty path segments
513 if (!splits[part_i][0]) {
514 continue;
515 }
516 if (!node->firstChild()) {
517 node->removeAttribute(splits[part_i]);
518 g_strfreev(splits);
519 return;
520 }
521 for (child = node->firstChild(); child; child = child->next()) {
522 if (!strcmp(splits[part_i], child->attribute("id"))) {
523 break;
524 }
525 }
526 node = child;
527 }
528 }
529 g_strfreev(splits);
530 }
531}
532
536class Preferences::_ObserverData
537{
538public:
539 _ObserverData(Inkscape::XML::Node *node, bool isAttr) : _node(node), _is_attr(isAttr) {}
540
541 Inkscape::XML::Node *_node;
542 bool _is_attr;
543};
544
546 observed_path(std::move(path))
547{
548}
549
551{
552 // on destruction remove observer to prevent invalid references
554 prefs->removeObserver(*this);
555}
556
557void Preferences::PrefNodeObserver::notifyAttributeChanged(XML::Node &node, GQuark name, Util::ptr_shared, Util::ptr_shared new_value)
558{
559 // filter out attributes we don't watch
560 gchar const *attr_name = g_quark_to_string(name);
561 if ( _filter.empty() || (_filter == attr_name) ) {
562 _ObserverData *d = Preferences::_get_pref_observer_data(_observer);
563 Glib::ustring notify_path = _observer.observed_path;
564
565 if (!d->_is_attr) {
566 std::vector<gchar const *> path_fragments;
567 notify_path.reserve(256); // this will make appending operations faster
568
569 // walk the XML tree, saving each of the id attributes in a vector
570 // we terminate when we hit the observer's attachment node, because the path to this node
571 // is already stored in notify_path
572 for (XML::NodeParentIterator n = &node; static_cast<XML::Node*>(n) != d->_node; ++n) {
573 path_fragments.push_back(n->attribute("id"));
574 }
575 // assemble the elements into a path
576 for (std::vector<gchar const *>::reverse_iterator i = path_fragments.rbegin(); i != path_fragments.rend(); ++i) {
577 notify_path.push_back('/');
578 notify_path.append(*i);
579 }
580
581 // append attribute name
582 notify_path.push_back('/');
583 notify_path.append(attr_name);
584 }
585 std::optional<Glib::ustring> new_value_converted = std::nullopt;
586 if (new_value.pointer() != nullptr) {
587 new_value_converted = Glib::ustring(new_value.pointer());
588 }
589 _observer.notify(Preferences::Entry(notify_path, new_value_converted));
590 }
591}
592
596XML::Node *Preferences::_findObserverNode(Glib::ustring const &pref_path, Glib::ustring &node_key, Glib::ustring &attr_key, bool create)
597{
598 // first assume that the last path element is an entry.
599 _keySplit(pref_path, node_key, attr_key);
600
601 // find the node corresponding to the "directory".
603 if (!node) {
604 return nullptr;
605 }
606
607 for (child = node->firstChild(); child; child = child->next()) {
608 // If there is a node with id corresponding to the attr key,
609 // this means that the last part of the path is actually a key (folder).
610 // Change values accordingly.
611 if (attr_key == child->attribute("id")) {
612 node = child;
613 attr_key = "";
614 node_key = pref_path;
615 break;
616 }
617 }
618 return node;
619}
620
622{
623 // prevent adding the same observer twice
624 if ( _observer_map.find(&o) == _observer_map.end() ) {
625 Glib::ustring node_key, attr_key;
627 node = _findObserverNode(o.observed_path, node_key, attr_key, true);
628 if (node) {
629 // set additional data
630 o._data.reset(new _ObserverData(node, !attr_key.empty()));
631
632 _observer_map[&o].reset(new PrefNodeObserver(o, attr_key));
633
634 // if we watch a single pref, we want to receive notifications only for a single node
635 if (o._data->_is_attr) {
636 node->addObserver( *(_observer_map[&o]) );
637 } else {
639 }
640 } else {
641 g_warning("Failed to add a preference observer because the key does not exist: %s",
642 o.observed_path.c_str());
643 }
644 }
645}
646
648{
649 // prevent removing an observer which was not added
650 auto it = _observer_map.find(&o);
651 if (it != _observer_map.end()) {
652 Inkscape::XML::Node *node = o._data->_node;
653 _ObserverData *priv_data = o._data.get();
654
655 if (priv_data->_is_attr) {
656 node->removeObserver(*it->second);
657 } else {
658 node->removeSubtreeObserver(*it->second);
659 }
660
661 _observer_map.erase(it);
662 }
663}
664
665
677Inkscape::XML::Node *Preferences::_getNode(Glib::ustring const &pref_key, bool create)
678{
679 // verify path
680 g_assert( pref_key.empty() || pref_key.at(0) == '/' ); // empty corresponds to root node
681 // No longer necessary, can cause problems with input devices which have a dot in the name
682 // g_assert( pref_key.find('.') == Glib::ustring::npos );
683
684 if (_prefs_doc == nullptr){
685 return nullptr;
686 }
688 Inkscape::XML::Node *child = nullptr;
689 gchar **splits = g_strsplit(pref_key.c_str(), "/", 0);
690
691 if ( splits ) {
692 for (int part_i = 0; splits[part_i]; ++part_i) {
693 // skip empty path segments
694 if (!splits[part_i][0]) {
695 continue;
696 }
697
698 for (child = node->firstChild(); child; child = child->next()) {
699 if (child->attribute("id") == nullptr) {
700 continue;
701 }
702 if (!strcmp(splits[part_i], child->attribute("id"))) {
703 break;
704 }
705 }
706
707 // If the previous loop found a matching key, child now contains the node
708 // matching the processed key part. If no node was found then it is NULL.
709 if (!child) {
710 if (create) {
711 // create the rest of the key
712 while(splits[part_i]) {
713 child = node->document()->createElement("group");
714 child->setAttribute("id", splits[part_i]);
716
717 ++part_i;
718 node = child;
719 }
720 g_strfreev(splits);
721 splits = nullptr;
722 return node;
723 } else {
724 g_strfreev(splits);
725 splits = nullptr;
726 return nullptr;
727 }
728 }
729
730 node = child;
731 }
732 g_strfreev(splits);
733 }
734 return node;
735}
736
740std::optional<Glib::ustring> Preferences::_getRawValue(Glib::ustring const &path)
741{
742 // create node and attribute keys
743 Glib::ustring node_key, attr_key;
744 _keySplit(path, node_key, attr_key);
745
746 // retrieve the attribute
747 Inkscape::XML::Node *node = _getNode(node_key, false);
748 if ( node == nullptr ) {
749 return std::nullopt;
750 }
751 gchar const *attr = node->attribute(attr_key.c_str());
752 if ( attr == nullptr ) {
753 return std::nullopt;
754 }
755 return attr;
756}
757
758void Preferences::_setRawValue(Glib::ustring const &path, Glib::ustring const &value)
759{
760 // create node and attribute keys
761 Glib::ustring node_key, attr_key;
762 _keySplit(path, node_key, attr_key);
763
764 // update cache first, so by the time notification change fires and observers are called,
765 // they have access to current settings even if they watch a group
766 if (_initialized) {
767 cachedEntry[path.raw()] = Entry(path, value);
768 }
769
770 // set the attribute
771 Inkscape::XML::Node *node = _getNode(node_key, true);
772 node->setAttribute(attr_key, value);
773}
774
775// The Entry::isValid* methods check if the preference exists, and then verify if the data would be
776// correctly converted to the requested type.
777
779{
780 if (!isSet()) {
781 return false;
782 }
783 auto const &s = _value.value().raw();
784 // format is currently "0"/"1", may change to "true"/"false" in the future
785 // see Preferences::setBool()
786 return (s == "1" || s == "0" || s == "true" || s == "false");
787}
788
790{
791 if (!isSet()) {
792 return false;
793 }
794
795 auto const &s = _value.value().raw();
796
797 // true, false are treated as 1, 0 by getInt(), even though it's not entirely appropriate
798 // we're gonna treat them as valid integers here
799 if (s == "true" || s == "false") {
800 // warn that we're treating "true" and "false" as integers
801 g_warning("Integer preference value are set as boolean: '%s', treating it as %d: %s", s.c_str(),
802 s == "true" ? 1 : 0, _pref_path.c_str());
803 return true;
804 }
805
806 errno = 0;
807
808 const char* cstr = s.c_str();
809 char* endPtr = nullptr;
810 long value = strtol(cstr, &endPtr, 0);
811 if (endPtr == cstr) {
812 // no valid number found
813 return false;
814 }
815 // checking for overflow is unecessary because long is the same size
816 // as int on all modern platforms, this is somewhat pedantic
817 if (errno == ERANGE || value < INT_MIN || value > INT_MAX) {
818 return false; // overflow
819 }
820
821 // getInt() will also happily retrieve unsigned integers as overflow them
822 // However we have a getUInt() method for that, we're gonna therefore
823 // treat them as invalid.
824
825 return true;
826}
827
829{
830 if (!isSet()) {
831 return false;
832 }
833
834 auto const &s = _value.value().raw();
835
836 errno = 0;
837 const char* cstr = s.c_str();
838 char* end_ptr = nullptr;
839 unsigned long value = strtoul(cstr, &end_ptr, 0);
840 if (end_ptr == cstr) {
841 return false;
842 }
843 if (errno == ERANGE || value > UINT_MAX) {
844 return false; // overflow
845 }
846
847 return true;
848}
849
851{
852 if (!isSet()) {
853 return false;
854 }
855
856 auto const &value_str = _value.value().raw();
857 std::string::size_type end_index = 0;
858
859 try {
860 Glib::Ascii::strtod(value_str, end_index, 0);
861 } catch (std::runtime_error const &e) {
862 return false;
863 }
864
865 if(end_index == 0) {
866 return false; // failed to read anything numeric
867 }
868
869 // extract the unit if any, and check if it's a valid unit
870 auto unit = value_str.substr(end_index);
871 if(!unit.empty()) {
872 return Util::UnitTable::get().hasUnit(unit);
873 }
874
875 return true;
876}
877
878bool Preferences::Entry::isConvertibleTo(Glib::ustring const &type) const
879{
880 auto from = getUnit();
881 if (!from.empty()) {
882 auto to = Util::UnitTable::get().getUnit(type);
883 return to->compatibleWith(from);
884 }
885
886 // if the unit is empty
887 return false;
888}
889
891{
892 if (!isSet()) {
893 return false;
894 }
895
896 return Colors::Color::parse(_value.value().raw()).has_value();
897}
898
899// The Entry::get* methods convert the preference string from the XML file back to the original value.
900// The conversions here are the inverse of Preferences::set*.
901
902bool Preferences::Entry::getBool(bool def) const
903{
904 if (!isSet()) {
905 return def;
906 }
907 if (cached_bool) {
908 return value_bool;
909 }
910 cached_bool = true;
911 // .raw() is only for performance reasons (std::string comparison is faster than Glib::ustr)
912 auto const &s = _value.value().raw();
913 // format is currently "0"/"1", may change to "true"/"false" in the future, see Preferences::setBool()
914 if (s == "1" || s == "true") {
915 value_bool = true;
916 } else if (s == "0" || s == "false") {
917 value_bool = false;
918 } else {
919 [[unlikely]];
920 value_bool = false;
921 g_warning("Bool preference value has invalid format. '%s' (raw value: %s)", _pref_path.c_str(), s.c_str());
922 }
923 return value_bool;
924}
925
926Colors::Color Preferences::Entry::getColor(std::string const &def) const
927{
928 if (isSet()) {
929 // Note: we don't cache the resulting Color object
930 // because this function is called rarely and therefore not performance-relevant
931 // (exemplary Inkscape startup: 40 calls to getColor vs. 10k calls to getBool())
932 if (auto res = Colors::Color::parse(_value.value().raw())) {
933 return *res;
934 }
935 }
936 if (auto res = Colors::Color::parse(def)) {
937 return *res;
938 }
939 // Transparent black is the default's default
940 return Colors::Color(0x00000000);
941}
942
944{
945 if (!isSet()) {
946 return def;
947 }
948 if (cached_int) {
949 return value_int;
950 }
951 cached_int = true;
952 // .raw() is only for performance reasons (std::string comparison is faster than Glib::ustr)
953 auto const &s = _value.value().raw();
954 if (s == "true") {
955 g_warning("Integer preference value is set as true, treating it as 1: %s", _pref_path.c_str());
956 value_int = 1;
957 } else if (s == "false") {
958 g_warning("Integer preference value is set as false, treating it as 0: %s", _pref_path.c_str());
959 value_int = 0;
960 } else {
961 int val = 0;
962
963 // TODO: We happily save unsigned integers (notably RGBA values) as signed integers and overflow as needed.
964 // We should consider adding an unsigned integer type to preferences or use HTML colors where appropriate
965 // (the latter would breaks backwards compatibility, though)
966 errno = 0;
967 val = (int)strtol(s.c_str(), nullptr, 0);
968 if (errno == ERANGE) {
969 errno = 0;
970 val = (int)strtoul(s.c_str(), nullptr, 0);
971 if (errno == ERANGE) {
972 g_warning("Integer preference out of range: '%s' (raw value: %s)", _pref_path.c_str(), s.c_str());
973 val = 0;
974 }
975 }
976 value_int = val;
977 }
978 return value_int;
979}
980
981int Preferences::Entry::getIntLimited(int def, int min, int max) const
982{
983 int val = getInt(def);
984 return (val >= min && val <= max ? val : def);
985}
986
987unsigned int Preferences::Entry::getUInt(unsigned int def) const
988{
989 if (!isSet()) {
990 return def;
991 }
992 if (cached_uint) {
993 return value_uint;
994 }
995 cached_uint = true;
996
997 // Note: 'strtoul' can also read overflowed (i.e. negative) signed int values that we used to save before we
998 // had the unsigned type, so this is fully backwards compatible and can be replaced seamlessly
999 unsigned int val = 0;
1000 auto const &s = _value.value();
1001 errno = 0;
1002 val = (unsigned int)strtoul(s.c_str(), nullptr, 0);
1003 if (errno == ERANGE) {
1004 g_warning("Unsigned integer preference out of range: '%s' (raw value: %s)", _pref_path.c_str(), s.c_str());
1005 val = 0;
1006 }
1007
1008 value_uint = val;
1009 return value_uint;
1010}
1011
1014{
1015 g_assert(_value.has_value());
1016 if (cached_double) {
1017 return value_double;
1018 }
1019 cached_double = true;
1020 try {
1021 value_double = Glib::Ascii::strtod(_value.value().raw());
1022 } catch (const std::runtime_error &e) {
1023 value_double = 0;
1024 g_warning("Double preference out of range: '%s' (raw value: %s)", _pref_path.c_str(), _value.value().c_str());
1025 }
1026 return value_double;
1027}
1028
1029double Preferences::Entry::getDouble(double def, Glib::ustring const &requested_unit) const
1030{
1031 if (!isSet()) {
1032 return def;
1033 }
1034
1035 double val = _getDoubleAssumeExisting();
1036 if (requested_unit.length() == 0) {
1037 // no unit specified, don't do conversion
1038 return val;
1039 }
1040 return Util::Quantity::convert(val, getUnit(), requested_unit);
1041}
1042
1043double Preferences::Entry::getDoubleLimited(double def, double min, double max, Glib::ustring const &unit) const
1044{
1045 double val = getDouble(def, unit);
1046 return (val >= min && val <= max ? val : def);
1047}
1048
1049Glib::ustring Preferences::Entry::getString(Glib::ustring const &def) const
1050{
1051 return _value.value_or(def);
1052}
1053
1054Glib::ustring Preferences::Entry::getUnit() const
1055{
1056 if (!isSet()) {
1057 return "";
1058 }
1059 if (cached_unit) {
1060 return value_unit;
1061 }
1062
1063 // Determine unit from preference value,
1064 // e.g., pref_value_str = "123.45px" --> value_unit = "px".
1065 auto const &pref_value_str = _value.value().raw();
1066 std::string::size_type end_index = 0;
1067 try {
1068 Glib::Ascii::strtod(pref_value_str, end_index, 0);
1069 } catch (const std::runtime_error &e) {
1070 end_index = 0;
1071 }
1072 if (end_index == 0) {
1073 [[unlikely]];
1074 // isSet() == true but the string is:
1075 // - is empty
1076 // - or does not start with a numeric value
1077 // - or the number is out of range (double over/underflow)
1078 // --> cannot determine unit
1079 g_warning("Double preference value has invalid format. Failed to extract unit for '%s' (raw value: %s)",
1080 _pref_path.c_str(), pref_value_str.c_str());
1081 value_unit = "";
1082 } else {
1083 value_unit = pref_value_str.substr(end_index, pref_value_str.size());
1084 }
1085
1086 cached_unit = true;
1087 return value_unit;
1088}
1089
1091{
1092 if (!isSet()) {
1093 return sp_repr_css_attr_new();
1094 }
1095 // Note: the resulting style object is not cached.
1096 // This does not hurt performance because getStyle() is called rarely.
1097 // (For a typical Inkscape startup, 20 calls to getStyle vs 10k calls to getBool.)
1099 sp_repr_css_attr_add_from_string(style, _value.value().c_str());
1100 return style;
1101}
1102
1104{
1105 // This method is quite "dirty". We ignore whatever is stored this Entry
1106 // and just get the style from Preferences.
1107 // A more beautiful solution would need major refactoring of Entry and Preferences.
1108 if (!isSet()) {
1109 return sp_repr_css_attr_new();
1110 }
1111
1113}
1114
1116{
1117 Glib::ustring path_base = _pref_path;
1118 path_base.erase(0, path_base.rfind('/') + 1);
1119 return path_base;
1120}
1121
1123{
1124 Glib::ustring node_key, attr_key;
1125 _keySplit(prefPath, node_key, attr_key);
1126
1127 Inkscape::XML::Node *node = _getNode(node_key, false);
1128 return sp_repr_css_attr_inherited(node, attr_key.c_str());
1129}
1130
1131// XML backend helper: Split the path into a node key and an attribute key.
1132void Preferences::_keySplit(Glib::ustring const &pref_path, Glib::ustring &node_key, Glib::ustring &attr_key)
1133{
1134 // everything after the last slash
1135 attr_key = pref_path.substr(pref_path.rfind('/') + 1, Glib::ustring::npos);
1136 // everything before the last slash
1137 node_key = pref_path.substr(0, pref_path.rfind('/'));
1138}
1139
1140void Preferences::_reportError(Glib::ustring const &msg, Glib::ustring const &secondary)
1141{
1142 _hasError = true;
1144 _lastErrSecondary = secondary;
1145 if (_errorHandler) {
1146 _errorHandler->handleError(msg, secondary);
1147 }
1148}
1150{
1151 _errorHandler = handler;
1152}
1153
1155{
1156 if (_instance)
1157 {
1158 if (save) {
1159 _instance->save();
1160 }
1161 delete _instance;
1162 _instance = nullptr;
1163 }
1164}
1165
1167{ //
1168 return Glib::filename_to_utf8(_prefs_filename);
1169}
1170
1172
1173
1175 Glib::ustring path, std::function<void (const Preferences::Entry& new_value)> callback) {
1176 assert(callback);
1177
1178 return PrefObserver(new Preferences::PreferencesObserver(std::move(path), std::move(callback)));
1179}
1180
1181Preferences::PreferencesObserver::PreferencesObserver(Glib::ustring path, std::function<void (const Preferences::Entry&)> callback) :
1182 Observer(std::move(path)), _callback(std::move(callback)) {
1183
1184 auto prefs = Inkscape::Preferences::get();
1185 prefs->addObserver(*this);
1186}
1187
1189 _callback(new_val);
1190}
1191
1193 auto prefs = Inkscape::Preferences::get();
1194 _callback(prefs->getEntry(observed_path));
1195}
1196
1197PrefObserver Preferences::createObserver(Glib::ustring path, std::function<void (const Preferences::Entry&)> callback) {
1198 return Preferences::PreferencesObserver::create(path, std::move(callback));
1199}
1200
1201PrefObserver Preferences::createObserver(Glib::ustring path, std::function<void ()> callback) {
1202 return createObserver(std::move(path), [=](const Entry&) { callback(); });
1203}
1204
1205Colors::Color Preferences::getColor(Glib::ustring const &pref_path, std::string const &def)
1206{
1207 return getEntry(pref_path).getColor(def);
1208}
1209
1210
1211} // namespace Inkscape
1212
1213/*
1214 Local Variables:
1215 mode:c++
1216 c-file-style:"stroustrup"
1217 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1218 indent-tabs-mode:nil
1219 fill-column:99
1220 End:
1221*/
1222// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
TODO: insert short description here.
void sp_attribute_purge_default_style(SPCSSAttr *css, unsigned int flags)
Remove CSS style properties with default values.
Utility functions related to parsing and validation of XML attributes.
@ SP_ATTRCLEAN_DEFAULT_REMOVE
Two-dimensional point that doubles as a vector.
Definition point.h:66
std::string toString(bool opacity=true) const
Format the color as a css string and return it.
Definition color.cpp:106
virtual void handleError(Glib::ustring const &primary, Glib::ustring const &secondary) const =0
Data type representing a typeless value of a preference.
unsigned int getUInt(unsigned int def=0) const
Interpret the preference as an unsigned integer.
bool isValidBool() const
Check if the preference value can be interpreted as a Boolean.
double getDouble(double def=0.0, Glib::ustring const &unit="") const
Interpret the preference as a floating point value.
double _getDoubleAssumeExisting() const
get double value, assert that the value is set
bool isValidUInt() const
Check if the preference value can be interpreted as an unsigned integer.
Glib::ustring getEntryName() const
Get the last component of the preference's path.
Glib::ustring getString(Glib::ustring const &def="") const
Interpret the preference as an UTF-8 string.
bool isValidDouble() const
Check if the preference value can be interpreted as a floating point value.
int getIntLimited(int def=0, int min=INT_MIN, int max=INT_MAX) const
Interpret the preference as a limited integer.
Glib::ustring getUnit() const
Interpret the preference as a number followed by a unit (without space), and return this unit string.
bool isConvertibleTo(Glib::ustring const &unit) const
Check if the preference value can be converted to a particular unit.
bool isValidColor() const
Check if the preference value can be interpreted as a color.
bool getBool(bool def=false) const
Interpret the preference as a Boolean value.
SPCSSAttr * getInheritedStyle() const
Interpret the preference as a CSS style with directory-based inheritance.
int getInt(int def=0) const
Interpret the preference as an integer.
Colors::Color getColor(std::string const &def) const
Interpret the preference as a css color value.
double getDoubleLimited(double def=0.0, double min=DBL_MIN, double max=DBL_MAX, Glib::ustring const &unit="") const
Interpret the preference as a limited floating point value.
SPCSSAttr * getStyle() const
Interpret the preference as a CSS style.
bool isValidInt() const
Check if the preference value can be interpreted as an integer without any overflow.
Base class for preference observers.
Definition preferences.h:87
std::unique_ptr< _ObserverData > _data
additional data used by the implementation while the observer is active
Glib::ustring const observed_path
Path which the observer watches.
Observer(Glib::ustring path)
Constructor.
Callback-based preferences observer.
void call()
Manually call the observer with the original, unchanged value.
PreferencesObserver(Glib::ustring path, std::function< void(const Preferences::Entry &new_value)> callback)
void notify(Preferences::Entry const &new_val) override
Notification about a preference change.
static std::unique_ptr< PreferencesObserver > create(Glib::ustring path, std::function< void(const Preferences::Entry &new_value)> callback)
Preference storage class.
Definition preferences.h:66
void _load()
Load the user's customized preferences.
void setDoubleUnit(Glib::ustring const &pref_path, double value, Glib::ustring const &unit_abbr)
Set a floating point value with unit.
Glib::ustring getUnit(Glib::ustring const &pref_path)
Retrieve the unit string.
double getDouble(Glib::ustring const &pref_path, double def=0.0, Glib::ustring const &unit="")
Retrieve a floating point value.
std::vector< Entry > getAllEntries(Glib::ustring const &path)
Get all entries from the specified directory.
void reset()
Deletes the preferences.xml file.
void setStyle(Glib::ustring const &pref_path, SPCSSAttr *style)
Set a CSS style.
void _keySplit(Glib::ustring const &pref_path, Glib::ustring &node_key, Glib::ustring &attr_key)
static Preferences * get()
Access the singleton Preferences object.
bool getLastError(Glib::ustring &primary, Glib::ustring &secondary)
Return details of the last encountered error, if any.
Colors::Color getColor(Glib::ustring const &pref_path, std::string const &def="black")
Glib::ustring getPrefsFilename() const
Get the preferences file name in UTF-8.
void setUInt(Glib::ustring const &pref_path, unsigned int value)
Set an unsigned integer value.
ErrorReporter * _errorHandler
Pointer to object reporting errors.
Entry const getEntry(Glib::ustring const &pref_path)
Retrieve a preference entry without specifying its type.
SPCSSAttr * getStyle(Glib::ustring const &pref_path)
Retrieve a CSS style.
XML::Node * _findObserverNode(Glib::ustring const &pref_path, Glib::ustring &node_key, Glib::ustring &attr_key, bool create)
Find the XML node to observe.
static Preferences * _instance
std::optional< Glib::ustring > _getRawValue(Glib::ustring const &path)
Get raw value for preference path, without any caching.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
static _ObserverData * _get_pref_observer_data(Observer &o)
void _setRawValue(Glib::ustring const &path, Glib::ustring const &value)
std::unique_ptr< PreferencesObserver > createObserver(Glib::ustring path, std::function< void(const Preferences::Entry &new_value)> callback)
Create an observer watching preference 'path' and calling provided function when preference changes.
void setColor(Glib::ustring const &pref_path, Colors::Color const &color)
Set an RGBA color value.
void setString(Glib::ustring const &pref_path, Glib::ustring const &value)
Set an UTF-8 string value.
Glib::ustring _lastErrPrimary
Last primary error message, if any.
Glib::ustring _lastErrSecondary
Last secondary error message, if any.
void setErrorHandler(ErrorReporter *handler)
void removeObserver(Observer &)
Remove an observer an prevent further notifications to it.
friend class PrefNodeObserver
SPCSSAttr * _getInheritedStyleForPath(Glib::ustring const &prefPath)
Interpret the preference as a CSS style with directory-based inheritance.
void _loadDefaults()
Load internal defaults.
XML::Document * _prefs_doc
XML document storing all the preferences.
XML::Node * _getNode(Glib::ustring const &pref_path, bool create=false)
Get the XML node corresponding to the given pref key.
void _reportError(Glib::ustring const &, Glib::ustring const &)
std::vector< Glib::ustring > getAllDirs(Glib::ustring const &path)
Get all subdirectories of the specified directory.
void setPoint(Glib::ustring const &pref_path, Geom::Point value)
Set a point value.
bool _writable
Will the preferences be saved at exit?
bool _hasError
Indication that some error has occurred;.
void setDouble(Glib::ustring const &pref_path, double value)
Set a floating point value.
void setInt(Glib::ustring const &pref_path, int value)
Set an integer value.
static void unload(bool save=true)
Unload all preferences.
void addObserver(Observer &)
Register a preference observer.
void setBool(Glib::ustring const &pref_path, bool value)
Set a Boolean value.
bool _initialized
Is this instance fully initialized? Caching should be avoided before.
void mergeStyle(Glib::ustring const &pref_path, SPCSSAttr *style)
Merge a CSS style with the current preference value.
std::unordered_map< std::string, Entry > cachedEntry
Cache for getEntry()
std::string _prefs_filename
Full filename (with directory) of the prefs file.
void save()
Save all preferences to the hard disk.
_ObsMap _observer_map
Map that keeps track of wrappers assigned to PrefObservers.
void remove(Glib::ustring const &pref_path)
Remove a node from prefs.
static double convert(double from_dist, Unit const *from, Unit const *to)
Convert distances.
Definition units.cpp:588
bool hasUnit(Glib::ustring const &name) const
Remove a unit definition from the given unit type table * / DISABLED, unsafe with the current passing...
Definition units.cpp:400
static UnitTable & get()
Definition units.cpp:441
Unit const * getUnit(Glib::ustring const &name) const
Retrieve a given unit based on its string identifier.
Definition units.cpp:316
bool compatibleWith(Unit const *u) const
Checks if a unit is compatible with the specified unit.
Definition units.cpp:192
char const * pointer() const
Definition share.h:33
Interface for XML node observers.
Interface for refcounted XML nodes.
Definition node.h:80
virtual Node * parent()=0
Get the parent of this node.
virtual void addSubtreeObserver(NodeObserver &observer)=0
Add an object that will be notified of the changes to this node and its descendants.
virtual void appendChild(Node *child)=0
Append a node as the last child of this node.
virtual char const * name() const =0
Get the name of the element node.
virtual const AttributeVector & attributeList() const =0
Get a list of the node's attributes.
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual void removeSubtreeObserver(NodeObserver &observer)=0
Remove an object from the subtree observers list.
virtual Node * firstChild()=0
Get the first child of this node.
virtual void mergeFrom(Node const *src, char const *key, bool extension=false, bool clean=false)=0
Merge all children of another node with the current.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
void removeAttribute(Inkscape::Util::const_char_ptr key)
Remove an attribute of this node.
Definition node.h:280
virtual void addObserver(NodeObserver &observer)=0
Add an object that will be notified of the changes to this node.
virtual Document * document()=0
Get the node's associated document.
virtual void removeChild(Node *child)=0
Remove a child of this node.
virtual void removeObserver(NodeObserver &observer)=0
Remove an object from the list of observers.
virtual Node * root()=0
Get the root node of this node's document.
Css & result
Glib::ustring msg
static char const *const current
Definition dir-util.cpp:71
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Inkscape::XML::Node * node
static R & release(R &r)
Decrements the reference count of a anchored object.
std::string profile_path()
Definition resource.cpp:415
Glib::ustring format_classic(T const &... args)
Helper class to stream background task notifications as a series of messages.
static Inkscape::XML::Document * migrateFromDoc
static Inkscape::XML::Document * loadImpl(std::string const &prefsFilename, Glib::ustring &errMsg)
std::unique_ptr< Preferences::PreferencesObserver > PrefObserver
static void migrateDetails(Inkscape::XML::Document *from, Inkscape::XML::Document *to)
STL namespace.
Interface for XML node observers.
TODO: insert short description here.
static char const preferences_skeleton[]
Singleton class to access the preferences file in a convenient way.
Ocnode * child[8]
Definition quantize.cpp:33
SPCSSAttr * sp_repr_css_attr_new()
Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
Definition repr-css.cpp:67
void sp_repr_css_write_string(SPCSSAttr *css, Glib::ustring &str)
Write a style attribute string from a list of properties stored in an SPCSAttr object.
Definition repr-css.cpp:243
void sp_repr_css_merge(SPCSSAttr *dst, SPCSSAttr *src)
Merges two SPCSSAttr's.
Definition repr-css.cpp:299
SPCSSAttr * sp_repr_css_attr_inherited(Node const *repr, gchar const *attr)
Creates a new SPCSSAttr with one attribute whose value is determined by cascading.
Definition repr-css.cpp:116
void sp_repr_css_attr_unref(SPCSSAttr *css)
Unreferences an SPCSSAttr (will be garbage collected if no references remain).
Definition repr-css.cpp:76
void sp_repr_css_attr_add_from_string(SPCSSAttr *css, gchar const *p)
Use libcroco to parse a string for CSS properties and then merge them into an existing SPCSSAttr.
Definition repr-css.cpp:341
Document * sp_repr_read_mem(const gchar *buffer, gint length, const gchar *default_ns)
Reads and parses XML from a buffer, returning it as an Document.
Definition repr-io.cpp:324
bool sp_repr_save_file(Document *doc, gchar const *const filename, gchar const *default_ns)
Returns true iff file successfully saved.
Definition repr-io.cpp:724
Inkscape::IO::Resource - simple resource API.
auto len
Definition safe-printf.h:21
guint32 GQuark
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
std::unique_ptr< Toolbar >(* create)()
Definition toolbars.cpp:56
Glib::ustring name
Definition toolbars.cpp:55