1 // This file is part of Golly.
2 // See docs/License.html for the copyright notice.
3 
4 #include "wx/wxprec.h"      // for compilers that support precompilation
5 #ifndef WX_PRECOMP
6     #include "wx/wx.h"      // for all others include the necessary headers
7 #endif
8 
9 #if wxUSE_TOOLTIPS
10     #include "wx/tooltip.h" // for wxToolTip
11 #endif
12 #include "wx/stdpaths.h"    // for wxStandardPaths
13 #include "wx/filename.h"    // for wxFileName
14 #include "wx/propdlg.h"     // for wxPropertySheetDialog
15 #include "wx/colordlg.h"    // for wxColourDialog
16 #include "wx/bookctrl.h"    // for wxBookCtrlBase
17 #include "wx/notebook.h"    // for wxNotebookEvent
18 #include "wx/spinctrl.h"    // for wxSpinCtrl
19 #include "wx/image.h"       // for wxImage
20 
21 #include "lifealgo.h"
22 #include "viewport.h"       // for MAX_MAG
23 #include "util.h"           // for linereader
24 
25 #include "wxgolly.h"        // for wxGetApp, mainptr, viewptr
26 #include "wxmain.h"         // for ID_*, mainptr->...
27 #include "wxview.h"         // for viewptr->..., glMajor, etc
28 #include "wxutils.h"        // for Warning, Fatal, Beep, FillRect
29 #include "wxhelp.h"         // for GetHelpFrame
30 #include "wxinfo.h"         // for GetInfoFrame
31 #include "wxalgos.h"        // for InitAlgorithms, NumAlgos, algoinfo, etc
32 #include "wxrender.h"       // for DrawOneIcon
33 #include "wxlayer.h"        // for currlayer, UpdateLayerColors
34 #include "wxscript.h"       // for inscript
35 #include "wxprefs.h"
36 
37 // cursor bitmaps
38 #include "bitmaps/pick_curs.xpm"
39 #include "bitmaps/hand_curs.xpm"
40 #include "bitmaps/zoomin_curs.xpm"
41 #include "bitmaps/zoomout_curs.xpm"
42 #ifdef __WXMSW__
43     #include "bitmaps/cross_curs.xpm"
44 #endif
45 
46 // -----------------------------------------------------------------------------
47 
48 // Golly's preferences file is a simple text file.  It's initially created in
49 // a user-specific data directory (datadir) but we look in the application
50 // directory (gollydir) first because this makes uninstalling simple and allows
51 // multiple copies/versions of the app to have separate preferences.
52 
53 const wxString PREFS_NAME = wxT("GollyPrefs");
54 
55 wxString prefspath;              // full path to prefs file
56 
57 // location of supplied scripts (relative to app)
58 const wxString SCRIPT_DIR = wxT("Scripts");
59 
60 const int PREFS_VERSION = 4;     // increment if necessary due to changes in syntax/semantics
61 int currversion = PREFS_VERSION; // might be changed by prefs_version
62 const int PREF_LINE_SIZE = 5000; // must be quite long for storing file paths
63 
64 // initialize exported preferences:
65 
66 wxString gollydir;               // path of directory containing app
67 wxString datadir;                // path of directory for user-specific data
68 wxString tempdir;                // path of directory for temporary data
69 wxString rulesdir;               // path of directory for app's rule data
70 wxString userrules;              // path of directory for user's rule data
71 wxString downloaddir;            // path of directory for downloaded data
72 
73 int debuglevel = 0;              // for displaying debug info if > 0
74 
75 int mainx = 30;                  // main window's initial location
76 int mainy = 40;
77 int mainwd = 800;                // main window's initial size
78 int mainht = 600;
79 bool maximize = false;           // maximize main window?
80 
81 int helpx = 60;                  // help window's initial location
82 int helpy = 60;
83 int helpwd = 700;                // help window's initial size
84 int helpht = 500;
85 #ifdef __WXMAC__
86     int helpfontsize = 12;       // font size in help window
87 #else
88     int helpfontsize = 10;       // font size in help window
89 #endif
90 
91 int infox = 90;                  // info window's initial location
92 int infoy = 90;
93 int infowd = 700;                // info window's initial size
94 int infoht = 500;
95 
96 int rulex = 200;                 // rule dialog's initial location
97 int ruley = 200;
98 int ruleexwd = 500;              // rule dialog's initial extra size
99 int ruleexht = 200;
100 bool showalgohelp = false;       // show algorithm help in rule dialog?
101 
102 char initrule[256] = "B3/S23";   // initial rule
103 bool initautofit = false;        // initial autofit setting
104 bool inithyperspeed = false;     // initial hyperspeed setting
105 bool initshowhashinfo = false;   // initial showhashinfo setting
106 bool showpopulation = true;      // show population counts while generating?
107 bool savexrle = true;            // save RLE file using XRLE format?
108 bool showtips = true;            // show button tips?
109 bool showtool = true;            // show tool bar?
110 bool showlayer = false;          // show layer bar?
111 bool showedit = true;            // show edit bar?
112 bool showallstates = false;      // show all cell states in edit bar?
113 bool showstatus = true;          // show status bar?
114 bool showexact = false;          // show exact numbers in status bar?
115 bool showscrollbars = true;      // show scroll bars?
116 bool showtimeline = false;       // show timeline bar?
117 bool showgridlines = true;       // display grid lines?
118 bool showoverlay = false;        // display the current overlay?
119 bool showicons = false;          // display icons for cell states?
120 bool smartscale = false;         // smarter scaling when zoomed out?
121 bool swapcolors = false;         // swap colors used for cell states?
122 bool scrollpencil = true;        // scroll if pencil cursor is dragged outside view?
123 bool scrollcross = true;         // scroll if cross cursor is dragged outside view?
124 bool scrollhand = true;          // scroll if hand cursor is dragged outside view?
125 bool allowundo = true;           // allow undo/redo?
126 bool allowbeep = true;           // okay to play beep sound?
127 bool restoreview = true;         // should reset/undo restore view?
128 int controlspos = 1;             // position of translucent controls (1 is top left corner)
129 int canchangerule = 0;           // if > 0 then paste can change rule
130 int randomfill = 50;             // random fill percentage (1..100)
131 int opacity = 50;                // percentage opacity of live cells in overlays (1..100)
132 int tileborder = 3;              // thickness of tiled window borders
133 int mingridmag = 2;              // minimum mag to draw grid lines
134 int boldspacing = 10;            // spacing of bold grid lines
135 bool showboldlines = true;       // show bold grid lines?
136 bool mathcoords = false;         // show Y values increasing upwards?
137 bool cellborders = true;         // should zoomed cells have borders?
138 bool syncviews = false;          // synchronize viewports?
139 bool synccursors = true;         // synchronize cursors?
140 bool stacklayers = false;        // stack all layers?
141 bool tilelayers = false;         // tile all layers?
142 bool askonnew = true;            // ask to save changes before creating new pattern?
143 bool askonload = true;           // ask to save changes before loading pattern file?
144 bool askondelete = true;         // ask to save changes before deleting layer?
145 bool askonquit = true;           // ask to save changes before quitting app?
146 bool warn_on_save = true;        // warn if saving non-starting generation?
147 int newmag = MAX_MAG;            // mag setting for new pattern
148 bool newremovesel = true;        // new pattern removes selection?
149 bool openremovesel = true;       // opening pattern removes selection?
150 wxCursor* newcurs = NULL;        // cursor after creating new pattern (if not NULL)
151 wxCursor* opencurs = NULL;       // cursor after opening pattern (if not NULL)
152 int mousewheelmode = 2;          // 0:Ignore, 1:forward=ZoomOut, 2:forward=ZoomIn
153 int wheelsens = MAX_SENSITIVITY; // mouse wheel sensitivity
154 int thumbrange = 10;             // thumb box scrolling range in terms of view wd/ht
155 int mindelay = 250;              // minimum millisec delay
156 int maxdelay = 2000;             // maximum millisec delay
157 wxString opensavedir;            // directory for Open and Save dialogs
158 wxString overlaydir;             // directory for Save Overlay dialog
159 wxString rundir;                 // directory for Run Script dialog
160 wxString choosedir;              // directory used by Choose File button
161 wxString filedir;                // directory used by Show Files
162 wxString texteditor;             // path of user's preferred text editor
163 wxString perllib;                // name of Perl library (loaded at runtime)
164 wxString pythonlib;              // name of Python library (loaded at runtime)
165 int dirwinwd = 180;              // width of directory window
166 bool showfiles = true;           // show file directory?
167 wxMenu* patternSubMenu = NULL;   // submenu of recent pattern files
168 wxMenu* scriptSubMenu = NULL;    // submenu of recent script files
169 int numpatterns = 0;             // current number of recent pattern files
170 int numscripts = 0;              // current number of recent script files
171 int maxpatterns = 20;            // maximum number of recent pattern files (1..MAX_RECENT)
172 int maxscripts = 20;             // maximum number of recent script files (1..MAX_RECENT)
173 wxArrayString namedrules;        // initialized in GetPrefs
174 
175 wxColor* borderrgb;              // color for border around bounded grid
176 wxColor* selectrgb;              // color for selected cells
177 wxColor* pastergb;               // color for pasted pattern
178 
179 // these settings must be global -- they are changed by GetPrefs *before* the
180 // view window is created
181 paste_location plocation = TopLeft;
182 paste_mode pmode = Or;
183 
184 // these must be global -- they are created before the view window is created
185 wxCursor* curs_pencil;           // for drawing cells
186 wxCursor* curs_pick;             // for picking cell states
187 wxCursor* curs_cross;            // for selecting cells
188 wxCursor* curs_hand;             // for moving view by dragging
189 wxCursor* curs_zoomin;           // for zooming in to a clicked cell
190 wxCursor* curs_zoomout;          // for zooming out from a clicked cell
191 wxCursor* curs_wait;             // for indicating a lengthy task is in progress
192 wxCursor* curs_hidden;           // for hiding cursor when mouse is in overlay
193 
194 // local (ie. non-exported) globals:
195 
196 int mingridindex;                // mingridmag - 2
197 int newcursindex;
198 int opencursindex;
199 
200 #if defined(__WXMAC__) && wxCHECK_VERSION(2,9,0)
201     // wxMOD_CONTROL has been changed to mean Command key down (sheesh!)
202     #define wxMOD_CONTROL wxMOD_RAW_CONTROL
203     #define ControlDown RawControlDown
204 #endif
205 
206 // set of modifier keys (note that MSVC didn't like MK_*)
207 const int mk_CMD     = 1;        // command key on Mac, control key on Win/Linux
208 const int mk_ALT     = 2;        // option key on Mac
209 const int mk_SHIFT   = 4;
210 #ifdef __WXMAC__
211 const int mk_CTRL    = 8;        // control key is separate modifier on Mac
212 const int MAX_MODS   = 16;
213 #else
214 const int MAX_MODS   = 8;
215 #endif
216 
217 // WXK_* key codes like WXK_F1 have values > 300, so to minimize the
218 // size of the keyaction table (see below) we use our own internal
219 // key codes for function keys and other special keys
220 const int IK_HOME       = 1;
221 const int IK_END        = 2;
222 const int IK_PAGEUP     = 3;
223 const int IK_PAGEDOWN   = 4;
224 const int IK_HELP       = 5;
225 const int IK_INSERT     = 6;
226 const int IK_DELETE     = 8;     // treat delete like backspace (consistent with GSF_dokey)
227 const int IK_TAB        = 9;
228 const int IK_RETURN     = 13;
229 const int IK_LEFT       = 28;
230 const int IK_RIGHT      = 29;
231 const int IK_UP         = 30;
232 const int IK_DOWN       = 31;
233 const int IK_F1         = 'A';   // we use shift+a for the real A, etc
234 const int IK_F24        = 'X';
235 const int MAX_KEYCODES  = 128;
236 
237 // names of the non-displayable keys we currently support;
238 // note that these names can be used in menu item accelerator strings
239 // so they must match legal wx names (listed in wxMenu::Append docs)
240 const char NK_HOME[]    = "Home";
241 const char NK_END[]     = "End";
242 const char NK_PGUP[]    = "PgUp";
243 const char NK_PGDN[]    = "PgDn";
244 const char NK_HELP[]    = "Help";
245 const char NK_INSERT[]  = "Insert";
246 const char NK_DELETE[]  = "Delete";
247 const char NK_TAB[]     = "Tab";
248 #ifdef __WXMSW__
249 const char NK_RETURN[]  = "Enter";
250 #else
251 const char NK_RETURN[]  = "Return";
252 #endif
253 const char NK_LEFT[]    = "Left";
254 const char NK_RIGHT[]   = "Right";
255 const char NK_UP[]      = "Up";
256 const char NK_DOWN[]    = "Down";
257 const char NK_SPACE[]   = "Space";
258 
259 const action_info nullaction = { DO_NOTHING, wxEmptyString };
260 
261 // table for converting key combinations into actions
262 action_info keyaction[MAX_KEYCODES][MAX_MODS] = {{ nullaction }};
263 
264 // strings for setting menu item accelerators
265 wxString accelerator[MAX_ACTIONS];
266 
267 // -----------------------------------------------------------------------------
268 
ConvertKeyAndModifiers(int wxkey,int wxmods,int * newkey,int * newmods)269 bool ConvertKeyAndModifiers(int wxkey, int wxmods, int* newkey, int* newmods)
270 {
271     // first convert given wx modifiers (set by wxKeyEvent::GetModifiers)
272     // to a corresponding set of mk_* values
273     int ourmods = 0;
274     if (wxmods & wxMOD_CMD)       ourmods |= mk_CMD;
275     if (wxmods & wxMOD_ALT)       ourmods |= mk_ALT;
276     if (wxmods & wxMOD_SHIFT)     ourmods |= mk_SHIFT;
277 #ifdef __WXMAC__
278     if (wxmods & wxMOD_CONTROL)   ourmods |= mk_CTRL;
279 #endif
280 
281     // now convert given wx key code to corresponding IK_* code
282     int ourkey;
283     if (wxkey >= 'A' && wxkey <= 'Z') {
284         // convert A..Z to shift+a..shift+z so we can use A..X
285         // for our internal function keys (IK_F1 to IK_F24)
286         ourkey = wxkey + 32;
287         ourmods |= mk_SHIFT;
288 
289     } else if (wxkey >= WXK_F1 && wxkey <= WXK_F24) {
290         // convert wx function key code to IK_F1..IK_F24
291         ourkey = IK_F1 + (wxkey - WXK_F1);
292 
293     } else if (wxkey >= WXK_NUMPAD0 && wxkey <= WXK_NUMPAD9) {
294         // treat numpad digits like ordinary digits
295         ourkey = '0' + (wxkey - WXK_NUMPAD0);
296 
297     } else {
298         switch (wxkey) {
299             case WXK_HOME:          ourkey = IK_HOME; break;
300             case WXK_END:           ourkey = IK_END; break;
301             case WXK_PAGEUP:        ourkey = IK_PAGEUP; break;
302             case WXK_PAGEDOWN:      ourkey = IK_PAGEDOWN; break;
303             case WXK_HELP:          ourkey = IK_HELP; break;
304             case WXK_INSERT:        ourkey = IK_INSERT; break;
305             case WXK_BACK:          // treat backspace like delete
306             case WXK_DELETE:        ourkey = IK_DELETE; break;
307             case WXK_TAB:           ourkey = IK_TAB; break;
308             case WXK_NUMPAD_ENTER:  // treat enter like return
309             case WXK_RETURN:        ourkey = IK_RETURN; break;
310             case WXK_LEFT:          ourkey = IK_LEFT; break;
311             case WXK_RIGHT:         ourkey = IK_RIGHT; break;
312             case WXK_UP:            ourkey = IK_UP; break;
313             case WXK_DOWN:          ourkey = IK_DOWN; break;
314             case WXK_ADD:           ourkey = '+'; break;
315             case WXK_SUBTRACT:      ourkey = '-'; break;
316             case WXK_DIVIDE:        ourkey = '/'; break;
317             case WXK_MULTIPLY:      ourkey = '*'; break;
318             default:                ourkey = wxkey;
319         }
320     }
321 
322     if (ourkey < 0 || ourkey >= MAX_KEYCODES) return false;
323 
324     *newkey = ourkey;
325     *newmods = ourmods;
326     return true;
327 }
328 
329 // -----------------------------------------------------------------------------
330 
FindAction(int wxkey,int wxmods)331 action_info FindAction(int wxkey, int wxmods)
332 {
333     // convert given wx key code and modifier set to our internal values
334     // and return the corresponding action
335     int ourkey, ourmods;
336     if ( ConvertKeyAndModifiers(wxkey, wxmods, &ourkey, &ourmods) ) {
337         return keyaction[ourkey][ourmods];
338     } else {
339         return nullaction;
340     }
341 }
342 
343 // -----------------------------------------------------------------------------
344 
AddDefaultKeyActions()345 void AddDefaultKeyActions()
346 {
347     // these default shortcuts are similar to the hard-wired shortcuts in v1.2
348 
349     // include some examples of DO_OPENFILE
350 #ifdef __WXMSW__
351     keyaction[(int)'h'][mk_ALT].id =       DO_OPENFILE;
352     keyaction[(int)'h'][mk_ALT].file =     wxT("Rules\\LifeHistory.rule");
353     keyaction[(int)'j'][mk_ALT].id =       DO_OPENFILE;
354     keyaction[(int)'j'][mk_ALT].file =     wxT("Scripts\\Lua\\toLife.lua");
355     keyaction[(int)'k'][mk_ALT].id =       DO_OPENFILE;
356     keyaction[(int)'k'][mk_ALT].file =     wxT("Scripts\\Lua\\toChangeState.lua");
357     keyaction[(int)'l'][mk_ALT].id =       DO_OPENFILE;
358     keyaction[(int)'l'][mk_ALT].file =     wxT("Help\\Lexicon\\lex.htm");
359     keyaction[(int)'s'][mk_SHIFT].id =     DO_OPENFILE;
360     keyaction[(int)'s'][mk_SHIFT].file =   wxT("Scripts\\Lua\\shift.lua");
361 #else
362     keyaction[(int)'h'][mk_ALT].id =       DO_OPENFILE;
363     keyaction[(int)'h'][mk_ALT].file =     wxT("Rules/LifeHistory.rule");
364     keyaction[(int)'j'][mk_ALT].id =       DO_OPENFILE;
365     keyaction[(int)'j'][mk_ALT].file =     wxT("Scripts/Lua/toLife.lua");
366     keyaction[(int)'k'][mk_ALT].id =       DO_OPENFILE;
367     keyaction[(int)'k'][mk_ALT].file =     wxT("Scripts/Lua/toChangeState.lua");
368     keyaction[(int)'l'][mk_ALT].id =       DO_OPENFILE;
369     keyaction[(int)'l'][mk_ALT].file =     wxT("Help/Lexicon/lex.htm");
370     keyaction[(int)'s'][mk_SHIFT].id =     DO_OPENFILE;
371     keyaction[(int)'s'][mk_SHIFT].file =   wxT("Scripts/Lua/shift.lua");
372 #endif
373 
374     // File menu
375     keyaction[(int)'n'][mk_CMD].id =    DO_NEWPATT;
376     keyaction[(int)'o'][mk_CMD].id =    DO_OPENPATT;
377     keyaction[(int)'o'][mk_SHIFT+mk_CMD].id = DO_OPENCLIP;
378     keyaction[(int)'s'][mk_CMD].id =    DO_SAVE;
379 #ifdef __WXMSW__
380     // Windows does not support ctrl+non-alphanumeric
381 #else
382     keyaction[(int)','][mk_CMD].id =    DO_PREFS;
383 #endif
384     keyaction[(int)','][0].id =         DO_PREFS;
385     keyaction[(int)'q'][mk_CMD].id =    DO_QUIT;
386 
387     // Edit menu
388     keyaction[(int)'z'][0].id =         DO_UNDO;
389     keyaction[(int)'z'][mk_CMD].id =    DO_UNDO;
390     keyaction[(int)'z'][mk_SHIFT].id =  DO_REDO;
391     keyaction[(int)'z'][mk_SHIFT+mk_CMD].id = DO_REDO;
392     keyaction[(int)'x'][mk_CMD].id =    DO_CUT;
393     keyaction[(int)'c'][mk_CMD].id =    DO_COPY;
394     keyaction[IK_DELETE][0].id =        DO_CLEAR;
395     keyaction[IK_DELETE][mk_SHIFT].id = DO_CLEAROUT;
396     keyaction[(int)'v'][0].id =         DO_PASTE;
397     keyaction[(int)'v'][mk_CMD].id =    DO_PASTE;
398     keyaction[(int)'m'][mk_SHIFT].id =  DO_PASTEMODE;
399     keyaction[(int)'l'][mk_SHIFT].id =  DO_PASTELOC;
400     keyaction[(int)'a'][0].id =         DO_SELALL;
401     keyaction[(int)'a'][mk_CMD].id =    DO_SELALL;
402     keyaction[(int)'k'][0].id =         DO_REMOVESEL;
403     keyaction[(int)'k'][mk_CMD].id =    DO_REMOVESEL;
404     keyaction[(int)'s'][0].id =         DO_SHRINKFIT;
405     keyaction[(int)'5'][mk_CMD].id =    DO_RANDFILL;
406     keyaction[(int)'y'][0].id =         DO_FLIPTB;
407     keyaction[(int)'x'][0].id =         DO_FLIPLR;
408     keyaction[(int)'>'][0].id =         DO_ROTATECW;
409     keyaction[(int)'<'][0].id =         DO_ROTATEACW;
410     keyaction[IK_F1+1][0].id =          DO_CURSDRAW;
411     keyaction[IK_F1+2][0].id =          DO_CURSPICK;
412     keyaction[IK_F1+3][0].id =          DO_CURSSEL;
413     keyaction[IK_F1+4][0].id =          DO_CURSMOVE;
414     keyaction[IK_F1+5][0].id =          DO_CURSIN;
415     keyaction[IK_F1+6][0].id =          DO_CURSOUT;
416     keyaction[(int)'c'][0].id =         DO_CURSCYCLE;
417 
418     // Control menu
419     keyaction[IK_RETURN][0].id =        DO_STARTSTOP;
420     keyaction[(int)' '][0].id =         DO_NEXTGEN;
421     keyaction[IK_TAB][0].id =           DO_NEXTSTEP;
422     keyaction[(int)'r'][mk_CMD].id =    DO_RESET;
423     keyaction[(int)'+'][0].id =         DO_FASTER;
424     keyaction[(int)'+'][mk_SHIFT].id =  DO_FASTER;
425     keyaction[(int)'='][0].id =         DO_FASTER;
426     keyaction[(int)'_'][0].id =         DO_SLOWER;
427     keyaction[(int)'_'][mk_SHIFT].id =  DO_SLOWER;
428     keyaction[(int)'-'][0].id =         DO_SLOWER;
429     keyaction[(int)'t'][0].id =         DO_AUTOFIT;
430     keyaction[(int)'t'][mk_CMD].id =    DO_AUTOFIT;
431     keyaction[(int)'u'][mk_CMD].id =    DO_HASHING;
432 #ifdef __WXMAC__
433     keyaction[(int)' '][mk_CTRL].id =   DO_ADVANCE;
434 #else
435     // on Windows/Linux mk_CMD is control key
436     keyaction[(int)' '][mk_CMD].id =    DO_ADVANCE;
437 #endif
438     keyaction[(int)' '][mk_SHIFT].id =  DO_ADVANCEOUT;
439     keyaction[(int)'t'][mk_SHIFT].id =  DO_TIMING;
440 
441     // View menu
442     keyaction[IK_LEFT][0].id =          DO_LEFT;
443     keyaction[IK_RIGHT][0].id =         DO_RIGHT;
444     keyaction[IK_UP][0].id =            DO_UP;
445     keyaction[IK_DOWN][0].id =          DO_DOWN;
446     keyaction[IK_F1+10][0].id =         DO_FULLSCREEN;
447     keyaction[(int)'f'][0].id =         DO_FIT;
448     keyaction[(int)'f'][mk_CMD].id =    DO_FIT;
449     keyaction[(int)'f'][mk_SHIFT].id =  DO_FITSEL;
450     keyaction[(int)'f'][mk_SHIFT+mk_CMD].id = DO_FITSEL;
451     keyaction[(int)'m'][0].id =         DO_MIDDLE;
452     keyaction[(int)'m'][mk_CMD].id =    DO_MIDDLE;
453     keyaction[(int)'0'][0].id =         DO_CHANGE00;
454     keyaction[(int)'9'][0].id =         DO_RESTORE00;
455     keyaction[(int)'9'][mk_CMD].id =    DO_RESTORE00;
456     keyaction[(int)']'][0].id =         DO_ZOOMIN;
457     keyaction[(int)'['][0].id =         DO_ZOOMOUT;
458 #ifdef __WXMSW__
459     // Windows does not support ctrl+non-alphanumeric
460 #else
461     keyaction[(int)']'][mk_CMD].id =    DO_ZOOMIN;
462     keyaction[(int)'['][mk_CMD].id =    DO_ZOOMOUT;
463 #endif
464     keyaction[(int)'1'][0].id =         DO_SCALE1;
465     keyaction[(int)'2'][0].id =         DO_SCALE2;
466     keyaction[(int)'4'][0].id =         DO_SCALE4;
467     keyaction[(int)'8'][0].id =         DO_SCALE8;
468     keyaction[(int)'6'][0].id =         DO_SCALE16;
469     keyaction[(int)'3'][0].id =         DO_SCALE32;
470     keyaction[(int)'\''][0].id =        DO_SHOWTOOL;
471     keyaction[(int)'\\'][0].id =        DO_SHOWLAYER;
472     keyaction[(int)'/'][0].id =         DO_SHOWEDIT;
473     keyaction[(int)'.'][0].id =         DO_SHOWSTATES;
474     keyaction[(int)';'][0].id =         DO_SHOWSTATUS;
475 #ifdef __WXMSW__
476     // Windows does not support ctrl+non-alphanumeric
477 #else
478     keyaction[(int)'\''][mk_CMD].id =   DO_SHOWTOOL;
479     keyaction[(int)'\\'][mk_CMD].id =   DO_SHOWLAYER;
480     keyaction[(int)'/'][mk_CMD].id =    DO_SHOWEDIT;
481     keyaction[(int)'.'][mk_CMD].id =    DO_SHOWSTATES;
482     keyaction[(int)';'][mk_CMD].id =    DO_SHOWSTATUS;
483 #endif
484     keyaction[(int)'e'][0].id =         DO_SHOWEXACT;
485     keyaction[(int)'e'][mk_CMD].id =    DO_SHOWEXACT;
486     keyaction[(int)'l'][0].id =         DO_SHOWGRID;
487     keyaction[(int)'l'][mk_CMD].id =    DO_SHOWGRID;
488     keyaction[(int)'b'][0].id =         DO_INVERT;
489     keyaction[(int)'b'][mk_CMD].id =    DO_INVERT;
490     keyaction[(int)'i'][0].id =         DO_INFO;
491     keyaction[(int)'i'][mk_CMD].id =    DO_INFO;
492 
493     // Layer menu
494     // none
495 
496     // Help menu
497     keyaction[(int)'h'][0].id =         DO_HELP;
498     keyaction[(int)'?'][0].id =         DO_HELP;
499     keyaction[IK_HELP][0].id =          DO_HELP;
500 #ifdef __WXMAC__
501     // cmd-? is the usual shortcut in Mac apps
502     keyaction[(int)'?'][mk_CMD].id =    DO_HELP;
503     // we can only detect shift+cmd+/ so we have to assume '?' is above '/' -- yuk
504     keyaction[(int)'/'][mk_SHIFT+mk_CMD].id = DO_HELP;
505 #else
506     // F1 is the usual shortcut in Win/Linux apps
507     keyaction[IK_F1][0].id =            DO_HELP;
508 #endif
509 }
510 
511 // -----------------------------------------------------------------------------
512 
GetActionName(action_id action)513 const char* GetActionName(action_id action)
514 {
515     switch (action) {
516         case DO_NOTHING:        return "NONE";
517         case DO_OPENFILE:       return "Open:";
518         // File menu
519         case DO_NEWPATT:        return "New Pattern";
520         case DO_OPENPATT:       return "Open Pattern...";
521         case DO_OPENCLIP:       return "Open Clipboard";
522         case DO_SHOWFILES:      return "Show Files";
523         case DO_FILEDIR:        return "Set File Folder...";
524         case DO_SAVE:           return "Save Pattern...";
525         case DO_SAVEXRLE:       return "Save Extended RLE";
526         case DO_RUNSCRIPT:      return "Run Script...";
527         case DO_RUNCLIP:        return "Run Clipboard";
528         case DO_PREFS:          return "Preferences...";
529         case DO_QUIT:           return "Quit Golly";
530         // Edit menu
531         case DO_UNDO:           return "Undo";
532         case DO_REDO:           return "Redo";
533         case DO_DISABLE:        return "Disable Undo/Redo";
534         case DO_CUT:            return "Cut Selection";
535         case DO_COPY:           return "Copy Selection";
536         case DO_CLEAR:          return "Clear Selection";
537         case DO_CLEAROUT:       return "Clear Outside";
538         case DO_PASTE:          return "Paste";
539         case DO_PASTEMODE:      return "Cycle Paste Mode";
540         case DO_PASTELOC:       return "Cycle Paste Location";
541         case DO_PASTESEL:       return "Paste to Selection";
542         case DO_SELALL:         return "Select All";
543         case DO_REMOVESEL:      return "Remove Selection";
544         case DO_SHRINK:         return "Shrink Selection";
545         case DO_SHRINKFIT:      return "Shrink and Fit";
546         case DO_RANDFILL:       return "Random Fill";
547         case DO_FLIPTB:         return "Flip Top-Bottom";
548         case DO_FLIPLR:         return "Flip Left-Right";
549         case DO_ROTATECW:       return "Rotate Clockwise";
550         case DO_ROTATEACW:      return "Rotate Anticlockwise";
551         case DO_CURSDRAW:       return "Cursor Mode: Draw";
552         case DO_CURSPICK:       return "Cursor Mode: Pick";
553         case DO_CURSSEL:        return "Cursor Mode: Select";
554         case DO_CURSMOVE:       return "Cursor Mode: Move";
555         case DO_CURSIN:         return "Cursor Mode: Zoom In";
556         case DO_CURSOUT:        return "Cursor Mode: Zoom Out";
557         case DO_CURSCYCLE:      return "Cycle Cursor Mode";
558         // Control menu
559         case DO_STARTSTOP:      return "Start/Stop Generating";
560         case DO_NEXTGEN:        return "Next Generation";
561         case DO_NEXTSTEP:       return "Next Step";
562         case DO_NEXTHIGHER:     return "Next Higher State";
563         case DO_NEXTLOWER:      return "Next Lower State";
564         case DO_RESET:          return "Reset";
565         case DO_SETGEN:         return "Set Generation...";
566         case DO_FASTER:         return "Faster";
567         case DO_SLOWER:         return "Slower";
568         case DO_SETBASE:        return "Set Base Step...";
569         case DO_AUTOFIT:        return "Auto Fit";
570         case DO_HASHING:        return "Use Hashing";
571         case DO_HYPER:          return "Hyperspeed";
572         case DO_HASHINFO:       return "Show Hash Info";
573         case DO_SHOWPOP:        return "Show Population";
574         case DO_RECORD:         return "Start/Stop Recording";
575         case DO_DELTIME:        return "Delete Timeline";
576         case DO_PLAYBACK:       return "Play Timeline Backwards";
577         case DO_SETRULE:        return "Set Rule...";
578         case DO_ADVANCE:        return "Advance Selection";
579         case DO_ADVANCEOUT:     return "Advance Outside";
580         case DO_TIMING:         return "Show Timing";
581         // View menu
582         case DO_LEFT:           return "Scroll Left";
583         case DO_RIGHT:          return "Scroll Right";
584         case DO_UP:             return "Scroll Up";
585         case DO_DOWN:           return "Scroll Down";
586         case DO_NE:             return "Scroll NE";
587         case DO_NW:             return "Scroll NW";
588         case DO_SE:             return "Scroll SE";
589         case DO_SW:             return "Scroll SW";
590         case DO_FULLSCREEN:     return "Full Screen";
591         case DO_FIT:            return "Fit Pattern";
592         case DO_FITSEL:         return "Fit Selection";
593         case DO_MIDDLE:         return "Middle";
594         case DO_CHANGE00:       return "Change Origin";
595         case DO_RESTORE00:      return "Restore Origin";
596         case DO_ZOOMIN:         return "Zoom In";
597         case DO_ZOOMOUT:        return "Zoom Out";
598         case DO_SCALE1:         return "Set Scale 1:1";
599         case DO_SCALE2:         return "Set Scale 1:2";
600         case DO_SCALE4:         return "Set Scale 1:4";
601         case DO_SCALE8:         return "Set Scale 1:8";
602         case DO_SCALE16:        return "Set Scale 1:16";
603         case DO_SCALE32:        return "Set Scale 1:32";
604         case DO_SMARTSCALE:     return "Smarter Scaling";
605         case DO_SHOWTOOL:       return "Show Tool Bar";
606         case DO_SHOWLAYER:      return "Show Layer Bar";
607         case DO_SHOWEDIT:       return "Show Edit Bar";
608         case DO_SHOWSTATES:     return "Show All States";
609         case DO_SHOWSCROLL:     return "Show Scroll Bars";
610         case DO_SHOWSTATUS:     return "Show Status Bar";
611         case DO_SHOWEXACT:      return "Show Exact Numbers";
612         case DO_SETCOLORS:      return "Set Layer Colors...";
613         case DO_SHOWICONS:      return "Show Cell Icons";
614         case DO_INVERT:         return "Invert Colors";
615         case DO_SHOWGRID:       return "Show Grid Lines";
616         case DO_SHOWTIME:       return "Show Timeline";
617         case DO_INFO:           return "Pattern Info";
618         // Layer menu
619         case DO_SAVEOVERLAY:    return "Save Overlay...";
620         case DO_SHOWOVERLAY:    return "Show Overlay";
621         case DO_DELOVERLAY:     return "Delete Overlay";
622         case DO_ADD:            return "Add Layer";
623         case DO_CLONE:          return "Clone Layer";
624         case DO_DUPLICATE:      return "Duplicate Layer";
625         case DO_DELETE:         return "Delete Layer";
626         case DO_DELOTHERS:      return "Delete Other Layers";
627         case DO_MOVELAYER:      return "Move Layer...";
628         case DO_NAMELAYER:      return "Name Layer...";
629         case DO_SYNCVIEWS:      return "Synchronize Views";
630         case DO_SYNCCURS:       return "Synchronize Cursors";
631         case DO_STACK:          return "Stack Layers";
632         case DO_TILE:           return "Tile Layers";
633         // Help menu
634         case DO_HELP:           return "Show Help";
635         case DO_ABOUT:          return "About Golly";
636         default:                Warning(_("Bug detected in GetActionName!"));
637     }
638     return "BUG";
639 }
640 
641 // -----------------------------------------------------------------------------
642 
643 // for case-insensitive string comparison
644 #ifdef __WXMSW__
645     #define ISTRCMP stricmp
646 #else
647     #define ISTRCMP strcasecmp
648 #endif
649 
GetKeyAction(char * value)650 void GetKeyAction(char* value)
651 {
652     // parse strings like "z undo" or "space+ctrl advance selection";
653     // note that some errors detected here can be Fatal because the user
654     // has to quit Golly anyway to edit the prefs file
655     char* start = value;
656     char* p = start;
657     int modset = 0;
658     int key = -1;
659 
660     // extract key, skipping first char in case it's '+'
661     if (*p > 0) p++;
662     while (1) {
663         if (*p == 0) {
664             Fatal(wxString::Format(_("Bad key_action value: %s"),
665                                    wxString(value,wxConvLocal).c_str()));
666         }
667         if (*p == ' ' || *p == '+') {
668             // we found end of key
669             char oldp = *p;
670             *p = 0;
671             int len = (int)strlen(start);
672             if (len == 1) {
673                 key = start[0];
674                 if (key < ' ' || key > '~') {
675                     // this can happen if the user's language setting is not English,
676                     // so change key and continue rather than call Fatal
677                     Warning(wxString::Format(_("Non-displayable key in key_action: %s"),
678                                              wxString(start,wxConvLocal).c_str()));
679                     key = '!';
680                 }
681                 if (key >= 'A' && key <= 'Z') {
682                     // convert A..Z to shift+a..shift+z so we can use A..X
683                     // for our internal function keys (IK_F1 to IK_F24)
684                     key += 32;
685                     modset |= mk_SHIFT;
686                 }
687             } else if (len > 1) {
688                 if ((start[0] == 'f' || start[0] == 'F') && start[1] >= '1' && start[1] <= '9') {
689                     // we have a function key
690                     char* p = &start[1];
691                     int num;
692                     sscanf(p, "%d", &num);
693                     if (num >= 1 && num <= 24) key = IK_F1 + num - 1;
694                 } else {
695                     if      (ISTRCMP(start, NK_HOME) == 0)    key = IK_HOME;
696                     else if (ISTRCMP(start, NK_END) == 0)     key = IK_END;
697                     else if (ISTRCMP(start, NK_PGUP) == 0)    key = IK_PAGEUP;
698                     else if (ISTRCMP(start, NK_PGDN) == 0)    key = IK_PAGEDOWN;
699                     else if (ISTRCMP(start, NK_HELP) == 0)    key = IK_HELP;
700                     else if (ISTRCMP(start, NK_INSERT) == 0)  key = IK_INSERT;
701                     else if (ISTRCMP(start, NK_DELETE) == 0)  key = IK_DELETE;
702                     else if (ISTRCMP(start, NK_TAB) == 0)     key = IK_TAB;
703                     else if (ISTRCMP(start, NK_RETURN) == 0)  key = IK_RETURN;
704                     else if (ISTRCMP(start, NK_LEFT) == 0)    key = IK_LEFT;
705                     else if (ISTRCMP(start, NK_RIGHT) == 0)   key = IK_RIGHT;
706                     else if (ISTRCMP(start, NK_UP) == 0)      key = IK_UP;
707                     else if (ISTRCMP(start, NK_DOWN) == 0)    key = IK_DOWN;
708                     else if (ISTRCMP(start, NK_SPACE) == 0)   key = ' ';
709                 }
710                 if (key < 0)
711                     Fatal(wxString::Format(_("Unknown key in key_action: %s"),
712                                            wxString(start,wxConvLocal).c_str()));
713             }
714             *p = oldp;     // restore ' ' or '+'
715             start = p;
716             start++;
717             break;
718         }
719         p++;
720     }
721 
722     // *p is ' ' or '+' so extract zero or more modifiers
723     while (*p != ' ') {
724         p++;
725         if (*p == 0) {
726             Fatal(wxString::Format(_("No action in key_action value: %s"),
727                                    wxString(value,wxConvLocal).c_str()));
728         }
729         if (*p == ' ' || *p == '+') {
730             // we found end of modifier
731             char oldp = *p;
732             *p = 0;
733 #ifdef __WXMAC__
734             if      (ISTRCMP(start, "cmd") == 0)   modset |= mk_CMD;
735             else if (ISTRCMP(start, "opt") == 0)   modset |= mk_ALT;
736             else if (ISTRCMP(start, "ctrl") == 0)  modset |= mk_CTRL;
737 #else
738             if      (ISTRCMP(start, "ctrl") == 0)  modset |= mk_CMD;
739             else if (ISTRCMP(start, "alt") == 0)   modset |= mk_ALT;
740 #endif
741             else if    (ISTRCMP(start, "shift") == 0) modset |= mk_SHIFT;
742             else
743                 Fatal(wxString::Format(_("Unknown modifier in key_action: %s"),
744                                        wxString(start,wxConvLocal).c_str()));
745             *p = oldp;     // restore ' ' or '+'
746             start = p;
747             start++;
748         }
749     }
750 
751     // *p is ' ' so skip and check the action string
752     p++;
753     action_info action = nullaction;
754 
755     // first look for "Open:" followed by file path
756     if (strncmp(p, "Open:", 5) == 0) {
757         action.id = DO_OPENFILE;
758         action.file = wxString(&p[5], wxConvLocal);
759     } else {
760         // assume DO_NOTHING is 0 and start with action 1
761         for (int i = 1; i < MAX_ACTIONS; i++) {
762             if (strcmp(p, GetActionName((action_id) i)) == 0) {
763                 action.id = (action_id) i;
764                 break;
765             }
766         }
767     }
768 
769     // test for some deprecated actions
770     if (action.id == DO_NOTHING) {
771         if (strcmp(p, "Swap Cell Colors") == 0) action.id = DO_INVERT;
772     }
773 
774     keyaction[key][modset] = action;
775 }
776 
777 // -----------------------------------------------------------------------------
778 
GetKeyCombo(int key,int modset)779 wxString GetKeyCombo(int key, int modset)
780 {
781     // build a key combo string for display in prefs dialog and help window
782     wxString result = wxEmptyString;
783 
784 #ifdef __WXMAC__
785     if (mk_ALT & modset)    result += wxT("Option-");
786     if (mk_SHIFT & modset)  result += wxT("Shift-");
787     if (mk_CTRL & modset)   result += wxT("Control-");
788     if (mk_CMD & modset)    result += wxT("Command-");
789 #else
790     if (mk_ALT & modset)    result += wxT("Alt+");
791     if (mk_SHIFT & modset)  result += wxT("Shift+");
792     if (mk_CMD & modset)    result += wxT("Control+");
793 #endif
794 
795     if (key >= IK_F1 && key <= IK_F24) {
796         // function key
797         result += wxString::Format(wxT("F%d"), key - IK_F1 + 1);
798 
799     } else if (key >= 'a' && key <= 'z') {
800         // display A..Z rather than a..z
801         result += wxChar(key - 32);
802 
803     } else if (key > ' ' && key <= '~') {
804         // displayable char, but excluding space (that's handled below)
805         result += wxChar(key);
806 
807     } else {
808         // non-displayable char
809         switch (key) {
810             // these strings can be more descriptive than the NK_* strings
811             case IK_HOME:     result += _("Home"); break;
812             case IK_END:      result += _("End"); break;
813             case IK_PAGEUP:   result += _("PageUp"); break;
814             case IK_PAGEDOWN: result += _("PageDown"); break;
815             case IK_HELP:     result += _("Help"); break;
816             case IK_INSERT:   result += _("Insert"); break;
817             case IK_DELETE:   result += _("Delete"); break;
818             case IK_TAB:      result += _("Tab"); break;
819 #ifdef __WXMSW__
820             case IK_RETURN:   result += _("Enter"); break;
821 #else
822             case IK_RETURN:   result += _("Return"); break;
823 #endif
824             case IK_LEFT:     result += _("Left"); break;
825             case IK_RIGHT:    result += _("Right"); break;
826             case IK_UP:       result += _("Up"); break;
827             case IK_DOWN:     result += _("Down"); break;
828             case ' ':         result += _("Space"); break;
829             default:          result = wxEmptyString;
830         }
831     }
832 
833     return result;
834 }
835 
836 // -----------------------------------------------------------------------------
837 
GetShortcutTable()838 wxString GetShortcutTable()
839 {
840     // return HTML data to display current keyboard shortcuts in help window
841     wxString result;
842     result += _("<html><title>Golly Help: Keyboard Shortcuts</title>");
843     result += _("<body bgcolor=\"#FFFFCE\">");
844     result += _("<p><font size=+1><b>Keyboard shortcuts</b></font>");
845     result += _("<p>Use <a href=\"prefs:keyboard\">Preferences > Keyboard</a>");
846     result += _(" to change the following keyboard shortcuts:");
847     result += _("<p><center>");
848     result += _("<table cellspacing=1 border=2 cols=2 width=\"90%\">");
849     result += _("<tr><td align=center>Key Combination</td><td align=center>Action</td></tr>");
850 
851     bool assigned[MAX_ACTIONS] = {false};
852 
853     for (int key = 0; key < MAX_KEYCODES; key++) {
854         for (int modset = 0; modset < MAX_MODS; modset++) {
855             action_info action = keyaction[key][modset];
856             if ( action.id != DO_NOTHING ) {
857                 assigned[action.id] = true;
858                 wxString keystring = GetKeyCombo(key, modset);
859                 if (key == '<') {
860                     keystring.Replace(_("<"), _("&lt;"));
861                 }
862                 result += _("<tr><td align=right>");
863                 result += keystring;
864                 result += _("&nbsp;</td><td>&nbsp;");
865                 result += wxString(GetActionName(action.id), wxConvLocal);
866                 if (action.id == DO_OPENFILE) {
867                     result += _("&nbsp;");
868                     result += action.file;
869                 }
870                 result += _("</td></tr>");
871             }
872         }
873     }
874 
875     result += _("</table></center>");
876 
877     // also list unassigned actions
878     result += _("<p>The following actions currently have no keyboard shortcuts:<p>");
879     for (int i = 1; i < MAX_ACTIONS; i++) {
880         if (!assigned[i]) {
881             wxString name = wxString(GetActionName((action_id) i),wxConvLocal);
882             result += wxString::Format(_("<dd>%s</dd>"), name.c_str());
883         }
884     }
885 
886     result += _("</body></html>");
887     return result;
888 }
889 
890 // -----------------------------------------------------------------------------
891 
GetModifiers(int modset)892 wxString GetModifiers(int modset)
893 {
894     wxString modkeys = wxEmptyString;
895 #ifdef __WXMAC__
896     if (mk_ALT & modset)    modkeys += wxT("+opt");
897     if (mk_SHIFT & modset)  modkeys += wxT("+shift");
898     if (mk_CTRL & modset)   modkeys += wxT("+ctrl");
899     if (mk_CMD & modset)    modkeys += wxT("+cmd");
900 #else
901     if (mk_ALT & modset)    modkeys += wxT("+alt");
902     if (mk_SHIFT & modset)  modkeys += wxT("+shift");
903     if (mk_CMD & modset)    modkeys += wxT("+ctrl");
904 #endif
905     return modkeys;
906 }
907 
908 // -----------------------------------------------------------------------------
909 
GetKeyName(int key)910 wxString GetKeyName(int key)
911 {
912     wxString result;
913 
914     if (key >= IK_F1 && key <= IK_F24) {
915         // function key
916         result.Printf(wxT("F%d"), key - IK_F1 + 1);
917 
918     } else if (key > ' ' && key <= '~') {
919         // displayable char, but excluding space (that's handled below)
920         result = wxChar(key);
921 
922     } else {
923         // non-displayable char
924         switch (key) {
925             case IK_HOME:     result = wxString(NK_HOME, wxConvLocal); break;
926             case IK_END:      result = wxString(NK_END, wxConvLocal); break;
927             case IK_PAGEUP:   result = wxString(NK_PGUP, wxConvLocal); break;
928             case IK_PAGEDOWN: result = wxString(NK_PGDN, wxConvLocal); break;
929             case IK_HELP:     result = wxString(NK_HELP, wxConvLocal); break;
930             case IK_INSERT:   result = wxString(NK_INSERT, wxConvLocal); break;
931             case IK_DELETE:   result = wxString(NK_DELETE, wxConvLocal); break;
932             case IK_TAB:      result = wxString(NK_TAB, wxConvLocal); break;
933             case IK_RETURN:   result = wxString(NK_RETURN, wxConvLocal); break;
934             case IK_LEFT:     result = wxString(NK_LEFT, wxConvLocal); break;
935             case IK_RIGHT:    result = wxString(NK_RIGHT, wxConvLocal); break;
936             case IK_UP:       result = wxString(NK_UP, wxConvLocal); break;
937             case IK_DOWN:     result = wxString(NK_DOWN, wxConvLocal); break;
938             case ' ':         result = wxString(NK_SPACE, wxConvLocal); break;
939             default:          result = wxEmptyString;
940         }
941     }
942 
943     return result;
944 }
945 
946 // -----------------------------------------------------------------------------
947 
SaveKeyActions(FILE * f)948 void SaveKeyActions(FILE* f)
949 {
950     bool assigned[MAX_ACTIONS] = {false};
951 
952     fputs("\n", f);
953     for (int key = 0; key < MAX_KEYCODES; key++) {
954         for (int modset = 0; modset < MAX_MODS; modset++) {
955             action_info action = keyaction[key][modset];
956             if ( action.id != DO_NOTHING ) {
957                 assigned[action.id] = true;
958                 fprintf(f, "key_action=%s%s %s%s\n",
959                         (const char*) GetKeyName(key).mb_str(wxConvLocal),
960                         (const char*) GetModifiers(modset).mb_str(wxConvLocal),
961                         GetActionName(action.id),
962                         (const char*) action.file.mb_str(wxConvLocal));
963             }
964         }
965     }
966 
967     // list all unassigned actions in comment lines
968     fputs("# unassigned actions:\n", f);
969     for (int i = 1; i < MAX_ACTIONS; i++) {
970         if ( !assigned[i] ) {
971             fprintf(f, "# key_action=key+mods %s", GetActionName((action_id) i));
972             if ( i == DO_OPENFILE ) fputs("file", f);
973             fputs("\n", f);
974         }
975     }
976     fputs("\n", f);
977 }
978 
979 // -----------------------------------------------------------------------------
980 
CreateAccelerator(action_id action,int modset,int key)981 void CreateAccelerator(action_id action, int modset, int key)
982 {
983     accelerator[action] = wxT("\t");
984 #ifdef __WXMAC__
985     if (modset & mk_CTRL) accelerator[action] += wxT("RawCtrl+");
986 #endif
987     if (modset & mk_CMD) accelerator[action] += wxT("Ctrl+");
988     if (modset & mk_ALT) accelerator[action] += wxT("Alt+");
989     if (modset & mk_SHIFT) accelerator[action] += wxT("Shift+");
990     if (key >= 'a' && key <= 'z') {
991         // convert a..z to A..Z
992         accelerator[action] += wxChar(key - 32);
993 #ifdef __WXMAC__
994     } else if (key == IK_DELETE) {
995         // must use "Back" to get correct symbol (<+ rather than +>)
996         accelerator[action] += wxT("Back");
997 #endif
998     } else {
999         accelerator[action] += GetKeyName(key);
1000     }
1001 }
1002 
1003 // -----------------------------------------------------------------------------
1004 
UpdateAcceleratorStrings()1005 void UpdateAcceleratorStrings()
1006 {
1007     for (int i = 0; i < MAX_ACTIONS; i++)
1008         accelerator[i] = wxEmptyString;
1009 
1010     // go thru keyaction table looking for key combos that are valid menu item
1011     // accelerators and construct suitable strings like "\tCtrl+Alt+Shift+K"
1012     // or "\tF12" or "\tReturn" etc
1013     for (int key = 0; key < MAX_KEYCODES; key++) {
1014         for (int modset = 0; modset < MAX_MODS; modset++) {
1015             action_info info = keyaction[key][modset];
1016             action_id action = info.id;
1017             if (action != DO_NOTHING && accelerator[action].IsEmpty()) {
1018                 // check if key can be used as an accelerator
1019                 if ((key >= ' ' && key <= '~') ||
1020                     (key >= IK_F1 && key <= IK_F24) ||
1021                     (key >= IK_LEFT && key <= IK_DOWN) ||
1022                     key == IK_HOME ||
1023                     key == IK_END ||
1024                     key == IK_PAGEUP ||
1025                     key == IK_PAGEDOWN ||
1026                     key == IK_DELETE ||
1027                     key == IK_TAB ||
1028                     key == IK_RETURN ) {
1029                     CreateAccelerator(action, modset, key);
1030                 }
1031             }
1032         }
1033     }
1034 
1035     // go thru keyaction table again looking only for key combos containing Ctrl;
1036     // we do this so that the Paste menu item will have the standard Ctrl+V
1037     // shortcut rather than a plain V if both those shortcuts are assigned
1038     for (int key = 0; key < MAX_KEYCODES; key++) {
1039         for (int modset = 0; modset < MAX_MODS; modset++) {
1040             action_info info = keyaction[key][modset];
1041             action_id action = info.id;
1042             if (action != DO_NOTHING && (modset & mk_CMD)) {
1043                 CreateAccelerator(action, modset, key);
1044             }
1045         }
1046     }
1047 }
1048 
1049 // -----------------------------------------------------------------------------
1050 
GetAccelerator(action_id action)1051 wxString GetAccelerator(action_id action)
1052 {
1053     return accelerator[action];
1054 }
1055 
1056 // -----------------------------------------------------------------------------
1057 
1058 // some method names have changed in wx 2.9
1059 #if wxCHECK_VERSION(2,9,0)
1060     #define GetLabelFromText GetLabelText
1061 #endif
1062 
RemoveAccelerator(wxMenuBar * mbar,int item,action_id action)1063 void RemoveAccelerator(wxMenuBar* mbar, int item, action_id action)
1064 {
1065     if (!accelerator[action].IsEmpty()) {
1066         // remove accelerator from given menu item
1067         mbar->SetLabel(item, wxMenuItem::GetLabelFromText(mbar->GetLabel(item)));
1068     }
1069 }
1070 
1071 // -----------------------------------------------------------------------------
1072 
SetAccelerator(wxMenuBar * mbar,int item,action_id action)1073 void SetAccelerator(wxMenuBar* mbar, int item, action_id action)
1074 {
1075     wxString accel = accelerator[action];
1076 
1077     if (inscript) {
1078         // RunScript has called mainptr->UpdateMenuAccelerators()
1079         // so remove accelerator from menu item to allow keyboard shortcuts
1080         // to be passed to script
1081         if (accel.IsEmpty()) return;
1082         if (action == DO_STARTSTOP) {
1083             // don't remove Escape from "Stop Script" menu item
1084             return;
1085         } else {
1086             accel = wxEmptyString;
1087         }
1088     } else if (viewptr->waitingforclick) {
1089         // PatternView::PasteTemporaryToCurrent has called mainptr->UpdateMenuAccelerators()
1090         // so remove accelerator to allow keyboard shortcuts while waiting for paste click
1091         if (accel.IsEmpty()) return;
1092         accel = wxEmptyString;
1093     }
1094 
1095     // we need to remove old accelerator string from GetLabel text
1096     mbar->SetLabel(item, wxMenuItem::GetLabelFromText(mbar->GetLabel(item)) + accel);
1097 }
1098 
1099 // -----------------------------------------------------------------------------
1100 
CreateCursors()1101 void CreateCursors()
1102 {
1103     curs_pencil = new wxCursor(wxCURSOR_PENCIL);
1104     if (curs_pencil == NULL) Fatal(_("Failed to create pencil cursor!"));
1105 
1106     wxBitmap bitmap_pick = XPM_BITMAP(pick_curs);
1107     wxImage image_pick = bitmap_pick.ConvertToImage();
1108     image_pick.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
1109     image_pick.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
1110     curs_pick = new wxCursor(image_pick);
1111     if (curs_pick == NULL) Fatal(_("Failed to create pick cursor!"));
1112 
1113 #ifdef __WXMSW__
1114     // don't use wxCURSOR_CROSS because it disappears on black background
1115     wxBitmap bitmap_cross = XPM_BITMAP(cross_curs);
1116     wxImage image_cross = bitmap_cross.ConvertToImage();
1117     image_cross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 8);
1118     image_cross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 8);
1119     curs_cross = new wxCursor(image_cross);
1120 #else
1121     curs_cross = new wxCursor(wxCURSOR_CROSS);
1122 #endif
1123     if (curs_cross == NULL) Fatal(_("Failed to create cross cursor!"));
1124 
1125     wxBitmap bitmap_hand = XPM_BITMAP(hand_curs);
1126     wxImage image_hand = bitmap_hand.ConvertToImage();
1127     image_hand.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 8);
1128     image_hand.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 8);
1129     curs_hand = new wxCursor(image_hand);
1130     if (curs_hand == NULL) Fatal(_("Failed to create hand cursor!"));
1131 
1132     wxBitmap bitmap_zoomin = XPM_BITMAP(zoomin_curs);
1133     wxImage image_zoomin = bitmap_zoomin.ConvertToImage();
1134     image_zoomin.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 6);
1135     image_zoomin.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 6);
1136     curs_zoomin = new wxCursor(image_zoomin);
1137     if (curs_zoomin == NULL) Fatal(_("Failed to create zoomin cursor!"));
1138 
1139     wxBitmap bitmap_zoomout = XPM_BITMAP(zoomout_curs);
1140     wxImage image_zoomout = bitmap_zoomout.ConvertToImage();
1141     image_zoomout.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 6);
1142     image_zoomout.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 6);
1143     curs_zoomout = new wxCursor(image_zoomout);
1144     if (curs_zoomout == NULL) Fatal(_("Failed to create zoomout cursor!"));
1145 
1146     curs_wait = new wxCursor(wxCURSOR_WAIT);
1147     if (curs_wait == NULL) Fatal(_("Failed to create wait cursor!"));
1148 
1149     curs_hidden = new wxCursor(wxCURSOR_BLANK);
1150     if (curs_hidden == NULL) Fatal(_("Failed to create hidden cursor!"));
1151 
1152     // default cursors for new pattern or after opening pattern
1153     newcurs = curs_pencil;
1154     opencurs = curs_zoomin;
1155 }
1156 
1157 // -----------------------------------------------------------------------------
1158 
FreeCursors()1159 void FreeCursors()
1160 {
1161     delete curs_pencil;
1162     delete curs_pick;
1163     delete curs_cross;
1164     delete curs_hand;
1165     delete curs_zoomin;
1166     delete curs_zoomout;
1167     delete curs_wait;
1168     delete curs_hidden;
1169 }
1170 
1171 // -----------------------------------------------------------------------------
1172 
CursorToString(wxCursor * curs)1173 const char* CursorToString(wxCursor* curs)
1174 {
1175     if (curs == curs_pencil)   return "Draw";
1176     if (curs == curs_pick)     return "Pick";
1177     if (curs == curs_cross)    return "Select";
1178     if (curs == curs_hand)     return "Move";
1179     if (curs == curs_zoomin)   return "Zoom In";
1180     if (curs == curs_zoomout)  return "Zoom Out";
1181     return "No Change";   // curs is NULL
1182 }
1183 
1184 // -----------------------------------------------------------------------------
1185 
StringToCursor(const char * s)1186 wxCursor* StringToCursor(const char* s)
1187 {
1188     if (strcmp(s, "Draw") == 0)      return curs_pencil;
1189     if (strcmp(s, "Pick") == 0)      return curs_pick;
1190     if (strcmp(s, "Select") == 0)    return curs_cross;
1191     if (strcmp(s, "Move") == 0)      return curs_hand;
1192     if (strcmp(s, "Zoom In") == 0)   return curs_zoomin;
1193     if (strcmp(s, "Zoom Out") == 0)  return curs_zoomout;
1194     return NULL;   // "No Change"
1195 }
1196 
1197 // -----------------------------------------------------------------------------
1198 
CursorToIndex(wxCursor * curs)1199 int CursorToIndex(wxCursor* curs)
1200 {
1201     if (curs == curs_pencil)   return 0;
1202     if (curs == curs_pick)     return 1;
1203     if (curs == curs_cross)    return 2;
1204     if (curs == curs_hand)     return 3;
1205     if (curs == curs_zoomin)   return 4;
1206     if (curs == curs_zoomout)  return 5;
1207     return 6;   // curs is NULL
1208 }
1209 
1210 // -----------------------------------------------------------------------------
1211 
IndexToCursor(int i)1212 wxCursor* IndexToCursor(int i)
1213 {
1214     if (i == 0) return curs_pencil;
1215     if (i == 1) return curs_pick;
1216     if (i == 2) return curs_cross;
1217     if (i == 3) return curs_hand;
1218     if (i == 4) return curs_zoomin;
1219     if (i == 5) return curs_zoomout;
1220     return NULL;   // "No Change"
1221 }
1222 
1223 // -----------------------------------------------------------------------------
1224 
1225 // following routines cannot be PatternView methods -- they are called by
1226 // GetPrefs() before the view window is created
1227 
GetPasteLocation()1228 const char* GetPasteLocation()
1229 {
1230     switch (plocation) {
1231         case TopLeft:     return "TopLeft";
1232         case TopRight:    return "TopRight";
1233         case BottomRight: return "BottomRight";
1234         case BottomLeft:  return "BottomLeft";
1235         case Middle:      return "Middle";
1236         default:          return "unknown";
1237     }
1238 }
1239 
1240 // -----------------------------------------------------------------------------
1241 
SetPasteLocation(const char * s)1242 void SetPasteLocation(const char* s)
1243 {
1244     if (strcmp(s, "TopLeft") == 0) {
1245         plocation = TopLeft;
1246     } else if (strcmp(s, "TopRight") == 0) {
1247         plocation = TopRight;
1248     } else if (strcmp(s, "BottomRight") == 0) {
1249         plocation = BottomRight;
1250     } else if (strcmp(s, "BottomLeft") == 0) {
1251         plocation = BottomLeft;
1252     } else {
1253         plocation = Middle;
1254     }
1255 }
1256 
1257 // -----------------------------------------------------------------------------
1258 
GetPasteMode()1259 const char* GetPasteMode()
1260 {
1261     switch (pmode) {
1262         case And:   return "And";
1263         case Copy:  return "Copy";
1264         case Or:    return "Or";
1265         case Xor:   return "Xor";
1266         default:    return "unknown";
1267     }
1268 }
1269 
1270 // -----------------------------------------------------------------------------
1271 
SetPasteMode(const char * s)1272 void SetPasteMode(const char* s)
1273 {
1274     if (strcmp(s, "And") == 0) {
1275         pmode = And;
1276     } else if (strcmp(s, "Copy") == 0) {
1277         pmode = Copy;
1278     } else if (strcmp(s, "Or") == 0) {
1279         pmode = Or;
1280     } else {
1281         pmode = Xor;
1282     }
1283 }
1284 
1285 // -----------------------------------------------------------------------------
1286 
UpdateStatusBrushes()1287 void UpdateStatusBrushes()
1288 {
1289     for (int i = 0; i < NumAlgos(); i++) {
1290         algoinfo[i]->statusbrush->SetColour(algoinfo[i]->statusrgb);
1291     }
1292 }
1293 
1294 // -----------------------------------------------------------------------------
1295 
CreateDefaultColors()1296 void CreateDefaultColors()
1297 {
1298     borderrgb  = new wxColor(128, 128, 128);  // 50% gray
1299     selectrgb  = new wxColor( 75, 175,   0);  // dark green (will be 50% transparent)
1300     pastergb   = new wxColor(255,   0,   0);  // red
1301 
1302     // set default status brushes (in case prefs file doesn't exist)
1303     UpdateStatusBrushes();
1304 }
1305 
1306 // -----------------------------------------------------------------------------
1307 
FreeDefaultColors()1308 void FreeDefaultColors()
1309 {
1310     delete borderrgb;
1311     delete selectrgb;
1312     delete pastergb;
1313 }
1314 
1315 // -----------------------------------------------------------------------------
1316 
GetColor(const char * value,wxColor * rgb)1317 void GetColor(const char* value, wxColor* rgb)
1318 {
1319     unsigned int r, g, b;
1320     sscanf(value, "%u,%u,%u", &r, &g, &b);
1321     rgb->Set(r, g, b);
1322 }
1323 
1324 // -----------------------------------------------------------------------------
1325 
SaveColor(FILE * f,const char * name,const wxColor * rgb)1326 void SaveColor(FILE* f, const char* name, const wxColor* rgb)
1327 {
1328     fprintf(f, "%s=%d,%d,%d\n", name, rgb->Red(), rgb->Green(), rgb->Blue());
1329 }
1330 
1331 // -----------------------------------------------------------------------------
1332 
GetRelPath(const char * value,wxString & path,const wxString & defdir=wxEmptyString,bool isdir=true)1333 void GetRelPath(const char* value, wxString& path,
1334                 const wxString& defdir = wxEmptyString,
1335                 bool isdir = true)
1336 {
1337     path = wxString(value, wxConvLocal);
1338     wxFileName fname(path);
1339 
1340     if (currversion < 4 && fname.IsAbsolute() && defdir.length() > 0) {
1341         // if old version's absolute path ends with defdir then update
1342         // path so new version will see correct dir
1343         wxString suffix = wxFILE_SEP_PATH + defdir;
1344         if (path.EndsWith(suffix)) {
1345             path = gollydir + defdir;
1346             // nicer if directory path ends with separator
1347             if (isdir && path.Last() != wxFILE_SEP_PATH) path += wxFILE_SEP_PATH;
1348             return;
1349         }
1350     }
1351 
1352     // if path isn't absolute then prepend Golly directory
1353     if (!fname.IsAbsolute()) path = gollydir + path;
1354 
1355     // if path doesn't exist then reset to default directory
1356     if (!wxFileName::DirExists(path)) path = gollydir + defdir;
1357 
1358     // nicer if directory path ends with separator
1359     if (isdir && path.Last() != wxFILE_SEP_PATH) path += wxFILE_SEP_PATH;
1360 }
1361 
1362 // -----------------------------------------------------------------------------
1363 
SaveRelPath(FILE * f,const char * name,wxString path)1364 void SaveRelPath(FILE* f, const char* name, wxString path)
1365 {
1366     // if given path is inside Golly directory then save as a relative path
1367     if (path.StartsWith(gollydir)) {
1368         // remove gollydir from start of path
1369         path.erase(0, gollydir.length());
1370     }
1371     fprintf(f, "%s=%s\n", name, (const char*)path.mb_str(wxConvLocal));
1372 }
1373 
1374 // -----------------------------------------------------------------------------
1375 
1376 #define STRINGIFY(arg) STR2(arg)
1377 #define STR2(arg) #arg
1378 const char* GOLLY_VERSION = STRINGIFY(VERSION);
1379 
SavePrefs()1380 void SavePrefs()
1381 {
1382     if (mainptr == NULL || currlayer == NULL) {
1383         // should never happen but play safe
1384         return;
1385     }
1386 
1387 #ifdef __WXMAC__
1388     // we need to convert prefspath to decomposed UTF8 so fopen will work
1389     FILE* f = fopen(prefspath.fn_str(), "w");
1390 #else
1391     FILE* f = fopen(prefspath.mb_str(wxConvLocal), "w");
1392 #endif
1393     if (f == NULL) {
1394         Warning(_("Could not save preferences file!"));
1395         return;
1396     }
1397 
1398     fprintf(f, "# NOTE: If you edit this file then do so when Golly isn't running\n");
1399     fprintf(f, "# otherwise all your changes will be clobbered when Golly quits.\n\n");
1400     fprintf(f, "prefs_version=%d\n", PREFS_VERSION);
1401     fprintf(f, "golly_version=%s\n", GOLLY_VERSION);
1402     wxString wxversion = wxVERSION_STRING;
1403     fprintf(f, "wx_version=%s\n", (const char*)wxversion.mb_str(wxConvLocal));
1404     fprintf(f, "opengl_version=%d.%d, glMaxTextureSize=%d\n", glMajor, glMinor, glMaxTextureSize);
1405 #if defined(__WXMAC__)
1406     fprintf(f, "platform=Mac\n");
1407 #elif defined(__WXMSW__)
1408     fprintf(f, "platform=Windows\n");
1409 #elif defined(__WXGTK__)
1410     fprintf(f, "platform=Linux\n");
1411 #else
1412     fprintf(f, "platform=unknown\n");
1413 #endif
1414     fprintf(f, "debug_level=%d\n", debuglevel);
1415 
1416     SaveKeyActions(f);
1417 
1418     // save main window's location and size
1419 #ifdef __WXMSW__
1420     if (mainptr->fullscreen || mainptr->IsIconized()) {
1421         // use mainx, mainy, mainwd, mainht set by mainptr->ToggleFullScreen()
1422         // or by mainptr->OnSize
1423     }
1424 #else
1425     if (mainptr->fullscreen) {
1426         // use mainx, mainy, mainwd, mainht set by mainptr->ToggleFullScreen()
1427     }
1428 #endif
1429     else
1430     {
1431         wxRect r = mainptr->GetRect();
1432         mainx = r.x;
1433         mainy = r.y;
1434         mainwd = r.width;
1435         mainht = r.height;
1436     }
1437     fprintf(f, "main_window=%d,%d,%d,%d\n", mainx, mainy, mainwd, mainht);
1438     fprintf(f, "maximize=%d\n", mainptr->IsMaximized() ? 1 : 0);
1439 
1440 #ifdef __WXMSW__
1441     if (GetHelpFrame() && !GetHelpFrame()->IsIconized())
1442 #else
1443     if (GetHelpFrame())
1444 #endif
1445     {
1446         wxRect r = GetHelpFrame()->GetRect();
1447         helpx = r.x;
1448         helpy = r.y;
1449         helpwd = r.width;
1450         helpht = r.height;
1451     }
1452     fprintf(f, "help_window=%d,%d,%d,%d\n", helpx, helpy, helpwd, helpht);
1453     fprintf(f, "help_font_size=%d (%d..%d)\n", helpfontsize, minfontsize, maxfontsize);
1454 
1455 #ifdef __WXMSW__
1456     if (GetInfoFrame() && !GetInfoFrame()->IsIconized())
1457 #else
1458     if (GetInfoFrame())
1459 #endif
1460     {
1461         wxRect r = GetInfoFrame()->GetRect();
1462         infox = r.x;
1463         infoy = r.y;
1464         infowd = r.width;
1465         infoht = r.height;
1466     }
1467     fprintf(f, "info_window=%d,%d,%d,%d\n", infox, infoy, infowd, infoht);
1468     fprintf(f, "rule_dialog=%d,%d,%d,%d\n", rulex, ruley, ruleexwd, ruleexht);
1469     fprintf(f, "show_algo_help=%d\n", showalgohelp ? 1 : 0);
1470 
1471     fprintf(f, "allow_undo=%d\n", allowundo ? 1 : 0);
1472     fprintf(f, "allow_beep=%d\n", allowbeep ? 1 : 0);
1473     fprintf(f, "restore_view=%d\n", restoreview ? 1 : 0);
1474     fprintf(f, "paste_location=%s\n", GetPasteLocation());
1475     fprintf(f, "paste_mode=%s\n", GetPasteMode());
1476     fprintf(f, "scroll_pencil=%d\n", scrollpencil ? 1 : 0);
1477     fprintf(f, "scroll_cross=%d\n", scrollcross ? 1 : 0);
1478     fprintf(f, "scroll_hand=%d\n", scrollhand ? 1 : 0);
1479     fprintf(f, "controls_pos=%d (0..4)\n", controlspos);
1480     fprintf(f, "can_change_rule=%d (0..2)\n", canchangerule);
1481     fprintf(f, "random_fill=%d (1..100)\n", randomfill);
1482     fprintf(f, "min_delay=%d (0..%d millisecs)\n", mindelay, MAX_DELAY);
1483     fprintf(f, "max_delay=%d (0..%d millisecs)\n", maxdelay, MAX_DELAY);
1484     fprintf(f, "auto_fit=%d\n", currlayer->autofit ? 1 : 0);
1485     fprintf(f, "hyperspeed=%d\n", currlayer->hyperspeed ? 1 : 0);
1486     fprintf(f, "hash_info=%d\n", currlayer->showhashinfo ? 1 : 0);
1487     fprintf(f, "show_population=%d\n", showpopulation ? 1 : 0);
1488 
1489     fputs("\n", f);
1490 
1491     fprintf(f, "init_algo=%s\n", GetAlgoName(currlayer->algtype));
1492     for (int i = 0; i < NumAlgos(); i++) {
1493         fputs("\n", f);
1494         fprintf(f, "algorithm=%s\n", GetAlgoName(i));
1495         fprintf(f, "max_mem=%d\n", algoinfo[i]->algomem);
1496         fprintf(f, "base_step=%d\n", algoinfo[i]->defbase);
1497         SaveColor(f, "status_rgb", &algoinfo[i]->statusrgb);
1498         SaveColor(f, "from_rgb", &algoinfo[i]->fromrgb);
1499         SaveColor(f, "to_rgb", &algoinfo[i]->torgb);
1500         fprintf(f, "use_gradient=%d\n", algoinfo[i]->gradient ? 1 : 0);
1501         fputs("colors=", f);
1502         for (int state = 0; state < algoinfo[i]->maxstates; state++) {
1503             // only write out state,r,g,b tuple if color is different to default
1504             if (algoinfo[i]->algor[state] != algoinfo[i]->defr[state] ||
1505                 algoinfo[i]->algog[state] != algoinfo[i]->defg[state] ||
1506                 algoinfo[i]->algob[state] != algoinfo[i]->defb[state] ) {
1507                 fprintf(f, "%d,%d,%d,%d,", state, algoinfo[i]->algor[state],
1508                         algoinfo[i]->algog[state],
1509                         algoinfo[i]->algob[state]);
1510             }
1511         }
1512         fputs("\n", f);
1513     }
1514 
1515     fputs("\n", f);
1516 
1517     fprintf(f, "rule=%s\n", currlayer->algo->getrule());
1518     if (namedrules.GetCount() > 1) {
1519         size_t i;
1520         for (i=1; i<namedrules.GetCount(); i++)
1521             fprintf(f, "named_rule=%s\n", (const char*)namedrules[i].mb_str(wxConvLocal));
1522     }
1523 
1524     fputs("\n", f);
1525 
1526     fprintf(f, "show_tips=%d\n", showtips ? 1 : 0);
1527     fprintf(f, "show_tool=%d\n", showtool ? 1 : 0);
1528     fprintf(f, "show_layer=%d\n", showlayer ? 1 : 0);
1529     fprintf(f, "show_edit=%d\n", showedit ? 1 : 0);
1530     fprintf(f, "show_states=%d\n", showallstates ? 1 : 0);
1531     fprintf(f, "show_status=%d\n", showstatus ? 1 : 0);
1532     fprintf(f, "show_exact=%d\n", showexact ? 1 : 0);
1533     fprintf(f, "show_scrollbars=%d\n", showscrollbars ? 1 : 0);
1534     fprintf(f, "show_timeline=%d\n", showtimeline ? 1 : 0);
1535     fprintf(f, "grid_lines=%d\n", showgridlines ? 1 : 0);
1536     fprintf(f, "overlay=%d\n", showoverlay ? 1 : 0);
1537     fprintf(f, "min_grid_mag=%d (2..%d)\n", mingridmag, MAX_MAG);
1538     fprintf(f, "bold_spacing=%d (2..%d)\n", boldspacing, MAX_SPACING);
1539     fprintf(f, "show_bold_lines=%d\n", showboldlines ? 1 : 0);
1540     fprintf(f, "math_coords=%d\n", mathcoords ? 1 : 0);
1541     fprintf(f, "cell_borders=%d\n", cellborders ? 1 : 0);
1542 
1543     fputs("\n", f);
1544 
1545     fprintf(f, "sync_views=%d\n", syncviews ? 1 : 0);
1546     fprintf(f, "sync_cursors=%d\n", synccursors ? 1 : 0);
1547     fprintf(f, "stack_layers=%d\n", stacklayers ? 1 : 0);
1548     fprintf(f, "tile_layers=%d\n", tilelayers ? 1 : 0);
1549     fprintf(f, "tile_border=%d (1..10)\n", tileborder);
1550     fprintf(f, "ask_on_new=%d\n", askonnew ? 1 : 0);
1551     fprintf(f, "ask_on_load=%d\n", askonload ? 1 : 0);
1552     fprintf(f, "ask_on_delete=%d\n", askondelete ? 1 : 0);
1553     fprintf(f, "ask_on_quit=%d\n", askonquit ? 1 : 0);
1554     fprintf(f, "warn_on_save=%d\n", warn_on_save ? 1 : 0);
1555 
1556     fputs("\n", f);
1557 
1558     fprintf(f, "show_icons=%d\n", showicons ? 1 : 0);
1559     fprintf(f, "smart_scale=%d\n", smartscale ? 1 : 0);
1560     fprintf(f, "swap_colors=%d\n", swapcolors ? 1 : 0);
1561     fprintf(f, "opacity=%d (1..100)\n", opacity);
1562     SaveColor(f, "border_rgb", borderrgb);
1563     SaveColor(f, "select_rgb", selectrgb);
1564     SaveColor(f, "paste_rgb", pastergb);
1565 
1566     fputs("\n", f);
1567 
1568     fprintf(f, "mouse_wheel_mode=%d\n", mousewheelmode);
1569     fprintf(f, "wheel_sensitivity=%d (1..%d)\n", wheelsens, MAX_SENSITIVITY);
1570     fprintf(f, "thumb_range=%d (2..%d)\n", thumbrange, MAX_THUMBRANGE);
1571     fprintf(f, "new_mag=%d (0..%d)\n", newmag, MAX_MAG);
1572     fprintf(f, "new_remove_sel=%d\n", newremovesel ? 1 : 0);
1573     fprintf(f, "new_cursor=%s\n", CursorToString(newcurs));
1574     fprintf(f, "open_remove_sel=%d\n", openremovesel ? 1 : 0);
1575     fprintf(f, "open_cursor=%s\n", CursorToString(opencurs));
1576     fprintf(f, "save_xrle=%d\n", savexrle ? 1 : 0);
1577 
1578     fputs("\n", f);
1579 
1580     SaveRelPath(f, "open_save_dir", opensavedir);
1581     SaveRelPath(f, "overlay_dir", overlaydir);
1582     SaveRelPath(f, "run_dir", rundir);
1583     SaveRelPath(f, "choose_dir", choosedir);
1584     SaveRelPath(f, "file_dir", filedir);
1585     SaveRelPath(f, "user_rules", userrules);
1586     SaveRelPath(f, "download_dir", downloaddir);
1587 
1588     fputs("\n", f);
1589 
1590     fprintf(f, "text_editor=%s\n", (const char*)texteditor.mb_str(wxConvLocal));
1591     fprintf(f, "perl_lib=%s\n", (const char*)perllib.mb_str(wxConvLocal));
1592     fprintf(f, "python_lib=%s\n", (const char*)pythonlib.mb_str(wxConvLocal));
1593     fprintf(f, "dir_width=%d\n", dirwinwd);
1594     fprintf(f, "show_files=%d\n", showfiles ? 1 : 0);
1595     fprintf(f, "max_patterns=%d (1..%d)\n", maxpatterns, MAX_RECENT);
1596     fprintf(f, "max_scripts=%d (1..%d)\n", maxscripts, MAX_RECENT);
1597 
1598     if (numpatterns > 0) {
1599         fputs("\n", f);
1600         int i;
1601         for (i = 0; i < numpatterns; i++) {
1602             wxMenuItem* item = patternSubMenu->FindItemByPosition(i);
1603             if (item) {
1604 #if wxCHECK_VERSION(2,9,0)
1605                 wxString path = item->GetItemLabel();
1606 #else
1607                 wxString path = item->GetText();
1608 #endif
1609 #ifdef __WXGTK__
1610                 // remove duplicate underscores
1611                 path.Replace(wxT("__"), wxT("_"));
1612 #endif
1613                 // remove duplicate ampersands
1614                 path.Replace(wxT("&&"), wxT("&"));
1615                 fprintf(f, "recent_pattern=%s\n", (const char*)path.mb_str(wxConvLocal));
1616             }
1617         }
1618     }
1619 
1620     if (numscripts > 0) {
1621         fputs("\n", f);
1622         int i;
1623         for (i = 0; i < numscripts; i++) {
1624             wxMenuItem* item = scriptSubMenu->FindItemByPosition(i);
1625             if (item) {
1626 #if wxCHECK_VERSION(2,9,0)
1627                 wxString path = item->GetItemLabel();
1628 #else
1629                 wxString path = item->GetText();
1630 #endif
1631 #ifdef __WXGTK__
1632                 // remove duplicate underscores
1633                 path.Replace(wxT("__"), wxT("_"));
1634 #endif
1635                 // remove duplicate ampersands
1636                 path.Replace(wxT("&&"), wxT("&"));
1637                 fprintf(f, "recent_script=%s\n", (const char*)path.mb_str(wxConvLocal));
1638             }
1639         }
1640     }
1641 
1642     fclose(f);
1643 }
1644 
1645 // -----------------------------------------------------------------------------
1646 
AddDefaultRules()1647 void AddDefaultRules()
1648 {
1649     namedrules.Add(wxT("LifeHistory|LifeHistory"));
1650     namedrules.Add(wxT("3-4 Life|B34/S34"));
1651     namedrules.Add(wxT("HighLife|B36/S23"));
1652     namedrules.Add(wxT("AntiLife|B0123478/S01234678"));
1653     namedrules.Add(wxT("Life without Death|B3/S012345678"));
1654     namedrules.Add(wxT("Plow World|B378/S012345678"));
1655     namedrules.Add(wxT("Day and Night|B3678/S34678"));
1656     namedrules.Add(wxT("Diamoeba|B35678/S5678"));
1657     namedrules.Add(wxT("LongLife|B345/S5"));
1658     namedrules.Add(wxT("Seeds|B2/S"));
1659     namedrules.Add(wxT("Persian Rug|B234/S"));
1660     namedrules.Add(wxT("Replicator|B1357/S1357"));
1661     namedrules.Add(wxT("Fredkin|B1357/S02468"));
1662     namedrules.Add(wxT("Morley|B368/S245"));
1663     namedrules.Add(wxT("Wolfram 22|W22"));
1664     namedrules.Add(wxT("Wolfram 30|W30"));
1665     namedrules.Add(wxT("Wolfram 110|W110"));
1666     namedrules.Add(wxT("WireWorld|WireWorld"));
1667     namedrules.Add(wxT("JvN29|JvN29"));
1668     namedrules.Add(wxT("Nobili32|Nobili32"));
1669     namedrules.Add(wxT("Hutton32|Hutton32"));
1670 }
1671 
1672 // -----------------------------------------------------------------------------
1673 
GetKeywordAndValue(linereader & lr,char * line,char ** keyword,char ** value)1674 bool GetKeywordAndValue(linereader& lr, char* line, char** keyword, char** value)
1675 {
1676     // the linereader class handles all line endings (CR, CR+LF, LF)
1677     // and terminates line buffer with \0
1678     while ( lr.fgets(line, PREF_LINE_SIZE) != 0 ) {
1679         if ( line[0] == '#' || line[0] == 0 ) {
1680             // skip comment line or empty line
1681         } else {
1682             // line should have format keyword=value
1683             *keyword = line;
1684             *value = line;
1685             while ( **value != '=' && **value != 0 ) *value += 1;
1686             **value = 0;   // terminate keyword
1687             *value += 1;
1688             return true;
1689         }
1690     }
1691     return false;
1692 }
1693 
1694 // -----------------------------------------------------------------------------
1695 
CheckVisibility(int * x,int * y,int * wd,int * ht)1696 void CheckVisibility(int* x, int* y, int* wd, int* ht)
1697 {
1698     wxRect maxrect = wxGetClientDisplayRect();
1699     // reset x,y if title bar isn't clearly visible
1700     if ( *y + 10 < maxrect.y || *y + 10 > maxrect.GetBottom() ||
1701         *x + 10 > maxrect.GetRight() || *x + *wd - 10 < maxrect.x ) {
1702         *x = wxDefaultCoord;
1703         *y = wxDefaultCoord;
1704     }
1705     // reduce wd,ht if too big for screen
1706     if (*wd > maxrect.width) *wd = maxrect.width;
1707     if (*ht > maxrect.height) *ht = maxrect.height;
1708 }
1709 
1710 // -----------------------------------------------------------------------------
1711 
ReplaceDeprecatedAlgo(char * algoname)1712 char* ReplaceDeprecatedAlgo(char* algoname)
1713 {
1714     if (strcmp(algoname, "RuleTable") == 0 ||
1715         strcmp(algoname, "RuleTree") == 0) {
1716         // RuleTable and RuleTree algos have been replaced by RuleLoader
1717         return (char*)"RuleLoader";
1718     } else {
1719         return algoname;
1720     }
1721 }
1722 
1723 // -----------------------------------------------------------------------------
1724 
InitPaths()1725 void InitPaths()
1726 {
1727 #ifdef __WXGTK__
1728     // on Linux we want datadir to be "~/.golly" rather than "~/.Golly"
1729     wxGetApp().SetAppName(_("golly"));
1730 #endif
1731 
1732     // init datadir and create the directory if it doesn't exist;
1733     // the directory will probably be:
1734     // Win: C:\Documents and Settings\username\Application Data\Golly
1735     // Mac: ~/Library/Application Support/Golly
1736     // Unix: ~/.golly
1737     datadir = wxStandardPaths::Get().GetUserDataDir();
1738     if ( !wxFileName::DirExists(datadir) ) {
1739         if ( !wxFileName::Mkdir(datadir, 0777, wxPATH_MKDIR_FULL) ) {
1740             Warning(_("Could not create a user-specific data directory!\nWill try to use the application directory instead."));
1741             datadir = gollydir;
1742         }
1743     }
1744     if (datadir.Last() != wxFILE_SEP_PATH) datadir += wxFILE_SEP_PATH;
1745 
1746     // init tempdir to a temporary directory unique to this process
1747     tempdir = wxFileName::CreateTempFileName(wxT("golly_"));
1748     // on Linux the file is in /tmp;
1749     // on my Mac the file is in /private/var/tmp/folders.502/TemporaryItems;
1750     // on WinXP the file is in C:\Documents and Settings\Andy\Local Settings\Temp
1751     // (or shorter equivalent C:\DOCUME~1\Andy\LOCALS~1\Temp) but the file name
1752     // is gol*.tmp (ie. only 1st 3 chars of the prefix are used, and .tmp is added)
1753     wxRemoveFile(tempdir);
1754     if ( !wxFileName::Mkdir(tempdir, 0777, wxPATH_MKDIR_FULL) ) {
1755         Warning(_("Could not create temporary directory:\n") + tempdir);
1756         // use standard directory for temp files
1757         tempdir = wxStandardPaths::Get().GetTempDir();
1758         if ( !wxFileName::DirExists(tempdir) ) {
1759             // should never happen, but play safe
1760             Fatal(_("Sorry, temporary directory does not exist:\n") + tempdir);
1761         }
1762     }
1763     if (tempdir.Last() != wxFILE_SEP_PATH) tempdir += wxFILE_SEP_PATH;
1764 
1765 #ifdef __WXGTK__
1766     // "Golly" is nicer for warning dialogs etc
1767     wxGetApp().SetAppName(_("Golly"));
1768 #endif
1769 
1770     // init prefspath -- look in gollydir first, then in datadir
1771     prefspath = gollydir + PREFS_NAME;
1772     if ( !wxFileExists(prefspath) ) {
1773         prefspath = datadir + PREFS_NAME;
1774     }
1775 }
1776 
1777 // -----------------------------------------------------------------------------
1778 
CreateMissingFolders()1779 void CreateMissingFolders()
1780 {
1781     // create userrules and downloaddir if they don't exist
1782     if ( !wxFileName::DirExists(userrules) ) {
1783         if ( !wxFileName::Mkdir(userrules, 0777, wxPATH_MKDIR_FULL) ) {
1784             Warning(_("Could not create your rules directory:\n") + userrules);
1785         }
1786     }
1787     if ( !wxFileName::DirExists(downloaddir) ) {
1788         if ( !wxFileName::Mkdir(downloaddir, 0777, wxPATH_MKDIR_FULL) ) {
1789             Warning(_("Could not create your download directory:\n") + downloaddir);
1790         }
1791     }
1792 }
1793 
1794 // -----------------------------------------------------------------------------
1795 
GetPrefs()1796 void GetPrefs()
1797 {
1798     int algoindex = -1;                 // unknown algorithm
1799     bool sawkeyaction = false;          // saw at least one key_action entry?
1800 
1801     MAX_MAG = 5;                        // maximum cell size = 32x32
1802     // this might be better for high-res screens???!!!
1803     // MAX_MAG = 6;                        // maximum cell size = 64x64
1804 
1805     InitPaths();                        // init datadir, tempdir and prefspath
1806     InitAlgorithms();                   // init algoinfo data
1807 
1808     rulesdir = gollydir + wxT("Rules");
1809     rulesdir += wxFILE_SEP_PATH;
1810 
1811     userrules = datadir + wxT("Rules");
1812     userrules += wxFILE_SEP_PATH;
1813 
1814     downloaddir = datadir + wxT("Downloads");
1815     downloaddir += wxFILE_SEP_PATH;
1816 
1817     rundir = gollydir + SCRIPT_DIR;
1818     opensavedir = gollydir;
1819     choosedir = gollydir;
1820     filedir = gollydir;
1821     overlaydir = datadir;
1822 
1823     // init the text editor to something reasonable
1824 #ifdef __WXMSW__
1825     texteditor = wxT("Notepad");
1826 #elif defined(__WXMAC__)
1827     texteditor = wxT("/Applications/TextEdit.app");
1828 #else // assume Linux
1829     // don't attempt to guess which editor might be available;
1830     // set the string empty so the user is asked to choose their
1831     // preferred editor the first time texteditor is used
1832     texteditor = wxEmptyString;
1833 #endif
1834 
1835     // init names of Perl and Python libraries
1836 #ifdef __WXMSW__
1837     perllib = wxT("perl510.dll");
1838     pythonlib = wxT("python27.dll");
1839 #elif defined(__WXMAC__)
1840     // not used (Perl & Python are loaded at link time)
1841     perllib = wxEmptyString;
1842     pythonlib = wxEmptyString;
1843 #else // assume Linux
1844     perllib = wxT(STRINGIFY(PERL_SHLIB));
1845     pythonlib = wxT(STRINGIFY(PYTHON_SHLIB));
1846 #endif
1847 
1848     // create curs_* and initialize newcurs and opencurs
1849     CreateCursors();
1850 
1851     CreateDefaultColors();
1852 
1853     // initialize Open Recent submenu
1854     patternSubMenu = new wxMenu();
1855     patternSubMenu->AppendSeparator();
1856     patternSubMenu->Append(ID_CLEAR_MISSING_PATTERNS, _("Clear Missing Files"));
1857     patternSubMenu->Append(ID_CLEAR_ALL_PATTERNS, _("Clear All Files"));
1858 
1859     // initialize Run Recent submenu
1860     scriptSubMenu = new wxMenu();
1861     scriptSubMenu->AppendSeparator();
1862     scriptSubMenu->Append(ID_CLEAR_MISSING_SCRIPTS, _("Clear Missing Files"));
1863     scriptSubMenu->Append(ID_CLEAR_ALL_SCRIPTS, _("Clear All Files"));
1864 
1865     namedrules.Add(wxT("Life|B3/S23"));      // must be 1st entry
1866 
1867     if ( !wxFileExists(prefspath) ) {
1868         AddDefaultRules();
1869         AddDefaultKeyActions();
1870         UpdateAcceleratorStrings();
1871         CreateMissingFolders();
1872         return;
1873     }
1874 
1875 #ifdef __WXMAC__
1876     // we need to convert prefspath to decomposed UTF8 so fopen will work
1877     FILE* f = fopen(prefspath.fn_str(), "r");
1878 #else
1879     FILE* f = fopen(prefspath.mb_str(wxConvLocal), "r");
1880 #endif
1881     if (f == NULL) {
1882         Warning(_("Could not read preferences file!"));
1883         return;
1884     }
1885 
1886     linereader reader(f);
1887     char line[PREF_LINE_SIZE];
1888     char* keyword;
1889     char* value;
1890     while ( GetKeywordAndValue(reader, line, &keyword, &value) ) {
1891 
1892         if (strcmp(keyword, "prefs_version") == 0) {
1893             sscanf(value, "%d", &currversion);
1894 
1895         } else if (strcmp(keyword, "debug_level") == 0) {
1896             sscanf(value, "%d", &debuglevel);
1897 
1898         } else if (strcmp(keyword, "key_action") == 0) {
1899             GetKeyAction(value);
1900             sawkeyaction = true;
1901 
1902         } else if (strcmp(keyword, "main_window") == 0) {
1903             sscanf(value, "%d,%d,%d,%d", &mainx, &mainy, &mainwd, &mainht);
1904             // avoid very small window
1905             if (mainwd < minmainwd) mainwd = minmainwd;
1906             if (mainht < minmainht) mainht = minmainht;
1907             CheckVisibility(&mainx, &mainy, &mainwd, &mainht);
1908 
1909         } else if (strcmp(keyword, "maximize") == 0) {
1910             maximize = value[0] == '1';
1911 
1912         } else if (strcmp(keyword, "help_window") == 0) {
1913             sscanf(value, "%d,%d,%d,%d", &helpx, &helpy, &helpwd, &helpht);
1914             if (helpwd < minhelpwd) helpwd = minhelpwd;
1915             if (helpht < minhelpht) helpht = minhelpht;
1916             CheckVisibility(&helpx, &helpy, &helpwd, &helpht);
1917 
1918         } else if (strcmp(keyword, "help_font_size") == 0) {
1919             sscanf(value, "%d", &helpfontsize);
1920             if (helpfontsize < minfontsize) helpfontsize = minfontsize;
1921             if (helpfontsize > maxfontsize) helpfontsize = maxfontsize;
1922 
1923         } else if (strcmp(keyword, "info_window") == 0) {
1924             sscanf(value, "%d,%d,%d,%d", &infox, &infoy, &infowd, &infoht);
1925             if (infowd < mininfowd) infowd = mininfowd;
1926             if (infoht < mininfoht) infoht = mininfoht;
1927             CheckVisibility(&infox, &infoy, &infowd, &infoht);
1928 
1929         } else if (strcmp(keyword, "rule_dialog") == 0) {
1930             sscanf(value, "%d,%d,%d,%d", &rulex, &ruley, &ruleexwd, &ruleexht);
1931             if (ruleexwd < 100) ruleexwd = 100;
1932             if (ruleexht < 0) ruleexht = 0;
1933             CheckVisibility(&rulex, &ruley, &ruleexwd, &ruleexht);
1934 
1935         } else if (strcmp(keyword, "show_algo_help") == 0) {
1936             showalgohelp = value[0] == '1';
1937 
1938         } else if (strcmp(keyword, "allow_undo") == 0) {
1939             allowundo = value[0] == '1';
1940 
1941         } else if (strcmp(keyword, "allow_beep") == 0) {
1942             allowbeep = value[0] == '1';
1943 
1944         } else if (strcmp(keyword, "restore_view") == 0) {
1945             restoreview = value[0] == '1';
1946 
1947         } else if (strcmp(keyword, "paste_location") == 0) {
1948             SetPasteLocation(value);
1949 
1950         } else if (strcmp(keyword, "paste_mode") == 0) {
1951             SetPasteMode(value);
1952 
1953         } else if (strcmp(keyword, "scroll_pencil") == 0) {
1954             scrollpencil = value[0] == '1';
1955 
1956         } else if (strcmp(keyword, "scroll_cross") == 0) {
1957             scrollcross = value[0] == '1';
1958 
1959         } else if (strcmp(keyword, "scroll_hand") == 0) {
1960             scrollhand = value[0] == '1';
1961 
1962         } else if (strcmp(keyword, "controls_pos") == 0) {
1963             sscanf(value, "%d", &controlspos);
1964             if (controlspos < 0) controlspos = 0;
1965             if (controlspos > 4) controlspos = 4;
1966 
1967         } else if (strcmp(keyword, "can_change_rule") == 0) {
1968             sscanf(value, "%d", &canchangerule);
1969             if (canchangerule < 0) canchangerule = 0;
1970             if (canchangerule > 2) canchangerule = 2;
1971 
1972         } else if (strcmp(keyword, "random_fill") == 0) {
1973             sscanf(value, "%d", &randomfill);
1974             if (randomfill < 1) randomfill = 1;
1975             if (randomfill > 100) randomfill = 100;
1976 
1977         } else if (strcmp(keyword, "q_base_step") == 0) {     // deprecated
1978             int base;
1979             sscanf(value, "%d", &base);
1980             if (base < 2) base = 2;
1981             if (base > MAX_BASESTEP) base = MAX_BASESTEP;
1982             algoinfo[QLIFE_ALGO]->defbase = base;
1983 
1984         } else if (strcmp(keyword, "h_base_step") == 0) {     // deprecated
1985             int base;
1986             sscanf(value, "%d", &base);
1987             if (base < 2) base = 2;
1988             if (base > MAX_BASESTEP) base = MAX_BASESTEP;
1989             algoinfo[HLIFE_ALGO]->defbase = base;
1990 
1991         } else if (strcmp(keyword, "algorithm") == 0) {
1992             if (strcmp(value, "RuleTable") == 0) {
1993                 // use deprecated RuleTable settings for RuleLoader
1994                 // (deprecated RuleTree settings will simply be ignored)
1995                 value = (char*)"RuleLoader";
1996             }
1997             algoindex = -1;
1998             for (int i = 0; i < NumAlgos(); i++) {
1999                 if (strcmp(value, GetAlgoName(i)) == 0) {
2000                     algoindex = i;
2001                     break;
2002                 }
2003             }
2004 
2005         } else if (strcmp(keyword, "max_mem") == 0) {
2006             if (algoindex >= 0 && algoindex < NumAlgos()) {
2007                 int maxmem;
2008                 sscanf(value, "%d", &maxmem);
2009                 if (maxmem < MIN_MEM_MB) maxmem = MIN_MEM_MB;
2010                 if (maxmem > MAX_MEM_MB) maxmem = MAX_MEM_MB;
2011                 algoinfo[algoindex]->algomem = maxmem;
2012             }
2013 
2014         } else if (strcmp(keyword, "base_step") == 0) {
2015             if (algoindex >= 0 && algoindex < NumAlgos()) {
2016                 int base;
2017                 sscanf(value, "%d", &base);
2018                 if (base < 2) base = 2;
2019                 if (base > MAX_BASESTEP) base = MAX_BASESTEP;
2020                 algoinfo[algoindex]->defbase = base;
2021             }
2022 
2023         } else if (strcmp(keyword, "status_rgb") == 0) {
2024             if (algoindex >= 0 && algoindex < NumAlgos())
2025                 GetColor(value, &algoinfo[algoindex]->statusrgb);
2026 
2027         } else if (strcmp(keyword, "from_rgb") == 0) {
2028             if (algoindex >= 0 && algoindex < NumAlgos())
2029                 GetColor(value, &algoinfo[algoindex]->fromrgb);
2030 
2031         } else if (strcmp(keyword, "to_rgb") == 0) {
2032             if (algoindex >= 0 && algoindex < NumAlgos())
2033                 GetColor(value, &algoinfo[algoindex]->torgb);
2034 
2035         } else if (strcmp(keyword, "use_gradient") == 0) {
2036             if (algoindex >= 0 && algoindex < NumAlgos())
2037                 algoinfo[algoindex]->gradient = value[0] == '1';
2038 
2039         } else if (strcmp(keyword, "colors") == 0) {
2040             if (algoindex >= 0 && algoindex < NumAlgos()) {
2041                 int state, r, g, b;
2042                 while (sscanf(value, "%d,%d,%d,%d,", &state, &r, &g, &b) == 4) {
2043                     if (state >= 0 && state < algoinfo[algoindex]->maxstates) {
2044                         algoinfo[algoindex]->algor[state] = r;
2045                         algoinfo[algoindex]->algog[state] = g;
2046                         algoinfo[algoindex]->algob[state] = b;
2047                     }
2048                     while (*value != ',') value++;
2049                     value++;
2050                     while (*value != ',') value++;
2051                     value++;
2052                     while (*value != ',') value++;
2053                     value++;
2054                     while (*value != ',') value++;
2055                     value++;
2056                 }
2057             }
2058 
2059         } else if (strcmp(keyword, "min_delay") == 0) {
2060             sscanf(value, "%d", &mindelay);
2061             if (mindelay < 0) mindelay = 0;
2062             if (mindelay > MAX_DELAY) mindelay = MAX_DELAY;
2063 
2064         } else if (strcmp(keyword, "max_delay") == 0) {
2065             sscanf(value, "%d", &maxdelay);
2066             if (maxdelay < 0) maxdelay = 0;
2067             if (maxdelay > MAX_DELAY) maxdelay = MAX_DELAY;
2068 
2069         } else if (strcmp(keyword, "auto_fit") == 0) {
2070             initautofit = value[0] == '1';
2071 
2072         } else if (strcmp(keyword, "hashing") == 0) {            // deprecated
2073             initalgo = value[0] == '1' ? HLIFE_ALGO : QLIFE_ALGO;
2074 
2075         } else if (strcmp(keyword, "init_algo") == 0) {
2076             value = ReplaceDeprecatedAlgo(value);
2077             int i = staticAlgoInfo::nameToIndex(value);
2078             if (i >= 0 && i < NumAlgos())
2079                 initalgo = i;
2080 
2081         } else if (strcmp(keyword, "hyperspeed") == 0) {
2082             inithyperspeed = value[0] == '1';
2083 
2084         } else if (strcmp(keyword, "hash_info") == 0) {
2085             initshowhashinfo = value[0] == '1';
2086 
2087         } else if (strcmp(keyword, "show_population") == 0) {
2088             showpopulation = value[0] == '1';
2089 
2090         } else if (strcmp(keyword, "max_hash_mem") == 0) {       // deprecated
2091             int maxmem;
2092             sscanf(value, "%d", &maxmem);
2093             if (maxmem < MIN_MEM_MB) maxmem = MIN_MEM_MB;
2094             if (maxmem > MAX_MEM_MB) maxmem = MAX_MEM_MB;
2095             // change all except QLIFE_ALGO
2096             for (int i = 0; i < NumAlgos(); i++)
2097                 if (i != QLIFE_ALGO) algoinfo[i]->algomem = maxmem;
2098 
2099         } else if (strcmp(keyword, "rule") == 0) {
2100             strncpy(initrule, value, sizeof(initrule));
2101 
2102         } else if (strcmp(keyword, "named_rule") == 0) {
2103             // value must have format "name|rule" with name and rule non-empty
2104             wxString str(value,wxConvLocal);
2105             int barcount = str.Freq('|');
2106             if (barcount == 0) {
2107                 Fatal(_("Missing \"|\" separator in named_rule entry: ") + str);
2108             } else if (barcount > 1) {
2109                 Fatal(_("Too many \"|\" separators in named_rule entry: ") + str);
2110             } else {
2111                 wxString name = str.BeforeFirst('|');
2112                 wxString rule = str.AfterFirst('|');
2113                 if (name.IsEmpty()) {
2114                     Fatal(_("Empty name in named_rule entry: ") + str);
2115                 } else if (rule.IsEmpty()) {
2116                     Fatal(_("Empty rule in named_rule entry: ") + str);
2117                 } else {
2118                     namedrules.Add(str);
2119                 }
2120             }
2121 
2122         } else if (strcmp(keyword, "show_tips") == 0) {
2123             showtips = value[0] == '1';
2124 
2125         } else if (strcmp(keyword, "show_tool") == 0) {
2126             showtool = value[0] == '1';
2127 
2128         } else if (strcmp(keyword, "show_layer") == 0) {
2129             showlayer = value[0] == '1';
2130 
2131         } else if (strcmp(keyword, "show_edit") == 0) {
2132             showedit = value[0] == '1';
2133 
2134         } else if (strcmp(keyword, "show_states") == 0) {
2135             showallstates = value[0] == '1';
2136 
2137         } else if (strcmp(keyword, "show_status") == 0) {
2138             showstatus = value[0] == '1';
2139 
2140         } else if (strcmp(keyword, "show_exact") == 0) {
2141             showexact = value[0] == '1';
2142 
2143         } else if (strcmp(keyword, "show_scrollbars") == 0) {
2144             showscrollbars = value[0] == '1';
2145 
2146         } else if (strcmp(keyword, "show_timeline") == 0) {
2147             showtimeline = value[0] == '1';
2148 
2149         } else if (strcmp(keyword, "grid_lines") == 0) {
2150             showgridlines = value[0] == '1';
2151 
2152         } else if (strcmp(keyword, "overlay") == 0) {
2153             showoverlay = value[0] == '1';
2154 
2155         } else if (strcmp(keyword, "min_grid_mag") == 0) {
2156             sscanf(value, "%d", &mingridmag);
2157             if (mingridmag < 2) mingridmag = 2;
2158             if (mingridmag > MAX_MAG) mingridmag = MAX_MAG;
2159 
2160         } else if (strcmp(keyword, "bold_spacing") == 0) {
2161             sscanf(value, "%d", &boldspacing);
2162             if (boldspacing < 2) boldspacing = 2;
2163             if (boldspacing > MAX_SPACING) boldspacing = MAX_SPACING;
2164 
2165         } else if (strcmp(keyword, "show_bold_lines") == 0) {
2166             showboldlines = value[0] == '1';
2167 
2168         } else if (strcmp(keyword, "math_coords") == 0) {
2169             mathcoords = value[0] == '1';
2170 
2171         } else if (strcmp(keyword, "cell_borders") == 0) {
2172             cellborders = value[0] == '1';
2173 
2174         } else if (strcmp(keyword, "sync_views") == 0) {
2175             syncviews = value[0] == '1';
2176 
2177         } else if (strcmp(keyword, "sync_cursors") == 0) {
2178             synccursors = value[0] == '1';
2179 
2180         } else if (strcmp(keyword, "stack_layers") == 0) {
2181             stacklayers = value[0] == '1';
2182 
2183         } else if (strcmp(keyword, "tile_layers") == 0) {
2184             tilelayers = value[0] == '1';
2185 
2186         } else if (strcmp(keyword, "tile_border") == 0) {
2187             sscanf(value, "%d", &tileborder);
2188             if (tileborder < 1) tileborder = 1;
2189             if (tileborder > 10) tileborder = 10;
2190 
2191         } else if (strcmp(keyword, "ask_on_new") == 0)    { askonnew = value[0] == '1';
2192         } else if (strcmp(keyword, "ask_on_load") == 0)   { askonload = value[0] == '1';
2193         } else if (strcmp(keyword, "ask_on_delete") == 0) { askondelete = value[0] == '1';
2194         } else if (strcmp(keyword, "ask_on_quit") == 0)   { askonquit = value[0] == '1';
2195         } else if (strcmp(keyword, "warn_on_save") == 0)  { warn_on_save = value[0] == '1';
2196 
2197         } else if (strcmp(keyword, "show_icons") == 0) {
2198             showicons = value[0] == '1';
2199 
2200         } else if (strcmp(keyword, "smart_scale") == 0) {
2201             smartscale = value[0] == '1';
2202 
2203         } else if (strcmp(keyword, "swap_colors") == 0) {
2204             swapcolors = value[0] == '1';
2205 
2206         } else if (strcmp(keyword, "opacity") == 0) {
2207             sscanf(value, "%d", &opacity);
2208             if (opacity < 1) opacity = 1;
2209             if (opacity > 100) opacity = 100;
2210 
2211         } else if (strcmp(keyword, "border_rgb") == 0) { GetColor(value, borderrgb);
2212         } else if (strcmp(keyword, "select_rgb") == 0) { GetColor(value, selectrgb);
2213         } else if (strcmp(keyword, "paste_rgb") == 0)  { GetColor(value, pastergb);
2214 
2215         } else if (strcmp(keyword, "dead_rgb") == 0) {
2216             // use deprecated value to set color of state 0 in all algos
2217             // (only done once because dead_rgb is no longer saved in prefs file)
2218             wxColor color;
2219             GetColor(value, &color);
2220             for (int i = 0; i < NumAlgos(); i++) {
2221                 algoinfo[i]->algor[0] = color.Red();
2222                 algoinfo[i]->algog[0] = color.Green();
2223                 algoinfo[i]->algob[0] = color.Blue();
2224             }
2225 
2226         } else if (strcmp(keyword, "qlife_rgb") == 0) {       // deprecated
2227             GetColor(value, &algoinfo[QLIFE_ALGO]->statusrgb);
2228 
2229         } else if (strcmp(keyword, "hlife_rgb") == 0) {       // deprecated
2230             GetColor(value, &algoinfo[HLIFE_ALGO]->statusrgb);
2231 
2232         } else if (strcmp(keyword, "mouse_wheel_mode") == 0) {
2233             sscanf(value, "%d", &mousewheelmode);
2234             if (mousewheelmode < 0) mousewheelmode = 0;
2235             if (mousewheelmode > 2) mousewheelmode = 2;
2236 
2237         } else if (strcmp(keyword, "wheel_sensitivity") == 0) {
2238             sscanf(value, "%d", &wheelsens);
2239             if (wheelsens < 1) wheelsens = 1;
2240             if (wheelsens > MAX_SENSITIVITY) wheelsens = MAX_SENSITIVITY;
2241 
2242         } else if (strcmp(keyword, "thumb_range") == 0) {
2243             sscanf(value, "%d", &thumbrange);
2244             if (thumbrange < 2) thumbrange = 2;
2245             if (thumbrange > MAX_THUMBRANGE) thumbrange = MAX_THUMBRANGE;
2246 
2247         } else if (strcmp(keyword, "new_mag") == 0) {
2248             sscanf(value, "%d", &newmag);
2249             if (newmag < 0) newmag = 0;
2250             if (newmag > MAX_MAG) newmag = MAX_MAG;
2251 
2252         } else if (strcmp(keyword, "new_remove_sel") == 0) {
2253             newremovesel = value[0] == '1';
2254 
2255         } else if (strcmp(keyword, "new_cursor") == 0) {
2256             newcurs = StringToCursor(value);
2257 
2258         } else if (strcmp(keyword, "open_remove_sel") == 0) {
2259             openremovesel = value[0] == '1';
2260 
2261         } else if (strcmp(keyword, "open_cursor") == 0) {
2262             opencurs = StringToCursor(value);
2263 
2264         } else if (strcmp(keyword, "save_xrle") == 0) {
2265             savexrle = value[0] == '1';
2266 
2267         } else if (strcmp(keyword, "open_save_dir") == 0) { GetRelPath(value, opensavedir);
2268         } else if (strcmp(keyword, "overlay_dir") == 0)   { GetRelPath(value, overlaydir);
2269         } else if (strcmp(keyword, "run_dir") == 0)       { GetRelPath(value, rundir, SCRIPT_DIR);
2270         } else if (strcmp(keyword, "choose_dir") == 0)    { GetRelPath(value, choosedir);
2271         } else if (strcmp(keyword, "file_dir") == 0)      { GetRelPath(value, filedir);
2272         } else if (strcmp(keyword, "pattern_dir") == 0)   { GetRelPath(value, filedir);     // deprecated
2273         } else if (strcmp(keyword, "user_rules") == 0)    { GetRelPath(value, userrules);
2274         } else if (strcmp(keyword, "download_dir") == 0)  { GetRelPath(value, downloaddir);
2275 
2276         } else if (strcmp(keyword, "text_editor") == 0) {
2277             texteditor = wxString(value,wxConvLocal);
2278 
2279         } else if (strcmp(keyword, "perl_lib") == 0) {
2280             perllib = wxString(value,wxConvLocal);
2281 
2282         } else if (strcmp(keyword, "python_lib") == 0) {
2283             pythonlib = wxString(value,wxConvLocal);
2284 
2285         } else if (strcmp(keyword, "dir_width") == 0) {
2286             sscanf(value, "%d", &dirwinwd);
2287             if (dirwinwd < MIN_DIRWD) dirwinwd = MIN_DIRWD;
2288 
2289         } else if (strcmp(keyword, "show_files") == 0 ||
2290                    strcmp(keyword, "show_patterns") == 0) {     // deprecated
2291             showfiles = value[0] == '1';
2292 
2293         } else if (strcmp(keyword, "show_scripts") == 0) {
2294             // deprecated
2295 
2296         } else if (strcmp(keyword, "max_patterns") == 0) {
2297             sscanf(value, "%d", &maxpatterns);
2298             if (maxpatterns < 1) maxpatterns = 1;
2299             if (maxpatterns > MAX_RECENT) maxpatterns = MAX_RECENT;
2300 
2301         } else if (strcmp(keyword, "max_scripts") == 0) {
2302             sscanf(value, "%d", &maxscripts);
2303             if (maxscripts < 1) maxscripts = 1;
2304             if (maxscripts > MAX_RECENT) maxscripts = MAX_RECENT;
2305 
2306         } else if (strcmp(keyword, "recent_pattern") == 0) {
2307             // append path to Open Recent submenu
2308             if (numpatterns < maxpatterns && value[0]) {
2309                 numpatterns++;
2310                 wxString path(value, wxConvLocal);
2311                 if (currversion < 2 && path.StartsWith(gollydir)) {
2312                     // remove gollydir from start of path
2313                     path.erase(0, gollydir.length());
2314                 }
2315                 // duplicate ampersands so they appear in menu
2316                 path.Replace(wxT("&"), wxT("&&"));
2317                 patternSubMenu->Insert(numpatterns - 1, ID_OPEN_RECENT + numpatterns, path);
2318             }
2319 
2320         } else if (strcmp(keyword, "recent_script") == 0) {
2321             // append path to Run Recent submenu
2322             if (numscripts < maxscripts && value[0]) {
2323                 numscripts++;
2324                 wxString path(value, wxConvLocal);
2325                 if (currversion < 2 && path.StartsWith(gollydir)) {
2326                     // remove gollydir from start of path
2327                     path.erase(0, gollydir.length());
2328                 }
2329                 // duplicate ampersands so they appear in menu
2330                 path.Replace(wxT("&"), wxT("&&"));
2331                 scriptSubMenu->Insert(numscripts - 1, ID_RUN_RECENT + numscripts, path);
2332             }
2333         }
2334     }
2335 
2336     reader.close();
2337 
2338     // colors for status brushes may have changed
2339     UpdateStatusBrushes();
2340 
2341     // stacklayers and tilelayers must not both be true
2342     if (stacklayers && tilelayers) tilelayers = false;
2343 
2344     // if no named_rule entries then add default names
2345     if (namedrules.GetCount() == 1) AddDefaultRules();
2346 
2347     // if no key_action entries then use default shortcuts
2348     if (!sawkeyaction) AddDefaultKeyActions();
2349 
2350     // initialize accelerator array
2351     UpdateAcceleratorStrings();
2352 
2353     // create some important directories if they don't exist
2354     CreateMissingFolders();
2355 }
2356 
2357 // -----------------------------------------------------------------------------
2358 
2359 // global data used in CellBoxes and PrefsDialog methods:
2360 
2361 static int coloralgo;         // currently selected algorithm in Color pane
2362 static int gradstates;        // current number of gradient states
2363 
2364 const int CELLSIZE = 16;      // wd and ht of each cell in CellBoxes
2365 const int NUMCOLS = 32;       // number of columns in CellBoxes
2366 const int NUMROWS = 8;        // number of rows in CellBoxes
2367 
2368 // -----------------------------------------------------------------------------
2369 
2370 // define a window for displaying cell colors/icons:
2371 
2372 class CellBoxes : public wxPanel
2373 {
2374 public:
CellBoxes(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size)2375     CellBoxes(wxWindow* parent, wxWindowID id, const wxPoint& pos,
2376               const wxSize& size) : wxPanel(parent, id, pos, size) { }
2377 
2378     wxStaticText* statebox;    // for showing state of cell under cursor
2379     wxStaticText* rgbbox;      // for showing color of cell under cursor
2380 
2381 private:
2382     void GetGradientColor(int state, unsigned char* r,
2383                           unsigned char* g,
2384                           unsigned char* b);
2385 
2386     void OnEraseBackground(wxEraseEvent& event);
2387     void OnPaint(wxPaintEvent& event);
2388     void OnMouseDown(wxMouseEvent& event);
2389     void OnMouseMotion(wxMouseEvent& event);
2390     void OnMouseExit(wxMouseEvent& event);
2391 
2392     DECLARE_EVENT_TABLE()
2393 };
2394 
BEGIN_EVENT_TABLE(CellBoxes,wxPanel)2395 BEGIN_EVENT_TABLE(CellBoxes, wxPanel)
2396 EVT_ERASE_BACKGROUND (CellBoxes::OnEraseBackground)
2397 EVT_PAINT            (CellBoxes::OnPaint)
2398 EVT_LEFT_DOWN        (CellBoxes::OnMouseDown)
2399 EVT_LEFT_DCLICK      (CellBoxes::OnMouseDown)
2400 EVT_MOTION           (CellBoxes::OnMouseMotion)
2401 EVT_ENTER_WINDOW     (CellBoxes::OnMouseMotion)
2402 EVT_LEAVE_WINDOW     (CellBoxes::OnMouseExit)
2403 END_EVENT_TABLE()
2404 
2405 // -----------------------------------------------------------------------------
2406 
2407 void CellBoxes::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
2408 {
2409     // do nothing
2410 }
2411 
2412 // -----------------------------------------------------------------------------
2413 
GetGradientColor(int state,unsigned char * r,unsigned char * g,unsigned char * b)2414 void CellBoxes::GetGradientColor(int state, unsigned char* r,
2415                                  unsigned char* g,
2416                                  unsigned char* b)
2417 {
2418     // calculate gradient color for given state (> 0 and < gradstates)
2419     AlgoData* ad = algoinfo[coloralgo];
2420     if (state == 1) {
2421         *r = ad->fromrgb.Red();
2422         *g = ad->fromrgb.Green();
2423         *b = ad->fromrgb.Blue();
2424     } else if (state == gradstates - 1) {
2425         *r = ad->torgb.Red();
2426         *g = ad->torgb.Green();
2427         *b = ad->torgb.Blue();
2428     } else {
2429         unsigned char r1 = ad->fromrgb.Red();
2430         unsigned char g1 = ad->fromrgb.Green();
2431         unsigned char b1 = ad->fromrgb.Blue();
2432         unsigned char r2 = ad->torgb.Red();
2433         unsigned char g2 = ad->torgb.Green();
2434         unsigned char b2 = ad->torgb.Blue();
2435         int N = gradstates - 2;
2436         double rfrac = (double)(r2 - r1) / (double)N;
2437         double gfrac = (double)(g2 - g1) / (double)N;
2438         double bfrac = (double)(b2 - b1) / (double)N;
2439         *r = (int)(r1 + (state-1) * rfrac + 0.5);
2440         *g = (int)(g1 + (state-1) * gfrac + 0.5);
2441         *b = (int)(b1 + (state-1) * bfrac + 0.5);
2442     }
2443 }
2444 
2445 // -----------------------------------------------------------------------------
2446 
OnPaint(wxPaintEvent & WXUNUSED (event))2447 void CellBoxes::OnPaint(wxPaintEvent& WXUNUSED(event))
2448 {
2449     wxPaintDC dc(this);
2450 
2451     dc.SetPen(*wxBLACK_PEN);
2452 
2453 #ifdef __WXMSW__
2454     // we have to use theme background color on Windows
2455     wxBrush bgbrush(GetBackgroundColour());
2456 #else
2457     wxBrush bgbrush(*wxTRANSPARENT_BRUSH);
2458 #endif
2459 
2460     // draw cell boxes
2461     wxRect r = wxRect(0, 0, CELLSIZE+1, CELLSIZE+1);
2462     int col = 0;
2463     for (int state = 0; state < 256; state++) {
2464         if (state < algoinfo[coloralgo]->maxstates) {
2465             if (state == 0) {
2466                 wxColor color(algoinfo[coloralgo]->algor[0],
2467                               algoinfo[coloralgo]->algog[0],
2468                               algoinfo[coloralgo]->algob[0]);
2469                 dc.SetBrush(wxBrush(color));
2470                 dc.DrawRectangle(r);
2471                 dc.SetBrush(wxNullBrush);
2472             } else if (showicons) {
2473                 wxBitmap** iconmaps = algoinfo[coloralgo]->icons15x15;
2474                 if (iconmaps && iconmaps[state]) {
2475                     dc.SetBrush(*wxTRANSPARENT_BRUSH);
2476                     dc.DrawRectangle(r);
2477                     dc.SetBrush(wxNullBrush);
2478                     if (algoinfo[coloralgo]->gradient) {
2479                         if (state > 0 && state < gradstates) {
2480                             unsigned char red, green, blue;
2481                             GetGradientColor(state, &red, &green, &blue);
2482                             DrawOneIcon(dc, r.x + 1, r.y + 1, iconmaps[state],
2483                                         algoinfo[coloralgo]->algor[0],
2484                                         algoinfo[coloralgo]->algog[0],
2485                                         algoinfo[coloralgo]->algob[0],
2486                                         red, green, blue,
2487                                         false);     // default icons are grayscale
2488                         } else {
2489                             dc.SetBrush(bgbrush);
2490                             dc.DrawRectangle(r);
2491                             dc.SetBrush(wxNullBrush);
2492                         }
2493                     } else {
2494                         DrawOneIcon(dc, r.x + 1, r.y + 1, iconmaps[state],
2495                                     algoinfo[coloralgo]->algor[0],
2496                                     algoinfo[coloralgo]->algog[0],
2497                                     algoinfo[coloralgo]->algob[0],
2498                                     algoinfo[coloralgo]->algor[state],
2499                                     algoinfo[coloralgo]->algog[state],
2500                                     algoinfo[coloralgo]->algob[state],
2501                                     false);     // default icons are grayscale
2502                     }
2503                 } else {
2504                     dc.SetBrush(bgbrush);
2505                     dc.DrawRectangle(r);
2506                     dc.SetBrush(wxNullBrush);
2507                 }
2508             } else if (algoinfo[coloralgo]->gradient) {
2509                 if (state > 0 && state < gradstates) {
2510                     unsigned char red, green, blue;
2511                     GetGradientColor(state, &red, &green, &blue);
2512                     wxColor color(red, green, blue);
2513                     dc.SetBrush(wxBrush(color));
2514                     dc.DrawRectangle(r);
2515                     dc.SetBrush(wxNullBrush);
2516                 } else {
2517                     dc.SetBrush(bgbrush);
2518                     dc.DrawRectangle(r);
2519                     dc.SetBrush(wxNullBrush);
2520                 }
2521             } else {
2522                 wxColor color(algoinfo[coloralgo]->algor[state],
2523                               algoinfo[coloralgo]->algog[state],
2524                               algoinfo[coloralgo]->algob[state]);
2525                 dc.SetBrush(wxBrush(color));
2526                 dc.DrawRectangle(r);
2527                 dc.SetBrush(wxNullBrush);
2528             }
2529 
2530         } else {
2531             // state >= maxstates
2532             dc.SetBrush(bgbrush);
2533             dc.DrawRectangle(r);
2534             dc.SetBrush(wxNullBrush);
2535         }
2536 
2537         col++;
2538         if (col < NUMCOLS) {
2539             r.x += CELLSIZE;
2540         } else {
2541             r.x = 0;
2542             r.y += CELLSIZE;
2543             col = 0;
2544         }
2545     }
2546 
2547     dc.SetPen(wxNullPen);
2548 }
2549 
2550 // -----------------------------------------------------------------------------
2551 
OnMouseDown(wxMouseEvent & event)2552 void CellBoxes::OnMouseDown(wxMouseEvent& event)
2553 {
2554     int col = event.GetX() / CELLSIZE;
2555     int row = event.GetY() / CELLSIZE;
2556     int state = row * NUMCOLS + col;
2557     if (state >= 0 && state < algoinfo[coloralgo]->maxstates) {
2558         if (algoinfo[coloralgo]->gradient && state > 0) {
2559             Beep();
2560         } else {
2561             // let user change color of this cell state
2562             wxColour rgb(algoinfo[coloralgo]->algor[state],
2563                          algoinfo[coloralgo]->algog[state],
2564                          algoinfo[coloralgo]->algob[state]);
2565             wxColourData data;
2566             data.SetChooseFull(true);    // for Windows
2567             data.SetColour(rgb);
2568 
2569             wxColourDialog dialog(this, &data);
2570             if ( dialog.ShowModal() == wxID_OK ) {
2571                 wxColourData retData = dialog.GetColourData();
2572                 wxColour c = retData.GetColour();
2573                 if (rgb != c) {
2574                     // change color
2575                     algoinfo[coloralgo]->algor[state] = c.Red();
2576                     algoinfo[coloralgo]->algog[state] = c.Green();
2577                     algoinfo[coloralgo]->algob[state] = c.Blue();
2578                     Refresh(false);
2579                 }
2580             }
2581         }
2582     }
2583 
2584     event.Skip();
2585 }
2586 
2587 // -----------------------------------------------------------------------------
2588 
OnMouseMotion(wxMouseEvent & event)2589 void CellBoxes::OnMouseMotion(wxMouseEvent& event)
2590 {
2591     int col = event.GetX() / CELLSIZE;
2592     int row = event.GetY() / CELLSIZE;
2593     int state = row * NUMCOLS + col;
2594     if (state < 0 || state > 255) {
2595         statebox->SetLabel(_(" "));
2596         rgbbox->SetLabel(_(" "));
2597     } else {
2598         statebox->SetLabel(wxString::Format(_("%d"),state));
2599         if (state < algoinfo[coloralgo]->maxstates) {
2600             unsigned char r, g, b;
2601             if (algoinfo[coloralgo]->gradient && state > 0) {
2602                 GetGradientColor(state, &r, &g, &b);
2603             } else {
2604                 r = algoinfo[coloralgo]->algor[state];
2605                 g = algoinfo[coloralgo]->algog[state];
2606                 b = algoinfo[coloralgo]->algob[state];
2607             }
2608             rgbbox->SetLabel(wxString::Format(_("%d,%d,%d"),r,g,b));
2609         } else {
2610             rgbbox->SetLabel(_(" "));
2611         }
2612     }
2613 }
2614 
2615 // -----------------------------------------------------------------------------
2616 
OnMouseExit(wxMouseEvent & WXUNUSED (event))2617 void CellBoxes::OnMouseExit(wxMouseEvent& WXUNUSED(event))
2618 {
2619     statebox->SetLabel(_(" "));
2620     rgbbox->SetLabel(_(" "));
2621 }
2622 
2623 // -----------------------------------------------------------------------------
2624 
2625 #if defined(__WXMAC__) && wxCHECK_VERSION(2,7,2)
2626     // fix wxMac 2.7.2+ bug in wxTextCtrl::SetSelection
2627     #define ALL_TEXT 0,999
2628 #else
2629     #define ALL_TEXT -1,-1
2630 #endif
2631 
2632 const wxString HASH_MEM_NOTE = _("MB (best if ~50% of RAM)");
2633 const wxString HASH_STEP_NOTE = _("(best if power of 2)");
2634 const wxString NONHASH_MEM_NOTE = _("MB (0 means no limit)");
2635 const wxString NONHASH_STEP_NOTE = _(" ");
2636 
2637 const int BITMAP_WD = 60;        // width of bitmap in color buttons
2638 const int BITMAP_HT = 20;        // height of bitmap in color buttons
2639 
2640 const int PAGESIZE = 10;         // scroll amount when paging
2641 
2642 static size_t currpage = 0;      // current page in PrefsDialog
2643 
2644 // these are global so we can remember current key combination
2645 static int currkey = ' ';
2646 static int currmods = mk_ALT + mk_SHIFT + mk_CMD;
2647 
2648 // -----------------------------------------------------------------------------
2649 
2650 enum {
2651     // these *_PAGE values must correspond to currpage values
2652     FILE_PAGE = 0,
2653     EDIT_PAGE,
2654     CONTROL_PAGE,
2655     VIEW_PAGE,
2656     LAYER_PAGE,
2657     COLOR_PAGE,
2658     KEYBOARD_PAGE
2659 };
2660 
2661 enum {
2662     // File prefs
2663     PREF_NEW_REM_SEL = wxID_HIGHEST + 1,  // avoid problems with FindWindowById
2664     PREF_NEW_CURSOR,
2665     PREF_NEW_SCALE,
2666     PREF_OPEN_REM_SEL,
2667     PREF_OPEN_CURSOR,
2668     PREF_MAX_PATTERNS,
2669     PREF_MAX_SCRIPTS,
2670     PREF_EDITOR_BUTT,
2671     PREF_EDITOR_BOX,
2672     PREF_DOWNLOAD_BUTT,
2673     PREF_DOWNLOAD_BOX,
2674     // Edit prefs
2675     PREF_RANDOM_FILL,
2676     PREF_PASTE_0,
2677     PREF_PASTE_1,
2678     PREF_PASTE_2,
2679     PREF_SCROLL_PENCIL,
2680     PREF_SCROLL_CROSS,
2681     PREF_SCROLL_HAND,
2682     PREF_BEEP,
2683     // Control prefs
2684     PREF_ALGO_MENU1,
2685     PREF_MAX_MEM,
2686     PREF_MEM_NOTE,
2687     PREF_BASE_STEP,
2688     PREF_STEP_NOTE,
2689     PREF_MIN_DELAY,
2690     PREF_MAX_DELAY,
2691     PREF_RULES_BUTT,
2692     PREF_RULES_BOX,
2693     // View prefs
2694     PREF_SHOW_TIPS,
2695     PREF_RESTORE,
2696     PREF_Y_UP,
2697     PREF_CELL_BORDERS,
2698     PREF_SHOW_BOLD,
2699     PREF_BOLD_SPACING,
2700     PREF_MIN_GRID_SCALE,
2701     PREF_MOUSE_WHEEL,
2702     PREF_SENSITIVITY,
2703     PREF_THUMB_RANGE,
2704     PREF_CONTROLS,
2705     // Layer prefs
2706     PREF_OPACITY,
2707     PREF_TILE_BORDER,
2708     PREF_ASK_NEW,
2709     PREF_ASK_LOAD,
2710     PREF_ASK_DELETE,
2711     PREF_ASK_QUIT,
2712     PREF_WARN_SAVE,
2713     // Color prefs
2714     PREF_ALGO_MENU2,
2715     PREF_GRADIENT_CHECK,
2716     PREF_ICON_CHECK,
2717     PREF_CELL_PANEL,
2718     PREF_SCROLL_BAR,
2719     PREF_STATE_BOX,
2720     PREF_RGB_BOX,
2721     PREF_STATUS_BUTT,
2722     PREF_FROM_BUTT,
2723     PREF_TO_BUTT,
2724     PREF_SELECT_BUTT,
2725     PREF_PASTE_BUTT,
2726     PREF_BORDER_BUTT,
2727     // Keyboard prefs
2728     PREF_KEYCOMBO,
2729     PREF_ACTION,
2730     PREF_CHOOSE,
2731     PREF_FILE_BOX
2732 };
2733 
2734 // define a multi-page dialog for changing various preferences
2735 
2736 class PrefsDialog : public wxPropertySheetDialog
2737 {
2738 public:
2739     PrefsDialog(wxWindow* parent, const wxString& page);
~PrefsDialog()2740     ~PrefsDialog() { delete onetimer; }
2741 
2742     wxPanel* CreateFilePrefs(wxWindow* parent);
2743     wxPanel* CreateEditPrefs(wxWindow* parent);
2744     wxPanel* CreateControlPrefs(wxWindow* parent);
2745     wxPanel* CreateViewPrefs(wxWindow* parent);
2746     wxPanel* CreateLayerPrefs(wxWindow* parent);
2747     wxPanel* CreateColorPrefs(wxWindow* parent);
2748     wxPanel* CreateKeyboardPrefs(wxWindow* parent);
2749 
2750     virtual bool TransferDataFromWindow();    // called when user hits OK
2751 
2752 #ifdef __WXMAC__
2753     void OnSpinCtrlChar(wxKeyEvent& event);
2754 #endif
2755 
2756     static void UpdateChosenFile();
2757 
2758 private:
2759     bool GetCheckVal(long id);
2760     int GetChoiceVal(long id);
2761     int GetSpinVal(long id);
2762     int GetRadioVal(long firstid, int numbuttons);
2763     bool BadSpinVal(int id, int minval, int maxval, const wxString& prefix);
2764     bool ValidatePage();
2765     wxBitmapButton* AddColorButton(wxWindow* parent, wxBoxSizer* hbox,
2766                                    int id, wxColor* rgb, const wxString& text);
2767     void ChangeButtonColor(int id, wxColor& rgb);
2768     void UpdateButtonColor(int id, wxColor& rgb);
2769     void UpdateScrollBar();
2770 
2771     void OnCheckBoxClicked(wxCommandEvent& event);
2772     void OnColorButton(wxCommandEvent& event);
2773     void OnPageChanging(wxNotebookEvent& event);
2774     void OnPageChanged(wxNotebookEvent& event);
2775     void OnChoice(wxCommandEvent& event);
2776     void OnButton(wxCommandEvent& event);
2777     void OnScroll(wxScrollEvent& event);
2778     void OnOneTimer(wxTimerEvent& event);
2779 
2780     bool ignore_page_event;             // used to prevent currpage being changed
2781     int algopos1;                       // selected algorithm in PREF_ALGO_MENU1
2782 
2783     int new_algomem[MAX_ALGOS];         // new max mem values for each algorithm
2784     int new_defbase[MAX_ALGOS];         // new default base step values for each algorithm
2785 
2786     CellBoxes* cellboxes;               // for displaying cell colors/icons
2787     wxCheckBox* gradcheck;              // use gradient?
2788     wxCheckBox* iconcheck;              // show icons?
2789     wxBitmapButton* frombutt;           // button to set gradient's start color
2790     wxBitmapButton* tobutt;             // button to set gradient's end color
2791     wxScrollBar* scrollbar;             // for changing number of gradient states
2792 
2793     wxString neweditor;                 // new text editor
2794     wxString newdownloaddir;            // new directory for downloaded files
2795     wxString newuserrules;              // new directory for user's rules
2796 
2797     wxTimer* onetimer;                  // one shot timer (see OnOneTimer)
2798 
2799     DECLARE_EVENT_TABLE()
2800 };
2801 
2802 BEGIN_EVENT_TABLE(PrefsDialog, wxPropertySheetDialog)
2803 EVT_CHECKBOX               (wxID_ANY,        PrefsDialog::OnCheckBoxClicked)
2804 EVT_BUTTON                 (wxID_ANY,        PrefsDialog::OnColorButton)
2805 EVT_NOTEBOOK_PAGE_CHANGING (wxID_ANY,        PrefsDialog::OnPageChanging)
2806 EVT_NOTEBOOK_PAGE_CHANGED  (wxID_ANY,        PrefsDialog::OnPageChanged)
2807 EVT_CHOICE                 (wxID_ANY,        PrefsDialog::OnChoice)
2808 EVT_BUTTON                 (wxID_ANY,        PrefsDialog::OnButton)
2809 EVT_COMMAND_SCROLL         (PREF_SCROLL_BAR, PrefsDialog::OnScroll)
2810 EVT_TIMER                  (wxID_ANY,        PrefsDialog::OnOneTimer)
2811 END_EVENT_TABLE()
2812 
2813 // -----------------------------------------------------------------------------
2814 
2815 // define a text control for showing current key combination
2816 
2817 class KeyComboCtrl : public wxTextCtrl
2818 {
2819 public:
KeyComboCtrl(wxWindow * parent,wxWindowID id,const wxString & value,const wxPoint & pos,const wxSize & size,int style=0)2820     KeyComboCtrl(wxWindow* parent, wxWindowID id, const wxString& value,
2821                  const wxPoint& pos, const wxSize& size, int style = 0)
2822     : wxTextCtrl(parent, id, value, pos, size, style) {}
~KeyComboCtrl()2823     ~KeyComboCtrl() {}
2824 
2825     // handlers to intercept keyboard events
2826     void OnKeyDown(wxKeyEvent& event);
2827     void OnChar(wxKeyEvent& event);
2828 
2829 private:
2830     int realkey;            // key code set by OnKeyDown
2831     wxString debugkey;      // display debug info for OnKeyDown and OnChar
2832 
2833     DECLARE_EVENT_TABLE()
2834 };
2835 
BEGIN_EVENT_TABLE(KeyComboCtrl,wxTextCtrl)2836 BEGIN_EVENT_TABLE(KeyComboCtrl, wxTextCtrl)
2837 EVT_KEY_DOWN  (KeyComboCtrl::OnKeyDown)
2838 EVT_CHAR      (KeyComboCtrl::OnChar)
2839 END_EVENT_TABLE()
2840 
2841 // -----------------------------------------------------------------------------
2842 
2843 void KeyComboCtrl::OnKeyDown(wxKeyEvent& event)
2844 {
2845     realkey = event.GetKeyCode();
2846     int mods = event.GetModifiers();
2847 
2848     if (debuglevel == 1) {
2849         // set debugkey now but don't show it until OnChar
2850         debugkey = wxString::Format(_("OnKeyDown: key=%d (%c) mods=%d"),
2851                                     realkey, realkey < 128 ? wxChar(realkey) : wxChar('?'), mods);
2852     }
2853 
2854     if (realkey == WXK_ESCAPE) {
2855         // escape key is reserved for other uses
2856         Beep();
2857         return;
2858     }
2859 
2860 #ifdef __WXOSX__
2861     // pass arrow key or function key or delete key directly to OnChar
2862     if ( (realkey >=  WXK_LEFT && realkey <= WXK_DOWN) ||
2863         (realkey >= WXK_F1 && realkey <= WXK_F24) || realkey == WXK_BACK ) {
2864         OnChar(event);
2865         return;
2866     }
2867 #endif
2868 
2869     // WARNING: logic must match that in PatternView::OnKeyDown
2870     if (mods == wxMOD_NONE || realkey > 127) {
2871         // tell OnChar handler to ignore realkey
2872         realkey = 0;
2873     }
2874 
2875 #ifdef __WXOSX__
2876     // pass ctrl/cmd-key combos directly to OnChar
2877     if (realkey > 0 && ((mods & wxMOD_CONTROL) || (mods & wxMOD_CMD))) {
2878         OnChar(event);
2879         return;
2880     }
2881 #endif
2882 
2883 #ifdef __WXMAC__
2884     // prevent ctrl-[ cancelling dialog (it translates to escape)
2885     if (realkey == '[' && (mods & wxMOD_CONTROL)) {
2886         OnChar(event);
2887         return;
2888     }
2889     // avoid translating option-E/I/N/U/`
2890     if (mods == wxMOD_ALT && (realkey == 'E' || realkey == 'I' || realkey == 'N' ||
2891                               realkey == 'U' || realkey == '`')) {
2892         OnChar(event);
2893         return;
2894     }
2895 #endif
2896 
2897 #ifdef __WXMSW__
2898     // on Windows, OnChar is NOT called for some ctrl-key combos like
2899     // ctrl-0..9 or ctrl-alt-key, so we call OnChar ourselves
2900     if (realkey > 0 && (mods & wxMOD_CONTROL)) {
2901         OnChar(event);
2902         return;
2903     }
2904 #endif
2905 
2906 /* this didn't work!!! -- OnKeyDown is not getting called
2907 #ifdef __WXGTK__
2908     // on Linux we need to avoid alt-C/O selecting Cancel/OK button
2909     if ((realkey == 'C' || realkey == 'O') && mods == wxMOD_ALT) {
2910         OnChar(event);
2911         return;
2912     }
2913 #endif
2914 */
2915 
2916     event.Skip();
2917 }
2918 
2919 // -----------------------------------------------------------------------------
2920 
2921 static bool inonchar = false;
2922 
OnChar(wxKeyEvent & event)2923 void KeyComboCtrl::OnChar(wxKeyEvent& event)
2924 {
2925     // avoid infinite recursion due to ChangeValue call below
2926     if (inonchar) { event.Skip(); return; }
2927     inonchar = true;
2928 
2929     int key = event.GetKeyCode();
2930     int mods = event.GetModifiers();
2931 
2932     if (debuglevel == 1) {
2933         debugkey += wxString::Format(_("\nOnChar: key=%d (%c) mods=%d"),
2934                                      key, key < 128 ? wxChar(key) : wxChar('?'), mods);
2935         Warning(debugkey);
2936     }
2937 
2938     // WARNING: logic must match that in PatternView::OnChar
2939     if (realkey > 0 && mods != wxMOD_NONE) {
2940 #ifdef __WXGTK__
2941         // sigh... wxGTK returns inconsistent results for shift-comma combos
2942         // so we assume that '<' is produced by pressing shift-comma
2943         // (which might only be true for US keyboards)
2944         if (key == '<' && (mods & wxMOD_SHIFT)) realkey = ',';
2945 #endif
2946 #ifdef __WXMSW__
2947         // sigh... wxMSW returns inconsistent results for some shift-key combos
2948         // so again we assume we're using a US keyboard
2949         if (key == '~' && (mods & wxMOD_SHIFT)) realkey = '`';
2950         if (key == '+' && (mods & wxMOD_SHIFT)) realkey = '=';
2951 #endif
2952         if (mods == wxMOD_SHIFT && key != realkey) {
2953             // use translated key code but remove shift key;
2954             // eg. we want shift-'/' to be seen as '?'
2955             mods = wxMOD_NONE;
2956         } else {
2957             // use key code seen by OnKeyDown
2958             key = realkey;
2959             if (key >= 'A' && key <= 'Z') key += 32;  // convert A..Z to a..z
2960         }
2961     }
2962 
2963     // convert wx key and mods to our internal key code and modifiers
2964     // and, if they are valid, display the key combo and update the action
2965     if ( ConvertKeyAndModifiers(key, mods, &currkey, &currmods) ) {
2966         wxChoice* actionmenu = (wxChoice*) FindWindowById(PREF_ACTION);
2967         if (actionmenu) {
2968             wxString keystring = GetKeyCombo(currkey, currmods);
2969             if (!keystring.IsEmpty()) {
2970                 ChangeValue(keystring);
2971             } else {
2972                 currkey = 0;
2973                 currmods = 0;
2974                 ChangeValue(_("UNKNOWN KEY"));
2975             }
2976             actionmenu->SetSelection(keyaction[currkey][currmods].id);
2977             PrefsDialog::UpdateChosenFile();
2978             SetFocus();
2979             SetSelection(ALL_TEXT);
2980         } else {
2981             Warning(_("Failed to find wxChoice control!"));
2982         }
2983     } else {
2984         // unsupported key combo
2985         Beep();
2986     }
2987 
2988     // do NOT pass event on to next handler
2989     // event.Skip();
2990 
2991     inonchar = false;
2992 }
2993 
2994 // -----------------------------------------------------------------------------
2995 
2996 #ifdef __WXMAC__
2997 
2998 // override key event handler for wxSpinCtrl to allow key checking
2999 // and to get tab key navigation to work correctly
3000 class MySpinCtrl : public wxSpinCtrl
3001 {
3002 public:
MySpinCtrl(wxWindow * parent,wxWindowID id,const wxString & str,const wxPoint & pos,const wxSize & size)3003     MySpinCtrl(wxWindow* parent, wxWindowID id, const wxString& str,
3004                const wxPoint& pos, const wxSize& size)
3005     : wxSpinCtrl(parent, id, str, pos, size)
3006     {
3007         // create a dynamic event handler for the underlying wxTextCtrl
3008         wxTextCtrl* textctrl = GetText();
3009         if (textctrl) {
3010             textctrl->Connect(wxID_ANY, wxEVT_CHAR,
3011                               wxKeyEventHandler(PrefsDialog::OnSpinCtrlChar));
3012         }
3013     }
3014 };
3015 
OnSpinCtrlChar(wxKeyEvent & event)3016 void PrefsDialog::OnSpinCtrlChar(wxKeyEvent& event)
3017 {
3018     int key = event.GetKeyCode();
3019 
3020     if (event.CmdDown()) {
3021         // allow handling of cmd-x/v/etc
3022         event.Skip();
3023 
3024     } else if ( key == WXK_TAB ) {
3025         // note that FindFocus() returns pointer to wxTextCtrl window in wxSpinCtrl
3026         if ( currpage == FILE_PAGE ) {
3027             wxSpinCtrl* s1 = (wxSpinCtrl*) FindWindowById(PREF_MAX_PATTERNS);
3028             wxSpinCtrl* s2 = (wxSpinCtrl*) FindWindowById(PREF_MAX_SCRIPTS);
3029             wxTextCtrl* t1 = s1->GetText();
3030             wxTextCtrl* t2 = s2->GetText();
3031             wxWindow* focus = FindFocus();
3032             if ( focus == t1 ) { s2->SetFocus(); s2->SetSelection(ALL_TEXT); }
3033             if ( focus == t2 ) { s1->SetFocus(); s1->SetSelection(ALL_TEXT); }
3034         } else if ( currpage == EDIT_PAGE ) {
3035             // only one spin ctrl on this page
3036             wxSpinCtrl* s1 = (wxSpinCtrl*) FindWindowById(PREF_RANDOM_FILL);
3037             if ( s1 ) { s1->SetFocus(); s1->SetSelection(ALL_TEXT); }
3038         } else if ( currpage == CONTROL_PAGE ) {
3039             wxSpinCtrl* s1 = (wxSpinCtrl*) FindWindowById(PREF_MAX_MEM);
3040             wxSpinCtrl* s2 = (wxSpinCtrl*) FindWindowById(PREF_BASE_STEP);
3041             wxSpinCtrl* s3 = (wxSpinCtrl*) FindWindowById(PREF_MIN_DELAY);
3042             wxSpinCtrl* s4 = (wxSpinCtrl*) FindWindowById(PREF_MAX_DELAY);
3043             wxTextCtrl* t1 = s1->GetText();
3044             wxTextCtrl* t2 = s2->GetText();
3045             wxTextCtrl* t3 = s3->GetText();
3046             wxTextCtrl* t4 = s4->GetText();
3047             wxWindow* focus = FindFocus();
3048             if ( focus == t1 ) { s2->SetFocus(); s2->SetSelection(ALL_TEXT); }
3049             if ( focus == t2 ) { s3->SetFocus(); s3->SetSelection(ALL_TEXT); }
3050             if ( focus == t3 ) { s4->SetFocus(); s4->SetSelection(ALL_TEXT); }
3051             if ( focus == t4 ) { s1->SetFocus(); s1->SetSelection(ALL_TEXT); }
3052         } else if ( currpage == VIEW_PAGE ) {
3053             wxSpinCtrl* s1 = (wxSpinCtrl*) FindWindowById(PREF_BOLD_SPACING);
3054             wxSpinCtrl* s2 = (wxSpinCtrl*) FindWindowById(PREF_SENSITIVITY);
3055             wxSpinCtrl* s3 = (wxSpinCtrl*) FindWindowById(PREF_THUMB_RANGE);
3056             wxTextCtrl* t1 = s1->GetText();
3057             wxTextCtrl* t2 = s2->GetText();
3058             wxTextCtrl* t3 = s3->GetText();
3059             wxWindow* focus = FindFocus();
3060             wxCheckBox* checkbox = (wxCheckBox*) FindWindowById(PREF_SHOW_BOLD);
3061             if (checkbox) {
3062                 if (checkbox->GetValue()) {
3063                     if ( focus == t1 ) { s2->SetFocus(); s2->SetSelection(ALL_TEXT); }
3064                     if ( focus == t2 ) { s3->SetFocus(); s3->SetSelection(ALL_TEXT); }
3065                     if ( focus == t3 ) { s1->SetFocus(); s1->SetSelection(ALL_TEXT); }
3066                 } else {
3067                     if ( focus == t2 ) { s3->SetFocus(); s3->SetSelection(ALL_TEXT); }
3068                     if ( focus == t3 ) { s2->SetFocus(); s2->SetSelection(ALL_TEXT); }
3069                 }
3070             } else {
3071                 Beep();
3072             }
3073         } else if ( currpage == LAYER_PAGE ) {
3074             wxSpinCtrl* s1 = (wxSpinCtrl*) FindWindowById(PREF_OPACITY);
3075             wxSpinCtrl* s2 = (wxSpinCtrl*) FindWindowById(PREF_TILE_BORDER);
3076             wxTextCtrl* t1 = s1->GetText();
3077             wxTextCtrl* t2 = s2->GetText();
3078             wxWindow* focus = FindFocus();
3079             if ( focus == t1 ) { s2->SetFocus(); s2->SetSelection(ALL_TEXT); }
3080             if ( focus == t2 ) { s1->SetFocus(); s1->SetSelection(ALL_TEXT); }
3081         } else if ( currpage == COLOR_PAGE ) {
3082             // no spin ctrls on this page
3083         } else if ( currpage == KEYBOARD_PAGE ) {
3084             // no spin ctrls on this page
3085         }
3086 
3087     } else if ( key >= ' ' && key <= '~' ) {
3088         if ( key >= '0' && key <= '9' ) {
3089             // allow digits
3090             event.Skip();
3091         } else {
3092             // disallow any other displayable ascii char
3093             Beep();
3094         }
3095 
3096     } else {
3097         event.Skip();
3098     }
3099 }
3100 
3101 #else
3102 
3103 #define MySpinCtrl wxSpinCtrl
3104 
3105 #endif // !__WXMAC__
3106 
3107 // -----------------------------------------------------------------------------
3108 
PrefsDialog(wxWindow * parent,const wxString & page)3109 PrefsDialog::PrefsDialog(wxWindow* parent, const wxString& page)
3110 {
3111     // not using validators so no need for this:
3112     // SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY);
3113 
3114     Create(parent, wxID_ANY, _("Preferences"));
3115     CreateButtons(wxOK | wxCANCEL);
3116 
3117     wxBookCtrlBase* notebook = GetBookCtrl();
3118 
3119     wxPanel* filePrefs = CreateFilePrefs(notebook);
3120     wxPanel* editPrefs = CreateEditPrefs(notebook);
3121     wxPanel* ctrlPrefs = CreateControlPrefs(notebook);
3122     wxPanel* viewPrefs = CreateViewPrefs(notebook);
3123     wxPanel* layerPrefs = CreateLayerPrefs(notebook);
3124     wxPanel* colorPrefs = CreateColorPrefs(notebook);
3125     wxPanel* keyboardPrefs = CreateKeyboardPrefs(notebook);
3126 
3127     // AddPage and SetSelection cause OnPageChanging and OnPageChanged to be called
3128     // so we use a flag to prevent currpage being changed (and unnecessary validation)
3129     ignore_page_event = true;
3130 
3131     notebook->AddPage(filePrefs, _("File"));
3132     notebook->AddPage(editPrefs, _("Edit"));
3133     notebook->AddPage(ctrlPrefs, _("Control"));
3134     notebook->AddPage(viewPrefs, _("View"));
3135     notebook->AddPage(layerPrefs, _("Layer"));
3136     notebook->AddPage(colorPrefs, _("Color"));
3137     notebook->AddPage(keyboardPrefs, _("Keyboard"));
3138 
3139     if (!page.IsEmpty()) {
3140         if (page == wxT("file"))            currpage = FILE_PAGE;
3141         else if (page == wxT("edit"))       currpage = EDIT_PAGE;
3142         else if (page == wxT("control"))    currpage = CONTROL_PAGE;
3143         else if (page == wxT("view"))       currpage = VIEW_PAGE;
3144         else if (page == wxT("layer"))      currpage = LAYER_PAGE;
3145         else if (page == wxT("color"))      currpage = COLOR_PAGE;
3146         else if (page == wxT("keyboard"))   currpage = KEYBOARD_PAGE;
3147     }
3148 
3149     // show the desired page
3150     notebook->SetSelection(currpage);
3151 
3152     ignore_page_event = false;
3153 
3154     LayoutDialog();
3155 
3156     // ensure top text box has focus and text is selected by creating
3157     // a one-shot timer which will call OnOneTimer after short delay
3158     onetimer = new wxTimer(this, wxID_ANY);
3159     if (onetimer) onetimer->Start(10, wxTIMER_ONE_SHOT);
3160 }
3161 
3162 // -----------------------------------------------------------------------------
3163 
OnOneTimer(wxTimerEvent & WXUNUSED (event))3164 void PrefsDialog::OnOneTimer(wxTimerEvent& WXUNUSED(event))
3165 {
3166     MySpinCtrl* s1 = NULL;
3167 
3168     if (currpage == FILE_PAGE) {
3169         s1 = (MySpinCtrl*) FindWindowById(PREF_MAX_PATTERNS);
3170 
3171     } else if (currpage == EDIT_PAGE) {
3172         s1 = (MySpinCtrl*) FindWindowById(PREF_RANDOM_FILL);
3173 
3174     } else if (currpage == CONTROL_PAGE) {
3175         s1 = (MySpinCtrl*) FindWindowById(PREF_MAX_MEM);
3176 
3177     } else if (currpage == VIEW_PAGE) {
3178         s1 = (MySpinCtrl*) FindWindowById(showgridlines ? PREF_BOLD_SPACING : PREF_SENSITIVITY);
3179 
3180     } else if (currpage == LAYER_PAGE) {
3181         s1 = (MySpinCtrl*) FindWindowById(PREF_OPACITY);
3182 
3183     } else if (currpage == COLOR_PAGE) {
3184         // no spin ctrls on this page
3185         return;
3186 
3187     } else if (currpage == KEYBOARD_PAGE) {
3188         KeyComboCtrl* k = (KeyComboCtrl*) FindWindowById(PREF_KEYCOMBO);
3189         if (k) {
3190             k->SetFocus();
3191             k->SetSelection(ALL_TEXT);
3192         }
3193         return;
3194     }
3195 
3196     if (s1) {
3197         s1->SetFocus();
3198         s1->SetSelection(ALL_TEXT);
3199     }
3200 }
3201 
3202 // -----------------------------------------------------------------------------
3203 
3204 // these consts are used to get nicely spaced controls on each platform:
3205 
3206 #ifdef __WXMAC__
3207     #define GROUPGAP (12)      // vertical gap between a group of controls
3208     #define SBTOPGAP (2)       // vertical gap before first item in wxStaticBoxSizer
3209     #define SBBOTGAP (2)       // vertical gap after last item in wxStaticBoxSizer
3210 #if wxCHECK_VERSION(3,0,0)
3211     #define SVGAP (8)          // vertical gap above wxSpinCtrl box
3212     #define S2VGAP (6)         // vertical gap between 2 wxSpinCtrl boxes
3213     #define SPINGAP (6)        // horizontal gap around each wxSpinCtrl box
3214 #else
3215     #define SVGAP (4)          // vertical gap above wxSpinCtrl box
3216     #define S2VGAP (0)         // vertical gap between 2 wxSpinCtrl boxes
3217     #define SPINGAP (3)        // horizontal gap around each wxSpinCtrl box
3218 #endif
3219     #define CH2VGAP (6)        // vertical gap between 2 check/radio boxes
3220     #define CVGAP (9)          // vertical gap above wxChoice box
3221     #define LRGAP (5)          // space left and right of vertically stacked boxes
3222     #define CHOICEGAP (6)      // horizontal gap to left of wxChoice box
3223 #elif defined(__WXMSW__)
3224     #define GROUPGAP (10)
3225     #define SBTOPGAP (7)
3226     #define SBBOTGAP (7)
3227     #define SVGAP (7)
3228     #define S2VGAP (5)
3229     #define CH2VGAP (8)
3230     #define CVGAP (7)
3231     #define LRGAP (5)
3232     #define SPINGAP (6)
3233     #define CHOICEGAP (6)
3234 #else // assume Linux
3235     #define GROUPGAP (10)
3236     #define SBTOPGAP (12)
3237     #define SBBOTGAP (7)
3238     #define SVGAP (7)
3239     #define S2VGAP (5)
3240     #define CH2VGAP (8)
3241     #define CVGAP (7)
3242     #define LRGAP (5)
3243     #define SPINGAP (6)
3244     #define CHOICEGAP (6)
3245 #endif
3246 
3247 // -----------------------------------------------------------------------------
3248 
CreateFilePrefs(wxWindow * parent)3249 wxPanel* PrefsDialog::CreateFilePrefs(wxWindow* parent)
3250 {
3251     wxPanel* panel = new wxPanel(parent, wxID_ANY);
3252     wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
3253     wxBoxSizer* vbox = new wxBoxSizer(wxVERTICAL);
3254 
3255     wxArrayString newcursorChoices;
3256     newcursorChoices.Add(wxT("Draw"));
3257     newcursorChoices.Add(wxT("Pick"));
3258     newcursorChoices.Add(wxT("Select"));
3259     newcursorChoices.Add(wxT("Move"));
3260     newcursorChoices.Add(wxT("Zoom In"));
3261     newcursorChoices.Add(wxT("Zoom Out"));
3262     newcursorChoices.Add(wxT("No Change"));
3263 
3264     wxArrayString opencursorChoices = newcursorChoices;
3265 
3266     wxArrayString newscaleChoices;
3267     newscaleChoices.Add(wxT("1:1"));
3268     newscaleChoices.Add(wxT("1:2"));
3269     newscaleChoices.Add(wxT("1:4"));
3270     newscaleChoices.Add(wxT("1:8"));
3271     newscaleChoices.Add(wxT("1:16"));
3272     newscaleChoices.Add(wxT("1:32"));
3273 
3274     // on new pattern
3275 
3276     wxStaticBox* sbox1 = new wxStaticBox(panel, wxID_ANY, _("On creating a new pattern:"));
3277     wxBoxSizer* ssizer1 = new wxStaticBoxSizer(sbox1, wxVERTICAL);
3278 
3279     wxCheckBox* check1 = new wxCheckBox(panel, PREF_NEW_REM_SEL, _("Remove selection"));
3280     wxBoxSizer* check1box = new wxBoxSizer(wxHORIZONTAL);
3281     check1box->Add(check1, 0, wxALL, 0);
3282 
3283     wxBoxSizer* setcurs1 = new wxBoxSizer(wxHORIZONTAL);
3284     setcurs1->Add(new wxStaticText(panel, wxID_STATIC, _("Set cursor:")), 0, wxALL, 0);
3285 
3286     wxBoxSizer* setscalebox = new wxBoxSizer(wxHORIZONTAL);
3287     setscalebox->Add(new wxStaticText(panel, wxID_STATIC, _("Set scale:")), 0, wxALL, 0);
3288 
3289     wxChoice* choice3 = new wxChoice(panel, PREF_NEW_CURSOR,
3290                                      wxDefaultPosition, wxDefaultSize,
3291                                      newcursorChoices);
3292 
3293     wxChoice* choice1 = new wxChoice(panel, PREF_NEW_SCALE,
3294                                      wxDefaultPosition, wxDefaultSize,
3295                                      newscaleChoices);
3296 
3297     wxBoxSizer* hbox1 = new wxBoxSizer(wxHORIZONTAL);
3298     hbox1->Add(check1box, 0, wxALIGN_CENTER_VERTICAL, 0);
3299     hbox1->AddStretchSpacer(20);
3300     hbox1->Add(setcurs1, 0, wxALIGN_CENTER_VERTICAL, 0);
3301     hbox1->Add(choice3, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, CHOICEGAP);
3302     hbox1->AddStretchSpacer(20);
3303 
3304     wxBoxSizer* hbox2 = new wxBoxSizer(wxHORIZONTAL);
3305     hbox2->Add(setscalebox, 0, wxALIGN_CENTER_VERTICAL, 0);
3306     hbox2->Add(choice1, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, CHOICEGAP);
3307 
3308     ssizer1->AddSpacer(SBTOPGAP);
3309     ssizer1->Add(hbox1, 1, wxGROW | wxLEFT | wxRIGHT, LRGAP);
3310     ssizer1->AddSpacer(CVGAP);
3311     ssizer1->Add(hbox2, 0, wxLEFT | wxRIGHT, LRGAP);
3312     ssizer1->AddSpacer(SBBOTGAP);
3313 
3314     // on opening pattern
3315 
3316     wxStaticBox* sbox2 = new wxStaticBox(panel, wxID_ANY, _("On opening a pattern file or the clipboard:"));
3317     wxBoxSizer* ssizer2 = new wxStaticBoxSizer(sbox2, wxVERTICAL);
3318 
3319     wxCheckBox* check2 = new wxCheckBox(panel, PREF_OPEN_REM_SEL, _("Remove selection"));
3320     wxBoxSizer* check2box = new wxBoxSizer(wxHORIZONTAL);
3321     check2box->Add(check2, 0, wxALL, 0);
3322 
3323     wxChoice* choice4 = new wxChoice(panel, PREF_OPEN_CURSOR,
3324                                      wxDefaultPosition, wxDefaultSize,
3325                                      opencursorChoices);
3326 
3327     wxBoxSizer* setcurs2 = new wxBoxSizer(wxHORIZONTAL);
3328     setcurs2->Add(new wxStaticText(panel, wxID_STATIC, _("Set cursor:")), 0, wxALL, 0);
3329 
3330     wxBoxSizer* hbox4 = new wxBoxSizer(wxHORIZONTAL);
3331     hbox4->Add(check2box, 0, wxALIGN_CENTER_VERTICAL, 0);
3332     hbox4->AddStretchSpacer(20);
3333     hbox4->Add(setcurs2, 0, wxALIGN_CENTER_VERTICAL, 0);
3334     hbox4->Add(choice4, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, CHOICEGAP);
3335     hbox4->AddStretchSpacer(20);
3336 
3337     ssizer2->AddSpacer(SBTOPGAP);
3338     ssizer2->Add(hbox4, 1, wxGROW | wxLEFT | wxRIGHT, LRGAP);
3339     ssizer2->AddSpacer(SBBOTGAP);
3340 
3341     // max_patterns and max_scripts
3342 
3343     wxBoxSizer* maxbox = new wxBoxSizer(wxHORIZONTAL);
3344     maxbox->Add(new wxStaticText(panel, wxID_STATIC, _("Maximum number of recent patterns:")),
3345                 0, wxALL, 0);
3346 
3347     wxBoxSizer* minbox = new wxBoxSizer(wxHORIZONTAL);
3348     minbox->Add(new wxStaticText(panel, wxID_STATIC, _("Maximum number of recent scripts:")),
3349                 0, wxALL, 0);
3350 
3351     // align spin controls by setting minbox same width as maxbox
3352     minbox->SetMinSize( maxbox->GetMinSize() );
3353 
3354     wxSpinCtrl* spin1 = new MySpinCtrl(panel, PREF_MAX_PATTERNS, wxEmptyString,
3355                                        wxDefaultPosition, wxSize(70, wxDefaultCoord));
3356 
3357     wxSpinCtrl* spin2 = new MySpinCtrl(panel, PREF_MAX_SCRIPTS, wxEmptyString,
3358                                        wxDefaultPosition, wxSize(70, wxDefaultCoord));
3359 
3360     wxBoxSizer* hpbox = new wxBoxSizer(wxHORIZONTAL);
3361     hpbox->Add(maxbox, 0, wxALIGN_CENTER_VERTICAL, 0);
3362     hpbox->Add(spin1, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, SPINGAP);
3363 
3364     wxBoxSizer* hsbox = new wxBoxSizer(wxHORIZONTAL);
3365     hsbox->Add(minbox, 0, wxALIGN_CENTER_VERTICAL, 0);
3366     hsbox->Add(spin2, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, SPINGAP);
3367 
3368     wxButton* editorbutt = new wxButton(panel, PREF_EDITOR_BUTT, _("Text Editor..."));
3369     wxStaticText* editorbox = new wxStaticText(panel, PREF_EDITOR_BOX, texteditor);
3370     neweditor = texteditor;
3371 
3372     wxButton* downloadbutt = new wxButton(panel, PREF_DOWNLOAD_BUTT, _("Downloads..."));
3373     wxStaticText* downloadbox = new wxStaticText(panel, PREF_DOWNLOAD_BOX, downloaddir);
3374     newdownloaddir = downloaddir;
3375 
3376     wxBoxSizer* hebox = new wxBoxSizer(wxHORIZONTAL);
3377     hebox->Add(editorbutt, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, 0);
3378     hebox->Add(editorbox, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, LRGAP);
3379 
3380     wxBoxSizer* hdbox = new wxBoxSizer(wxHORIZONTAL);
3381     hdbox->Add(downloadbutt, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, 0);
3382     hdbox->Add(downloadbox, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, LRGAP);
3383 
3384     vbox->Add(ssizer1, 0, wxGROW | wxALL, 2);
3385     vbox->AddSpacer(10);
3386     vbox->Add(ssizer2, 0, wxGROW | wxALL, 2);
3387     vbox->AddSpacer(10);
3388     vbox->Add(hpbox, 0, wxLEFT | wxRIGHT, LRGAP);
3389     vbox->AddSpacer(S2VGAP);
3390     vbox->Add(hsbox, 0, wxLEFT | wxRIGHT, LRGAP);
3391     vbox->AddSpacer(10);
3392     vbox->Add(hebox, 0, wxLEFT | wxRIGHT, LRGAP);
3393     vbox->AddSpacer(10);
3394     vbox->Add(hdbox, 0, wxLEFT | wxRIGHT, LRGAP);
3395     vbox->AddSpacer(5);
3396 
3397     // init control values
3398     check1->SetValue(newremovesel);
3399     check2->SetValue(openremovesel);
3400     choice1->SetSelection(newmag);
3401     newcursindex = CursorToIndex(newcurs);
3402     opencursindex = CursorToIndex(opencurs);
3403     choice3->SetSelection(newcursindex);
3404     choice4->SetSelection(opencursindex);
3405     spin1->SetRange(1, MAX_RECENT); spin1->SetValue(maxpatterns);
3406     spin2->SetRange(1, MAX_RECENT); spin2->SetValue(maxscripts);
3407     spin1->SetFocus();
3408     spin1->SetSelection(ALL_TEXT);
3409 
3410     topSizer->Add(vbox, 1, wxGROW | wxLEFT | wxALL, 5);
3411     panel->SetSizer(topSizer);
3412     topSizer->Fit(panel);
3413     return panel;
3414 }
3415 
3416 // -----------------------------------------------------------------------------
3417 
CreateEditPrefs(wxWindow * parent)3418 wxPanel* PrefsDialog::CreateEditPrefs(wxWindow* parent)
3419 {
3420     wxPanel* panel = new wxPanel(parent, wxID_ANY);
3421     wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
3422     wxBoxSizer* vbox = new wxBoxSizer(wxVERTICAL);
3423 
3424     // random_fill
3425 
3426     wxBoxSizer* hbox1 = new wxBoxSizer(wxHORIZONTAL);
3427     hbox1->Add(new wxStaticText(panel, wxID_STATIC, _("Random fill percentage:")),
3428                0, wxALIGN_CENTER_VERTICAL, 0);
3429     wxSpinCtrl* spin1 = new MySpinCtrl(panel, PREF_RANDOM_FILL, wxEmptyString,
3430                                        wxDefaultPosition, wxSize(70, wxDefaultCoord));
3431     hbox1->Add(spin1, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, SPINGAP);
3432 
3433     // can_change_rule
3434 
3435     wxStaticBox* sbox1 = new wxStaticBox(panel, wxID_ANY, _("When pasting a clipboard pattern:"));
3436     wxBoxSizer* ssizer1 = new wxStaticBoxSizer(sbox1, wxVERTICAL);
3437 
3438     wxRadioButton* radio0 = new wxRadioButton(panel, PREF_PASTE_0, _("Never change rule"),
3439                                               wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
3440     wxRadioButton* radio1 = new wxRadioButton(panel, PREF_PASTE_1,
3441                                               _("Only change rule if one is specified and the universe is empty"));
3442     wxRadioButton* radio2 = new wxRadioButton(panel, PREF_PASTE_2,
3443                                               _("Always change rule if one is specified"));
3444 
3445     ssizer1->AddSpacer(SBTOPGAP);
3446     ssizer1->Add(radio0, 0, wxLEFT | wxRIGHT, LRGAP);
3447     ssizer1->AddSpacer(CH2VGAP);
3448     ssizer1->Add(radio1, 0, wxLEFT | wxRIGHT, LRGAP);
3449     ssizer1->AddSpacer(CH2VGAP);
3450     ssizer1->Add(radio2, 0, wxLEFT | wxRIGHT, LRGAP);
3451     ssizer1->AddSpacer(SBBOTGAP);
3452 
3453     // scroll_pencil, scroll_cross, scroll_hand
3454 
3455     wxStaticBox* sbox2 = new wxStaticBox(panel, wxID_ANY,
3456                                          _("If the cursor is dragged outside the viewport:"));
3457     wxBoxSizer* ssizer2 = new wxStaticBoxSizer(sbox2, wxVERTICAL);
3458 
3459     wxCheckBox* check1 = new wxCheckBox(panel, PREF_SCROLL_PENCIL,
3460                                         _("Scroll when drawing cells (using the pencil cursor)"));
3461     wxCheckBox* check2 = new wxCheckBox(panel, PREF_SCROLL_CROSS,
3462                                         _("Scroll when selecting cells (using the cross cursor)"));
3463     wxCheckBox* check3 = new wxCheckBox(panel, PREF_SCROLL_HAND,
3464                                         _("Scroll when moving view (using the hand cursor)"));
3465 
3466     ssizer2->AddSpacer(SBTOPGAP);
3467     ssizer2->Add(check1, 0, wxLEFT | wxRIGHT, LRGAP);
3468     ssizer2->AddSpacer(CH2VGAP);
3469     ssizer2->Add(check2, 0, wxLEFT | wxRIGHT, LRGAP);
3470     ssizer2->AddSpacer(CH2VGAP);
3471     ssizer2->Add(check3, 0, wxLEFT | wxRIGHT, LRGAP);
3472     ssizer2->AddSpacer(SBBOTGAP);
3473 
3474     // allow_beep
3475 
3476     wxCheckBox* check4 = new wxCheckBox(panel, PREF_BEEP, _("Allow beep sound"));
3477 
3478     vbox->AddSpacer(SVGAP);
3479     vbox->Add(hbox1, 0, wxLEFT | wxRIGHT, LRGAP);
3480     vbox->AddSpacer(GROUPGAP);
3481     vbox->Add(ssizer1, 0, wxGROW | wxALL, 2);
3482     vbox->AddSpacer(GROUPGAP);
3483     vbox->Add(ssizer2, 0, wxGROW | wxALL, 2);
3484     vbox->AddSpacer(GROUPGAP);
3485     vbox->Add(check4, 0, wxLEFT | wxRIGHT, LRGAP);
3486 
3487     // init control values
3488     spin1->SetRange(1, 100);
3489     spin1->SetValue(randomfill);
3490     spin1->SetFocus();
3491     spin1->SetSelection(ALL_TEXT);
3492     radio0->SetValue(canchangerule == 0);
3493     radio1->SetValue(canchangerule == 1);
3494     radio2->SetValue(canchangerule == 2);
3495     check1->SetValue(scrollpencil);
3496     check2->SetValue(scrollcross);
3497     check3->SetValue(scrollhand);
3498     check4->SetValue(allowbeep);
3499 
3500     topSizer->Add(vbox, 1, wxGROW | wxLEFT | wxALL, 5);
3501     panel->SetSizer(topSizer);
3502     topSizer->Fit(panel);
3503     return panel;
3504 }
3505 
3506 // -----------------------------------------------------------------------------
3507 
CreateControlPrefs(wxWindow * parent)3508 wxPanel* PrefsDialog::CreateControlPrefs(wxWindow* parent)
3509 {
3510     wxPanel* panel = new wxPanel(parent, wxID_ANY);
3511     wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
3512     wxBoxSizer* vbox = new wxBoxSizer(wxVERTICAL);
3513 
3514     // create a choice menu to select algo
3515 
3516     wxArrayString algoChoices;
3517     for (int i = 0; i < NumAlgos(); i++) {
3518         algoChoices.Add( wxString(GetAlgoName(i), wxConvLocal) );
3519     }
3520     wxChoice* algomenu = new wxChoice(panel, PREF_ALGO_MENU1,
3521                                       wxDefaultPosition, wxDefaultSize, algoChoices);
3522     algopos1 = currlayer->algtype;
3523 
3524     wxBoxSizer* longbox = new wxBoxSizer(wxHORIZONTAL);
3525     longbox->Add(new wxStaticText(panel, wxID_STATIC, _("Settings for this algorithm:")),
3526                  0, wxALL, 0);
3527 
3528     wxBoxSizer* menubox = new wxBoxSizer(wxHORIZONTAL);
3529     menubox->Add(longbox, 0, wxALIGN_CENTER_VERTICAL, 0);
3530     menubox->Add(algomenu, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, CHOICEGAP);
3531 
3532     // maximum memory and base step
3533 
3534     wxBoxSizer* membox = new wxBoxSizer(wxHORIZONTAL);
3535     membox->Add(new wxStaticText(panel, wxID_STATIC, _("Maximum memory:")), 0, wxALL, 0);
3536 
3537     wxBoxSizer* basebox = new wxBoxSizer(wxHORIZONTAL);
3538     basebox->Add(new wxStaticText(panel, wxID_STATIC, _("Default base step:")), 0, wxALL, 0);
3539 
3540     // align spin controls
3541     membox->SetMinSize( longbox->GetMinSize() );
3542     basebox->SetMinSize( longbox->GetMinSize() );
3543 
3544     wxBoxSizer* hbox1 = new wxBoxSizer(wxHORIZONTAL);
3545     hbox1->Add(membox, 0, wxALIGN_CENTER_VERTICAL, 0);
3546     wxSpinCtrl* spin1 = new MySpinCtrl(panel, PREF_MAX_MEM, wxEmptyString,
3547                                        wxDefaultPosition, wxSize(80, wxDefaultCoord));
3548     hbox1->Add(spin1, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, SPINGAP);
3549 
3550     wxString memnote = algoinfo[algopos1]->canhash ? HASH_MEM_NOTE : NONHASH_MEM_NOTE;
3551     hbox1->Add(new wxStaticText(panel, PREF_MEM_NOTE, memnote),
3552                0, wxALIGN_CENTER_VERTICAL, 0);
3553 
3554     wxBoxSizer* hbox2 = new wxBoxSizer(wxHORIZONTAL);
3555     hbox2->Add(basebox, 0, wxALIGN_CENTER_VERTICAL, 0);
3556     wxSpinCtrl* spin2 = new MySpinCtrl(panel, PREF_BASE_STEP, wxEmptyString,
3557                                        wxDefaultPosition, wxSize(80, wxDefaultCoord));
3558     hbox2->Add(spin2, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, SPINGAP);
3559 
3560     wxString stepnote = algoinfo[algopos1]->canhash ? HASH_STEP_NOTE : NONHASH_STEP_NOTE;
3561     hbox2->Add(new wxStaticText(panel, PREF_STEP_NOTE, stepnote),
3562                0, wxALIGN_CENTER_VERTICAL, 0);
3563 
3564     // min_delay and max_delay
3565 
3566     wxBoxSizer* minbox = new wxBoxSizer(wxHORIZONTAL);
3567     minbox->Add(new wxStaticText(panel, wxID_STATIC, _("Minimum delay:")), 0, wxALL, 0);
3568 
3569     wxBoxSizer* maxbox = new wxBoxSizer(wxHORIZONTAL);
3570     maxbox->Add(new wxStaticText(panel, wxID_STATIC, _("Maximum delay:")), 0, wxALL, 0);
3571 
3572     // align spin controls
3573     minbox->SetMinSize( maxbox->GetMinSize() );
3574 
3575     wxBoxSizer* hbox3 = new wxBoxSizer(wxHORIZONTAL);
3576     hbox3->Add(minbox, 0, wxALIGN_CENTER_VERTICAL, 0);
3577     wxSpinCtrl* spin3 = new MySpinCtrl(panel, PREF_MIN_DELAY, wxEmptyString,
3578                                        wxDefaultPosition, wxSize(80, wxDefaultCoord));
3579     hbox3->Add(spin3, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, SPINGAP);
3580     hbox3->Add(new wxStaticText(panel, wxID_STATIC, _("millisecs")),
3581                0, wxALIGN_CENTER_VERTICAL, 0);
3582 
3583     wxBoxSizer* hbox4 = new wxBoxSizer(wxHORIZONTAL);
3584     hbox4->Add(maxbox, 0, wxALIGN_CENTER_VERTICAL, 0);
3585     wxSpinCtrl* spin4 = new MySpinCtrl(panel, PREF_MAX_DELAY, wxEmptyString,
3586                                        wxDefaultPosition, wxSize(80, wxDefaultCoord));
3587     hbox4->Add(spin4, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, SPINGAP);
3588     hbox4->Add(new wxStaticText(panel, wxID_STATIC, _("millisecs")),
3589                0, wxALIGN_CENTER_VERTICAL, 0);
3590 
3591     // user_rules
3592 
3593     wxButton* rulesbutt = new wxButton(panel, PREF_RULES_BUTT, _("Your Rules..."));
3594     wxStaticText* rulesbox = new wxStaticText(panel, PREF_RULES_BOX, userrules);
3595     newuserrules = userrules;
3596 
3597     wxBoxSizer* hrbox = new wxBoxSizer(wxHORIZONTAL);
3598     hrbox->Add(rulesbutt, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, 0);
3599     hrbox->Add(rulesbox, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, LRGAP);
3600 
3601     wxString note = _("Golly looks for .rule files in the above folder before looking in the Rules folder.");
3602     wxBoxSizer* notebox = new wxBoxSizer(wxHORIZONTAL);
3603     notebox->Add(new wxStaticText(panel, wxID_STATIC, note));
3604 
3605     // position things
3606     vbox->AddSpacer(5);
3607     vbox->Add(menubox, 0, wxLEFT | wxRIGHT, LRGAP);
3608     vbox->AddSpacer(SVGAP);
3609     vbox->Add(hbox1, 0, wxLEFT | wxRIGHT, LRGAP);
3610     vbox->AddSpacer(S2VGAP);
3611     vbox->Add(hbox2, 0, wxLEFT | wxRIGHT, LRGAP);
3612 
3613     vbox->AddSpacer(5);
3614     vbox->AddSpacer(GROUPGAP);
3615     vbox->Add(hbox3, 0, wxLEFT | wxRIGHT, LRGAP);
3616     vbox->AddSpacer(S2VGAP);
3617     vbox->Add(hbox4, 0, wxLEFT | wxRIGHT, LRGAP);
3618 
3619     vbox->AddSpacer(15);
3620     vbox->AddSpacer(GROUPGAP);
3621     vbox->Add(hrbox, 0, wxLEFT | wxRIGHT, LRGAP);
3622     vbox->AddSpacer(15);
3623     vbox->Add(notebox, 0, wxLEFT, LRGAP);
3624 
3625     // init control values;
3626     // to avoid a wxGTK bug we use SetRange and SetValue rather than specifying
3627     // the min,max,init values in the wxSpinCtrl constructor
3628     spin1->SetRange(MIN_MEM_MB, MAX_MEM_MB);
3629     spin1->SetValue(algoinfo[algopos1]->algomem);
3630     spin2->SetRange(2, MAX_BASESTEP);
3631     spin2->SetValue(algoinfo[algopos1]->defbase);
3632     spin3->SetRange(0, MAX_DELAY);           spin3->SetValue(mindelay);
3633     spin4->SetRange(0, MAX_DELAY);           spin4->SetValue(maxdelay);
3634     spin1->SetFocus();
3635     spin1->SetSelection(ALL_TEXT);
3636     algomenu->SetSelection(algopos1);
3637 
3638     for (int i = 0; i < NumAlgos(); i++) {
3639         new_algomem[i] = algoinfo[i]->algomem;
3640         new_defbase[i] = algoinfo[i]->defbase;
3641     }
3642 
3643     topSizer->Add(vbox, 1, wxGROW | wxLEFT | wxALL, 5);
3644     panel->SetSizer(topSizer);
3645     topSizer->Fit(panel);
3646     return panel;
3647 }
3648 
3649 // -----------------------------------------------------------------------------
3650 
CreateViewPrefs(wxWindow * parent)3651 wxPanel* PrefsDialog::CreateViewPrefs(wxWindow* parent)
3652 {
3653     wxPanel* panel = new wxPanel(parent, wxID_ANY);
3654     wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
3655     wxBoxSizer* vbox = new wxBoxSizer(wxVERTICAL);
3656 
3657     // show_tips
3658 
3659 #if wxUSE_TOOLTIPS
3660     wxCheckBox* check3 = new wxCheckBox(panel, PREF_SHOW_TIPS, _("Show button tips"));
3661 #endif
3662 
3663     // restore_view
3664 
3665     wxCheckBox* check4 = new wxCheckBox(panel, PREF_RESTORE, _("Reset/Undo will restore view"));
3666 
3667     // math_coords
3668 
3669     wxCheckBox* check1 = new wxCheckBox(panel, PREF_Y_UP, _("Y coordinates increase upwards"));
3670 
3671     // zoomed cell borders
3672 
3673     wxCheckBox* check5 = new wxCheckBox(panel, PREF_CELL_BORDERS, _("Zoomed cells have borders"));
3674 
3675     // show_bold_lines and bold_spacing
3676 
3677     wxBoxSizer* hbox2 = new wxBoxSizer(wxHORIZONTAL);
3678     wxCheckBox* check2 = new wxCheckBox(panel, PREF_SHOW_BOLD, _("Show bold grid lines every"));
3679 
3680     wxSpinCtrl* spin2 = new MySpinCtrl(panel, PREF_BOLD_SPACING, wxEmptyString,
3681                                        wxDefaultPosition, wxSize(70, wxDefaultCoord));
3682 
3683     hbox2->Add(check2, 0, wxALIGN_CENTER_VERTICAL, 0);
3684     hbox2->Add(spin2, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, SPINGAP);
3685     hbox2->Add(new wxStaticText(panel, wxID_STATIC, _("cells")),
3686                0, wxALIGN_CENTER_VERTICAL, 0);
3687 
3688     // min_grid_mag (2..MAX_MAG)
3689 
3690     wxBoxSizer* hbox3 = new wxBoxSizer(wxHORIZONTAL);
3691 
3692     wxArrayString mingridChoices;
3693     mingridChoices.Add(wxT("1:4"));
3694     mingridChoices.Add(wxT("1:8"));
3695     mingridChoices.Add(wxT("1:16"));
3696     mingridChoices.Add(wxT("1:32"));
3697     wxChoice* choice3 = new wxChoice(panel, PREF_MIN_GRID_SCALE,
3698                                      wxDefaultPosition, wxDefaultSize,
3699                                      mingridChoices);
3700 
3701     wxBoxSizer* longbox = new wxBoxSizer(wxHORIZONTAL);
3702     longbox->Add(new wxStaticText(panel, wxID_STATIC, _("Minimum scale for grid:")),
3703                  0, wxALL, 0);
3704 
3705     hbox3->Add(longbox, 0, wxALIGN_CENTER_VERTICAL, 0);
3706     hbox3->Add(choice3, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, CHOICEGAP);
3707 
3708     // mouse_wheel_mode
3709 
3710     wxBoxSizer* wheelbox = new wxBoxSizer(wxHORIZONTAL);
3711     wheelbox->Add(new wxStaticText(panel, wxID_STATIC, _("Mouse wheel action:")), 0, wxALL, 0);
3712 
3713     // align by setting wheelbox same width as longbox
3714     wheelbox->SetMinSize( longbox->GetMinSize() );
3715 
3716     wxArrayString mousewheelChoices;
3717     mousewheelChoices.Add(_("Disabled"));
3718     mousewheelChoices.Add(_("Forward zooms out"));
3719     mousewheelChoices.Add(_("Forward zooms in"));
3720     wxChoice* choice4 = new wxChoice(panel, PREF_MOUSE_WHEEL, wxDefaultPosition, wxDefaultSize,
3721                                      mousewheelChoices);
3722 
3723     wxBoxSizer* hbox4 = new wxBoxSizer(wxHORIZONTAL);
3724     hbox4->Add(wheelbox, 0, wxALIGN_CENTER_VERTICAL, 0);
3725     hbox4->Add(choice4, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, CHOICEGAP);
3726 
3727     // wheel_sensitivity
3728 
3729     wxBoxSizer* senslabel = new wxBoxSizer(wxHORIZONTAL);
3730     senslabel->Add(new wxStaticText(panel, wxID_STATIC, _("Wheel sensitivity:")),
3731                    0, wxALIGN_CENTER_VERTICAL, 0);
3732 
3733     senslabel->SetMinSize( longbox->GetMinSize() );
3734 
3735     wxBoxSizer* hbox7 = new wxBoxSizer(wxHORIZONTAL);
3736     hbox7->Add(senslabel, 0, wxALIGN_CENTER_VERTICAL, 0);
3737     wxSpinCtrl* spin4 = new MySpinCtrl(panel, PREF_SENSITIVITY, wxEmptyString,
3738                                        wxDefaultPosition, wxSize(70, wxDefaultCoord));
3739     hbox7->Add(spin4, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, SPINGAP);
3740 
3741     // thumb_range
3742 
3743     wxBoxSizer* thumblabel = new wxBoxSizer(wxHORIZONTAL);
3744     thumblabel->Add(new wxStaticText(panel, wxID_STATIC, _("Thumb scroll range:")),
3745                     0, wxALIGN_CENTER_VERTICAL, 0);
3746 
3747     thumblabel->SetMinSize( longbox->GetMinSize() );
3748 
3749     wxBoxSizer* hbox5 = new wxBoxSizer(wxHORIZONTAL);
3750     hbox5->Add(thumblabel, 0, wxALIGN_CENTER_VERTICAL, 0);
3751     wxSpinCtrl* spin5 = new MySpinCtrl(panel, PREF_THUMB_RANGE, wxEmptyString,
3752                                        wxDefaultPosition, wxSize(70, wxDefaultCoord));
3753     hbox5->Add(spin5, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, SPINGAP);
3754     hbox5->Add(new wxStaticText(panel, wxID_STATIC, _("times view size")),
3755                0, wxALIGN_CENTER_VERTICAL, 0);
3756 
3757     // controls_pos
3758 
3759     wxBoxSizer* posbox = new wxBoxSizer(wxHORIZONTAL);
3760     posbox->Add(new wxStaticText(panel, wxID_STATIC, _("Translucent buttons:")), 0, wxALL, 0);
3761 
3762     // align by setting posbox same width as longbox
3763     posbox->SetMinSize( longbox->GetMinSize() );
3764 
3765     wxArrayString posChoices;
3766     posChoices.Add(_("Disabled"));
3767     posChoices.Add(_("Top left corner"));
3768     posChoices.Add(_("Top right corner"));
3769     posChoices.Add(_("Bottom right corner"));
3770     posChoices.Add(_("Bottom left corner"));
3771     wxChoice* choice5 = new wxChoice(panel, PREF_CONTROLS, wxDefaultPosition, wxDefaultSize,
3772                                      posChoices);
3773 
3774     wxBoxSizer* hbox6 = new wxBoxSizer(wxHORIZONTAL);
3775     hbox6->Add(posbox, 0, wxALIGN_CENTER_VERTICAL, 0);
3776     hbox6->Add(choice5, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, CHOICEGAP);
3777 
3778     // position things
3779     vbox->AddSpacer(5);
3780 #if wxUSE_TOOLTIPS
3781     vbox->Add(check3, 0, wxLEFT | wxRIGHT, LRGAP);
3782     vbox->AddSpacer(CH2VGAP + 3);
3783 #endif
3784     vbox->Add(check4, 0, wxLEFT | wxRIGHT, LRGAP);
3785     vbox->AddSpacer(CH2VGAP + 3);
3786     vbox->Add(check1, 0, wxLEFT | wxRIGHT, LRGAP);
3787     vbox->AddSpacer(SVGAP);
3788     vbox->Add(check5, 0, wxLEFT | wxRIGHT, LRGAP);
3789     vbox->AddSpacer(SVGAP);
3790     vbox->Add(hbox2, 0, wxLEFT | wxRIGHT, LRGAP);
3791     vbox->AddSpacer(SVGAP);
3792 #ifdef __WXMAC__
3793     vbox->AddSpacer(10);
3794 #endif
3795     vbox->Add(hbox3, 0, wxLEFT | wxRIGHT, LRGAP);
3796     vbox->AddSpacer(CVGAP);
3797     vbox->Add(hbox4, 0, wxLEFT | wxRIGHT, LRGAP);
3798     vbox->AddSpacer(SVGAP);
3799     vbox->Add(hbox7, 0, wxLEFT | wxRIGHT, LRGAP);
3800     vbox->AddSpacer(SVGAP);
3801     vbox->Add(hbox5, 0, wxLEFT | wxRIGHT, LRGAP);
3802     vbox->AddSpacer(SVGAP);
3803     vbox->Add(hbox6, 0, wxLEFT | wxRIGHT, LRGAP);
3804 
3805     // init control values
3806 #if wxUSE_TOOLTIPS
3807     check3->SetValue(showtips);
3808 #endif
3809     check4->SetValue(restoreview);
3810     check1->SetValue(mathcoords);
3811     check5->SetValue(cellborders);
3812     check2->SetValue(showboldlines);
3813     spin4->SetRange(1, MAX_SENSITIVITY); spin4->SetValue(wheelsens);
3814     spin5->SetRange(2, MAX_THUMBRANGE);  spin5->SetValue(thumbrange);
3815     spin2->SetRange(2, MAX_SPACING);     spin2->SetValue(boldspacing);
3816     spin2->Enable(showboldlines);
3817     if (showboldlines) {
3818         spin2->SetFocus();
3819         spin2->SetSelection(ALL_TEXT);
3820     } else {
3821         spin4->SetFocus();
3822         spin4->SetSelection(ALL_TEXT);
3823     }
3824     mingridindex = mingridmag - 2;
3825     choice3->SetSelection(mingridindex);
3826     choice4->SetSelection(mousewheelmode);
3827     choice5->SetSelection(controlspos);
3828 
3829     topSizer->Add(vbox, 1, wxGROW | wxLEFT | wxALL, 5);
3830     panel->SetSizer(topSizer);
3831     topSizer->Fit(panel);
3832     return panel;
3833 }
3834 
3835 // -----------------------------------------------------------------------------
3836 
CreateLayerPrefs(wxWindow * parent)3837 wxPanel* PrefsDialog::CreateLayerPrefs(wxWindow* parent)
3838 {
3839     wxPanel* panel = new wxPanel(parent, wxID_ANY);
3840     wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
3841     wxBoxSizer* vbox = new wxBoxSizer(wxVERTICAL);
3842 
3843     // opacity
3844 
3845     wxBoxSizer* opacitybox = new wxBoxSizer(wxHORIZONTAL);
3846     opacitybox->Add(new wxStaticText(panel, wxID_STATIC,
3847                                      _("Opacity percentage when drawing stacked layers:")),
3848                     0, wxALIGN_CENTER_VERTICAL, 0);
3849     wxSpinCtrl* spin1 = new MySpinCtrl(panel, PREF_OPACITY, wxEmptyString,
3850                                        wxDefaultPosition, wxSize(70, wxDefaultCoord));
3851     opacitybox->Add(spin1, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, SPINGAP);
3852 
3853     // tile_border
3854 
3855     wxBoxSizer* borderbox = new wxBoxSizer(wxHORIZONTAL);
3856     borderbox->Add(new wxStaticText(panel, wxID_STATIC,
3857                                     _("Border thickness for tiled layers:")),
3858                    0, wxALIGN_CENTER_VERTICAL, 0);
3859     wxSpinCtrl* spin2 = new MySpinCtrl(panel, PREF_TILE_BORDER, wxEmptyString,
3860                                        wxDefaultPosition, wxSize(70, wxDefaultCoord));
3861     borderbox->Add(spin2, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, SPINGAP);
3862 
3863     // ask_on_new, ask_on_load, ask_on_delete, ask_on_quit, warn_on_save
3864 
3865     wxStaticBox* sbox1 = new wxStaticBox(panel, wxID_ANY, _("Ask to save changes to layer before:"));
3866     wxBoxSizer* ssizer1 = new wxStaticBoxSizer(sbox1, wxVERTICAL);
3867 
3868     wxCheckBox* check1 = new wxCheckBox(panel, PREF_ASK_NEW, _("Creating a new pattern"));
3869     wxCheckBox* check2 = new wxCheckBox(panel, PREF_ASK_LOAD, _("Opening a pattern file"));
3870     wxCheckBox* check3 = new wxCheckBox(panel, PREF_ASK_DELETE, _("Deleting layer"));
3871     wxCheckBox* check4 = new wxCheckBox(panel, PREF_ASK_QUIT, _("Quitting application"));
3872     wxCheckBox* check5 = new wxCheckBox(panel, PREF_WARN_SAVE, _("Warn if saving non-starting generation"));
3873 
3874     wxBoxSizer* b1 = new wxBoxSizer(wxHORIZONTAL);
3875     wxBoxSizer* b2 = new wxBoxSizer(wxHORIZONTAL);
3876     wxBoxSizer* b3 = new wxBoxSizer(wxHORIZONTAL);
3877     wxBoxSizer* b4 = new wxBoxSizer(wxHORIZONTAL);
3878     b1->Add(check1, 0, wxALL, 0);
3879     b2->Add(check2, 0, wxALL, 0);
3880     b3->Add(check3, 0, wxALL, 0);
3881     b4->Add(check4, 0, wxALL, 0);
3882     wxSize wd1 = b1->GetMinSize();
3883     wxSize wd2 = b2->GetMinSize();
3884     wxSize wd3 = b3->GetMinSize();
3885     wxSize wd4 = b4->GetMinSize();
3886     if (wd1.GetWidth() > wd2.GetWidth())
3887         b2->SetMinSize(wd1);
3888     else
3889         b1->SetMinSize(wd2);
3890     if (wd3.GetWidth() > wd4.GetWidth())
3891         b4->SetMinSize(wd3);
3892     else
3893         b3->SetMinSize(wd4);
3894 
3895     wxBoxSizer* hbox1 = new wxBoxSizer(wxHORIZONTAL);
3896     hbox1->Add(b1, 0, wxLEFT | wxRIGHT, LRGAP);
3897     hbox1->AddStretchSpacer(20);
3898     hbox1->Add(b3, 0, wxLEFT | wxRIGHT, LRGAP);
3899     hbox1->AddStretchSpacer(20);
3900 
3901     wxBoxSizer* hbox2 = new wxBoxSizer(wxHORIZONTAL);
3902     hbox2->Add(b2, 0, wxLEFT | wxRIGHT, LRGAP);
3903     hbox2->AddStretchSpacer(20);
3904     hbox2->Add(b4, 0, wxLEFT | wxRIGHT, LRGAP);
3905     hbox2->AddStretchSpacer(20);
3906 
3907     ssizer1->AddSpacer(SBTOPGAP);
3908     ssizer1->Add(hbox1, 1, wxGROW | wxLEFT | wxRIGHT, LRGAP);
3909     ssizer1->AddSpacer(CH2VGAP);
3910     ssizer1->Add(hbox2, 1, wxGROW | wxLEFT | wxRIGHT, LRGAP);
3911     ssizer1->AddSpacer(SBBOTGAP);
3912 
3913     // position things
3914     vbox->AddSpacer(SVGAP);
3915     vbox->Add(opacitybox, 0, wxLEFT | wxRIGHT, LRGAP);
3916     vbox->AddSpacer(S2VGAP);
3917     vbox->Add(borderbox, 0, wxLEFT | wxRIGHT, LRGAP);
3918     vbox->AddSpacer(GROUPGAP);
3919     vbox->Add(ssizer1, 0, wxGROW | wxALL, 2);
3920     vbox->AddSpacer(GROUPGAP);
3921     vbox->Add(check5, 0, wxLEFT | wxRIGHT, LRGAP);
3922 
3923     // init control values
3924     spin1->SetRange(1, 100);
3925     spin1->SetValue(opacity);
3926 
3927     spin2->SetRange(1, 10);
3928     spin2->SetValue(tileborder);
3929 
3930     spin1->SetFocus();
3931     spin1->SetSelection(ALL_TEXT);
3932 
3933     check1->SetValue(askonnew);
3934     check2->SetValue(askonload);
3935     check3->SetValue(askondelete);
3936     check4->SetValue(askonquit);
3937     check5->SetValue(warn_on_save);
3938 
3939     topSizer->Add(vbox, 1, wxGROW | wxLEFT | wxALL, 5);
3940     panel->SetSizer(topSizer);
3941     topSizer->Fit(panel);
3942     return panel;
3943 }
3944 
3945 // -----------------------------------------------------------------------------
3946 
AddColorButton(wxWindow * parent,wxBoxSizer * hbox,int id,wxColor * rgb,const wxString & text)3947 wxBitmapButton* PrefsDialog::AddColorButton(wxWindow* parent, wxBoxSizer* hbox,
3948                                             int id, wxColor* rgb, const wxString& text)
3949 {
3950     wxBitmap bitmap(BITMAP_WD, BITMAP_HT);
3951     wxMemoryDC dc;
3952     dc.SelectObject(bitmap);
3953     wxRect rect(0, 0, BITMAP_WD, BITMAP_HT);
3954     wxBrush brush(*rgb);
3955     FillRect(dc, rect, brush);
3956     dc.SelectObject(wxNullBitmap);
3957 
3958     wxBitmapButton* bb = new wxBitmapButton(parent, id, bitmap, wxPoint(0,0),
3959 #if defined(__WXOSX_COCOA__)
3960                                             wxSize(BITMAP_WD + 12, BITMAP_HT + 12));
3961 #else
3962                                             wxDefaultSize);
3963 #endif
3964     if (bb) {
3965         hbox->Add(new wxStaticText(parent, wxID_STATIC, text), 0, wxALIGN_CENTER_VERTICAL, 0);
3966         hbox->Add(bb, 0, wxALIGN_CENTER_VERTICAL, 0);
3967     }
3968     return bb;
3969 }
3970 
3971 // -----------------------------------------------------------------------------
3972 
CreateColorPrefs(wxWindow * parent)3973 wxPanel* PrefsDialog::CreateColorPrefs(wxWindow* parent)
3974 {
3975     wxPanel* panel = new wxPanel(parent, wxID_ANY);
3976     wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
3977     wxBoxSizer* vbox = new wxBoxSizer(wxVERTICAL);
3978 
3979     // create a choice menu to select algo
3980     wxArrayString algoChoices;
3981     for (int i = 0; i < NumAlgos(); i++) {
3982         algoChoices.Add( wxString(GetAlgoName(i), wxConvLocal) );
3983     }
3984     wxChoice* algomenu = new wxChoice(panel, PREF_ALGO_MENU2,
3985                                       wxDefaultPosition, wxDefaultSize, algoChoices);
3986     coloralgo = currlayer->algtype;
3987     algomenu->SetSelection(coloralgo);
3988 
3989     // create bitmap buttons
3990     wxBoxSizer* statusbox = new wxBoxSizer(wxHORIZONTAL);
3991     wxBoxSizer* frombox = new wxBoxSizer(wxHORIZONTAL);
3992     wxBoxSizer* tobox = new wxBoxSizer(wxHORIZONTAL);
3993     wxBoxSizer* colorbox = new wxBoxSizer(wxHORIZONTAL);
3994     AddColorButton(panel, statusbox, PREF_STATUS_BUTT,
3995                    &algoinfo[coloralgo]->statusrgb, _("Status bar: "));
3996     frombutt = AddColorButton(panel, frombox, PREF_FROM_BUTT, &algoinfo[coloralgo]->fromrgb, _(""));
3997     tobutt = AddColorButton(panel, tobox, PREF_TO_BUTT, &algoinfo[coloralgo]->torgb, _(" to "));
3998     AddColorButton(panel, colorbox, PREF_SELECT_BUTT, selectrgb, _("Selection: "));
3999     // don't use AddSpacer(20) because that will also add 20 *vertical* units!
4000     colorbox->AddSpacer(10);
4001     colorbox->AddSpacer(10);
4002     AddColorButton(panel, colorbox, PREF_PASTE_BUTT, pastergb, _("Paste: "));
4003     // don't use AddSpacer(20) because that will also add 20 *vertical* units!
4004     colorbox->AddSpacer(10);
4005     colorbox->AddSpacer(10);
4006     AddColorButton(panel, colorbox, PREF_BORDER_BUTT, borderrgb, _("Grid border: "));
4007 
4008     wxBoxSizer* algobox = new wxBoxSizer(wxHORIZONTAL);
4009     wxBoxSizer* algolabel = new wxBoxSizer(wxHORIZONTAL);
4010     algolabel->Add(new wxStaticText(panel, wxID_STATIC, _("Default colors for:")), 0, wxALL, 0);
4011     algobox->Add(algolabel, 0, wxALIGN_CENTER_VERTICAL, 0);
4012     algobox->Add(algomenu, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 6);
4013     algobox->AddStretchSpacer();
4014     algobox->Add(statusbox, 0, wxALIGN_CENTER_VERTICAL | wxALL, 0);
4015     algobox->AddStretchSpacer();
4016 
4017     gradcheck = new wxCheckBox(panel, PREF_GRADIENT_CHECK, _("Use gradient from "));
4018     gradcheck->SetValue(algoinfo[coloralgo]->gradient);
4019 
4020     wxBoxSizer* gradbox = new wxBoxSizer(wxHORIZONTAL);
4021     gradbox->Add(gradcheck, 0, wxALIGN_CENTER_VERTICAL, 0);
4022     gradbox->Add(frombox, 0, wxALIGN_CENTER_VERTICAL, 0);
4023     gradbox->Add(tobox, 0, wxALIGN_CENTER_VERTICAL, 0);
4024     gradbox->AddSpacer(10);
4025 
4026     // create scroll bar filling right part of gradbox
4027     wxSize minsize = gradbox->GetMinSize();
4028     int scrollbarwd = NUMCOLS*CELLSIZE+1 - minsize.GetWidth();
4029 #ifdef __WXMAC__
4030     int scrollbarht = 15;   // must be this height on Mac
4031 #else
4032     int scrollbarht = 16;
4033 #endif
4034     scrollbar = new wxScrollBar(panel, PREF_SCROLL_BAR, wxDefaultPosition,
4035                                 wxSize(scrollbarwd, scrollbarht), wxSB_HORIZONTAL);
4036     if (scrollbar == NULL) Fatal(_("Failed to create scroll bar!"));
4037     gradbox->Add(scrollbar, 0, wxALIGN_CENTER_VERTICAL, 0);
4038 
4039     gradstates = algoinfo[coloralgo]->maxstates;
4040     UpdateScrollBar();
4041     scrollbar->Enable(algoinfo[coloralgo]->gradient);
4042     frombutt->Enable(algoinfo[coloralgo]->gradient);
4043     tobutt->Enable(algoinfo[coloralgo]->gradient);
4044 
4045     // create child window for displaying cell colors/icons
4046     cellboxes = new CellBoxes(panel, PREF_CELL_PANEL, wxPoint(0,0),
4047                               wxSize(NUMCOLS*CELLSIZE+1,NUMROWS*CELLSIZE+1));
4048 
4049     iconcheck = new wxCheckBox(panel, PREF_ICON_CHECK, _("Show icons"));
4050     iconcheck->SetValue(showicons);
4051 
4052     wxStaticText* statebox = new wxStaticText(panel, PREF_STATE_BOX, _("999"));
4053     cellboxes->statebox = statebox;
4054     wxBoxSizer* hbox1 = new wxBoxSizer(wxHORIZONTAL);
4055     hbox1->Add(statebox, 0, 0, 0);
4056     hbox1->SetMinSize( hbox1->GetMinSize() );
4057 
4058     wxStaticText* rgbbox = new wxStaticText(panel, PREF_RGB_BOX, _("999,999,999"));
4059     cellboxes->rgbbox = rgbbox;
4060     wxBoxSizer* hbox2 = new wxBoxSizer(wxHORIZONTAL);
4061     hbox2->Add(rgbbox, 0, 0, 0);
4062     hbox2->SetMinSize( hbox2->GetMinSize() );
4063 
4064     statebox->SetLabel(_(" "));
4065     rgbbox->SetLabel(_(" "));
4066 
4067     wxBoxSizer* botbox = new wxBoxSizer(wxHORIZONTAL);
4068     botbox->Add(new wxStaticText(panel, wxID_STATIC, _("State: ")), 0, wxALIGN_CENTER_VERTICAL, 0);
4069     botbox->Add(hbox1, 0, wxALIGN_CENTER_VERTICAL, 0);
4070     botbox->Add(20, 0, 0);
4071     botbox->Add(new wxStaticText(panel, wxID_STATIC, _("RGB: ")), 0, wxALIGN_CENTER_VERTICAL, 0);
4072     botbox->Add(hbox2, 0, wxALIGN_CENTER_VERTICAL, 0);
4073     botbox->AddStretchSpacer();
4074     botbox->Add(iconcheck, 0, wxALIGN_CENTER_VERTICAL, 0);
4075 
4076     //!!! avoid wxMac bug -- can't click on bitmap buttons inside wxStaticBoxSizer
4077     //!!! wxStaticBox* sbox1 = new wxStaticBox(panel, wxID_ANY, _("Cell colors:"));
4078     //!!! wxBoxSizer* ssizer1 = new wxStaticBoxSizer(sbox1, wxVERTICAL);
4079     wxBoxSizer* ssizer1 = new wxBoxSizer(wxVERTICAL);
4080 
4081     ssizer1->AddSpacer(10);
4082     ssizer1->Add(gradbox, 0, wxLEFT | wxRIGHT, 0);
4083     ssizer1->AddSpacer(8);
4084     ssizer1->Add(cellboxes, 0, wxLEFT | wxRIGHT, 0);
4085     ssizer1->AddSpacer(8);
4086     ssizer1->Add(botbox, 1, wxGROW | wxLEFT | wxRIGHT, 0);
4087     ssizer1->AddSpacer(SBBOTGAP);
4088 
4089     wxStaticText* sbox2 = new wxStaticText(panel, wxID_STATIC, _("Global colors used by all algorithms:"));
4090     wxBoxSizer* ssizer2 = new wxBoxSizer(wxVERTICAL);
4091 
4092     ssizer2->Add(sbox2, 0, 0, 0);
4093     ssizer2->AddSpacer(10);
4094     ssizer2->Add(colorbox, 1, wxGROW | wxLEFT | wxRIGHT, 0);
4095 
4096     vbox->AddSpacer(5);
4097     vbox->Add(algobox, 1, wxGROW | wxLEFT | wxRIGHT, LRGAP);
4098     vbox->Add(ssizer1, 0, wxGROW | wxLEFT | wxRIGHT, LRGAP);
4099     vbox->AddSpacer(15);
4100     vbox->Add(ssizer2, 0, wxGROW | wxLEFT | wxRIGHT, LRGAP);
4101     vbox->AddSpacer(2);
4102 
4103     topSizer->Add(vbox, 1, wxGROW | wxLEFT | wxALL, 5);
4104     panel->SetSizer(topSizer);
4105     topSizer->Fit(panel);
4106     return panel;
4107 }
4108 
4109 // -----------------------------------------------------------------------------
4110 
CreateKeyboardPrefs(wxWindow * parent)4111 wxPanel* PrefsDialog::CreateKeyboardPrefs(wxWindow* parent)
4112 {
4113     wxPanel* panel = new wxPanel(parent, wxID_ANY);
4114     wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
4115     wxBoxSizer* vbox = new wxBoxSizer(wxVERTICAL);
4116 
4117     // make sure this is the first control added so it gets focus on a page change
4118     KeyComboCtrl* keycombo =
4119     new KeyComboCtrl(panel, PREF_KEYCOMBO, wxEmptyString,
4120                      wxDefaultPosition, wxSize(230, wxDefaultCoord),
4121                      wxTE_CENTER
4122                      | wxTE_PROCESS_TAB
4123                      | wxTE_PROCESS_ENTER   // so enter key won't select OK on Windows
4124 #ifdef __WXOSX__
4125                      // avoid wxTE_RICH2 otherwise we see scroll bar
4126                      );
4127 #else
4128                      | wxTE_RICH2 );        // better for Windows
4129 #endif
4130 
4131     wxArrayString actionChoices;
4132     for (int i = 0; i < MAX_ACTIONS; i++) {
4133         actionChoices.Add( wxString(GetActionName((action_id) i), wxConvLocal) );
4134     }
4135     actionChoices[DO_OPENFILE] = _("Open Chosen File");
4136     wxChoice* actionmenu = new wxChoice(panel, PREF_ACTION,
4137                                         wxDefaultPosition, wxDefaultSize, actionChoices);
4138 
4139     wxBoxSizer* hbox0 = new wxBoxSizer(wxHORIZONTAL);
4140     hbox0->Add(new wxStaticText(panel, wxID_STATIC,
4141                                 _("Type a key combination, then select the desired action:")));
4142 
4143     wxBoxSizer* keybox = new wxBoxSizer(wxVERTICAL);
4144     keybox->Add(new wxStaticText(panel, wxID_STATIC, _("Key Combination")), 0, wxALIGN_CENTER, 0);
4145     keybox->AddSpacer(5);
4146     keybox->Add(keycombo, 0, wxALIGN_CENTER, 0);
4147 
4148     wxBoxSizer* actbox = new wxBoxSizer(wxVERTICAL);
4149     actbox->Add(new wxStaticText(panel, wxID_STATIC, _("Action")), 0, wxALIGN_CENTER, 0);
4150     actbox->AddSpacer(5);
4151     actbox->Add(actionmenu, 0, wxALIGN_CENTER, 0);
4152 
4153     wxBoxSizer* hbox1 = new wxBoxSizer(wxHORIZONTAL);
4154     hbox1->Add(keybox, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, LRGAP);
4155     hbox1->AddSpacer(15);
4156     hbox1->Add(actbox, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, LRGAP);
4157 
4158     wxButton* choose = new wxButton(panel, PREF_CHOOSE, _("Choose File..."));
4159     wxStaticText* filebox = new wxStaticText(panel, PREF_FILE_BOX, wxEmptyString);
4160 
4161     wxBoxSizer* hbox2 = new wxBoxSizer(wxHORIZONTAL);
4162     hbox2->Add(choose, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, LRGAP);
4163     hbox2->Add(filebox, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, LRGAP);
4164 
4165     wxBoxSizer* midbox = new wxBoxSizer(wxVERTICAL);
4166     midbox->Add(hbox1, 0, wxLEFT | wxRIGHT, LRGAP);
4167     midbox->AddSpacer(15);
4168     midbox->Add(hbox2, 0, wxLEFT, LRGAP);
4169 
4170     wxString notes = _("Note:");
4171     notes += _("\n- Different key combinations can be assigned to the same action.");
4172     notes += _("\n- The Escape key is reserved for hard-wired actions.");
4173     wxBoxSizer* hbox3 = new wxBoxSizer(wxHORIZONTAL);
4174     hbox3->Add(new wxStaticText(panel, wxID_STATIC, notes));
4175 
4176     vbox->AddSpacer(5);
4177     vbox->Add(hbox0, 0, wxLEFT, LRGAP);
4178     vbox->AddSpacer(15);
4179     vbox->Add(midbox, 0, wxALIGN_CENTER, 0);
4180     vbox->AddSpacer(30);
4181     vbox->Add(hbox3, 0, wxLEFT, LRGAP);
4182 
4183     // initialize controls
4184     keycombo->ChangeValue( GetKeyCombo(currkey, currmods) );
4185     actionmenu->SetSelection( keyaction[currkey][currmods].id );
4186     UpdateChosenFile();
4187     keycombo->SetFocus();
4188     keycombo->SetSelection(ALL_TEXT);
4189 
4190     topSizer->Add(vbox, 1, wxGROW | wxLEFT | wxALL, 5);
4191     panel->SetSizer(topSizer);
4192     topSizer->Fit(panel);
4193     return panel;
4194 }
4195 
4196 // -----------------------------------------------------------------------------
4197 
UpdateChosenFile()4198 void PrefsDialog::UpdateChosenFile()
4199 {
4200     wxStaticText* filebox = (wxStaticText*) FindWindowById(PREF_FILE_BOX);
4201     if (filebox) {
4202         action_id action = keyaction[currkey][currmods].id;
4203         if (action == DO_OPENFILE) {
4204             // display current file name
4205             filebox->SetLabel(keyaction[currkey][currmods].file);
4206         } else {
4207             // clear file name; don't set keyaction[currkey][currmods].file empty
4208             // here because user might change their mind (TransferDataFromWindow
4209             // will eventually set the file empty)
4210             filebox->SetLabel(wxEmptyString);
4211         }
4212     }
4213 }
4214 
4215 // -----------------------------------------------------------------------------
4216 
OnChoice(wxCommandEvent & event)4217 void PrefsDialog::OnChoice(wxCommandEvent& event)
4218 {
4219     int id = event.GetId();
4220 
4221     if ( id == PREF_ACTION ) {
4222         int i = event.GetSelection();
4223         if (i >= 0 && i < MAX_ACTIONS) {
4224             action_id action = (action_id) i;
4225             keyaction[currkey][currmods].id = action;
4226 
4227             if ( action == DO_OPENFILE && keyaction[currkey][currmods].file.IsEmpty() ) {
4228                 // call OnButton (which will call UpdateChosenFile)
4229                 wxCommandEvent buttevt(wxEVT_COMMAND_BUTTON_CLICKED, PREF_CHOOSE);
4230                 OnButton(buttevt);
4231             } else {
4232                 UpdateChosenFile();
4233             }
4234         }
4235     }
4236 
4237     else if ( id == PREF_ALGO_MENU1 ) {
4238         int i = event.GetSelection();
4239         if (i >= 0 && i < NumAlgos() && i != algopos1) {
4240             // first update values for previous selection
4241             new_algomem[algopos1] = GetSpinVal(PREF_MAX_MEM);
4242             new_defbase[algopos1] = GetSpinVal(PREF_BASE_STEP);
4243             algopos1 = i;
4244 
4245             // show values for new selection
4246             wxSpinCtrl* s1 = (wxSpinCtrl*) FindWindowById(PREF_MAX_MEM);
4247             wxSpinCtrl* s2 = (wxSpinCtrl*) FindWindowById(PREF_BASE_STEP);
4248             if (s1 && s2) {
4249                 s1->SetValue(new_algomem[algopos1]);
4250                 s2->SetValue(new_defbase[algopos1]);
4251                 wxWindow* focus = FindFocus();
4252 #ifdef __WXMAC__
4253                 // FindFocus returns pointer to text ctrl
4254                 wxTextCtrl* t1 = s1->GetText();
4255                 wxTextCtrl* t2 = s2->GetText();
4256                 if (focus == t1) { s1->SetFocus(); s1->SetSelection(ALL_TEXT); }
4257                 if (focus == t2) { s2->SetFocus(); s2->SetSelection(ALL_TEXT); }
4258 #else
4259                 // probably pointless -- pop-up menu has focus on Win & Linux???
4260                 if (focus == s1) { s1->SetFocus(); s1->SetSelection(ALL_TEXT); }
4261                 if (focus == s2) { s2->SetFocus(); s2->SetSelection(ALL_TEXT); }
4262 #endif
4263             }
4264 
4265             // change comments depending on whether or not algo uses hashing
4266             wxStaticText* membox = (wxStaticText*) FindWindowById(PREF_MEM_NOTE);
4267             wxStaticText* stepbox = (wxStaticText*) FindWindowById(PREF_STEP_NOTE);
4268             if (membox && stepbox) {
4269                 if (algoinfo[algopos1]->canhash) {
4270                     membox->SetLabel(HASH_MEM_NOTE);
4271                     stepbox->SetLabel(HASH_STEP_NOTE);
4272                 } else {
4273                     membox->SetLabel(NONHASH_MEM_NOTE);
4274                     stepbox->SetLabel(NONHASH_STEP_NOTE);
4275                 }
4276             }
4277         }
4278     }
4279 
4280     else if ( id == PREF_ALGO_MENU2 ) {
4281         int i = event.GetSelection();
4282         if (i >= 0 && i < NumAlgos() && i != coloralgo) {
4283             coloralgo = i;
4284 
4285             AlgoData* ad = algoinfo[coloralgo];
4286 
4287             // update colors in some bitmap buttons
4288             UpdateButtonColor(PREF_STATUS_BUTT, ad->statusrgb);
4289             UpdateButtonColor(PREF_FROM_BUTT, ad->fromrgb);
4290             UpdateButtonColor(PREF_TO_BUTT, ad->torgb);
4291 
4292             gradstates = ad->maxstates;
4293             UpdateScrollBar();
4294 
4295             gradcheck->SetValue(ad->gradient);
4296             scrollbar->Enable(ad->gradient);
4297             frombutt->Enable(ad->gradient);
4298             tobutt->Enable(ad->gradient);
4299 
4300             cellboxes->Refresh(false);
4301         }
4302     }
4303 }
4304 
4305 // -----------------------------------------------------------------------------
4306 
ChooseTextEditor(wxWindow * parent,wxString & result)4307 void ChooseTextEditor(wxWindow* parent, wxString& result)
4308 {
4309     #ifdef __WXMSW__
4310         wxString filetypes = _("Applications (*.exe)|*.exe");
4311     #elif defined(__WXMAC__)
4312         wxString filetypes = _("Applications (*.app)|*.app");
4313     #else // assume Linux
4314         wxString filetypes = _("All files (*)|*");
4315     #endif
4316 
4317     wxFileDialog opendlg(parent, _("Choose a text editor"),
4318                          wxEmptyString, wxEmptyString, filetypes,
4319                          wxFD_OPEN | wxFD_FILE_MUST_EXIST);
4320 
4321     #ifdef __WXMSW__
4322         opendlg.SetDirectory(_("C:\\Program Files"));
4323     #elif defined(__WXMAC__)
4324         opendlg.SetDirectory(_("/Applications"));
4325     #else // assume Linux
4326         opendlg.SetDirectory(_("/usr/bin"));
4327     #endif
4328 
4329     if ( opendlg.ShowModal() == wxID_OK ) {
4330         result = opendlg.GetPath();
4331     } else {
4332         result = wxEmptyString;
4333     }
4334 }
4335 
4336 // -----------------------------------------------------------------------------
4337 
OnButton(wxCommandEvent & event)4338 void PrefsDialog::OnButton(wxCommandEvent& event)
4339 {
4340     int id = event.GetId();
4341 
4342     if ( id == PREF_CHOOSE ) {
4343         // ask user to choose an appropriate file
4344         wxString filetypes = _("All files (*)|*");
4345         filetypes +=         _("|Pattern (*.rle;*.mc;*.lif)|*.rle;*.mc;*.lif");
4346 #ifdef ENABLE_PERL
4347         filetypes +=         _("|Script (*.lua;*.pl;*.py)|*.lua;*.pl;*.py");
4348 #else
4349         filetypes +=         _("|Script (*.lua;*.py)|*.lua;*.py");
4350 #endif
4351         filetypes +=         _("|Rule (*.rule)|*.rule");
4352         filetypes +=         _("|HTML (*.html;*.htm)|*.html;*.htm");
4353 
4354         wxFileDialog opendlg(this, _("Choose a pattern/script/rule/HTML file"),
4355                              choosedir, wxEmptyString, filetypes,
4356                              wxFD_OPEN | wxFD_FILE_MUST_EXIST);
4357 
4358         if ( opendlg.ShowModal() == wxID_OK ) {
4359             wxFileName fullpath( opendlg.GetPath() );
4360             choosedir = fullpath.GetPath();
4361             wxString path = opendlg.GetPath();
4362             if (path.StartsWith(gollydir)) {
4363                 // remove gollydir from start of path
4364                 path.erase(0, gollydir.length());
4365             }
4366             keyaction[currkey][currmods].file = path;
4367             keyaction[currkey][currmods].id = DO_OPENFILE;
4368             wxChoice* actionmenu = (wxChoice*) FindWindowById(PREF_ACTION);
4369             if (actionmenu) {
4370                 actionmenu->SetSelection(DO_OPENFILE);
4371             }
4372         }
4373 
4374         UpdateChosenFile();
4375 
4376     } else if ( id == PREF_EDITOR_BUTT ) {
4377         // ask user to choose a text editor
4378         wxString result;
4379         ChooseTextEditor(this, result);
4380         if ( !result.IsEmpty() ) {
4381             neweditor = result;
4382             wxStaticText* editorbox = (wxStaticText*) FindWindowById(PREF_EDITOR_BOX);
4383             if (editorbox) {
4384                 editorbox->SetLabel(neweditor);
4385             }
4386         }
4387 
4388     } else if ( id == PREF_DOWNLOAD_BUTT ) {
4389         // ask user to choose folder for downloaded files
4390         wxDirDialog dirdlg(this, _("Choose a folder for downloaded files"),
4391                            newdownloaddir, wxDD_NEW_DIR_BUTTON);
4392         if ( dirdlg.ShowModal() == wxID_OK ) {
4393             wxString newdir = dirdlg.GetPath();
4394             if (newdir.Last() != wxFILE_SEP_PATH) newdir += wxFILE_SEP_PATH;
4395             if (newdownloaddir != newdir) {
4396                 newdownloaddir = newdir;
4397                 wxStaticText* dirbox = (wxStaticText*) FindWindowById(PREF_DOWNLOAD_BOX);
4398                 if (dirbox) {
4399                     dirbox->SetLabel(newdownloaddir);
4400                 }
4401             }
4402         }
4403 
4404     } else if ( id == PREF_RULES_BUTT ) {
4405         // ask user to choose folder for their rules
4406         wxDirDialog dirdlg(this, _("Choose a folder for your rules"),
4407                            newuserrules, wxDD_NEW_DIR_BUTTON);
4408         if ( dirdlg.ShowModal() == wxID_OK ) {
4409             wxString newdir = dirdlg.GetPath();
4410             if (newdir.Last() != wxFILE_SEP_PATH) newdir += wxFILE_SEP_PATH;
4411             if (newuserrules != newdir) {
4412                 newuserrules = newdir;
4413                 wxStaticText* dirbox = (wxStaticText*) FindWindowById(PREF_RULES_BOX);
4414                 if (dirbox) {
4415                     dirbox->SetLabel(newuserrules);
4416                 }
4417             }
4418         }
4419     }
4420 
4421     event.Skip();  // need this so other buttons work correctly
4422 }
4423 
4424 // -----------------------------------------------------------------------------
4425 
OnCheckBoxClicked(wxCommandEvent & event)4426 void PrefsDialog::OnCheckBoxClicked(wxCommandEvent& event)
4427 {
4428     int id = event.GetId();
4429 
4430     if ( id == PREF_SHOW_BOLD ) {
4431         // enable/disable PREF_BOLD_SPACING spin control
4432         wxCheckBox* checkbox = (wxCheckBox*) FindWindow(PREF_SHOW_BOLD);
4433         wxSpinCtrl* spinctrl = (wxSpinCtrl*) FindWindow(PREF_BOLD_SPACING);
4434         if (checkbox && spinctrl) {
4435             bool ticked = checkbox->GetValue();
4436             spinctrl->Enable(ticked);
4437             if (ticked) spinctrl->SetFocus();
4438         }
4439 
4440     } else if ( id == PREF_GRADIENT_CHECK ) {
4441         AlgoData* ad = algoinfo[coloralgo];
4442         ad->gradient = gradcheck->GetValue() == 1;
4443         scrollbar->Enable(ad->gradient);
4444         frombutt->Enable(ad->gradient);
4445         tobutt->Enable(ad->gradient);
4446         cellboxes->Refresh(false);
4447 
4448     } else if ( id == PREF_ICON_CHECK ) {
4449         showicons = iconcheck->GetValue() == 1;
4450         cellboxes->Refresh(false);
4451     }
4452 }
4453 
4454 // -----------------------------------------------------------------------------
4455 
UpdateButtonColor(int id,wxColor & rgb)4456 void PrefsDialog::UpdateButtonColor(int id, wxColor& rgb)
4457 {
4458     wxBitmapButton* bb = (wxBitmapButton*) FindWindow(id);
4459     if (bb) {
4460         wxBitmap bitmap(BITMAP_WD, BITMAP_HT);
4461         wxMemoryDC dc;
4462         dc.SelectObject(bitmap);
4463         wxRect rect(0, 0, BITMAP_WD, BITMAP_HT);
4464         wxBrush brush(rgb);
4465         FillRect(dc, rect, brush);
4466         dc.SelectObject(wxNullBitmap);
4467         bb->SetBitmapLabel(bitmap);
4468         bb->Refresh();
4469     }
4470 }
4471 
4472 // -----------------------------------------------------------------------------
4473 
ChangeButtonColor(int id,wxColor & rgb)4474 void PrefsDialog::ChangeButtonColor(int id, wxColor& rgb)
4475 {
4476     wxColourData data;
4477     data.SetChooseFull(true);    // for Windows
4478     data.SetColour(rgb);
4479 
4480     wxColourDialog dialog(this, &data);
4481     if ( dialog.ShowModal() == wxID_OK ) {
4482         wxColourData retData = dialog.GetColourData();
4483         wxColour c = retData.GetColour();
4484 
4485         if (rgb != c) {
4486             // change given color
4487             rgb.Set(c.Red(), c.Green(), c.Blue());
4488 
4489             // also change color of bitmap in corresponding button
4490             UpdateButtonColor(id, rgb);
4491 
4492             if (id == PREF_FROM_BUTT || id == PREF_TO_BUTT) {
4493                 cellboxes->Refresh(false);
4494             }
4495         }
4496     }
4497 }
4498 
4499 // -----------------------------------------------------------------------------
4500 
OnColorButton(wxCommandEvent & event)4501 void PrefsDialog::OnColorButton(wxCommandEvent& event)
4502 {
4503     int id = event.GetId();
4504 
4505     if ( id == PREF_STATUS_BUTT ) {
4506         ChangeButtonColor(id, algoinfo[coloralgo]->statusrgb);
4507 
4508     } else if ( id == PREF_FROM_BUTT ) {
4509         ChangeButtonColor(id, algoinfo[coloralgo]->fromrgb);
4510 
4511     } else if ( id == PREF_TO_BUTT ) {
4512         ChangeButtonColor(id, algoinfo[coloralgo]->torgb);
4513 
4514     } else if ( id == PREF_SELECT_BUTT ) {
4515         ChangeButtonColor(id, *selectrgb);
4516 
4517     } else if ( id == PREF_PASTE_BUTT ) {
4518         ChangeButtonColor(id, *pastergb);
4519 
4520     } else if ( id == PREF_BORDER_BUTT ) {
4521         ChangeButtonColor(id, *borderrgb);
4522 
4523     } else {
4524         // process other buttons like Cancel and OK
4525         event.Skip();
4526     }
4527 }
4528 
4529 // -----------------------------------------------------------------------------
4530 
UpdateScrollBar()4531 void PrefsDialog::UpdateScrollBar()
4532 {
4533     AlgoData* ad = algoinfo[coloralgo];
4534     scrollbar->SetScrollbar(gradstates - ad->minstates, 1,
4535                             ad->maxstates - ad->minstates + 1,  // range
4536                             PAGESIZE, true);
4537 }
4538 
4539 // -----------------------------------------------------------------------------
4540 
OnScroll(wxScrollEvent & event)4541 void PrefsDialog::OnScroll(wxScrollEvent& event)
4542 {
4543     WXTYPE type = event.GetEventType();
4544 
4545     if (type == wxEVT_SCROLL_LINEUP) {
4546         gradstates--;
4547         if (gradstates < algoinfo[coloralgo]->minstates)
4548             gradstates = algoinfo[coloralgo]->minstates;
4549         cellboxes->Refresh(false);
4550 
4551     } else if (type == wxEVT_SCROLL_LINEDOWN) {
4552         gradstates++;
4553         if (gradstates > algoinfo[coloralgo]->maxstates)
4554             gradstates = algoinfo[coloralgo]->maxstates;
4555         cellboxes->Refresh(false);
4556 
4557     } else if (type == wxEVT_SCROLL_PAGEUP) {
4558         gradstates -= PAGESIZE;
4559         if (gradstates < algoinfo[coloralgo]->minstates)
4560             gradstates = algoinfo[coloralgo]->minstates;
4561         cellboxes->Refresh(false);
4562 
4563     } else if (type == wxEVT_SCROLL_PAGEDOWN) {
4564         gradstates += PAGESIZE;
4565         if (gradstates > algoinfo[coloralgo]->maxstates)
4566             gradstates = algoinfo[coloralgo]->maxstates;
4567         cellboxes->Refresh(false);
4568 
4569     } else if (type == wxEVT_SCROLL_THUMBTRACK) {
4570         gradstates = algoinfo[coloralgo]->minstates + event.GetPosition();
4571         if (gradstates < algoinfo[coloralgo]->minstates)
4572             gradstates = algoinfo[coloralgo]->minstates;
4573         if (gradstates > algoinfo[coloralgo]->maxstates)
4574             gradstates = algoinfo[coloralgo]->maxstates;
4575         cellboxes->Refresh(false);
4576 
4577     } else if (type == wxEVT_SCROLL_THUMBRELEASE) {
4578         UpdateScrollBar();
4579     }
4580 }
4581 
4582 // -----------------------------------------------------------------------------
4583 
GetCheckVal(long id)4584 bool PrefsDialog::GetCheckVal(long id)
4585 {
4586     wxCheckBox* checkbox = (wxCheckBox*) FindWindow(id);
4587     if (checkbox) {
4588         return checkbox->GetValue();
4589     } else {
4590         Warning(_("Bug in GetCheckVal!"));
4591         return false;
4592     }
4593 }
4594 
4595 // -----------------------------------------------------------------------------
4596 
GetChoiceVal(long id)4597 int PrefsDialog::GetChoiceVal(long id)
4598 {
4599     wxChoice* choice = (wxChoice*) FindWindow(id);
4600     if (choice) {
4601         return choice->GetSelection();
4602     } else {
4603         Warning(_("Bug in GetChoiceVal!"));
4604         return 0;
4605     }
4606 }
4607 
4608 // -----------------------------------------------------------------------------
4609 
GetRadioVal(long firstid,int numbuttons)4610 int PrefsDialog::GetRadioVal(long firstid, int numbuttons)
4611 {
4612     for (int i = 0; i < numbuttons; i++) {
4613         wxRadioButton* radio = (wxRadioButton*) FindWindow(firstid + i);
4614         if (radio->GetValue()) return i;
4615     }
4616     Warning(_("Bug in GetRadioVal!"));
4617     return 0;
4618 }
4619 
4620 // -----------------------------------------------------------------------------
4621 
GetSpinVal(long id)4622 int PrefsDialog::GetSpinVal(long id)
4623 {
4624     wxSpinCtrl* spinctrl = (wxSpinCtrl*) FindWindow(id);
4625     if (spinctrl) {
4626         return spinctrl->GetValue();
4627     } else {
4628         Warning(_("Bug in GetSpinVal!"));
4629         return 0;
4630     }
4631 }
4632 
4633 // -----------------------------------------------------------------------------
4634 
BadSpinVal(int id,int minval,int maxval,const wxString & prefix)4635 bool PrefsDialog::BadSpinVal(int id, int minval, int maxval, const wxString& prefix)
4636 {
4637     wxSpinCtrl* spinctrl = (wxSpinCtrl*) FindWindow(id);
4638     // spinctrl->GetValue() always returns a value within range even if
4639     // the text ctrl doesn't contain a valid number -- yuk!
4640     int i = spinctrl->GetValue();
4641     if (i < minval || i > maxval) {
4642         wxString msg;
4643         msg.Printf(_("%s must be from %d to %d."), prefix.c_str(), minval, maxval);
4644         Warning(msg);
4645         spinctrl->SetFocus();
4646         spinctrl->SetSelection(ALL_TEXT);
4647         return true;
4648     } else {
4649         return false;
4650     }
4651 }
4652 
4653 // -----------------------------------------------------------------------------
4654 
ValidatePage()4655 bool PrefsDialog::ValidatePage()
4656 {
4657     // validate all spin control values on current page
4658     if (currpage == FILE_PAGE) {
4659         if ( BadSpinVal(PREF_MAX_PATTERNS, 1, MAX_RECENT, _("Maximum number of recent patterns")) )
4660             return false;
4661         if ( BadSpinVal(PREF_MAX_SCRIPTS, 1, MAX_RECENT, _("Maximum number of recent scripts")) )
4662             return false;
4663 
4664     } else if (currpage == EDIT_PAGE) {
4665         if ( BadSpinVal(PREF_RANDOM_FILL, 1, 100, _("Random fill percentage")) )
4666             return false;
4667 
4668     } else if (currpage == CONTROL_PAGE) {
4669         if ( BadSpinVal(PREF_MAX_MEM, MIN_MEM_MB, MAX_MEM_MB, _("Maximum memory")) )
4670             return false;
4671         if ( BadSpinVal(PREF_BASE_STEP, 2, MAX_BASESTEP, _("Default base step")) )
4672             return false;
4673         if ( BadSpinVal(PREF_MIN_DELAY, 0, MAX_DELAY, _("Minimum delay")) )
4674             return false;
4675         if ( BadSpinVal(PREF_MAX_DELAY, 0, MAX_DELAY, _("Maximum delay")) )
4676             return false;
4677 
4678     } else if (currpage == VIEW_PAGE) {
4679         if ( BadSpinVal(PREF_BOLD_SPACING, 2, MAX_SPACING, _("Spacing of bold grid lines")) )
4680             return false;
4681         if ( BadSpinVal(PREF_SENSITIVITY, 1, MAX_SENSITIVITY, _("Wheel sensitivity")) )
4682             return false;
4683         if ( BadSpinVal(PREF_THUMB_RANGE, 2, MAX_THUMBRANGE, _("Thumb scrolling range")) )
4684             return false;
4685 
4686     } else if (currpage == LAYER_PAGE) {
4687         if ( BadSpinVal(PREF_OPACITY, 1, 100, _("Percentage opacity")) )
4688             return false;
4689         if ( BadSpinVal(PREF_TILE_BORDER, 1, 10, _("Tile border thickness")) )
4690             return false;
4691 
4692     } else if (currpage == COLOR_PAGE) {
4693         // no spin ctrls on this page
4694 
4695     } else if (currpage == KEYBOARD_PAGE) {
4696         // no spin ctrls on this page
4697 
4698     } else {
4699         Warning(_("Bug in ValidatePage!"));
4700         return false;
4701     }
4702 
4703     return true;
4704 }
4705 
4706 // -----------------------------------------------------------------------------
4707 
OnPageChanging(wxNotebookEvent & event)4708 void PrefsDialog::OnPageChanging(wxNotebookEvent& event)
4709 {
4710     if (ignore_page_event) return;
4711     // validate current page and veto change if invalid
4712     if (!ValidatePage()) event.Veto();
4713 }
4714 
4715 // -----------------------------------------------------------------------------
4716 
OnPageChanged(wxNotebookEvent & event)4717 void PrefsDialog::OnPageChanged(wxNotebookEvent& event)
4718 {
4719     if (ignore_page_event) return;
4720     currpage = event.GetSelection();
4721 
4722 #ifdef __WXMSW__
4723     // ensure key combo box has focus
4724     if (currpage == KEYBOARD_PAGE) {
4725         KeyComboCtrl* keycombo = (KeyComboCtrl*) FindWindowById(PREF_KEYCOMBO);
4726         if (keycombo) {
4727             keycombo->SetFocus();
4728             keycombo->SetSelection(ALL_TEXT);
4729         }
4730     }
4731 #endif
4732 }
4733 
4734 // -----------------------------------------------------------------------------
4735 
TransferDataFromWindow()4736 bool PrefsDialog::TransferDataFromWindow()
4737 {
4738     if (!ValidatePage()) return false;
4739 
4740     // set global prefs to current control values
4741 
4742     // FILE_PAGE
4743     newremovesel  = GetCheckVal(PREF_NEW_REM_SEL);
4744     newcursindex  = GetChoiceVal(PREF_NEW_CURSOR);
4745     newmag        = GetChoiceVal(PREF_NEW_SCALE);
4746     openremovesel = GetCheckVal(PREF_OPEN_REM_SEL);
4747     opencursindex = GetChoiceVal(PREF_OPEN_CURSOR);
4748     maxpatterns   = GetSpinVal(PREF_MAX_PATTERNS);
4749     maxscripts    = GetSpinVal(PREF_MAX_SCRIPTS);
4750     texteditor    = neweditor;
4751     downloaddir   = newdownloaddir;
4752 
4753     // EDIT_PAGE
4754     randomfill    = GetSpinVal(PREF_RANDOM_FILL);
4755     canchangerule = GetRadioVal(PREF_PASTE_0, 3);
4756     scrollpencil  = GetCheckVal(PREF_SCROLL_PENCIL);
4757     scrollcross   = GetCheckVal(PREF_SCROLL_CROSS);
4758     scrollhand    = GetCheckVal(PREF_SCROLL_HAND);
4759     allowbeep     = GetCheckVal(PREF_BEEP);
4760 
4761     // CONTROL_PAGE
4762     new_algomem[algopos1] = GetSpinVal(PREF_MAX_MEM);
4763     new_defbase[algopos1] = GetSpinVal(PREF_BASE_STEP);
4764     for (int i = 0; i < NumAlgos(); i++) {
4765         algoinfo[i]->algomem = new_algomem[i];
4766         algoinfo[i]->defbase = new_defbase[i];
4767     }
4768     mindelay = GetSpinVal(PREF_MIN_DELAY);
4769     maxdelay = GetSpinVal(PREF_MAX_DELAY);
4770     userrules = newuserrules;
4771 
4772     // VIEW_PAGE
4773 #if wxUSE_TOOLTIPS
4774     showtips       = GetCheckVal(PREF_SHOW_TIPS);
4775     wxToolTip::Enable(showtips);
4776 #endif
4777     restoreview    = GetCheckVal(PREF_RESTORE);
4778     mathcoords     = GetCheckVal(PREF_Y_UP);
4779     cellborders    = GetCheckVal(PREF_CELL_BORDERS);
4780     showboldlines  = GetCheckVal(PREF_SHOW_BOLD);
4781     boldspacing    = GetSpinVal(PREF_BOLD_SPACING);
4782     mingridindex   = GetChoiceVal(PREF_MIN_GRID_SCALE);
4783     mousewheelmode = GetChoiceVal(PREF_MOUSE_WHEEL);
4784     wheelsens      = GetSpinVal(PREF_SENSITIVITY);
4785     thumbrange     = GetSpinVal(PREF_THUMB_RANGE);
4786     controlspos    = GetChoiceVal(PREF_CONTROLS);
4787 
4788     // LAYER_PAGE
4789     opacity         = GetSpinVal(PREF_OPACITY);
4790     tileborder      = GetSpinVal(PREF_TILE_BORDER);
4791     askonnew        = GetCheckVal(PREF_ASK_NEW);
4792     askonload       = GetCheckVal(PREF_ASK_LOAD);
4793     askondelete     = GetCheckVal(PREF_ASK_DELETE);
4794     askonquit       = GetCheckVal(PREF_ASK_QUIT);
4795     warn_on_save    = GetCheckVal(PREF_WARN_SAVE);
4796 
4797     // COLOR_PAGE
4798     // no need to validate anything
4799 
4800     // KEYBOARD_PAGE
4801     // go thru keyaction table and make sure the file field is empty
4802     // if the action isn't DO_OPENFILE
4803     for (int key = 0; key < MAX_KEYCODES; key++)
4804         for (int modset = 0; modset < MAX_MODS; modset++)
4805             if ( keyaction[key][modset].id != DO_OPENFILE &&
4806                 !keyaction[key][modset].file.IsEmpty() )
4807                 keyaction[key][modset].file = wxEmptyString;
4808 
4809     // update globals corresponding to some wxChoice menu selections
4810     mingridmag = mingridindex + 2;
4811     newcurs = IndexToCursor(newcursindex);
4812     opencurs = IndexToCursor(opencursindex);
4813 
4814     return true;
4815 }
4816 
4817 // -----------------------------------------------------------------------------
4818 
4819 // class for saving and restoring AlgoData color info in ChangePrefs()
4820 class SaveColorInfo {
4821 public:
SaveColorInfo(int algo)4822     SaveColorInfo(int algo) {
4823         AlgoData* ad = algoinfo[algo];
4824         statusrgb = ad->statusrgb;
4825         gradient = ad->gradient;
4826         fromrgb = ad->fromrgb;
4827         torgb = ad->torgb;
4828         for (int i = 0; i < ad->maxstates; i++) {
4829             algor[i] = ad->algor[i];
4830             algog[i] = ad->algog[i];
4831             algob[i] = ad->algob[i];
4832         }
4833     }
4834 
RestoreColorInfo(int algo)4835     void RestoreColorInfo(int algo) {
4836         AlgoData* ad = algoinfo[algo];
4837         ad->statusrgb = statusrgb;
4838         ad->gradient = gradient;
4839         ad->fromrgb = fromrgb;
4840         ad->torgb = torgb;
4841         for (int i = 0; i < ad->maxstates; i++) {
4842             ad->algor[i] = algor[i];
4843             ad->algog[i] = algog[i];
4844             ad->algob[i] = algob[i];
4845         }
4846     }
4847 
ColorInfoChanged(int algo)4848     bool ColorInfoChanged(int algo) {
4849         AlgoData* ad = algoinfo[algo];
4850         // ignore ad->statusrgb
4851         if (ad->gradient != gradient) return true;
4852         if (gradient && ad->fromrgb != fromrgb) return true;
4853         if (gradient && ad->torgb != torgb) return true;
4854         for (int i = 0; i < ad->maxstates; i++) {
4855             if (ad->algor[i] != algor[i]) return true;
4856             if (ad->algog[i] != algog[i]) return true;
4857             if (ad->algob[i] != algob[i]) return true;
4858         }
4859         // get here if there was no change
4860         return false;
4861     }
4862 
4863     // this must match color info in AlgoData
4864     wxColor statusrgb;
4865     bool gradient;
4866     wxColor fromrgb;
4867     wxColor torgb;
4868     unsigned char algor[256];
4869     unsigned char algog[256];
4870     unsigned char algob[256];
4871 };
4872 
4873 // -----------------------------------------------------------------------------
4874 
ChangePrefs(const wxString & page)4875 bool ChangePrefs(const wxString& page)
4876 {
4877     // save current keyboard shortcuts so we can restore them or detect a change
4878     action_info savekeyaction[MAX_KEYCODES][MAX_MODS];
4879     for (int key = 0; key < MAX_KEYCODES; key++)
4880         for (int modset = 0; modset < MAX_MODS; modset++)
4881             savekeyaction[key][modset] = keyaction[key][modset];
4882 
4883     bool wasswapped = swapcolors;
4884     if (swapcolors) {
4885         swapcolors = false;
4886         InvertCellColors();
4887         mainptr->UpdateEverything();
4888     }
4889 
4890     // save current color info so we can restore it if user cancels changes
4891     wxColor save_selectrgb = *selectrgb;
4892     wxColor save_pastergb = *pastergb;
4893     wxColor save_borderrgb = *borderrgb;
4894     SaveColorInfo* save_info[MAX_ALGOS];
4895     for (int i = 0; i < NumAlgos(); i++) {
4896         save_info[i] = new SaveColorInfo(i);
4897     }
4898 
4899     // save showicons option in case user cancels dialog
4900     bool saveshowicons = showicons;
4901 
4902     // save the default base step for the current layer's algo so we can detect a change
4903     int old_defbase = algoinfo[currlayer->algtype]->defbase;
4904 
4905     bool result;
4906     PrefsDialog dialog(mainptr, page);
4907     int button = dialog.ShowModal();
4908     viewptr->ResetMouseDown();
4909     if (button == wxID_OK) {
4910         // TransferDataFromWindow has validated and updated all global prefs;
4911         // if a keyboard shortcut changed then update menu item accelerators
4912         for (int key = 0; key < MAX_KEYCODES; key++)
4913             for (int modset = 0; modset < MAX_MODS; modset++)
4914                 if (savekeyaction[key][modset].id != keyaction[key][modset].id) {
4915                     // first update accelerator array
4916                     UpdateAcceleratorStrings();
4917                     mainptr->UpdateMenuAccelerators();
4918                     goto done;
4919                 }
4920         done:
4921 
4922         // if the default base step for the current layer's algo changed
4923         // then reset the current base step (this should result in less confusion)
4924         if (old_defbase != algoinfo[currlayer->algtype]->defbase) {
4925             currlayer->currbase = algoinfo[currlayer->algtype]->defbase;
4926             mainptr->SetGenIncrement();
4927         }
4928 
4929         // if the default colors/icons for the current layer's algo changed
4930         // then reset the current layer's colors (and any clones)
4931         if (save_info[currlayer->algtype]->ColorInfoChanged(currlayer->algtype)) {
4932             UpdateLayerColors();
4933         }
4934 
4935         result = true;
4936     } else {
4937         // user hit Cancel, so restore keyaction array in case it was changed
4938         for (int key = 0; key < MAX_KEYCODES; key++)
4939             for (int modset = 0; modset < MAX_MODS; modset++)
4940                 keyaction[key][modset] = savekeyaction[key][modset];
4941 
4942         // restore color info saved above
4943         *selectrgb = save_selectrgb;
4944         *pastergb = save_pastergb;
4945         *borderrgb = save_borderrgb;
4946         for (int i = 0; i < NumAlgos(); i++) {
4947             save_info[i]->RestoreColorInfo(i);
4948         }
4949 
4950         // restore showicons option
4951         showicons = saveshowicons;
4952 
4953         result = false;
4954     }
4955 
4956     UpdateStatusBrushes();
4957 
4958     for (int i = 0; i < NumAlgos(); i++) {
4959         delete save_info[i];
4960     }
4961 
4962     if (wasswapped) {
4963         swapcolors = true;
4964         InvertCellColors();
4965         // let caller do this
4966         // mainptr->UpdateEverything();
4967     }
4968 
4969     return result;
4970 }
4971