1 #include "Settings.h"
2 
3 #include <utility>
4 
5 #include "model/FormatDefinitions.h"
6 #include "util/DeviceListHelper.h"
7 
8 #include "ButtonConfig.h"
9 #include "Util.h"
10 #include "filesystem.h"
11 #include "i18n.h"
12 
13 constexpr auto const* DEFAULT_FONT = "Sans";
14 constexpr auto DEFAULT_FONT_SIZE = 12;
15 
16 #define SAVE_BOOL_PROP(var) xmlNode = saveProperty((const char*)#var, (var) ? "true" : "false", root)
17 #define SAVE_STRING_PROP(var) xmlNode = saveProperty((const char*)#var, (var).empty() ? "" : (var).c_str(), root)
18 #define SAVE_INT_PROP(var) xmlNode = saveProperty((const char*)#var, var, root)
19 #define SAVE_UINT_PROP(var) xmlNode = savePropertyUnsigned((const char*)#var, var, root)
20 #define SAVE_DOUBLE_PROP(var) xmlNode = savePropertyDouble((const char*)#var, var, root)
21 #define ATTACH_COMMENT(var)                     \
22     com = xmlNewComment((const xmlChar*)(var)); \
23     xmlAddPrevSibling(xmlNode, com);
24 
Settings(fs::path filepath)25 Settings::Settings(fs::path filepath): filepath(std::move(filepath)) { loadDefault(); }
26 
~Settings()27 Settings::~Settings() {
28     for (auto& i: this->buttonConfig) {
29         delete i;
30         i = nullptr;
31     }
32 }
33 
loadDefault()34 void Settings::loadDefault() {
35     this->pressureSensitivity = true;
36     this->minimumPressure = 0.05;
37     this->pressureMultiplier = 1.0;
38     this->pressureGuessing = false;
39     this->zoomGesturesEnabled = true;
40 
41     this->maximized = false;
42     this->showPairedPages = false;
43     this->presentationMode = false;
44 
45     this->numColumns = 1;  // only one of these applies at a time
46     this->numRows = 1;
47     this->viewFixedRows = false;
48 
49     this->layoutVertical = false;
50     this->layoutRightToLeft = false;
51     this->layoutBottomToTop = false;
52 
53     this->numPairsOffset = 1;
54 
55     this->zoomStep = 10.0;
56     this->zoomStepScroll = 2.0;
57 
58     this->displayDpi = 72;
59 
60     this->font.setName(DEFAULT_FONT);
61     this->font.setSize(DEFAULT_FONT_SIZE);
62 
63     this->mainWndWidth = 800;
64     this->mainWndHeight = 600;
65 
66     this->showSidebar = true;
67     this->sidebarWidth = 150;
68 
69     this->showToolbar = true;
70 
71     this->sidebarOnRight = false;
72 
73     this->scrollbarOnLeft = false;
74 
75     this->menubarVisible = true;
76 
77     this->autoloadMostRecent = false;
78     this->autoloadPdfXoj = true;
79 
80     this->stylusCursorType = STYLUS_CURSOR_DOT;
81     this->highlightPosition = false;
82     this->cursorHighlightColor = 0x80FFFF00;  // Yellow with 50% opacity
83     this->cursorHighlightRadius = 30.0;
84     this->cursorHighlightBorderColor = 0x800000FF;  // Blue with 50% opacity
85     this->cursorHighlightBorderWidth = 0.0;
86     this->darkTheme = false;
87     this->useStockIcons = false;
88     this->scrollbarHideType = SCROLLBAR_HIDE_NONE;
89     this->disableScrollbarFadeout = false;
90 
91     // Set this for autosave frequency in minutes.
92     this->autosaveTimeout = 3;
93     this->autosaveEnabled = true;
94 
95     this->addHorizontalSpace = false;
96     this->addHorizontalSpaceAmount = 150;
97     this->addVerticalSpace = false;
98     this->addVerticalSpaceAmount = 150;
99 
100     // Drawing direction emulates modifier keys
101     this->drawDirModsRadius = 50;
102     this->drawDirModsEnabled = false;
103 
104     this->snapRotation = true;
105     this->snapRotationTolerance = 0.30;
106 
107     this->snapGrid = true;
108     this->snapGridTolerance = 0.50;
109     this->snapGridSize = DEFAULT_GRID_SIZE;
110 
111     this->touchDrawing = false;
112 
113     this->defaultSaveName = _("%F-Note-%H-%M");
114 
115     // Eraser
116     this->buttonConfig[BUTTON_ERASER] =
117             new ButtonConfig(TOOL_ERASER, Color{0x000000U}, TOOL_SIZE_NONE, DRAWING_TYPE_DEFAULT, ERASER_TYPE_NONE);
118     // Middle button
119     this->buttonConfig[BUTTON_MOUSE_MIDDLE] =
120             new ButtonConfig(TOOL_HAND, Color{0x000000U}, TOOL_SIZE_NONE, DRAWING_TYPE_DEFAULT, ERASER_TYPE_NONE);
121     // Right button
122     this->buttonConfig[BUTTON_MOUSE_RIGHT] =
123             new ButtonConfig(TOOL_NONE, Color{0x000000U}, TOOL_SIZE_NONE, DRAWING_TYPE_DEFAULT, ERASER_TYPE_NONE);
124     // Touch
125     this->buttonConfig[BUTTON_TOUCH] =
126             new ButtonConfig(TOOL_NONE, Color{0x000000U}, TOOL_SIZE_NONE, DRAWING_TYPE_DEFAULT, ERASER_TYPE_NONE);
127     // Default config
128     this->buttonConfig[BUTTON_DEFAULT] =
129             new ButtonConfig(TOOL_PEN, Color{0x000000U}, TOOL_SIZE_FINE, DRAWING_TYPE_DEFAULT, ERASER_TYPE_NONE);
130     // Pen button 1
131     this->buttonConfig[BUTTON_STYLUS_ONE] =
132             new ButtonConfig(TOOL_NONE, Color{0x000000U}, TOOL_SIZE_NONE, DRAWING_TYPE_DEFAULT, ERASER_TYPE_NONE);
133     // Pen button 2
134     this->buttonConfig[BUTTON_STYLUS_TWO] =
135             new ButtonConfig(TOOL_NONE, Color{0x000000U}, TOOL_SIZE_NONE, DRAWING_TYPE_DEFAULT, ERASER_TYPE_NONE);
136 
137     this->fullscreenHideElements = "mainMenubar";
138     this->presentationHideElements = "mainMenubar,sidebarContents";
139 
140     this->touchZoomStartThreshold = 0.0;
141 
142     this->pageRerenderThreshold = 5.0;
143     this->pdfPageCacheSize = 10;
144     this->preloadPagesBefore = 3U;
145     this->preloadPagesAfter = 5U;
146     this->eagerPageCleanup = true;
147 
148     this->selectionBorderColor = 0xff0000U;  // red
149     this->selectionMarkerColor = 0x729fcfU;  // light blue
150 
151     this->backgroundColor = 0xdcdad5U;
152 
153     // clang-format off
154 	this->pageTemplate = "xoj/template\ncopyLastPageSettings=true\nsize=595.275591x841.889764\nbackgroundType=lined\nbackgroundColor=#ffffff\n";
155     // clang-format on
156 
157     this->audioSampleRate = 44100.0;
158     this->audioInputDevice = -1;
159     this->audioOutputDevice = -1;
160     this->audioGain = 1.0;
161     this->defaultSeekTime = 5;
162 
163     this->pluginEnabled = "";
164     this->pluginDisabled = "";
165 
166     this->numIgnoredStylusEvents = 0;
167 
168     this->inputSystemTPCButton = false;
169     this->inputSystemDrawOutsideWindow = true;
170 
171     this->strokeFilterIgnoreTime = 150;
172     this->strokeFilterIgnoreLength = 1;
173     this->strokeFilterSuccessiveTime = 500;
174     this->strokeFilterEnabled = false;
175     this->doActionOnStrokeFiltered = false;
176     this->trySelectOnStrokeFiltered = false;
177 
178     this->snapRecognizedShapesEnabled = false;
179     this->restoreLineWidthEnabled = false;
180 
181     this->inTransaction = false;
182 
183     /**
184      * Stabilizer related settings
185      */
186     this->stabilizerAveragingMethod = StrokeStabilizer::AveragingMethod::NONE;
187     this->stabilizerPreprocessor = StrokeStabilizer::Preprocessor::NONE;
188     this->stabilizerBuffersize = 20;
189     this->stabilizerSigma = 0.5;
190     this->stabilizerDeadzoneRadius = 1.3;
191     this->stabilizerCuspDetection = true;
192     this->stabilizerDrag = 0.4;
193     this->stabilizerMass = 5.0;
194     this->stabilizerFinalizeStroke = true;
195     /**/
196 }
197 
198 /**
199  * tempg_ascii_strtod
200  * 	Transition to using g_ascii_strtod to minimize disruption. May, 2019.
201  *  Delete this and replace calls to this function with calls to g_ascii_strtod() in 2020.
202  * 	See: https://developer.gnome.org/glib/stable/glib-String-Utility-Functions.html#g-strtod
203  */
tempg_ascii_strtod(const gchar * txt,gchar ** endptr)204 auto tempg_ascii_strtod(const gchar* txt, gchar** endptr) -> double {
205     return g_strtod(txt,
206                     endptr);  //  makes best guess between locale formatted and C formatted numbers. See link above.
207 }
208 
209 
parseData(xmlNodePtr cur,SElement & elem)210 void Settings::parseData(xmlNodePtr cur, SElement& elem) {
211     for (xmlNodePtr x = cur->children; x != nullptr; x = x->next) {
212         if (!xmlStrcmp(x->name, reinterpret_cast<const xmlChar*>("data"))) {
213             xmlChar* name = xmlGetProp(x, reinterpret_cast<const xmlChar*>("name"));
214             parseData(x, elem.child(reinterpret_cast<const char*>(name)));
215             xmlFree(name);
216         } else if (!xmlStrcmp(x->name, reinterpret_cast<const xmlChar*>("attribute"))) {
217             xmlChar* name = xmlGetProp(x, reinterpret_cast<const xmlChar*>("name"));
218             xmlChar* value = xmlGetProp(x, reinterpret_cast<const xmlChar*>("value"));
219             xmlChar* type = xmlGetProp(x, reinterpret_cast<const xmlChar*>("type"));
220 
221             string sType = reinterpret_cast<const char*>(type);
222 
223             if (sType == "int") {
224                 int i = atoi(reinterpret_cast<const char*>(value));
225                 elem.setInt(reinterpret_cast<const char*>(name), i);
226             } else if (sType == "double") {
227                 double d = tempg_ascii_strtod(reinterpret_cast<const char*>(value),
228                                               nullptr);  // g_ascii_strtod ignores locale setting.
229                 elem.setDouble(reinterpret_cast<const char*>(name), d);
230             } else if (sType == "hex") {
231                 int i = 0;
232                 if (sscanf(reinterpret_cast<const char*>(value), "%x", &i)) {
233                     elem.setIntHex(reinterpret_cast<const char*>(name), i);
234                 } else {
235                     g_warning("Settings::Unknown hex value: %s:%s\n", name, value);
236                 }
237             } else if (sType == "string") {
238                 elem.setString(reinterpret_cast<const char*>(name), reinterpret_cast<const char*>(value));
239             } else if (sType == "boolean") {
240                 elem.setBool(reinterpret_cast<const char*>(name),
241                              strcmp(reinterpret_cast<const char*>(value), "true") == 0);
242             } else {
243                 g_warning("Settings::Unknown datatype: %s\n", sType.c_str());
244             }
245 
246             xmlFree(name);
247             xmlFree(type);
248             xmlFree(value);
249         } else {
250             g_warning("Settings::parseData: Unknown XML node: %s\n", x->name);
251             continue;
252         }
253     }
254 }
255 
parseItem(xmlDocPtr doc,xmlNodePtr cur)256 void Settings::parseItem(xmlDocPtr doc, xmlNodePtr cur) {
257     // Parse data map
258     if (!xmlStrcmp(cur->name, reinterpret_cast<const xmlChar*>("data"))) {
259         xmlChar* name = xmlGetProp(cur, reinterpret_cast<const xmlChar*>("name"));
260         if (name == nullptr) {
261             g_warning("Settings::%s:No name property!\n", cur->name);
262             return;
263         }
264 
265         parseData(cur, data[reinterpret_cast<const char*>(name)]);
266 
267         xmlFree(name);
268         return;
269     }
270 
271     if (cur->type == XML_COMMENT_NODE) {
272         return;
273     }
274 
275     if (xmlStrcmp(cur->name, reinterpret_cast<const xmlChar*>("property"))) {
276         g_warning("Settings::Unknown XML node: %s\n", cur->name);
277         return;
278     }
279 
280     xmlChar* name = xmlGetProp(cur, reinterpret_cast<const xmlChar*>("name"));
281     if (name == nullptr) {
282         g_warning("Settings::%s:No name property!\n", cur->name);
283         return;
284     }
285 
286     if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("font")) == 0) {
287         xmlFree(name);
288         xmlChar* font = nullptr;
289         xmlChar* size = nullptr;
290 
291         font = xmlGetProp(cur, reinterpret_cast<const xmlChar*>("font"));
292         if (font) {
293             this->font.setName(reinterpret_cast<const char*>(font));
294             xmlFree(font);
295         }
296 
297         size = xmlGetProp(cur, reinterpret_cast<const xmlChar*>("size"));
298         if (size) {
299             double dSize = DEFAULT_FONT_SIZE;
300             if (sscanf(reinterpret_cast<const char*>(size), "%lf", &dSize) == 1) {
301                 this->font.setSize(dSize);
302             }
303             xmlFree(size);
304         }
305         return;
306     }
307 
308     xmlChar* value = xmlGetProp(cur, reinterpret_cast<const xmlChar*>("value"));
309     if (value == nullptr) {
310         xmlFree(name);
311         g_warning("No value property!\n");
312         return;
313     }
314 
315     // TODO(fabian): remove this typo fix in 2-3 release cycles
316     if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("presureSensitivity")) == 0) {
317         this->pressureSensitivity = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
318     }
319     if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("pressureSensitivity")) == 0) {
320         this->pressureSensitivity = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
321     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("minimumPressure")) == 0) {
322         this->minimumPressure = g_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
323     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("pressureMultiplier")) == 0) {
324         this->pressureMultiplier = g_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
325     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("zoomGesturesEnabled")) == 0) {
326         this->zoomGesturesEnabled = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
327     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("selectedToolbar")) == 0) {
328         this->selectedToolbar = reinterpret_cast<const char*>(value);
329     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("lastSavePath")) == 0) {
330         this->lastSavePath = fs::u8path(reinterpret_cast<const char*>(value));
331     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("lastOpenPath")) == 0) {
332         this->lastOpenPath = fs::u8path(reinterpret_cast<const char*>(value));
333     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("lastImagePath")) == 0) {
334         this->lastImagePath = fs::u8path(reinterpret_cast<const char*>(value));
335     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("zoomStep")) == 0) {
336         this->zoomStep = tempg_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
337     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("zoomStepScroll")) == 0) {
338         this->zoomStepScroll = tempg_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
339     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("displayDpi")) == 0) {
340         this->displayDpi = g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10);
341     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("mainWndWidth")) == 0) {
342         this->mainWndWidth = g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10);
343     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("mainWndHeight")) == 0) {
344         this->mainWndHeight = g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10);
345     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("maximized")) == 0) {
346         this->maximized = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
347     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("showToolbar")) == 0) {
348         this->showToolbar = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
349     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("showSidebar")) == 0) {
350         this->showSidebar = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
351     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("sidebarWidth")) == 0) {
352         this->sidebarWidth = std::max<int>(g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10), 50);
353     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("sidebarOnRight")) == 0) {
354         this->sidebarOnRight = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
355     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("scrollbarOnLeft")) == 0) {
356         this->scrollbarOnLeft = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
357     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("menubarVisible")) == 0) {
358         this->menubarVisible = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
359     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("numColumns")) == 0) {
360         this->numColumns = g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10);
361     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("numRows")) == 0) {
362         this->numRows = g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10);
363     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("viewFixedRows")) == 0) {
364         this->viewFixedRows = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
365     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("layoutVertical")) == 0) {
366         this->layoutVertical = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
367     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("layoutRightToLeft")) == 0) {
368         this->layoutRightToLeft = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
369     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("layoutBottomToTop")) == 0) {
370         this->layoutBottomToTop = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
371     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("showPairedPages")) == 0) {
372         this->showPairedPages = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
373     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("numPairsOffset")) == 0) {
374         this->numPairsOffset = g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10);
375     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("presentationMode")) == 0) {
376         this->presentationMode = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
377     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("autoloadMostRecent")) == 0) {
378         this->autoloadMostRecent = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
379     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("autoloadPdfXoj")) == 0) {
380         this->autoloadPdfXoj = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
381     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("stylusCursorType")) == 0) {
382         this->stylusCursorType = stylusCursorTypeFromString(reinterpret_cast<const char*>(value));
383     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("highlightPosition")) == 0) {
384         this->highlightPosition = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
385     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("cursorHighlightColor")) == 0) {
386         this->cursorHighlightColor = g_ascii_strtoull(reinterpret_cast<const char*>(value), nullptr, 10);
387     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("cursorHighlightRadius")) == 0) {
388         this->cursorHighlightRadius = g_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
389     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("cursorHighlightBorderColor")) == 0) {
390         this->cursorHighlightBorderColor = g_ascii_strtoull(reinterpret_cast<const char*>(value), nullptr, 10);
391     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("cursorHighlightBorderWidth")) == 0) {
392         this->cursorHighlightBorderWidth = g_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
393     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("darkTheme")) == 0) {
394         this->darkTheme = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
395     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("useStockIcons")) == 0) {
396         this->useStockIcons = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
397     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("defaultSaveName")) == 0) {
398         this->defaultSaveName = reinterpret_cast<const char*>(value);
399     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("pluginEnabled")) == 0) {
400         this->pluginEnabled = reinterpret_cast<const char*>(value);
401     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("pluginDisabled")) == 0) {
402         this->pluginDisabled = reinterpret_cast<const char*>(value);
403     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("pageTemplate")) == 0) {
404         this->pageTemplate = reinterpret_cast<const char*>(value);
405     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("sizeUnit")) == 0) {
406         this->sizeUnit = reinterpret_cast<const char*>(value);
407     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("audioFolder")) == 0) {
408         this->audioFolder = reinterpret_cast<const char*>(value);
409     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("autosaveEnabled")) == 0) {
410         this->autosaveEnabled = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
411     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("autosaveTimeout")) == 0) {
412         this->autosaveTimeout = g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10);
413     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("fullscreenHideElements")) == 0) {
414         this->fullscreenHideElements = reinterpret_cast<const char*>(value);
415     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("presentationHideElements")) == 0) {
416         this->presentationHideElements = reinterpret_cast<const char*>(value);
417     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("touchZoomStartThreshold")) == 0) {
418         this->touchZoomStartThreshold = g_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
419     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("pageRerenderThreshold")) == 0) {
420         this->pageRerenderThreshold = g_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
421     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("pdfPageCacheSize")) == 0) {
422         this->pdfPageCacheSize = g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10);
423     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("preloadPagesBefore")) == 0) {
424         this->preloadPagesBefore = g_ascii_strtoull(reinterpret_cast<const char*>(value), nullptr, 10);
425     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("preloadPagesAfter")) == 0) {
426         this->preloadPagesAfter = g_ascii_strtoull(reinterpret_cast<const char*>(value), nullptr, 10);
427     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("eagerPageCleanup")) == 0) {
428         this->eagerPageCleanup = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
429     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("selectionBorderColor")) == 0) {
430         this->selectionBorderColor = Color(g_ascii_strtoull(reinterpret_cast<const char*>(value), nullptr, 10));
431     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("selectionMarkerColor")) == 0) {
432         this->selectionMarkerColor = Color(g_ascii_strtoull(reinterpret_cast<const char*>(value), nullptr, 10));
433     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("backgroundColor")) == 0) {
434         this->backgroundColor = Color(g_ascii_strtoull(reinterpret_cast<const char*>(value), nullptr, 10));
435     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("addHorizontalSpace")) == 0) {
436         this->addHorizontalSpace = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
437     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("addHorizontalSpaceAmount")) == 0) {
438         this->addHorizontalSpaceAmount = g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10);
439     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("addVerticalSpace")) == 0) {
440         this->addVerticalSpace = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
441     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("addVerticalSpaceAmount")) == 0) {
442         this->addVerticalSpaceAmount = g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10);
443     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("drawDirModsEnabled")) == 0) {
444         this->drawDirModsEnabled = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
445     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("drawDirModsRadius")) == 0) {
446         this->drawDirModsRadius = g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10);
447     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("snapRotation")) == 0) {
448         this->snapRotation = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
449     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("snapRotationTolerance")) == 0) {
450         this->snapRotationTolerance = tempg_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
451     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("snapGrid")) == 0) {
452         this->snapGrid = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
453     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("snapGridSize")) == 0) {
454         this->snapGridSize = tempg_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
455     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("snapGridTolerance")) == 0) {
456         this->snapGridTolerance = tempg_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
457     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("touchDrawing")) == 0) {
458         this->touchDrawing = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
459     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("pressureGuessing")) == 0) {
460         this->pressureGuessing = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
461     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("scrollbarHideType")) == 0) {
462         if (xmlStrcmp(value, reinterpret_cast<const xmlChar*>("both")) == 0) {
463             this->scrollbarHideType = SCROLLBAR_HIDE_BOTH;
464         } else if (xmlStrcmp(value, reinterpret_cast<const xmlChar*>("horizontal")) == 0) {
465             this->scrollbarHideType = SCROLLBAR_HIDE_HORIZONTAL;
466         } else if (xmlStrcmp(value, reinterpret_cast<const xmlChar*>("vertical")) == 0) {
467             this->scrollbarHideType = SCROLLBAR_HIDE_VERTICAL;
468         } else {
469             this->scrollbarHideType = SCROLLBAR_HIDE_NONE;
470         }
471     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("disableScrollbarFadeout")) == 0) {
472         this->disableScrollbarFadeout = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
473     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("audioSampleRate")) == 0) {
474         this->audioSampleRate = tempg_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
475     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("audioGain")) == 0) {
476         this->audioGain = tempg_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
477     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("defaultSeekTime")) == 0) {
478         this->defaultSeekTime = tempg_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
479     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("audioInputDevice")) == 0) {
480         this->audioInputDevice = g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10);
481     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("audioOutputDevice")) == 0) {
482         this->audioOutputDevice = g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10);
483     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("numIgnoredStylusEvents")) == 0) {
484         this->numIgnoredStylusEvents =
485                 std::max<int>(g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10), 0);
486     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("inputSystemTPCButton")) == 0) {
487         this->inputSystemTPCButton = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
488     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("inputSystemDrawOutsideWindow")) == 0) {
489         this->inputSystemDrawOutsideWindow = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
490     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("strokeFilterIgnoreTime")) == 0) {
491         this->strokeFilterIgnoreTime = g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10);
492     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("strokeFilterIgnoreLength")) == 0) {
493         this->strokeFilterIgnoreLength = tempg_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
494     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("strokeFilterSuccessiveTime")) == 0) {
495         this->strokeFilterSuccessiveTime = g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10);
496     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("strokeFilterEnabled")) == 0) {
497         this->strokeFilterEnabled = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
498     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("doActionOnStrokeFiltered")) == 0) {
499         this->doActionOnStrokeFiltered = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
500     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("trySelectOnStrokeFiltered")) == 0) {
501         this->trySelectOnStrokeFiltered = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
502     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("latexSettings.autoCheckDependencies")) == 0) {
503         this->latexSettings.autoCheckDependencies = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
504     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("latexSettings.globalTemplatePath")) == 0) {
505         std::string v(reinterpret_cast<char*>(value));
506         this->latexSettings.globalTemplatePath = fs::u8path(v);
507     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("latexSettings.genCmd")) == 0) {
508         this->latexSettings.genCmd = reinterpret_cast<char*>(value);
509     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("snapRecognizedShapesEnabled")) == 0) {
510         this->snapRecognizedShapesEnabled = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
511     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("restoreLineWidthEnabled")) == 0) {
512         this->restoreLineWidthEnabled = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
513     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("preferredLocale")) == 0) {
514         this->preferredLocale = reinterpret_cast<char*>(value);
515         /**
516          * Stabilizer related settings
517          */
518     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("stabilizerAveragingMethod")) == 0) {
519         this->stabilizerAveragingMethod =
520                 (StrokeStabilizer::AveragingMethod)g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10);
521     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("stabilizerPreprocessor")) == 0) {
522         this->stabilizerPreprocessor =
523                 (StrokeStabilizer::Preprocessor)g_ascii_strtoll(reinterpret_cast<const char*>(value), nullptr, 10);
524     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("stabilizerBuffersize")) == 0) {
525         this->stabilizerBuffersize = g_ascii_strtoull(reinterpret_cast<const char*>(value), nullptr, 10);
526     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("stabilizerSigma")) == 0) {
527         this->stabilizerSigma = tempg_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
528     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("stabilizerDeadzoneRadius")) == 0) {
529         this->stabilizerDeadzoneRadius = tempg_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
530     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("stabilizerDrag")) == 0) {
531         this->stabilizerDrag = tempg_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
532     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("stabilizerMass")) == 0) {
533         this->stabilizerMass = tempg_ascii_strtod(reinterpret_cast<const char*>(value), nullptr);
534     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("stabilizerCuspDetection")) == 0) {
535         this->stabilizerCuspDetection = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
536     } else if (xmlStrcmp(name, reinterpret_cast<const xmlChar*>("stabilizerFinalizeStroke")) == 0) {
537         this->stabilizerFinalizeStroke = xmlStrcmp(value, reinterpret_cast<const xmlChar*>("true")) == 0;
538     }
539     /**/
540 
541     xmlFree(name);
542     xmlFree(value);
543 }
544 
loadDeviceClasses()545 void Settings::loadDeviceClasses() {
546     SElement& s = getCustomElement("deviceClasses");
547     for (auto device: s.children()) {
548         SElement& deviceNode = device.second;
549         int deviceClass = 0;
550         int deviceSource = 0;
551         deviceNode.getInt("deviceClass", deviceClass);
552         deviceNode.getInt("deviceSource", deviceSource);
553         inputDeviceClasses.emplace(device.first, std::make_pair(static_cast<InputDeviceTypeOption>(deviceClass),
554                                                                 static_cast<GdkInputSource>(deviceSource)));
555     }
556 }
557 
loadButtonConfig()558 void Settings::loadButtonConfig() {
559     SElement& s = getCustomElement("buttonConfig");
560 
561     for (int i = 0; i < BUTTON_COUNT; i++) {
562         SElement& e = s.child(buttonToString(static_cast<Button>(i)));
563         ButtonConfig* cfg = buttonConfig[i];
564 
565         string sType;
566         if (e.getString("tool", sType)) {
567             ToolType type = toolTypeFromString(sType);
568             cfg->action = type;
569 
570             if (type == TOOL_PEN || type == TOOL_HIGHLIGHTER) {
571                 string drawingType;
572                 if (e.getString("drawingType", drawingType)) {
573                     cfg->drawingType = drawingTypeFromString(drawingType);
574                 }
575 
576                 string sSize;
577                 if (e.getString("size", sSize)) {
578                     cfg->size = toolSizeFromString(sSize);
579                 } else {
580                     // If not specified: do not change
581                     cfg->size = TOOL_SIZE_NONE;
582                 }
583             }
584 
585             if (type == TOOL_PEN || type == TOOL_HIGHLIGHTER || type == TOOL_TEXT) {
586                 if (int iColor; e.getInt("color", iColor)) {
587                     cfg->color = Color(iColor);
588                 }
589             }
590 
591             if (type == TOOL_ERASER) {
592                 string sEraserMode;
593                 if (e.getString("eraserMode", sEraserMode)) {
594                     cfg->eraserMode = eraserTypeFromString(sEraserMode);
595                 } else {
596                     // If not specified: do not change
597                     cfg->eraserMode = ERASER_TYPE_NONE;
598                 }
599 
600                 string sSize;
601                 if (e.getString("size", sSize)) {
602                     cfg->size = toolSizeFromString(sSize);
603                 } else {
604                     // If not specified: do not change
605                     cfg->size = TOOL_SIZE_NONE;
606                 }
607             }
608 
609             // Touch device
610             if (i == BUTTON_TOUCH) {
611                 if (!e.getString("device", cfg->device)) {
612                     cfg->device = "";
613                 }
614 
615                 e.getBool("disableDrawing", cfg->disableDrawing);
616             }
617         } else {
618             continue;
619         }
620     }
621 }
622 
load()623 auto Settings::load() -> bool {
624     xmlKeepBlanksDefault(0);
625 
626     if (!fs::exists(filepath)) {
627         g_warning("configfile does not exist %s\n", filepath.string().c_str());
628         return false;
629     }
630 
631     xmlDocPtr doc = xmlParseFile(filepath.u8string().c_str());
632 
633     if (doc == nullptr) {
634         g_warning("Settings::load:: doc == null, could not load Settings!\n");
635         return false;
636     }
637 
638     xmlNodePtr cur = xmlDocGetRootElement(doc);
639     if (cur == nullptr) {
640         g_message("The settings file \"%s\" is empty", filepath.string().c_str());
641         xmlFreeDoc(doc);
642 
643         return false;
644     }
645 
646     if (xmlStrcmp(cur->name, reinterpret_cast<const xmlChar*>("settings"))) {
647         g_message("File \"%s\" is of the wrong type", filepath.string().c_str());
648         xmlFreeDoc(doc);
649 
650         return false;
651     }
652 
653     cur = xmlDocGetRootElement(doc);
654     cur = cur->xmlChildrenNode;
655 
656     while (cur != nullptr) {
657         parseItem(doc, cur);
658 
659         cur = cur->next;
660     }
661 
662     xmlFreeDoc(doc);
663 
664     loadButtonConfig();
665     loadDeviceClasses();
666 
667     return true;
668 }
669 
savePropertyDouble(const gchar * key,double value,xmlNodePtr parent)670 auto Settings::savePropertyDouble(const gchar* key, double value, xmlNodePtr parent) -> xmlNodePtr {
671     char text[G_ASCII_DTOSTR_BUF_SIZE];
672     //  g_ascii_ version uses C locale always.
673     g_ascii_formatd(text, G_ASCII_DTOSTR_BUF_SIZE, Util::PRECISION_FORMAT_STRING, value);
674     xmlNodePtr xmlNode = saveProperty(key, text, parent);
675     return xmlNode;
676 }
677 
saveProperty(const gchar * key,int value,xmlNodePtr parent)678 auto Settings::saveProperty(const gchar* key, int value, xmlNodePtr parent) -> xmlNodePtr {
679     char* text = g_strdup_printf("%i", value);
680     xmlNodePtr xmlNode = saveProperty(key, text, parent);
681     g_free(text);
682     return xmlNode;
683 }
684 
savePropertyUnsigned(const gchar * key,unsigned int value,xmlNodePtr parent)685 auto Settings::savePropertyUnsigned(const gchar* key, unsigned int value, xmlNodePtr parent) -> xmlNodePtr {
686     char* text = g_strdup_printf("%u", value);
687     xmlNodePtr xmlNode = saveProperty(key, text, parent);
688     g_free(text);
689     return xmlNode;
690 }
691 
saveProperty(const gchar * key,const gchar * value,xmlNodePtr parent)692 auto Settings::saveProperty(const gchar* key, const gchar* value, xmlNodePtr parent) -> xmlNodePtr {
693     xmlNodePtr xmlNode = xmlNewChild(parent, nullptr, reinterpret_cast<const xmlChar*>("property"), nullptr);
694 
695     xmlSetProp(xmlNode, reinterpret_cast<const xmlChar*>("name"), reinterpret_cast<const xmlChar*>(key));
696 
697     xmlSetProp(xmlNode, reinterpret_cast<const xmlChar*>("value"), reinterpret_cast<const xmlChar*>(value));
698 
699     return xmlNode;
700 }
701 
saveDeviceClasses()702 void Settings::saveDeviceClasses() {
703     SElement& s = getCustomElement("deviceClasses");
704 
705     for (auto& device: inputDeviceClasses) {
706         const std::string& name = device.first;
707         InputDeviceTypeOption& deviceClass = device.second.first;
708         GdkInputSource& source = device.second.second;
709         SElement& e = s.child(name);
710         e.setInt("deviceClass", static_cast<int>(deviceClass));
711         e.setInt("deviceSource", source);
712     }
713 }
714 
saveButtonConfig()715 void Settings::saveButtonConfig() {
716     SElement& s = getCustomElement("buttonConfig");
717     s.clear();
718 
719     for (int i = 0; i < BUTTON_COUNT; i++) {
720         SElement& e = s.child(buttonToString(static_cast<Button>(i)));
721         ButtonConfig* cfg = buttonConfig[i];
722 
723         ToolType type = cfg->action;
724         e.setString("tool", toolTypeToString(type));
725 
726         if (type == TOOL_PEN || type == TOOL_HIGHLIGHTER) {
727             e.setString("drawingType", drawingTypeToString(cfg->drawingType));
728             e.setString("size", toolSizeToString(cfg->size));
729         }  // end if pen or highlighter
730 
731         if (type == TOOL_PEN || type == TOOL_HIGHLIGHTER || type == TOOL_TEXT) {
732             e.setIntHex("color", int32_t(cfg->color));
733         }
734 
735         if (type == TOOL_ERASER) {
736             e.setString("eraserMode", eraserTypeToString(cfg->eraserMode));
737             e.setString("size", toolSizeToString(cfg->size));
738         }
739 
740         // Touch device
741         if (i == BUTTON_TOUCH) {
742             e.setString("device", cfg->device);
743             e.setBool("disableDrawing", cfg->disableDrawing);
744         }
745     }
746 }
747 
748 /**
749  * Do not save settings until transactionEnd() is called
750  */
transactionStart()751 void Settings::transactionStart() { inTransaction = true; }
752 
753 /**
754  * Stop transaction and save settings
755  */
transactionEnd()756 void Settings::transactionEnd() {
757     inTransaction = false;
758     save();
759 }
760 
save()761 void Settings::save() {
762     if (inTransaction) {
763         return;
764     }
765 
766     xmlDocPtr doc = nullptr;
767     xmlNodePtr root = nullptr;
768     xmlNodePtr xmlNode = nullptr;
769 
770     xmlIndentTreeOutput = true;
771 
772     doc = xmlNewDoc(reinterpret_cast<const xmlChar*>("1.0"));
773     if (doc == nullptr) {
774         return;
775     }
776 
777     saveButtonConfig();
778     saveDeviceClasses();
779 
780     /* Create metadata root */
781     root = xmlNewDocNode(doc, nullptr, reinterpret_cast<const xmlChar*>("settings"), nullptr);
782     xmlDocSetRootElement(doc, root);
783     xmlNodePtr com = xmlNewComment(
784             reinterpret_cast<const xmlChar*>("The Xournal++ settings file. Do not edit this file! "
785                                              "Most settings are available in the Settings dialog, "
786                                              "the others are commented in this file, but handle with care!"));
787     xmlAddPrevSibling(root, com);
788 
789     SAVE_BOOL_PROP(pressureSensitivity);
790     SAVE_DOUBLE_PROP(minimumPressure);
791     SAVE_DOUBLE_PROP(pressureMultiplier);
792 
793     SAVE_BOOL_PROP(zoomGesturesEnabled);
794 
795     SAVE_STRING_PROP(selectedToolbar);
796 
797     auto lastSavePath = this->lastSavePath.u8string();
798     auto lastOpenPath = this->lastOpenPath.u8string();
799     auto lastImagePath = this->lastImagePath.u8string();
800     SAVE_STRING_PROP(lastSavePath);
801     SAVE_STRING_PROP(lastOpenPath);
802     SAVE_STRING_PROP(lastImagePath);
803 
804     SAVE_DOUBLE_PROP(zoomStep);
805     SAVE_DOUBLE_PROP(zoomStepScroll);
806     SAVE_INT_PROP(displayDpi);
807     SAVE_INT_PROP(mainWndWidth);
808     SAVE_INT_PROP(mainWndHeight);
809     SAVE_BOOL_PROP(maximized);
810 
811     SAVE_BOOL_PROP(showToolbar);
812 
813     SAVE_BOOL_PROP(showSidebar);
814     SAVE_INT_PROP(sidebarWidth);
815 
816     SAVE_BOOL_PROP(sidebarOnRight);
817     SAVE_BOOL_PROP(scrollbarOnLeft);
818     SAVE_BOOL_PROP(menubarVisible);
819     SAVE_INT_PROP(numColumns);
820     SAVE_INT_PROP(numRows);
821     SAVE_BOOL_PROP(viewFixedRows);
822     SAVE_BOOL_PROP(showPairedPages);
823     SAVE_BOOL_PROP(layoutVertical);
824     SAVE_BOOL_PROP(layoutRightToLeft);
825     SAVE_BOOL_PROP(layoutBottomToTop);
826     SAVE_INT_PROP(numPairsOffset);
827     SAVE_BOOL_PROP(presentationMode);
828 
829     SAVE_STRING_PROP(fullscreenHideElements);
830     ATTACH_COMMENT("Which gui elements are hidden if you are in Fullscreen mode, separated by a colon (,)");
831 
832     SAVE_STRING_PROP(presentationHideElements);
833     ATTACH_COMMENT("Which gui elements are hidden if you are in Presentation mode, separated by a colon (,)");
834 
835     xmlNode = saveProperty("stylusCursorType", stylusCursorTypeToString(this->stylusCursorType), root);
836     ATTACH_COMMENT("The cursor icon used with a stylus, allowed values are \"none\", \"dot\", \"big\"");
837 
838     SAVE_BOOL_PROP(highlightPosition);
839     SAVE_UINT_PROP(cursorHighlightColor);
840     SAVE_UINT_PROP(cursorHighlightBorderColor);
841     SAVE_DOUBLE_PROP(cursorHighlightRadius);
842     SAVE_DOUBLE_PROP(cursorHighlightBorderWidth);
843     SAVE_BOOL_PROP(darkTheme);
844     SAVE_BOOL_PROP(useStockIcons);
845 
846     SAVE_BOOL_PROP(disableScrollbarFadeout);
847 
848     if (this->scrollbarHideType == SCROLLBAR_HIDE_BOTH) {
849         xmlNode = saveProperty("scrollbarHideType", "both", root);
850     } else if (this->scrollbarHideType == SCROLLBAR_HIDE_HORIZONTAL) {
851         xmlNode = saveProperty("scrollbarHideType", "horizontal", root);
852     } else if (this->scrollbarHideType == SCROLLBAR_HIDE_VERTICAL) {
853         xmlNode = saveProperty("scrollbarHideType", "vertical", root);
854     } else {
855         xmlNode = saveProperty("scrollbarHideType", "none", root);
856     }
857     ATTACH_COMMENT(
858             "Hides scroolbars in the main window, allowed values: \"none\", \"horizontal\", \"vertical\", \"both\"");
859 
860     SAVE_BOOL_PROP(autoloadMostRecent);
861     SAVE_BOOL_PROP(autoloadPdfXoj);
862     SAVE_STRING_PROP(defaultSaveName);
863 
864     SAVE_BOOL_PROP(autosaveEnabled);
865     SAVE_INT_PROP(autosaveTimeout);
866 
867     SAVE_BOOL_PROP(addHorizontalSpace);
868     SAVE_INT_PROP(addHorizontalSpaceAmount);
869     SAVE_BOOL_PROP(addVerticalSpace);
870     SAVE_INT_PROP(addVerticalSpaceAmount);
871 
872     SAVE_BOOL_PROP(drawDirModsEnabled);
873     SAVE_INT_PROP(drawDirModsRadius);
874 
875 
876     SAVE_BOOL_PROP(snapRotation);
877     SAVE_DOUBLE_PROP(snapRotationTolerance);
878     SAVE_BOOL_PROP(snapGrid);
879     SAVE_DOUBLE_PROP(snapGridTolerance);
880     SAVE_DOUBLE_PROP(snapGridSize);
881 
882     SAVE_BOOL_PROP(touchDrawing);
883     SAVE_BOOL_PROP(pressureGuessing);
884 
885     SAVE_UINT_PROP(selectionBorderColor);
886     SAVE_UINT_PROP(backgroundColor);
887     SAVE_UINT_PROP(selectionMarkerColor);
888 
889     SAVE_DOUBLE_PROP(touchZoomStartThreshold);
890     SAVE_DOUBLE_PROP(pageRerenderThreshold);
891 
892     SAVE_INT_PROP(pdfPageCacheSize);
893     ATTACH_COMMENT("The count of rendered PDF pages which will be cached.");
894     SAVE_UINT_PROP(preloadPagesBefore);
895     SAVE_UINT_PROP(preloadPagesAfter);
896     SAVE_BOOL_PROP(eagerPageCleanup);
897 
898     SAVE_STRING_PROP(pageTemplate);
899     ATTACH_COMMENT("Config for new pages");
900 
901     SAVE_STRING_PROP(sizeUnit);
902 
903     SAVE_STRING_PROP(audioFolder);
904     SAVE_INT_PROP(audioInputDevice);
905     SAVE_INT_PROP(audioOutputDevice);
906     SAVE_DOUBLE_PROP(audioSampleRate);
907     SAVE_DOUBLE_PROP(audioGain);
908     SAVE_INT_PROP(defaultSeekTime);
909 
910     SAVE_STRING_PROP(pluginEnabled);
911     SAVE_STRING_PROP(pluginDisabled);
912 
913     SAVE_INT_PROP(strokeFilterIgnoreTime);
914     SAVE_DOUBLE_PROP(strokeFilterIgnoreLength);
915     SAVE_INT_PROP(strokeFilterSuccessiveTime);
916     SAVE_BOOL_PROP(strokeFilterEnabled);
917     SAVE_BOOL_PROP(doActionOnStrokeFiltered);
918     SAVE_BOOL_PROP(trySelectOnStrokeFiltered);
919 
920     SAVE_BOOL_PROP(snapRecognizedShapesEnabled);
921     SAVE_BOOL_PROP(restoreLineWidthEnabled);
922 
923     SAVE_INT_PROP(numIgnoredStylusEvents);
924 
925     SAVE_BOOL_PROP(inputSystemTPCButton);
926     SAVE_BOOL_PROP(inputSystemDrawOutsideWindow);
927 
928     SAVE_STRING_PROP(preferredLocale);
929 
930     /**
931      * Stabilizer related settings
932      */
933     saveProperty("stabilizerAveragingMethod", static_cast<int>(stabilizerAveragingMethod), root);
934     saveProperty("stabilizerPreprocessor", static_cast<int>(stabilizerPreprocessor), root);
935     SAVE_UINT_PROP(stabilizerBuffersize);
936     SAVE_DOUBLE_PROP(stabilizerSigma);
937     SAVE_DOUBLE_PROP(stabilizerDeadzoneRadius);
938     SAVE_DOUBLE_PROP(stabilizerDrag);
939     SAVE_DOUBLE_PROP(stabilizerMass);
940     SAVE_BOOL_PROP(stabilizerCuspDetection);
941     SAVE_BOOL_PROP(stabilizerFinalizeStroke);
942     /**/
943 
944     SAVE_BOOL_PROP(latexSettings.autoCheckDependencies);
945     // Inline SAVE_STRING_PROP(latexSettings.globalTemplatePath) since it
946     // breaks on Windows due to the native character representation being
947     // wchar_t instead of char
948     fs::path& p = latexSettings.globalTemplatePath;
949     xmlNode = saveProperty("latexSettings.globalTemplatePath", p.empty() ? "" : p.u8string().c_str(), root);
950     SAVE_STRING_PROP(latexSettings.genCmd);
951 
952     xmlNodePtr xmlFont = nullptr;
953     xmlFont = xmlNewChild(root, nullptr, reinterpret_cast<const xmlChar*>("property"), nullptr);
954     xmlSetProp(xmlFont, reinterpret_cast<const xmlChar*>("name"), reinterpret_cast<const xmlChar*>("font"));
955     xmlSetProp(xmlFont, reinterpret_cast<const xmlChar*>("font"),
956                reinterpret_cast<const xmlChar*>(this->font.getName().c_str()));
957 
958     char sSize[G_ASCII_DTOSTR_BUF_SIZE];
959 
960     g_ascii_formatd(sSize, G_ASCII_DTOSTR_BUF_SIZE, Util::PRECISION_FORMAT_STRING, this->font.getSize());  // no locale
961     xmlSetProp(xmlFont, reinterpret_cast<const xmlChar*>("size"), reinterpret_cast<const xmlChar*>(sSize));
962 
963 
964     for (std::map<string, SElement>::value_type p: data) { saveData(root, p.first, p.second); }
965 
966     xmlSaveFormatFileEnc(filepath.u8string().c_str(), doc, "UTF-8", 1);
967     xmlFreeDoc(doc);
968 }
969 
saveData(xmlNodePtr root,const string & name,SElement & elem)970 void Settings::saveData(xmlNodePtr root, const string& name, SElement& elem) {
971     xmlNodePtr xmlNode = xmlNewChild(root, nullptr, reinterpret_cast<const xmlChar*>("data"), nullptr);
972 
973     xmlSetProp(xmlNode, reinterpret_cast<const xmlChar*>("name"), reinterpret_cast<const xmlChar*>(name.c_str()));
974 
975     for (auto const& [aname, attrib]: elem.attributes()) {
976         string type;
977         string value;
978 
979         if (attrib.type == ATTRIBUTE_TYPE_BOOLEAN) {
980             type = "boolean";
981 
982             if (attrib.iValue) {
983                 value = "true";
984             } else {
985                 value = "false";
986             }
987         } else if (attrib.type == ATTRIBUTE_TYPE_INT) {
988             type = "int";
989 
990             char* tmp = g_strdup_printf("%i", attrib.iValue);
991             value = tmp;
992             g_free(tmp);
993         } else if (attrib.type == ATTRIBUTE_TYPE_DOUBLE) {
994             type = "double";
995 
996             char tmp[G_ASCII_DTOSTR_BUF_SIZE];
997             g_ascii_formatd(tmp, G_ASCII_DTOSTR_BUF_SIZE, Util::PRECISION_FORMAT_STRING, attrib.dValue);
998             value = tmp;
999         } else if (attrib.type == ATTRIBUTE_TYPE_INT_HEX) {
1000             type = "hex";
1001 
1002             char* tmp = g_strdup_printf("%06x", attrib.iValue);
1003             value = tmp;
1004             g_free(tmp);
1005         } else if (attrib.type == ATTRIBUTE_TYPE_STRING) {
1006             type = "string";
1007             value = attrib.sValue;
1008         } else {
1009             // Unknown type or empty attribute
1010             continue;
1011         }
1012 
1013         xmlNodePtr at = nullptr;
1014         at = xmlNewChild(xmlNode, nullptr, reinterpret_cast<const xmlChar*>("attribute"), nullptr);
1015 
1016         xmlSetProp(at, reinterpret_cast<const xmlChar*>("name"), reinterpret_cast<const xmlChar*>(aname.c_str()));
1017         xmlSetProp(at, reinterpret_cast<const xmlChar*>("type"), reinterpret_cast<const xmlChar*>(type.c_str()));
1018         xmlSetProp(at, reinterpret_cast<const xmlChar*>("value"), reinterpret_cast<const xmlChar*>(value.c_str()));
1019 
1020         if (!attrib.comment.empty()) {
1021             xmlNodePtr com = xmlNewComment(reinterpret_cast<const xmlChar*>(attrib.comment.c_str()));
1022             xmlAddPrevSibling(xmlNode, com);
1023         }
1024     }
1025 
1026     for (std::map<string, SElement>::value_type p: elem.children()) { saveData(xmlNode, p.first, p.second); }
1027 }
1028 
1029 // Getter- / Setter
isPressureSensitivity() const1030 auto Settings::isPressureSensitivity() const -> bool { return this->pressureSensitivity; }
1031 
isZoomGesturesEnabled() const1032 auto Settings::isZoomGesturesEnabled() const -> bool { return this->zoomGesturesEnabled; }
1033 
setZoomGesturesEnabled(bool enable)1034 void Settings::setZoomGesturesEnabled(bool enable) {
1035     if (this->zoomGesturesEnabled == enable) {
1036         return;
1037     }
1038     this->zoomGesturesEnabled = enable;
1039     save();
1040 }
1041 
isSidebarOnRight() const1042 auto Settings::isSidebarOnRight() const -> bool { return this->sidebarOnRight; }
1043 
setSidebarOnRight(bool right)1044 void Settings::setSidebarOnRight(bool right) {
1045     if (this->sidebarOnRight == right) {
1046         return;
1047     }
1048 
1049     this->sidebarOnRight = right;
1050 
1051     save();
1052 }
1053 
isScrollbarOnLeft() const1054 auto Settings::isScrollbarOnLeft() const -> bool { return this->scrollbarOnLeft; }
1055 
setScrollbarOnLeft(bool right)1056 void Settings::setScrollbarOnLeft(bool right) {
1057     if (this->scrollbarOnLeft == right) {
1058         return;
1059     }
1060 
1061     this->scrollbarOnLeft = right;
1062 
1063     save();
1064 }
1065 
isMenubarVisible() const1066 auto Settings::isMenubarVisible() const -> bool { return this->menubarVisible; }
1067 
setMenubarVisible(bool visible)1068 void Settings::setMenubarVisible(bool visible) {
1069     if (this->menubarVisible == visible) {
1070         return;
1071     }
1072 
1073     this->menubarVisible = visible;
1074 
1075     save();
1076 }
1077 
getAutosaveTimeout() const1078 auto Settings::getAutosaveTimeout() const -> int { return this->autosaveTimeout; }
1079 
setAutosaveTimeout(int autosave)1080 void Settings::setAutosaveTimeout(int autosave) {
1081     if (this->autosaveTimeout == autosave) {
1082         return;
1083     }
1084 
1085     this->autosaveTimeout = autosave;
1086 
1087     save();
1088 }
1089 
isAutosaveEnabled() const1090 auto Settings::isAutosaveEnabled() const -> bool { return this->autosaveEnabled; }
1091 
setAutosaveEnabled(bool autosave)1092 void Settings::setAutosaveEnabled(bool autosave) {
1093     if (this->autosaveEnabled == autosave) {
1094         return;
1095     }
1096 
1097     this->autosaveEnabled = autosave;
1098 
1099     save();
1100 }
1101 
getAddVerticalSpace() const1102 auto Settings::getAddVerticalSpace() const -> bool { return this->addVerticalSpace; }
1103 
setAddVerticalSpace(bool space)1104 void Settings::setAddVerticalSpace(bool space) { this->addVerticalSpace = space; }
1105 
getAddVerticalSpaceAmount() const1106 auto Settings::getAddVerticalSpaceAmount() const -> int { return this->addVerticalSpaceAmount; }
1107 
setAddVerticalSpaceAmount(int pixels)1108 void Settings::setAddVerticalSpaceAmount(int pixels) {
1109     if (this->addVerticalSpaceAmount == pixels) {
1110         return;
1111     }
1112 
1113     this->addVerticalSpaceAmount = pixels;
1114     save();
1115 }
1116 
1117 
getAddHorizontalSpace() const1118 auto Settings::getAddHorizontalSpace() const -> bool { return this->addHorizontalSpace; }
1119 
setAddHorizontalSpace(bool space)1120 void Settings::setAddHorizontalSpace(bool space) { this->addHorizontalSpace = space; }
1121 
getAddHorizontalSpaceAmount() const1122 auto Settings::getAddHorizontalSpaceAmount() const -> int { return this->addHorizontalSpaceAmount; }
1123 
setAddHorizontalSpaceAmount(int pixels)1124 void Settings::setAddHorizontalSpaceAmount(int pixels) {
1125     if (this->addHorizontalSpaceAmount == pixels) {
1126         return;
1127     }
1128 
1129     this->addHorizontalSpaceAmount = pixels;
1130     save();
1131 }
1132 
1133 
getDrawDirModsEnabled() const1134 auto Settings::getDrawDirModsEnabled() const -> bool { return this->drawDirModsEnabled; }
1135 
setDrawDirModsEnabled(bool enable)1136 void Settings::setDrawDirModsEnabled(bool enable) { this->drawDirModsEnabled = enable; }
1137 
getDrawDirModsRadius() const1138 auto Settings::getDrawDirModsRadius() const -> int { return this->drawDirModsRadius; }
1139 
setDrawDirModsRadius(int pixels)1140 void Settings::setDrawDirModsRadius(int pixels) {
1141     if (this->drawDirModsRadius == pixels) {
1142         return;
1143     }
1144 
1145     this->drawDirModsRadius = pixels;
1146     save();
1147 }
1148 
getStylusCursorType() const1149 auto Settings::getStylusCursorType() const -> StylusCursorType { return this->stylusCursorType; }
1150 
setStylusCursorType(StylusCursorType type)1151 void Settings::setStylusCursorType(StylusCursorType type) {
1152     if (this->stylusCursorType == type) {
1153         return;
1154     }
1155 
1156     this->stylusCursorType = type;
1157 
1158     save();
1159 }
1160 
isHighlightPosition() const1161 auto Settings::isHighlightPosition() const -> bool { return this->highlightPosition; }
1162 
setHighlightPosition(bool highlight)1163 void Settings::setHighlightPosition(bool highlight) {
1164     if (this->highlightPosition == highlight) {
1165         return;
1166     }
1167 
1168     this->highlightPosition = highlight;
1169     save();
1170 }
1171 
getCursorHighlightColor() const1172 auto Settings::getCursorHighlightColor() const -> Color { return this->cursorHighlightColor; }
1173 
setCursorHighlightColor(Color color)1174 void Settings::setCursorHighlightColor(Color color) {
1175     if (this->cursorHighlightColor != color) {
1176         this->cursorHighlightColor = color;
1177         save();
1178     }
1179 }
1180 
getCursorHighlightRadius() const1181 auto Settings::getCursorHighlightRadius() const -> double { return this->cursorHighlightRadius; }
1182 
setCursorHighlightRadius(double radius)1183 void Settings::setCursorHighlightRadius(double radius) {
1184     if (this->cursorHighlightRadius != radius) {
1185         this->cursorHighlightRadius = radius;
1186         save();
1187     }
1188 }
1189 
getCursorHighlightBorderColor() const1190 auto Settings::getCursorHighlightBorderColor() const -> Color { return this->cursorHighlightBorderColor; }
1191 
setCursorHighlightBorderColor(Color color)1192 void Settings::setCursorHighlightBorderColor(Color color) {
1193     if (this->cursorHighlightBorderColor != color) {
1194         this->cursorHighlightBorderColor = color;
1195         save();
1196     }
1197 }
1198 
getCursorHighlightBorderWidth() const1199 auto Settings::getCursorHighlightBorderWidth() const -> double { return this->cursorHighlightBorderWidth; }
1200 
setCursorHighlightBorderWidth(double radius)1201 void Settings::setCursorHighlightBorderWidth(double radius) {
1202     if (this->cursorHighlightBorderWidth != radius) {
1203         this->cursorHighlightBorderWidth = radius;
1204         save();
1205     }
1206 }
1207 
isSnapRotation() const1208 auto Settings::isSnapRotation() const -> bool { return this->snapRotation; }
1209 
setSnapRotation(bool b)1210 void Settings::setSnapRotation(bool b) {
1211     if (this->snapRotation == b) {
1212         return;
1213     }
1214 
1215     this->snapRotation = b;
1216     save();
1217 }
1218 
getSnapRotationTolerance() const1219 auto Settings::getSnapRotationTolerance() const -> double { return this->snapRotationTolerance; }
1220 
setSnapRotationTolerance(double tolerance)1221 void Settings::setSnapRotationTolerance(double tolerance) {
1222     this->snapRotationTolerance = tolerance;
1223     save();
1224 }
1225 
isSnapGrid() const1226 auto Settings::isSnapGrid() const -> bool { return this->snapGrid; }
1227 
setSnapGrid(bool b)1228 void Settings::setSnapGrid(bool b) {
1229     if (this->snapGrid == b) {
1230         return;
1231     }
1232 
1233     this->snapGrid = b;
1234     save();
1235 }
1236 
setSnapGridTolerance(double tolerance)1237 void Settings::setSnapGridTolerance(double tolerance) {
1238     this->snapGridTolerance = tolerance;
1239     save();
1240 }
1241 
getSnapGridTolerance() const1242 auto Settings::getSnapGridTolerance() const -> double { return this->snapGridTolerance; }
getSnapGridSize() const1243 auto Settings::getSnapGridSize() const -> double { return this->snapGridSize; };
setSnapGridSize(double gridSize)1244 void Settings::setSnapGridSize(double gridSize) {
1245     if (this->snapGridSize == gridSize) {
1246         return;
1247     }
1248     this->snapGridSize = gridSize;
1249     save();
1250 }
1251 
getTouchDrawingEnabled() const1252 auto Settings::getTouchDrawingEnabled() const -> bool { return this->touchDrawing; }
1253 
setTouchDrawingEnabled(bool b)1254 void Settings::setTouchDrawingEnabled(bool b) {
1255     if (this->touchDrawing == b) {
1256         return;
1257     }
1258 
1259     this->touchDrawing = b;
1260     save();
1261 }
1262 
isPressureGuessingEnabled() const1263 auto Settings::isPressureGuessingEnabled() const -> bool { return this->pressureGuessing; }
setPressureGuessingEnabled(bool b)1264 void Settings::setPressureGuessingEnabled(bool b) {
1265     if (this->pressureGuessing == b) {
1266         return;
1267     }
1268 
1269     this->pressureGuessing = b;
1270     save();
1271 }
1272 
getMinimumPressure() const1273 double Settings::getMinimumPressure() const { return this->minimumPressure; }
setMinimumPressure(double minimumPressure)1274 void Settings::setMinimumPressure(double minimumPressure) {
1275     if (this->minimumPressure == minimumPressure) {
1276         return;
1277     }
1278 
1279     this->minimumPressure = minimumPressure;
1280     save();
1281 }
1282 
getPressureMultiplier() const1283 double Settings::getPressureMultiplier() const { return this->pressureMultiplier; }
setPressureMultiplier(double multiplier)1284 void Settings::setPressureMultiplier(double multiplier) {
1285     if (this->pressureMultiplier == multiplier) {
1286         return;
1287     }
1288 
1289     this->pressureMultiplier = multiplier;
1290     save();
1291 }
1292 
getScrollbarHideType() const1293 auto Settings::getScrollbarHideType() const -> ScrollbarHideType { return this->scrollbarHideType; }
1294 
setScrollbarHideType(ScrollbarHideType type)1295 void Settings::setScrollbarHideType(ScrollbarHideType type) {
1296     if (this->scrollbarHideType == type) {
1297         return;
1298     }
1299 
1300     this->scrollbarHideType = type;
1301 
1302     save();
1303 }
1304 
isAutoloadMostRecent() const1305 auto Settings::isAutoloadMostRecent() const -> bool { return this->autoloadMostRecent; }
1306 
setAutoloadMostRecent(bool load)1307 void Settings::setAutoloadMostRecent(bool load) {
1308     if (this->autoloadMostRecent == load) {
1309         return;
1310     }
1311     this->autoloadMostRecent = load;
1312     save();
1313 }
1314 
isAutoloadPdfXoj() const1315 auto Settings::isAutoloadPdfXoj() const -> bool { return this->autoloadPdfXoj; }
1316 
setAutoloadPdfXoj(bool load)1317 void Settings::setAutoloadPdfXoj(bool load) {
1318     if (this->autoloadPdfXoj == load) {
1319         return;
1320     }
1321     this->autoloadPdfXoj = load;
1322     save();
1323 }
1324 
getDefaultSaveName() const1325 auto Settings::getDefaultSaveName() const -> string const& { return this->defaultSaveName; }
1326 
setDefaultSaveName(const string & name)1327 void Settings::setDefaultSaveName(const string& name) {
1328     if (this->defaultSaveName == name) {
1329         return;
1330     }
1331 
1332     this->defaultSaveName = name;
1333 
1334     save();
1335 }
1336 
getPageTemplate() const1337 auto Settings::getPageTemplate() const -> string const& { return this->pageTemplate; }
1338 
setPageTemplate(const string & pageTemplate)1339 void Settings::setPageTemplate(const string& pageTemplate) {
1340     if (this->pageTemplate == pageTemplate) {
1341         return;
1342     }
1343 
1344     this->pageTemplate = pageTemplate;
1345 
1346     save();
1347 }
1348 
getAudioFolder() const1349 auto Settings::getAudioFolder() const -> string const& { return this->audioFolder; }
1350 
setAudioFolder(const string & audioFolder)1351 void Settings::setAudioFolder(const string& audioFolder) {
1352     if (this->audioFolder == audioFolder) {
1353         return;
1354     }
1355 
1356     this->audioFolder = audioFolder;
1357 
1358     save();
1359 }
1360 
getSizeUnit() const1361 auto Settings::getSizeUnit() const -> string const& { return sizeUnit; }
1362 
setSizeUnit(const string & sizeUnit)1363 void Settings::setSizeUnit(const string& sizeUnit) {
1364     if (this->sizeUnit == sizeUnit) {
1365         return;
1366     }
1367 
1368     this->sizeUnit = sizeUnit;
1369 
1370     save();
1371 }
1372 
1373 /**
1374  * Get size index in XOJ_UNITS
1375  */
getSizeUnitIndex() const1376 auto Settings::getSizeUnitIndex() const -> int {
1377     string unit = getSizeUnit();
1378 
1379     for (int i = 0; i < XOJ_UNIT_COUNT; i++) {
1380         if (unit == XOJ_UNITS[i].name) {
1381             return i;
1382         }
1383     }
1384 
1385     return 0;
1386 }
1387 
1388 /**
1389  * Set size index in XOJ_UNITS
1390  */
setSizeUnitIndex(int sizeUnitId)1391 void Settings::setSizeUnitIndex(int sizeUnitId) {
1392     if (sizeUnitId < 0 || sizeUnitId >= XOJ_UNIT_COUNT) {
1393         sizeUnitId = 0;
1394     }
1395 
1396     setSizeUnit(XOJ_UNITS[sizeUnitId].name);
1397 }
1398 
setShowPairedPages(bool showPairedPages)1399 void Settings::setShowPairedPages(bool showPairedPages) {
1400     if (this->showPairedPages == showPairedPages) {
1401         return;
1402     }
1403 
1404     this->showPairedPages = showPairedPages;
1405     save();
1406 }
1407 
isShowPairedPages() const1408 auto Settings::isShowPairedPages() const -> bool { return this->showPairedPages; }
1409 
setPresentationMode(bool presentationMode)1410 void Settings::setPresentationMode(bool presentationMode) {
1411     if (this->presentationMode == presentationMode) {
1412         return;
1413     }
1414 
1415     this->presentationMode = presentationMode;
1416     save();
1417 }
1418 
isPresentationMode() const1419 auto Settings::isPresentationMode() const -> bool { return this->presentationMode; }
1420 
setPressureSensitivity(gboolean presureSensitivity)1421 void Settings::setPressureSensitivity(gboolean presureSensitivity) {
1422     if (this->pressureSensitivity == presureSensitivity) {
1423         return;
1424     }
1425     this->pressureSensitivity = presureSensitivity;
1426 
1427     save();
1428 }
1429 
setPairsOffset(int numOffset)1430 void Settings::setPairsOffset(int numOffset) {
1431     if (this->numPairsOffset == numOffset) {
1432         return;
1433     }
1434 
1435     this->numPairsOffset = numOffset;
1436     save();
1437 }
1438 
getPairsOffset() const1439 auto Settings::getPairsOffset() const -> int { return this->numPairsOffset; }
1440 
setViewColumns(int numColumns)1441 void Settings::setViewColumns(int numColumns) {
1442     if (this->numColumns == numColumns) {
1443         return;
1444     }
1445 
1446     this->numColumns = numColumns;
1447     save();
1448 }
1449 
getViewColumns() const1450 auto Settings::getViewColumns() const -> int { return this->numColumns; }
1451 
1452 
setViewRows(int numRows)1453 void Settings::setViewRows(int numRows) {
1454     if (this->numRows == numRows) {
1455         return;
1456     }
1457 
1458     this->numRows = numRows;
1459     save();
1460 }
1461 
getViewRows() const1462 auto Settings::getViewRows() const -> int { return this->numRows; }
1463 
setViewFixedRows(bool viewFixedRows)1464 void Settings::setViewFixedRows(bool viewFixedRows) {
1465     if (this->viewFixedRows == viewFixedRows) {
1466         return;
1467     }
1468 
1469     this->viewFixedRows = viewFixedRows;
1470     save();
1471 }
1472 
isViewFixedRows() const1473 auto Settings::isViewFixedRows() const -> bool { return this->viewFixedRows; }
1474 
setViewLayoutVert(bool vert)1475 void Settings::setViewLayoutVert(bool vert) {
1476     if (this->layoutVertical == vert) {
1477         return;
1478     }
1479 
1480     this->layoutVertical = vert;
1481     save();
1482 }
1483 
getViewLayoutVert() const1484 auto Settings::getViewLayoutVert() const -> bool { return this->layoutVertical; }
1485 
setViewLayoutR2L(bool r2l)1486 void Settings::setViewLayoutR2L(bool r2l) {
1487     if (this->layoutRightToLeft == r2l) {
1488         return;
1489     }
1490 
1491     this->layoutRightToLeft = r2l;
1492     save();
1493 }
1494 
getViewLayoutR2L() const1495 auto Settings::getViewLayoutR2L() const -> bool { return this->layoutRightToLeft; }
1496 
setViewLayoutB2T(bool b2t)1497 void Settings::setViewLayoutB2T(bool b2t) {
1498     if (this->layoutBottomToTop == b2t) {
1499         return;
1500     }
1501 
1502     this->layoutBottomToTop = b2t;
1503     save();
1504 }
1505 
getViewLayoutB2T() const1506 auto Settings::getViewLayoutB2T() const -> bool { return this->layoutBottomToTop; }
1507 
setLastSavePath(fs::path p)1508 void Settings::setLastSavePath(fs::path p) {
1509     this->lastSavePath = std::move(p);
1510     save();
1511 }
1512 
getLastSavePath() const1513 auto Settings::getLastSavePath() const -> fs::path const& { return this->lastSavePath; }
1514 
setLastOpenPath(fs::path p)1515 void Settings::setLastOpenPath(fs::path p) {
1516     this->lastOpenPath = std::move(p);
1517     save();
1518 }
1519 
getLastOpenPath() const1520 auto Settings::getLastOpenPath() const -> fs::path const& { return this->lastOpenPath; }
1521 
setLastImagePath(const fs::path & path)1522 void Settings::setLastImagePath(const fs::path& path) {
1523     if (this->lastImagePath == path) {
1524         return;
1525     }
1526     this->lastImagePath = path;
1527     save();
1528 }
1529 
getLastImagePath() const1530 auto Settings::getLastImagePath() const -> fs::path const& { return this->lastImagePath; }
1531 
setZoomStep(double zoomStep)1532 void Settings::setZoomStep(double zoomStep) {
1533     if (this->zoomStep == zoomStep) {
1534         return;
1535     }
1536     this->zoomStep = zoomStep;
1537     save();
1538 }
1539 
getZoomStep() const1540 auto Settings::getZoomStep() const -> double { return this->zoomStep; }
1541 
setZoomStepScroll(double zoomStepScroll)1542 void Settings::setZoomStepScroll(double zoomStepScroll) {
1543     if (this->zoomStepScroll == zoomStepScroll) {
1544         return;
1545     }
1546     this->zoomStepScroll = zoomStepScroll;
1547     save();
1548 }
1549 
getZoomStepScroll() const1550 auto Settings::getZoomStepScroll() const -> double { return this->zoomStepScroll; }
1551 
setDisplayDpi(int dpi)1552 void Settings::setDisplayDpi(int dpi) {
1553     if (this->displayDpi == dpi) {
1554         return;
1555     }
1556     this->displayDpi = dpi;
1557     save();
1558 }
1559 
getDisplayDpi() const1560 auto Settings::getDisplayDpi() const -> int { return this->displayDpi; }
1561 
setDarkTheme(bool dark)1562 void Settings::setDarkTheme(bool dark) {
1563     if (this->darkTheme == dark) {
1564         return;
1565     }
1566     this->darkTheme = dark;
1567     save();
1568 }
1569 
isDarkTheme() const1570 auto Settings::isDarkTheme() const -> bool { return this->darkTheme; }
1571 
setAreStockIconsUsed(bool use)1572 void Settings::setAreStockIconsUsed(bool use) {
1573     if (this->useStockIcons == use) {
1574         return;
1575     }
1576     this->useStockIcons = use;
1577     save();
1578 }
1579 
areStockIconsUsed() const1580 auto Settings::areStockIconsUsed() const -> bool { return this->useStockIcons; }
1581 
isSidebarVisible() const1582 auto Settings::isSidebarVisible() const -> bool { return this->showSidebar; }
1583 
setSidebarVisible(bool visible)1584 void Settings::setSidebarVisible(bool visible) {
1585     if (this->showSidebar == visible) {
1586         return;
1587     }
1588     this->showSidebar = visible;
1589     save();
1590 }
1591 
isToolbarVisible() const1592 auto Settings::isToolbarVisible() const -> bool { return this->showToolbar; }
1593 
setToolbarVisible(bool visible)1594 void Settings::setToolbarVisible(bool visible) {
1595     if (this->showToolbar == visible) {
1596         return;
1597     }
1598     this->showToolbar = visible;
1599     save();
1600 }
1601 
getSidebarWidth() const1602 auto Settings::getSidebarWidth() const -> int { return this->sidebarWidth; }
1603 
setSidebarWidth(int width)1604 void Settings::setSidebarWidth(int width) {
1605     width = std::max(width, 50);
1606 
1607     if (this->sidebarWidth == width) {
1608         return;
1609     }
1610     this->sidebarWidth = width;
1611     save();
1612 }
1613 
setMainWndSize(int width,int height)1614 void Settings::setMainWndSize(int width, int height) {
1615     this->mainWndWidth = width;
1616     this->mainWndHeight = height;
1617 
1618     save();
1619 }
1620 
getMainWndWidth() const1621 auto Settings::getMainWndWidth() const -> int { return this->mainWndWidth; }
1622 
getMainWndHeight() const1623 auto Settings::getMainWndHeight() const -> int { return this->mainWndHeight; }
1624 
isMainWndMaximized() const1625 auto Settings::isMainWndMaximized() const -> bool { return this->maximized; }
1626 
setMainWndMaximized(bool max)1627 void Settings::setMainWndMaximized(bool max) { this->maximized = max; }
1628 
setSelectedToolbar(const string & name)1629 void Settings::setSelectedToolbar(const string& name) {
1630     if (this->selectedToolbar == name) {
1631         return;
1632     }
1633     this->selectedToolbar = name;
1634     save();
1635 }
1636 
getSelectedToolbar() const1637 auto Settings::getSelectedToolbar() const -> string const& { return this->selectedToolbar; }
1638 
getCustomElement(const string & name)1639 auto Settings::getCustomElement(const string& name) -> SElement& { return this->data[name]; }
1640 
customSettingsChanged()1641 void Settings::customSettingsChanged() { save(); }
1642 
getButtonConfig(int id)1643 auto Settings::getButtonConfig(int id) -> ButtonConfig* {
1644     if (id < 0 || id >= BUTTON_COUNT) {
1645         g_error("Settings::getButtonConfig try to get id=%i out of range!", id);
1646         return nullptr;
1647     }
1648     return this->buttonConfig[id];
1649 }
1650 
getFullscreenHideElements() const1651 auto Settings::getFullscreenHideElements() const -> string const& { return this->fullscreenHideElements; }
1652 
setFullscreenHideElements(string elements)1653 void Settings::setFullscreenHideElements(string elements) {
1654     this->fullscreenHideElements = std::move(elements);
1655     save();
1656 }
1657 
getPresentationHideElements() const1658 auto Settings::getPresentationHideElements() const -> string const& { return this->presentationHideElements; }
1659 
setPresentationHideElements(string elements)1660 void Settings::setPresentationHideElements(string elements) {
1661     this->presentationHideElements = std::move(elements);
1662     save();
1663 }
1664 
getTouchZoomStartThreshold() const1665 auto Settings::getTouchZoomStartThreshold() const -> double { return this->touchZoomStartThreshold; }
setTouchZoomStartThreshold(double threshold)1666 void Settings::setTouchZoomStartThreshold(double threshold) {
1667     if (this->touchZoomStartThreshold == threshold) {
1668         return;
1669     }
1670 
1671     this->touchZoomStartThreshold = threshold;
1672     save();
1673 }
1674 
1675 
getPDFPageRerenderThreshold() const1676 auto Settings::getPDFPageRerenderThreshold() const -> double { return this->pageRerenderThreshold; }
setPDFPageRerenderThreshold(double threshold)1677 void Settings::setPDFPageRerenderThreshold(double threshold) {
1678     if (this->pageRerenderThreshold == threshold) {
1679         return;
1680     }
1681 
1682     this->pageRerenderThreshold = threshold;
1683     save();
1684 }
1685 
getPdfPageCacheSize() const1686 auto Settings::getPdfPageCacheSize() const -> int { return this->pdfPageCacheSize; }
1687 
setPdfPageCacheSize(int size)1688 void Settings::setPdfPageCacheSize(int size) {
1689     if (this->pdfPageCacheSize == size) {
1690         return;
1691     }
1692     this->pdfPageCacheSize = size;
1693     save();
1694 }
1695 
getPreloadPagesBefore() const1696 auto Settings::getPreloadPagesBefore() const -> unsigned int { return this->preloadPagesBefore; }
1697 
setPreloadPagesBefore(unsigned int n)1698 void Settings::setPreloadPagesBefore(unsigned int n) {
1699     if (this->preloadPagesBefore == n) {
1700         return;
1701     }
1702     this->preloadPagesBefore = n;
1703     save();
1704 }
1705 
getPreloadPagesAfter() const1706 auto Settings::getPreloadPagesAfter() const -> unsigned int { return this->preloadPagesAfter; }
1707 
setPreloadPagesAfter(unsigned int n)1708 void Settings::setPreloadPagesAfter(unsigned int n) {
1709     if (this->preloadPagesAfter == n) {
1710         return;
1711     }
1712     this->preloadPagesAfter = n;
1713     save();
1714 }
1715 
isEagerPageCleanup() const1716 auto Settings::isEagerPageCleanup() const -> bool { return this->eagerPageCleanup; }
1717 
setEagerPageCleanup(bool b)1718 void Settings::setEagerPageCleanup(bool b) {
1719     if (this->eagerPageCleanup == b) {
1720         return;
1721     }
1722     this->eagerPageCleanup = b;
1723     save();
1724 }
1725 
getBorderColor() const1726 auto Settings::getBorderColor() const -> Color { return this->selectionBorderColor; }
1727 
setBorderColor(Color color)1728 void Settings::setBorderColor(Color color) {
1729     if (this->selectionBorderColor == color) {
1730         return;
1731     }
1732     this->selectionBorderColor = color;
1733     save();
1734 }
1735 
getSelectionColor() const1736 auto Settings::getSelectionColor() const -> Color { return this->selectionMarkerColor; }
1737 
setSelectionColor(Color color)1738 void Settings::setSelectionColor(Color color) {
1739     if (this->selectionMarkerColor == color) {
1740         return;
1741     }
1742     this->selectionMarkerColor = color;
1743     save();
1744 }
1745 
getBackgroundColor() const1746 auto Settings::getBackgroundColor() const -> Color { return this->backgroundColor; }
1747 
setBackgroundColor(Color color)1748 void Settings::setBackgroundColor(Color color) {
1749     if (this->backgroundColor == color) {
1750         return;
1751     }
1752     this->backgroundColor = color;
1753     save();
1754 }
1755 
getFont()1756 auto Settings::getFont() -> XojFont& { return this->font; }
1757 
setFont(const XojFont & font)1758 void Settings::setFont(const XojFont& font) {
1759     this->font = font;
1760     save();
1761 }
1762 
1763 
getAudioInputDevice() const1764 auto Settings::getAudioInputDevice() const -> PaDeviceIndex { return this->audioInputDevice; }
1765 
setAudioInputDevice(PaDeviceIndex deviceIndex)1766 void Settings::setAudioInputDevice(PaDeviceIndex deviceIndex) {
1767     if (this->audioInputDevice == deviceIndex) {
1768         return;
1769     }
1770     this->audioInputDevice = deviceIndex;
1771     save();
1772 }
1773 
getAudioOutputDevice() const1774 auto Settings::getAudioOutputDevice() const -> PaDeviceIndex { return this->audioOutputDevice; }
1775 
setAudioOutputDevice(PaDeviceIndex deviceIndex)1776 void Settings::setAudioOutputDevice(PaDeviceIndex deviceIndex) {
1777     if (this->audioOutputDevice == deviceIndex) {
1778         return;
1779     }
1780     this->audioOutputDevice = deviceIndex;
1781     save();
1782 }
1783 
getAudioSampleRate() const1784 auto Settings::getAudioSampleRate() const -> double { return this->audioSampleRate; }
1785 
setAudioSampleRate(double sampleRate)1786 void Settings::setAudioSampleRate(double sampleRate) {
1787     if (this->audioSampleRate == sampleRate) {
1788         return;
1789     }
1790     this->audioSampleRate = sampleRate;
1791     save();
1792 }
1793 
getAudioGain() const1794 auto Settings::getAudioGain() const -> double { return this->audioGain; }
1795 
setAudioGain(double gain)1796 void Settings::setAudioGain(double gain) {
1797     if (this->audioGain == gain) {
1798         return;
1799     }
1800     this->audioGain = gain;
1801     save();
1802 }
1803 
getDefaultSeekTime() const1804 auto Settings::getDefaultSeekTime() const -> unsigned int { return this->defaultSeekTime; }
1805 
setDefaultSeekTime(unsigned int t)1806 void Settings::setDefaultSeekTime(unsigned int t) {
1807     if (this->defaultSeekTime == t) {
1808         return;
1809     }
1810     this->defaultSeekTime = t;
1811     save();
1812 }
1813 
getPluginEnabled() const1814 auto Settings::getPluginEnabled() const -> string const& { return this->pluginEnabled; }
1815 
setPluginEnabled(const string & pluginEnabled)1816 void Settings::setPluginEnabled(const string& pluginEnabled) {
1817     if (this->pluginEnabled == pluginEnabled) {
1818         return;
1819     }
1820     this->pluginEnabled = pluginEnabled;
1821     save();
1822 }
1823 
getPluginDisabled() const1824 auto Settings::getPluginDisabled() const -> string const& { return this->pluginDisabled; }
1825 
setPluginDisabled(const string & pluginDisabled)1826 void Settings::setPluginDisabled(const string& pluginDisabled) {
1827     if (this->pluginDisabled == pluginDisabled) {
1828         return;
1829     }
1830     this->pluginDisabled = pluginDisabled;
1831     save();
1832 }
1833 
1834 
getStrokeFilter(int * ignoreTime,double * ignoreLength,int * successiveTime) const1835 void Settings::getStrokeFilter(int* ignoreTime, double* ignoreLength, int* successiveTime) const {
1836     *ignoreTime = this->strokeFilterIgnoreTime;
1837     *ignoreLength = this->strokeFilterIgnoreLength;
1838     *successiveTime = this->strokeFilterSuccessiveTime;
1839 }
1840 
setStrokeFilter(int ignoreTime,double ignoreLength,int successiveTime)1841 void Settings::setStrokeFilter(int ignoreTime, double ignoreLength, int successiveTime) {
1842     this->strokeFilterIgnoreTime = ignoreTime;
1843     this->strokeFilterIgnoreLength = ignoreLength;
1844     this->strokeFilterSuccessiveTime = successiveTime;
1845 }
1846 
setStrokeFilterEnabled(bool enabled)1847 void Settings::setStrokeFilterEnabled(bool enabled) { this->strokeFilterEnabled = enabled; }
1848 
getStrokeFilterEnabled() const1849 auto Settings::getStrokeFilterEnabled() const -> bool { return this->strokeFilterEnabled; }
1850 
setDoActionOnStrokeFiltered(bool enabled)1851 void Settings::setDoActionOnStrokeFiltered(bool enabled) { this->doActionOnStrokeFiltered = enabled; }
1852 
getDoActionOnStrokeFiltered() const1853 auto Settings::getDoActionOnStrokeFiltered() const -> bool { return this->doActionOnStrokeFiltered; }
1854 
setTrySelectOnStrokeFiltered(bool enabled)1855 void Settings::setTrySelectOnStrokeFiltered(bool enabled) { this->trySelectOnStrokeFiltered = enabled; }
1856 
getTrySelectOnStrokeFiltered() const1857 auto Settings::getTrySelectOnStrokeFiltered() const -> bool { return this->trySelectOnStrokeFiltered; }
1858 
setSnapRecognizedShapesEnabled(bool enabled)1859 void Settings::setSnapRecognizedShapesEnabled(bool enabled) { this->snapRecognizedShapesEnabled = enabled; }
1860 
getSnapRecognizedShapesEnabled() const1861 auto Settings::getSnapRecognizedShapesEnabled() const -> bool { return this->snapRecognizedShapesEnabled; }
1862 
1863 
setRestoreLineWidthEnabled(bool enabled)1864 void Settings::setRestoreLineWidthEnabled(bool enabled) { this->restoreLineWidthEnabled = enabled; }
1865 
getRestoreLineWidthEnabled() const1866 auto Settings::getRestoreLineWidthEnabled() const -> bool { return this->restoreLineWidthEnabled; }
1867 
setPreferredLocale(std::string const & locale)1868 auto Settings::setPreferredLocale(std::string const& locale) -> void { this->preferredLocale = locale; }
1869 
getPreferredLocale() const1870 auto Settings::getPreferredLocale() const -> std::string { return this->preferredLocale; }
1871 
setIgnoredStylusEvents(int numEvents)1872 void Settings::setIgnoredStylusEvents(int numEvents) {
1873     if (this->numIgnoredStylusEvents == numEvents) {
1874         return;
1875     }
1876     this->numIgnoredStylusEvents = std::max<int>(numEvents, 0);
1877     save();
1878 }
1879 
getIgnoredStylusEvents() const1880 auto Settings::getIgnoredStylusEvents() const -> int { return this->numIgnoredStylusEvents; }
1881 
setInputSystemTPCButtonEnabled(bool tpcButtonEnabled)1882 void Settings::setInputSystemTPCButtonEnabled(bool tpcButtonEnabled) {
1883     if (this->inputSystemTPCButton == tpcButtonEnabled) {
1884         return;
1885     }
1886     this->inputSystemTPCButton = tpcButtonEnabled;
1887     save();
1888 }
1889 
getInputSystemTPCButtonEnabled() const1890 auto Settings::getInputSystemTPCButtonEnabled() const -> bool { return this->inputSystemTPCButton; }
1891 
setInputSystemDrawOutsideWindowEnabled(bool drawOutsideWindowEnabled)1892 void Settings::setInputSystemDrawOutsideWindowEnabled(bool drawOutsideWindowEnabled) {
1893     if (this->inputSystemDrawOutsideWindow == drawOutsideWindowEnabled) {
1894         return;
1895     }
1896     this->inputSystemDrawOutsideWindow = drawOutsideWindowEnabled;
1897     save();
1898 }
1899 
getInputSystemDrawOutsideWindowEnabled() const1900 auto Settings::getInputSystemDrawOutsideWindowEnabled() const -> bool { return this->inputSystemDrawOutsideWindow; }
1901 
setDeviceClassForDevice(GdkDevice * device,InputDeviceTypeOption deviceClass)1902 void Settings::setDeviceClassForDevice(GdkDevice* device, InputDeviceTypeOption deviceClass) {
1903     this->setDeviceClassForDevice(gdk_device_get_name(device), gdk_device_get_source(device), deviceClass);
1904 }
1905 
setDeviceClassForDevice(const string & deviceName,GdkInputSource deviceSource,InputDeviceTypeOption deviceClass)1906 void Settings::setDeviceClassForDevice(const string& deviceName, GdkInputSource deviceSource,
1907                                        InputDeviceTypeOption deviceClass) {
1908     auto it = inputDeviceClasses.find(deviceName);
1909     if (it != inputDeviceClasses.end()) {
1910         it->second.first = deviceClass;
1911         it->second.second = deviceSource;
1912     } else {
1913         inputDeviceClasses.emplace(deviceName, std::make_pair(deviceClass, deviceSource));
1914     }
1915 }
1916 
getKnownInputDevices() const1917 auto Settings::getKnownInputDevices() const -> std::vector<InputDevice> {
1918     std::vector<InputDevice> inputDevices;
1919     for (auto pair: inputDeviceClasses) {
1920         const std::string& name = pair.first;
1921         GdkInputSource& source = pair.second.second;
1922         inputDevices.emplace_back(name, source);
1923     }
1924     return inputDevices;
1925 }
1926 
getDeviceClassForDevice(GdkDevice * device) const1927 auto Settings::getDeviceClassForDevice(GdkDevice* device) const -> InputDeviceTypeOption {
1928     return this->getDeviceClassForDevice(gdk_device_get_name(device), gdk_device_get_source(device));
1929 }
1930 
getDeviceClassForDevice(const string & deviceName,GdkInputSource deviceSource) const1931 auto Settings::getDeviceClassForDevice(const string& deviceName, GdkInputSource deviceSource) const
1932         -> InputDeviceTypeOption {
1933     auto search = inputDeviceClasses.find(deviceName);
1934     if (search != inputDeviceClasses.end()) {
1935         return search->second.first;
1936     }
1937 
1938 
1939     InputDeviceTypeOption deviceType = InputDeviceTypeOption::Disabled;
1940     switch (deviceSource) {
1941         case GDK_SOURCE_CURSOR:
1942 #if (GDK_MAJOR_VERSION >= 3 && GDK_MINOR_VERSION >= 22)
1943         case GDK_SOURCE_TABLET_PAD:
1944 #endif
1945         case GDK_SOURCE_KEYBOARD:
1946             deviceType = InputDeviceTypeOption::Disabled;
1947             break;
1948         case GDK_SOURCE_MOUSE:
1949         case GDK_SOURCE_TOUCHPAD:
1950 #if (GDK_MAJOR_VERSION >= 3 && GDK_MINOR_VERSION >= 22)
1951         case GDK_SOURCE_TRACKPOINT:
1952 #endif
1953             deviceType = InputDeviceTypeOption::Mouse;
1954             break;
1955         case GDK_SOURCE_PEN:
1956             deviceType = InputDeviceTypeOption::Pen;
1957             break;
1958         case GDK_SOURCE_ERASER:
1959             deviceType = InputDeviceTypeOption::Eraser;
1960             break;
1961         case GDK_SOURCE_TOUCHSCREEN:
1962             deviceType = InputDeviceTypeOption::Touchscreen;
1963             break;
1964         default:
1965             deviceType = InputDeviceTypeOption::Disabled;
1966     }
1967     return deviceType;
1968 }
1969 
isScrollbarFadeoutDisabled() const1970 auto Settings::isScrollbarFadeoutDisabled() const -> bool { return disableScrollbarFadeout; }
1971 
setScrollbarFadeoutDisabled(bool disable)1972 void Settings::setScrollbarFadeoutDisabled(bool disable) {
1973     if (disableScrollbarFadeout == disable) {
1974         return;
1975     }
1976     disableScrollbarFadeout = disable;
1977     save();
1978 }
1979 
1980 //////////////////////////////////////////////////
1981 
SAttribute()1982 SAttribute::SAttribute() {
1983     this->dValue = 0;
1984     this->iValue = 0;
1985     this->type = ATTRIBUTE_TYPE_NONE;
1986 }
1987 
SAttribute(const SAttribute & attrib)1988 SAttribute::SAttribute(const SAttribute& attrib) { *this = attrib; }
1989 
~SAttribute()1990 SAttribute::~SAttribute() {
1991     this->iValue = 0;
1992     this->type = ATTRIBUTE_TYPE_NONE;
1993 }
1994 
1995 //////////////////////////////////////////////////
1996 
attributes()1997 auto SElement::attributes() -> std::map<string, SAttribute>& { return this->element->attributes; }
1998 
children()1999 auto SElement::children() -> std::map<string, SElement>& { return this->element->children; }
2000 
clear()2001 void SElement::clear() {
2002     this->element->attributes.clear();
2003     this->element->children.clear();
2004 }
2005 
child(const string & name)2006 auto SElement::child(const string& name) -> SElement& { return this->element->children[name]; }
2007 
setComment(const string & name,const string & comment)2008 void SElement::setComment(const string& name, const string& comment) {
2009     SAttribute& attrib = this->element->attributes[name];
2010     attrib.comment = comment;
2011 }
2012 
setIntHex(const string & name,const int value)2013 void SElement::setIntHex(const string& name, const int value) {
2014     SAttribute& attrib = this->element->attributes[name];
2015     attrib.iValue = value;
2016     attrib.type = ATTRIBUTE_TYPE_INT_HEX;
2017 }
2018 
setInt(const string & name,const int value)2019 void SElement::setInt(const string& name, const int value) {
2020     SAttribute& attrib = this->element->attributes[name];
2021     attrib.iValue = value;
2022     attrib.type = ATTRIBUTE_TYPE_INT;
2023 }
2024 
setBool(const string & name,const bool value)2025 void SElement::setBool(const string& name, const bool value) {
2026     SAttribute& attrib = this->element->attributes[name];
2027     attrib.iValue = value;
2028     attrib.type = ATTRIBUTE_TYPE_BOOLEAN;
2029 }
2030 
setString(const string & name,const string & value)2031 void SElement::setString(const string& name, const string& value) {
2032     SAttribute& attrib = this->element->attributes[name];
2033     attrib.sValue = value;
2034     attrib.type = ATTRIBUTE_TYPE_STRING;
2035 }
2036 
setDouble(const string & name,const double value)2037 void SElement::setDouble(const string& name, const double value) {
2038     SAttribute& attrib = this->element->attributes[name];
2039     attrib.dValue = value;
2040     attrib.type = ATTRIBUTE_TYPE_DOUBLE;
2041 }
2042 
getDouble(const string & name,double & value)2043 auto SElement::getDouble(const string& name, double& value) -> bool {
2044     SAttribute& attrib = this->element->attributes[name];
2045     if (attrib.type == ATTRIBUTE_TYPE_NONE) {
2046         this->element->attributes.erase(name);
2047         return false;
2048     }
2049 
2050     if (attrib.type != ATTRIBUTE_TYPE_DOUBLE) {
2051         return false;
2052     }
2053 
2054     value = attrib.dValue;
2055 
2056     return true;
2057 }
2058 
getInt(const string & name,int & value)2059 auto SElement::getInt(const string& name, int& value) -> bool {
2060     SAttribute& attrib = this->element->attributes[name];
2061     if (attrib.type == ATTRIBUTE_TYPE_NONE) {
2062         this->element->attributes.erase(name);
2063         return false;
2064     }
2065 
2066     if (attrib.type != ATTRIBUTE_TYPE_INT && attrib.type != ATTRIBUTE_TYPE_INT_HEX) {
2067         return false;
2068     }
2069 
2070     value = attrib.iValue;
2071 
2072     return true;
2073 }
2074 
getBool(const string & name,bool & value)2075 auto SElement::getBool(const string& name, bool& value) -> bool {
2076     SAttribute& attrib = this->element->attributes[name];
2077     if (attrib.type == ATTRIBUTE_TYPE_NONE) {
2078         this->element->attributes.erase(name);
2079         return false;
2080     }
2081 
2082     if (attrib.type != ATTRIBUTE_TYPE_BOOLEAN) {
2083         return false;
2084     }
2085 
2086     value = attrib.iValue;
2087 
2088     return true;
2089 }
2090 
getString(const string & name,string & value)2091 auto SElement::getString(const string& name, string& value) -> bool {
2092     SAttribute& attrib = this->element->attributes[name];
2093     if (attrib.type == ATTRIBUTE_TYPE_NONE) {
2094         this->element->attributes.erase(name);
2095         return false;
2096     }
2097 
2098     if (attrib.type != ATTRIBUTE_TYPE_STRING) {
2099         return false;
2100     }
2101 
2102     value = attrib.sValue;
2103 
2104     return true;
2105 }
2106 
2107 /**
2108  * Stabilizer related getters and setters
2109  */
getStabilizerCuspDetection() const2110 auto Settings::getStabilizerCuspDetection() const -> bool { return stabilizerCuspDetection; }
getStabilizerFinalizeStroke() const2111 auto Settings::getStabilizerFinalizeStroke() const -> bool { return stabilizerFinalizeStroke; }
getStabilizerBuffersize() const2112 auto Settings::getStabilizerBuffersize() const -> size_t { return stabilizerBuffersize; }
getStabilizerDeadzoneRadius() const2113 auto Settings::getStabilizerDeadzoneRadius() const -> double { return stabilizerDeadzoneRadius; }
getStabilizerDrag() const2114 auto Settings::getStabilizerDrag() const -> double { return stabilizerDrag; }
getStabilizerMass() const2115 auto Settings::getStabilizerMass() const -> double { return stabilizerMass; }
getStabilizerSigma() const2116 auto Settings::getStabilizerSigma() const -> double { return stabilizerSigma; }
getStabilizerAveragingMethod() const2117 auto Settings::getStabilizerAveragingMethod() const -> StrokeStabilizer::AveragingMethod {
2118     return stabilizerAveragingMethod;
2119 }
getStabilizerPreprocessor() const2120 auto Settings::getStabilizerPreprocessor() const -> StrokeStabilizer::Preprocessor { return stabilizerPreprocessor; }
2121 
setStabilizerCuspDetection(bool cuspDetection)2122 void Settings::setStabilizerCuspDetection(bool cuspDetection) {
2123     if (stabilizerCuspDetection == cuspDetection) {
2124         return;
2125     }
2126     stabilizerCuspDetection = cuspDetection;
2127     save();
2128 }
setStabilizerFinalizeStroke(bool finalizeStroke)2129 void Settings::setStabilizerFinalizeStroke(bool finalizeStroke) {
2130     if (stabilizerFinalizeStroke == finalizeStroke) {
2131         return;
2132     }
2133     stabilizerFinalizeStroke = finalizeStroke;
2134     save();
2135 }
setStabilizerBuffersize(size_t buffersize)2136 void Settings::setStabilizerBuffersize(size_t buffersize) {
2137     if (stabilizerBuffersize == buffersize) {
2138         return;
2139     }
2140     stabilizerBuffersize = buffersize;
2141     save();
2142 }
setStabilizerDeadzoneRadius(double deadzoneRadius)2143 void Settings::setStabilizerDeadzoneRadius(double deadzoneRadius) {
2144     if (stabilizerDeadzoneRadius == deadzoneRadius) {
2145         return;
2146     }
2147     stabilizerDeadzoneRadius = deadzoneRadius;
2148     save();
2149 }
setStabilizerDrag(double drag)2150 void Settings::setStabilizerDrag(double drag) {
2151     if (stabilizerDrag == drag) {
2152         return;
2153     }
2154     stabilizerDrag = drag;
2155     save();
2156 }
setStabilizerMass(double mass)2157 void Settings::setStabilizerMass(double mass) {
2158     if (stabilizerMass == mass) {
2159         return;
2160     }
2161     stabilizerMass = mass;
2162     save();
2163 }
setStabilizerSigma(double sigma)2164 void Settings::setStabilizerSigma(double sigma) {
2165     if (stabilizerSigma == sigma) {
2166         return;
2167     }
2168     stabilizerSigma = sigma;
2169     save();
2170 }
setStabilizerAveragingMethod(StrokeStabilizer::AveragingMethod averagingMethod)2171 void Settings::setStabilizerAveragingMethod(StrokeStabilizer::AveragingMethod averagingMethod) {
2172     const StrokeStabilizer::AveragingMethod method =
2173             StrokeStabilizer::isValid(averagingMethod) ? averagingMethod : StrokeStabilizer::AveragingMethod::NONE;
2174 
2175     if (stabilizerAveragingMethod == method) {
2176         return;
2177     }
2178     stabilizerAveragingMethod = method;
2179     save();
2180 }
setStabilizerPreprocessor(StrokeStabilizer::Preprocessor preprocessor)2181 void Settings::setStabilizerPreprocessor(StrokeStabilizer::Preprocessor preprocessor) {
2182     const StrokeStabilizer::Preprocessor p =
2183             StrokeStabilizer::isValid(preprocessor) ? preprocessor : StrokeStabilizer::Preprocessor::NONE;
2184 
2185     if (stabilizerPreprocessor == p) {
2186         return;
2187     }
2188     stabilizerPreprocessor = p;
2189     save();
2190 }
2191