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(_("<"), _("<"));
861 }
862 result += _("<tr><td align=right>");
863 result += keystring;
864 result += _(" </td><td> ");
865 result += wxString(GetActionName(action.id), wxConvLocal);
866 if (action.id == DO_OPENFILE) {
867 result += _(" ");
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