1 // This file is part of Golly.
2 // See docs/License.html for the copyright notice.
3 
4 #include "lifealgo.h"
5 #include "viewport.h"       // for MAX_MAG
6 #include "util.h"           // for linereader, lifeerrors
7 
8 #include "utils.h"          // for gColor, SetColor, Warning, Fatal, Beep
9 #include "status.h"         // for DisplayMessage
10 #include "algos.h"          // for NumAlgos, algoinfo, etc
11 #include "layer.h"          // for currlayer
12 #include "prefs.h"
13 
14 #ifdef ANDROID_GUI
15     #include "jnicalls.h"   // for BeginProgress, etc
16 #endif
17 
18 #ifdef WEB_GUI
19     #include "webcalls.h"   // for BeginProgress, etc
20 #endif
21 
22 #ifdef IOS_GUI
23     #import "PatternViewController.h"   // for BeginProgress, etc
24 #endif
25 
26 // -----------------------------------------------------------------------------
27 
28 // Golly's preferences file is a simple text file.
29 
30 const int PREFS_VERSION = 1;        // increment if necessary due to changes in syntax/semantics
31 int currversion = PREFS_VERSION;    // might be changed by prefs_version
32 const int PREF_LINE_SIZE = 5000;    // must be quite long for storing file paths
33 
34 std::string supplieddir;            // path of parent directory for supplied help/patterns/rules
35 std::string helpdir;                // path of directory for supplied help
36 std::string patternsdir;            // path of directory for supplied patterns
37 std::string rulesdir;               // path of directory for supplied rules
38 std::string userdir;                // path of parent directory for user's rules/patterns/downloads
39 std::string userrules;              // path of directory for user's rules
40 std::string savedir;                // path of directory for user's saved patterns
41 std::string downloaddir;            // path of directory for user's downloaded files
42 std::string tempdir;                // path of directory for temporary data
43 std::string clipfile;               // path of temporary file for storing clipboard data
44 std::string prefsfile;              // path of file for storing user's preferences
45 
46 // initialize exported preferences:
47 
48 int debuglevel = 0;                 // for displaying debug info if > 0
49 int helpfontsize = 10;              // font size in help window
50 char initrule[256] = "B3/S23";      // initial rule
51 bool initautofit = false;           // initial autofit setting
52 bool inithyperspeed = false;        // initial hyperspeed setting
53 bool initshowhashinfo = false;      // initial showhashinfo setting
54 bool savexrle = true;               // save RLE file using XRLE format?
55 bool showtool = true;               // show tool bar?
56 bool showlayer = false;             // show layer bar?
57 bool showedit = true;               // show edit bar?
58 bool showallstates = false;         // show all cell states in edit bar?
59 bool showstatus = true;             // show status bar?
60 bool showexact = false;             // show exact numbers in status bar?
61 bool showtimeline = false;          // show timeline bar?
62 bool showtiming = false;            // show timing messages?
63 bool showgridlines = true;          // display grid lines?
64 bool showicons = false;             // display icons for cell states?
65 bool swapcolors = false;            // swap colors used for cell states?
66 bool allowundo = true;              // allow undo/redo?
67 bool allowbeep = true;              // okay to play beep sound?
68 bool restoreview = true;            // should reset/undo restore view?
69 int canchangerule = 1;              // if > 0 then paste can change rule if pattern is empty
70 int randomfill = 50;                // random fill percentage (1..100)
71 int opacity = 80;                   // percentage opacity of live cells in overlays (1..100)
72 int tileborder = 3;                 // thickness of tiled window borders
73 int mingridmag = 2;                 // minimum mag to draw grid lines
74 int boldspacing = 10;               // spacing of bold grid lines
75 bool showboldlines = true;          // show bold grid lines?
76 bool mathcoords = false;            // show Y values increasing upwards?
77 bool syncviews = false;             // synchronize viewports?
78 bool syncmodes = true;              // synchronize touch modes?
79 bool stacklayers = false;           // stack all layers?
80 bool tilelayers = false;            // tile all layers?
81 bool asktosave = true;              // ask to save changes?
82 int newmag = 5;                     // mag setting for new pattern
83 bool newremovesel = true;           // new pattern removes selection?
84 bool openremovesel = true;          // opening pattern removes selection?
85 int mindelay = 250;                 // minimum millisec delay
86 int maxdelay = 2000;                // maximum millisec delay
87 int maxhashmem = 100;               // maximum memory (in MB) for hashlife-based algos
88 
89 int numpatterns = 0;                // current number of recent pattern files
90 int maxpatterns = 20;               // maximum number of recent pattern files
91 std::list<std::string> recentpatterns; // list of recent pattern files
92 
93 gColor borderrgb;                   // color for border around bounded grid
94 gColor selectrgb;                   // color for selected cells
95 gColor pastergb;                    // color for pasted pattern
96 
97 paste_mode pmode = Or;              // logical paste mode
98 
99 // -----------------------------------------------------------------------------
100 
GetPasteMode()101 const char* GetPasteMode()
102 {
103     switch (pmode) {
104         case And:   return "AND";
105         case Copy:  return "COPY";
106         case Or:    return "OR";
107         case Xor:   return "XOR";
108         default:    return "unknown";
109     }
110 }
111 
112 // -----------------------------------------------------------------------------
113 
SetPasteMode(const char * s)114 void SetPasteMode(const char* s)
115 {
116     if (strcmp(s, "AND") == 0) {
117         pmode = And;
118     } else if (strcmp(s, "COPY") == 0) {
119         pmode = Copy;
120     } else if (strcmp(s, "OR") == 0) {
121         pmode = Or;
122     } else {
123         pmode = Xor;
124     }
125 }
126 
127 // -----------------------------------------------------------------------------
128 
CreateDefaultColors()129 void CreateDefaultColors()
130 {
131     SetColor(borderrgb, 128, 128, 128);     // 50% gray
132     SetColor(selectrgb,  75, 175,   0);     // dark green
133     SetColor(pastergb,  255,   0,   0);     // red
134 }
135 
136 // -----------------------------------------------------------------------------
137 
GetColor(const char * value,gColor & rgb)138 void GetColor(const char* value, gColor& rgb)
139 {
140     unsigned int r, g, b;
141     sscanf(value, "%u,%u,%u", &r, &g, &b);
142     SetColor(rgb, r, g, b);
143 }
144 
145 // -----------------------------------------------------------------------------
146 
SaveColor(FILE * f,const char * name,const gColor rgb)147 void SaveColor(FILE* f, const char* name, const gColor rgb)
148 {
149     fprintf(f, "%s=%u,%u,%u\n", name, rgb.r, rgb.g, rgb.b);
150 }
151 
152 // -----------------------------------------------------------------------------
153 
SavePrefs()154 void SavePrefs()
155 {
156     if (currlayer == NULL) {
157         // should never happen but play safe
158         Warning("Bug: currlayer is NULL!");
159         return;
160     }
161 
162     FILE* f = fopen(prefsfile.c_str(), "w");
163     if (f == NULL) {
164         Warning("Could not save preferences file!");
165         return;
166     }
167 
168     fprintf(f, "prefs_version=%d\n", PREFS_VERSION);
169     fprintf(f, "debug_level=%d\n", debuglevel);
170     fprintf(f, "help_font_size=%d (%d..%d)\n", helpfontsize, minfontsize, maxfontsize);
171     fprintf(f, "allow_undo=%d\n", allowundo ? 1 : 0);
172     fprintf(f, "allow_beep=%d\n", allowbeep ? 1 : 0);
173     fprintf(f, "restore_view=%d\n", restoreview ? 1 : 0);
174     fprintf(f, "paste_mode=%s\n", GetPasteMode());
175     fprintf(f, "can_change_rule=%d (0..2)\n", canchangerule);
176     fprintf(f, "random_fill=%d (1..100)\n", randomfill);
177     fprintf(f, "min_delay=%d (0..%d millisecs)\n", mindelay, MAX_DELAY);
178     fprintf(f, "max_delay=%d (0..%d millisecs)\n", maxdelay, MAX_DELAY);
179     fprintf(f, "auto_fit=%d\n", currlayer->autofit ? 1 : 0);
180     fprintf(f, "hyperspeed=%d\n", currlayer->hyperspeed ? 1 : 0);
181     fprintf(f, "hash_info=%d\n", currlayer->showhashinfo ? 1 : 0);
182     fprintf(f, "max_hash_mem=%d\n", maxhashmem);
183 
184     fputs("\n", f);
185 
186     fprintf(f, "init_algo=%s\n", GetAlgoName(currlayer->algtype));
187     for (int i = 0; i < NumAlgos(); i++) {
188         fputs("\n", f);
189         fprintf(f, "algorithm=%s\n", GetAlgoName(i));
190         fprintf(f, "base_step=%d\n", algoinfo[i]->defbase);
191         SaveColor(f, "status_rgb", algoinfo[i]->statusrgb);
192         SaveColor(f, "from_rgb", algoinfo[i]->fromrgb);
193         SaveColor(f, "to_rgb", algoinfo[i]->torgb);
194         fprintf(f, "use_gradient=%d\n", algoinfo[i]->gradient ? 1 : 0);
195         fputs("colors=", f);
196         for (int state = 0; state < algoinfo[i]->maxstates; state++) {
197             // only write out state,r,g,b tuple if color is different to default
198             if (algoinfo[i]->algor[state] != algoinfo[i]->defr[state] ||
199                 algoinfo[i]->algog[state] != algoinfo[i]->defg[state] ||
200                 algoinfo[i]->algob[state] != algoinfo[i]->defb[state] ) {
201                 fprintf(f, "%d,%d,%d,%d,", state, algoinfo[i]->algor[state],
202                                                   algoinfo[i]->algog[state],
203                                                   algoinfo[i]->algob[state]);
204             }
205         }
206         fputs("\n", f);
207     }
208 
209     fputs("\n", f);
210 
211     fprintf(f, "rule=%s\n", currlayer->algo->getrule());
212     fprintf(f, "show_tool=%d\n", showtool ? 1 : 0);
213     fprintf(f, "show_layer=%d\n", showlayer ? 1 : 0);
214     fprintf(f, "show_edit=%d\n", showedit ? 1 : 0);
215     fprintf(f, "show_states=%d\n", showallstates ? 1 : 0);
216     fprintf(f, "show_status=%d\n", showstatus ? 1 : 0);
217     fprintf(f, "show_exact=%d\n", showexact ? 1 : 0);
218     fprintf(f, "show_timeline=%d\n", showtimeline ? 1 : 0);
219     fprintf(f, "show_timing=%d\n", showtiming ? 1 : 0);
220     fprintf(f, "grid_lines=%d\n", showgridlines ? 1 : 0);
221     fprintf(f, "min_grid_mag=%d (2..%d)\n", mingridmag, MAX_MAG);
222     fprintf(f, "bold_spacing=%d (2..%d)\n", boldspacing, MAX_SPACING);
223     fprintf(f, "show_bold_lines=%d\n", showboldlines ? 1 : 0);
224     fprintf(f, "math_coords=%d\n", mathcoords ? 1 : 0);
225 
226     fputs("\n", f);
227 
228     fprintf(f, "sync_views=%d\n", syncviews ? 1 : 0);
229     fprintf(f, "sync_modes=%d\n", syncmodes ? 1 : 0);
230     fprintf(f, "stack_layers=%d\n", stacklayers ? 1 : 0);
231     fprintf(f, "tile_layers=%d\n", tilelayers ? 1 : 0);
232     fprintf(f, "tile_border=%d (1..10)\n", tileborder);
233     fprintf(f, "ask_to_save=%d\n", asktosave ? 1 : 0);
234 
235     fputs("\n", f);
236 
237     fprintf(f, "show_icons=%d\n", showicons ? 1 : 0);
238     fprintf(f, "swap_colors=%d\n", swapcolors ? 1 : 0);
239     fprintf(f, "opacity=%d (1..100)\n", opacity);
240     SaveColor(f, "border_rgb", borderrgb);
241     SaveColor(f, "select_rgb", selectrgb);
242     SaveColor(f, "paste_rgb", pastergb);
243 
244     fputs("\n", f);
245 
246     fprintf(f, "new_mag=%d (0..%d)\n", newmag, MAX_MAG);
247     fprintf(f, "new_remove_sel=%d\n", newremovesel ? 1 : 0);
248     fprintf(f, "open_remove_sel=%d\n", openremovesel ? 1 : 0);
249     fprintf(f, "save_xrle=%d\n", savexrle ? 1 : 0);
250     fprintf(f, "max_patterns=%d (1..%d)\n", maxpatterns, MAX_RECENT);
251 
252     if (!recentpatterns.empty()) {
253         fputs("\n", f);
254         std::list<std::string>::iterator next = recentpatterns.begin();
255         while (next != recentpatterns.end()) {
256             std::string path = *next;
257             fprintf(f, "recent_pattern=%s\n", path.c_str());
258             next++;
259         }
260     }
261 
262     fclose(f);
263 }
264 
265 // -----------------------------------------------------------------------------
266 
GetKeywordAndValue(linereader & lr,char * line,char ** keyword,char ** value)267 bool GetKeywordAndValue(linereader& lr, char* line, char** keyword, char** value)
268 {
269     // the linereader class handles all line endings (CR, CR+LF, LF)
270     // and terminates line buffer with \0
271     while ( lr.fgets(line, PREF_LINE_SIZE) != 0 ) {
272         if ( line[0] == '#' || line[0] == 0 ) {
273             // skip comment line or empty line
274         } else {
275             // line should have format keyword=value
276             *keyword = line;
277             *value = line;
278             while ( **value != '=' && **value != 0 ) *value += 1;
279             **value = 0;   // terminate keyword
280             *value += 1;
281             return true;
282         }
283     }
284     return false;
285 }
286 
287 // -----------------------------------------------------------------------------
288 
ReplaceDeprecatedAlgo(char * algoname)289 char* ReplaceDeprecatedAlgo(char* algoname)
290 {
291     if (strcmp(algoname, "RuleTable") == 0 ||
292         strcmp(algoname, "RuleTree") == 0) {
293         // RuleTable and RuleTree algos have been replaced by RuleLoader
294         return (char*)"RuleLoader";
295     } else {
296         return algoname;
297     }
298 }
299 
300 // -----------------------------------------------------------------------------
301 
302 // let gollybase code call Fatal, Warning, BeginProgress, etc
303 
304 class my_errors : public lifeerrors
305 {
306 public:
fatal(const char * s)307     virtual void fatal(const char* s) {
308         Fatal(s);
309     }
310 
warning(const char * s)311     virtual void warning(const char* s) {
312         Warning(s);
313     }
314 
status(const char * s)315     virtual void status(const char* s) {
316         DisplayMessage(s);
317     }
318 
beginprogress(const char * s)319     virtual void beginprogress(const char* s) {
320         BeginProgress(s);
321         // init flag for isaborted() calls
322         aborted = false;
323     }
324 
abortprogress(double f,const char * s)325     virtual bool abortprogress(double f, const char* s) {
326         return AbortProgress(f, s);
327     }
328 
endprogress()329     virtual void endprogress() {
330         EndProgress();
331     }
332 
getuserrules()333     virtual const char* getuserrules() {
334         return (const char*) userrules.c_str();
335     }
336 
getrulesdir()337     virtual const char* getrulesdir() {
338         return (const char*) rulesdir.c_str();
339     }
340 };
341 
342 static my_errors myerrhandler;    // create instance
343 
344 // -----------------------------------------------------------------------------
345 
GetPrefs()346 void GetPrefs()
347 {
348     int algoindex = -1;     // unknown algorithm
349 
350     // let gollybase code call Fatal, Warning, BeginProgress, etc
351     lifeerrors::seterrorhandler(&myerrhandler);
352 
353     CreateDefaultColors();
354 
355     FILE* f = fopen(prefsfile.c_str(), "r");
356     if (f == NULL) {
357         // should only happen 1st time app is run
358         return;
359     }
360 
361     linereader reader(f);
362     char line[PREF_LINE_SIZE];
363     char* keyword;
364     char* value;
365     while ( GetKeywordAndValue(reader, line, &keyword, &value) ) {
366 
367         if (strcmp(keyword, "prefs_version") == 0) {
368             sscanf(value, "%d", &currversion);
369 
370         } else if (strcmp(keyword, "debug_level") == 0) {
371             sscanf(value, "%d", &debuglevel);
372 
373         } else if (strcmp(keyword, "help_font_size") == 0) {
374             sscanf(value, "%d", &helpfontsize);
375             if (helpfontsize < minfontsize) helpfontsize = minfontsize;
376             if (helpfontsize > maxfontsize) helpfontsize = maxfontsize;
377 
378         } else if (strcmp(keyword, "allow_undo") == 0) {
379             allowundo = value[0] == '1';
380 
381         } else if (strcmp(keyword, "allow_beep") == 0) {
382             allowbeep = value[0] == '1';
383 
384         } else if (strcmp(keyword, "restore_view") == 0) {
385             restoreview = value[0] == '1';
386 
387         } else if (strcmp(keyword, "paste_mode") == 0) {
388             SetPasteMode(value);
389 
390         } else if (strcmp(keyword, "can_change_rule") == 0) {
391             sscanf(value, "%d", &canchangerule);
392             if (canchangerule < 0) canchangerule = 0;
393             if (canchangerule > 2) canchangerule = 2;
394 
395         } else if (strcmp(keyword, "random_fill") == 0) {
396             sscanf(value, "%d", &randomfill);
397             if (randomfill < 1) randomfill = 1;
398             if (randomfill > 100) randomfill = 100;
399 
400         } else if (strcmp(keyword, "algorithm") == 0) {
401             if (strcmp(value, "RuleTable") == 0) {
402                 // use deprecated RuleTable settings for RuleLoader
403                 // (deprecated RuleTree settings will simply be ignored)
404                 value = (char*)"RuleLoader";
405             }
406             algoindex = -1;
407             for (int i = 0; i < NumAlgos(); i++) {
408                 if (strcmp(value, GetAlgoName(i)) == 0) {
409                     algoindex = i;
410                     break;
411                 }
412             }
413 
414         } else if (strcmp(keyword, "base_step") == 0) {
415             if (algoindex >= 0 && algoindex < NumAlgos()) {
416                 int base;
417                 sscanf(value, "%d", &base);
418                 if (base < 2) base = 2;
419                 if (base > MAX_BASESTEP) base = MAX_BASESTEP;
420                 algoinfo[algoindex]->defbase = base;
421             }
422 
423         } else if (strcmp(keyword, "status_rgb") == 0) {
424             if (algoindex >= 0 && algoindex < NumAlgos())
425                 GetColor(value, algoinfo[algoindex]->statusrgb);
426 
427         } else if (strcmp(keyword, "from_rgb") == 0) {
428             if (algoindex >= 0 && algoindex < NumAlgos())
429                 GetColor(value, algoinfo[algoindex]->fromrgb);
430 
431         } else if (strcmp(keyword, "to_rgb") == 0) {
432             if (algoindex >= 0 && algoindex < NumAlgos())
433                 GetColor(value, algoinfo[algoindex]->torgb);
434 
435         } else if (strcmp(keyword, "use_gradient") == 0) {
436             if (algoindex >= 0 && algoindex < NumAlgos())
437                 algoinfo[algoindex]->gradient = value[0] == '1';
438 
439         } else if (strcmp(keyword, "colors") == 0) {
440             if (algoindex >= 0 && algoindex < NumAlgos()) {
441                 int state, r, g, b;
442                 while (sscanf(value, "%d,%d,%d,%d,", &state, &r, &g, &b) == 4) {
443                     if (state >= 0 && state < algoinfo[algoindex]->maxstates) {
444                         algoinfo[algoindex]->algor[state] = r;
445                         algoinfo[algoindex]->algog[state] = g;
446                         algoinfo[algoindex]->algob[state] = b;
447                     }
448                     while (*value != ',') value++; value++;
449                     while (*value != ',') value++; value++;
450                     while (*value != ',') value++; value++;
451                     while (*value != ',') value++; value++;
452                 }
453             }
454 
455         } else if (strcmp(keyword, "min_delay") == 0) {
456             sscanf(value, "%d", &mindelay);
457             if (mindelay < 0) mindelay = 0;
458             if (mindelay > MAX_DELAY) mindelay = MAX_DELAY;
459 
460         } else if (strcmp(keyword, "max_delay") == 0) {
461             sscanf(value, "%d", &maxdelay);
462             if (maxdelay < 0) maxdelay = 0;
463             if (maxdelay > MAX_DELAY) maxdelay = MAX_DELAY;
464 
465         } else if (strcmp(keyword, "auto_fit") == 0) {
466             initautofit = value[0] == '1';
467 
468         } else if (strcmp(keyword, "init_algo") == 0) {
469             value = ReplaceDeprecatedAlgo(value);
470             int i = staticAlgoInfo::nameToIndex(value);
471             if (i >= 0 && i < NumAlgos())
472                 initalgo = i;
473 
474         } else if (strcmp(keyword, "hyperspeed") == 0) {
475             inithyperspeed = value[0] == '1';
476 
477         } else if (strcmp(keyword, "hash_info") == 0) {
478             initshowhashinfo = value[0] == '1';
479 
480         } else if (strcmp(keyword, "max_hash_mem") == 0) {
481             sscanf(value, "%d", &maxhashmem);
482             if (maxhashmem < MIN_MEM_MB) maxhashmem = MIN_MEM_MB;
483             if (maxhashmem > MAX_MEM_MB) maxhashmem = MAX_MEM_MB;
484 
485         } else if (strcmp(keyword, "rule") == 0) {
486             strncpy(initrule, value, sizeof(initrule));
487 
488         } else if (strcmp(keyword, "show_tool") == 0) {
489             showtool = value[0] == '1';
490 
491         } else if (strcmp(keyword, "show_layer") == 0) {
492             showlayer = value[0] == '1';
493 
494         } else if (strcmp(keyword, "show_edit") == 0) {
495             showedit = value[0] == '1';
496 
497         } else if (strcmp(keyword, "show_states") == 0) {
498             showallstates = value[0] == '1';
499 
500         } else if (strcmp(keyword, "show_status") == 0) {
501             showstatus = value[0] == '1';
502 
503         } else if (strcmp(keyword, "show_exact") == 0) {
504             showexact = value[0] == '1';
505 
506         } else if (strcmp(keyword, "show_timeline") == 0) {
507             showtimeline = value[0] == '1';
508 
509         } else if (strcmp(keyword, "show_timing") == 0) {
510             showtiming = value[0] == '1';
511 
512         } else if (strcmp(keyword, "grid_lines") == 0) {
513             showgridlines = value[0] == '1';
514 
515         } else if (strcmp(keyword, "min_grid_mag") == 0) {
516             sscanf(value, "%d", &mingridmag);
517             if (mingridmag < 2) mingridmag = 2;
518             if (mingridmag > MAX_MAG) mingridmag = MAX_MAG;
519 
520         } else if (strcmp(keyword, "bold_spacing") == 0) {
521             sscanf(value, "%d", &boldspacing);
522             if (boldspacing < 2) boldspacing = 2;
523             if (boldspacing > MAX_SPACING) boldspacing = MAX_SPACING;
524 
525         } else if (strcmp(keyword, "show_bold_lines") == 0) {
526             showboldlines = value[0] == '1';
527 
528         } else if (strcmp(keyword, "math_coords") == 0) {
529             mathcoords = value[0] == '1';
530 
531         } else if (strcmp(keyword, "sync_views") == 0) {
532             syncviews = value[0] == '1';
533 
534         } else if (strcmp(keyword, "sync_modes") == 0) {
535             syncmodes = value[0] == '1';
536 
537         } else if (strcmp(keyword, "stack_layers") == 0) {
538             stacklayers = value[0] == '1';
539 
540         } else if (strcmp(keyword, "tile_layers") == 0) {
541             tilelayers = value[0] == '1';
542 
543         } else if (strcmp(keyword, "tile_border") == 0) {
544             sscanf(value, "%d", &tileborder);
545             if (tileborder < 1) tileborder = 1;
546             if (tileborder > 10) tileborder = 10;
547 
548         } else if (strcmp(keyword, "ask_to_save") == 0) {
549             asktosave = value[0] == '1';
550 
551         } else if (strcmp(keyword, "show_icons") == 0) {
552             showicons = value[0] == '1';
553 
554         } else if (strcmp(keyword, "swap_colors") == 0) {
555             swapcolors = value[0] == '1';
556 
557         } else if (strcmp(keyword, "opacity") == 0) {
558             sscanf(value, "%d", &opacity);
559             if (opacity < 1) opacity = 1;
560             if (opacity > 100) opacity = 100;
561 
562         } else if (strcmp(keyword, "border_rgb") == 0) { GetColor(value, borderrgb);
563         } else if (strcmp(keyword, "select_rgb") == 0) { GetColor(value, selectrgb);
564         } else if (strcmp(keyword, "paste_rgb") == 0)  { GetColor(value, pastergb);
565 
566         } else if (strcmp(keyword, "new_mag") == 0) {
567             sscanf(value, "%d", &newmag);
568             if (newmag < 0) newmag = 0;
569             if (newmag > MAX_MAG) newmag = MAX_MAG;
570 
571         } else if (strcmp(keyword, "new_remove_sel") == 0) {
572             newremovesel = value[0] == '1';
573 
574         } else if (strcmp(keyword, "open_remove_sel") == 0) {
575             openremovesel = value[0] == '1';
576 
577         } else if (strcmp(keyword, "save_xrle") == 0) {
578             savexrle = value[0] == '1';
579 
580         } else if (strcmp(keyword, "max_patterns") == 0) {
581             sscanf(value, "%d", &maxpatterns);
582             if (maxpatterns < 1) maxpatterns = 1;
583             if (maxpatterns > MAX_RECENT) maxpatterns = MAX_RECENT;
584 
585         } else if (strcmp(keyword, "recent_pattern") == 0) {
586             if (numpatterns < maxpatterns && value[0]) {
587                 // append path to recentpatterns if file exists
588                 std::string path = value;
589                 if (path.find("Patterns/") == 0 || FileExists(userdir + path)) {
590                     recentpatterns.push_back(path);
591                     numpatterns++;
592                 }
593             }
594         }
595     }
596     reader.close();
597 
598     // stacklayers and tilelayers must not both be true
599     if (stacklayers && tilelayers) tilelayers = false;
600 }
601