1 // This file is part of Golly.
2 // See docs/License.html for the copyright notice.
3 
4 #include <string.h>        // for strlen and strcpy
5 
6 #include "wx/wxprec.h"     // for compilers that support precompilation
7 #ifndef WX_PRECOMP
8     #include "wx/wx.h"     // for all others include the necessary headers
9 #endif
10 
11 #include <limits.h>        // for INT_MAX
12 #include "wx/filename.h"   // for wxFileName
13 
14 #include "wxgolly.h"       // for wxGetApp, mainptr, viewptr, statusptr
15 #include "wxmain.h"        // for mainptr->...
16 #include "wxselect.h"      // for Selection
17 #include "wxedit.h"        // for ToggleEditBar, ToggleAllStates, UpdateEditBar
18 #include "wxview.h"        // for viewptr->...
19 #include "wxstatus.h"      // for statusptr->...
20 #include "wxutils.h"       // for Warning, Beep, IsScriptFile
21 #include "wxprefs.h"       // for gollydir, allowundo, etc
22 #include "wxundo.h"        // for undoredo->...
23 #include "wxalgos.h"       // for *_ALGO, algoinfo
24 #include "wxlayer.h"       // for currlayer, SyncClones
25 #include "wxtimeline.h"    // for TimelineExists, ToggleTimelineBar
26 #include "wxlua.h"         // for RunLuaScript, AbortLuaScript
27 #include "wxperl.h"        // for RunPerlScript, AbortPerlScript
28 #include "wxoverlay.h"     // for curroverlay
29 #include "wxscript.h"
30 
31 // =============================================================================
32 
33 // exported globals:
34 bool inscript = false;     // a script is running?
35 bool pass_key_events;      // pass keyboard events to script?
36 bool pass_mouse_events;    // pass mouse events to script?
37 bool pass_file_events;     // pass file events to script?
38 bool canswitch;            // can user switch layers while script is running?
39 bool stop_after_script;    // stop generating pattern after running script?
40 bool autoupdate;           // update display after each change to current universe?
41 bool allowcheck;           // allow event checking?
42 bool showprogress;         // script can display the progress dialog?
43 wxString scripterr;        // Lua/Perl/Python error message
44 wxString mousepos;         // current mouse position
45 wxString scripttitle;      // window title set by settitle command
46 wxString rle3path;         // path of .rle3 file to be sent to 3D.lua via GSF_getevent
47 
48 // local globals:
49 static bool luascript = false;      // a Lua script is running?
50 static bool plscript = false;       // a Perl script is running?
51 static bool showtitle;              // need to update window title?
52 static bool updateedit;             // need to update edit bar?
53 static bool exitcalled;             // GSF_exit was called?
54 static wxString scriptchars;        // non-escape chars saved by PassKeyToScript
55 static wxString scriptloc;          // location of script file
56 static wxArrayString eventqueue;    // FIFO queue for keyboard/mouse events
57 
58 // constants:
59 const int maxcomments = 128 * 1024; // maximum comment size
60 
61 // -----------------------------------------------------------------------------
62 
DoAutoUpdate()63 void DoAutoUpdate()
64 {
65     if (autoupdate && !mainptr->IsIconized()) {
66         inscript = false;
67         mainptr->UpdatePatternAndStatus(true);  // call Update()
68         if (showtitle) {
69             mainptr->SetWindowTitle(wxEmptyString);
70             showtitle = false;
71         }
72         inscript = true;
73 
74         #ifdef __WXGTK__
75             // needed on Linux to see update immediately
76             insideYield = true;
77             wxGetApp().Yield(true);
78             insideYield = false;
79         #endif
80     }
81 }
82 
83 // -----------------------------------------------------------------------------
84 
ShowTitleLater()85 void ShowTitleLater()
86 {
87     // called from SetWindowTitle when inscript is true;
88     // show title at next update (or at end of script)
89     showtitle = true;
90 }
91 
92 // -----------------------------------------------------------------------------
93 
ChangeWindowTitle(const wxString & name)94 void ChangeWindowTitle(const wxString& name)
95 {
96     if (autoupdate) {
97         // update title bar right now
98         inscript = false;
99         mainptr->SetWindowTitle(name);
100         inscript = true;
101         showtitle = false;       // update has been done
102     } else {
103         // show it later but must still update currlayer->currname and menu item
104         mainptr->SetWindowTitle(name);
105         // showtitle is now true
106     }
107 }
108 
109 // =============================================================================
110 
111 // The following Golly Script Functions are used to reduce code duplication.
112 // They are called by corresponding pl_* and py_* functions in wxperl.cpp
113 // and wxpython.cpp respectively.
114 
GSF_open(const wxString & filename,int remember)115 const char* GSF_open(const wxString& filename, int remember)
116 {
117     // convert non-absolute filename to absolute path relative to scriptloc
118     wxFileName fullname(filename);
119     if (!fullname.IsAbsolute()) fullname = scriptloc + filename;
120 
121     // return error message here if file doesn't exist
122     wxString fullpath = fullname.GetFullPath();
123     if (!wxFileName::FileExists(fullpath)) {
124         return "open error: given file does not exist.";
125     }
126 
127     // temporarily set pass_file_events to false so OpenFile won't pass
128     // a file event back to this script
129     bool savepass = pass_file_events;
130     pass_file_events = false;
131 
132     // only add file to Open Recent submenu if remember flag is non-zero
133     mainptr->OpenFile(fullpath, remember != 0);
134     DoAutoUpdate();
135 
136     // restore pass_file_events
137     pass_file_events = savepass;
138 
139     return NULL;
140 }
141 
142 // -----------------------------------------------------------------------------
143 
GSF_save(const wxString & filename,const char * format,int remember)144 const char* GSF_save(const wxString& filename, const char* format, int remember)
145 {
146     // convert non-absolute filename to absolute path relative to scriptloc
147     wxFileName fullname(filename);
148     if (!fullname.IsAbsolute()) fullname = scriptloc + filename;
149 
150     // only add file to Open Recent submenu if remember flag is non-zero
151     return mainptr->SaveFile(fullname.GetFullPath(), wxString(format,wxConvLocal), remember != 0);
152 }
153 
154 // -----------------------------------------------------------------------------
155 
GSF_setdir(const char * dirname,const wxString & newdir)156 const char* GSF_setdir(const char* dirname, const wxString& newdir)
157 {
158     wxString dirpath = newdir;
159     if (dirpath.Last() != wxFILE_SEP_PATH) dirpath += wxFILE_SEP_PATH;
160     if (!wxFileName::DirExists(dirpath)) {
161         return "New directory does not exist.";
162     }
163 
164     if (strcmp(dirname, "app") == 0) {
165         return "Application directory cannot be changed.";
166 
167     } else if (strcmp(dirname, "data") == 0) {
168         return "Data directory cannot be changed.";
169 
170     } else if (strcmp(dirname, "temp") == 0) {
171         return "Temporary directory cannot be changed.";
172 
173     } else if (strcmp(dirname, "rules") == 0) {
174         userrules = dirpath;
175 
176     } else if (strcmp(dirname, "files") == 0 ||
177                strcmp(dirname, "patterns") == 0) {  // deprecated
178         // change filedir and update panel if currently shown
179         mainptr->SetFileDir(dirpath);
180 
181     } else if (strcmp(dirname, "download") == 0) {
182         downloaddir = dirpath;
183 
184     } else {
185         return "Unknown directory name.";
186     }
187 
188     return NULL;   // success
189 }
190 
191 // -----------------------------------------------------------------------------
192 
GSF_getdir(const char * dirname)193 const char* GSF_getdir(const char* dirname)
194 {
195     wxString dirpath;
196 
197     if      (strcmp(dirname, "app") == 0)        dirpath = gollydir;
198     else if (strcmp(dirname, "data") == 0)       dirpath = datadir;
199     else if (strcmp(dirname, "temp") == 0)       dirpath = tempdir;
200     else if (strcmp(dirname, "rules") == 0)      dirpath = userrules;
201     else if (strcmp(dirname, "files") == 0)      dirpath = filedir;
202     else if (strcmp(dirname, "patterns") == 0)   dirpath = filedir;         // deprecated
203     else if (strcmp(dirname, "scripts") == 0)    dirpath = filedir;         // ditto
204     else if (strcmp(dirname, "download") == 0)   dirpath = downloaddir;
205     else {
206         return NULL;   // unknown directory name
207     }
208 
209     // make sure directory path ends with separator
210     if (dirpath.Last() != wxFILE_SEP_PATH) dirpath += wxFILE_SEP_PATH;
211 
212     // need to be careful converting Unicode wxString to char*
213     static wxCharBuffer dirbuff;
214     #ifdef __WXMAC__
215         // we need to convert dirpath to decomposed UTF8 so fopen will work
216         dirbuff = dirpath.fn_str();
217     #else
218         dirbuff = dirpath.mb_str(wxConvLocal);
219     #endif
220     return (const char*) dirbuff;
221 }
222 
223 // -----------------------------------------------------------------------------
224 
GSF_os()225 const char* GSF_os()
226 {
227     // return a string that specifies the current operating system
228     #if defined(__WXMSW__)
229         return "Windows";
230     #elif defined(__WXMAC__)
231         return "Mac";
232     #elif defined(__WXGTK__)
233         return "Linux";
234     #else
235         return "unknown";
236     #endif
237 }
238 
239 // -----------------------------------------------------------------------------
240 
GSF_setalgo(const char * algostring)241 const char* GSF_setalgo(const char* algostring)
242 {
243     // find index for given algo name
244     char* algoname = ReplaceDeprecatedAlgo((char*) algostring);
245     algo_type algoindex = -1;
246     for (int i = 0; i < NumAlgos(); i++) {
247         if (strcmp(algoname, GetAlgoName(i)) == 0) {
248             algoindex = i;
249             break;
250         }
251     }
252     if (algoindex < 0) return "Unknown algorithm.";
253 
254     if (algoindex != currlayer->algtype) {
255         mainptr->ChangeAlgorithm(algoindex);
256         if (algoindex != currlayer->algtype) {
257             return "Algorithm could not be changed (pattern is too big to convert).";
258         } else {
259             // rule might have changed
260             ChangeWindowTitle(wxEmptyString);
261             // pattern might have changed or colors might have changed
262             DoAutoUpdate();
263         }
264     }
265 
266     return NULL;
267 }
268 
269 // -----------------------------------------------------------------------------
270 
GSF_setrule(const char * rulestring)271 const char* GSF_setrule(const char* rulestring)
272 {
273     wxString oldrule = wxString(currlayer->algo->getrule(),wxConvLocal);
274     int oldmaxstate = currlayer->algo->NumCellStates() - 1;
275 
276     // selection might change if grid becomes smaller,
277     // so save current selection for RememberRuleChange/RememberAlgoChange
278     viewptr->SaveCurrentSelection();
279 
280     // inscript should be true but play safe
281     if (allowundo && !currlayer->stayclean && inscript) {
282         // note that we must save pending gen changes BEFORE changing rule
283         // otherwise temporary files will store incorrect rule info
284         SavePendingChanges();
285     }
286 
287     const char* err;
288     if (rulestring == NULL || rulestring[0] == 0) {
289         // set normal Life
290         err = currlayer->algo->setrule("B3/S23");
291     } else {
292         err = currlayer->algo->setrule(rulestring);
293     }
294     if (err) {
295         // try to find another algorithm that supports the new rule
296         for (int i = 0; i < NumAlgos(); i++) {
297             if (i != currlayer->algtype) {
298                 lifealgo* tempalgo = CreateNewUniverse(i);
299                 err = tempalgo->setrule(rulestring);
300                 delete tempalgo;
301                 if (!err) {
302                     // change the current algorithm and switch to the new rule
303                     mainptr->ChangeAlgorithm(i, wxString(rulestring,wxConvLocal));
304                     if (i != currlayer->algtype) {
305                         RestoreRule(oldrule);
306                         return "Algorithm could not be changed (pattern is too big to convert).";
307                     } else {
308                         ChangeWindowTitle(wxEmptyString);
309                         DoAutoUpdate();
310                         return NULL;
311                     }
312                 }
313             }
314         }
315         RestoreRule(oldrule);
316         return "Given rule is not valid in any algorithm.";
317     }
318 
319     // check if the rule string changed, or the number of states changed
320     // (the latter might happen if user edited a .rule file)
321     wxString newrule = wxString(currlayer->algo->getrule(),wxConvLocal);
322     int newmaxstate = currlayer->algo->NumCellStates() - 1;
323     if (oldrule != newrule || oldmaxstate != newmaxstate) {
324         // show new rule in main window's title but don't change name
325         ChangeWindowTitle(wxEmptyString);
326 
327 		// if pattern exists and is at starting gen then ensure savestart is true
328 		// so that SaveStartingPattern will save pattern to suitable file
329 		// (and thus undo/reset will work correctly)
330 		if (currlayer->algo->getGeneration() == currlayer->startgen && !currlayer->algo->isEmpty()) {
331 			currlayer->savestart = true;
332 		}
333 
334         // if grid is bounded then remove any live cells outside grid edges
335         if (currlayer->algo->gridwd > 0 || currlayer->algo->gridht > 0) {
336             mainptr->ClearOutsideGrid();
337         }
338 
339         // rule change might have changed the number of cell states;
340         // if there are fewer states then pattern might change
341         if (newmaxstate < oldmaxstate && !currlayer->algo->isEmpty()) {
342             mainptr->ReduceCellStates(newmaxstate);
343         }
344 
345         if (allowundo && !currlayer->stayclean) {
346             currlayer->undoredo->RememberRuleChange(oldrule);
347         }
348     }
349 
350     // switch to default colors and icons for new rule (we need to do this even if
351     // oldrule == newrule in case there's a new/changed .rule file)
352     UpdateLayerColors();
353 
354     // pattern or colors or icons might have changed
355     DoAutoUpdate();
356 
357     return NULL;
358 }
359 
360 // -----------------------------------------------------------------------------
361 
GSF_setgen(const char * genstring)362 const char* GSF_setgen(const char* genstring)
363 {
364     const char* err = mainptr->ChangeGenCount(genstring);
365     if (!err) DoAutoUpdate();
366 
367     return err;
368 }
369 
370 // -----------------------------------------------------------------------------
371 
GSF_setpos(const char * x,const char * y)372 const char* GSF_setpos(const char* x, const char* y)
373 {
374     // disallow alphabetic chars in x,y
375     int i;
376     int xlen = (int)strlen(x);
377     for (i = 0; i < xlen; i++)
378         if ( (x[i] >= 'a' && x[i] <= 'z') || (x[i] >= 'A' && x[i] <= 'Z') )
379             return "Illegal character in x value.";
380 
381     int ylen = (int)strlen(y);
382     for (i = 0; i < ylen; i++)
383         if ( (y[i] >= 'a' && y[i] <= 'z') || (y[i] >= 'A' && y[i] <= 'Z') )
384             return "Illegal character in y value.";
385 
386     bigint bigx(x);
387     bigint bigy(y);
388 
389     // check if x,y is outside bounded grid
390     if ( (currlayer->algo->gridwd > 0 &&
391             (bigx < currlayer->algo->gridleft || bigx > currlayer->algo->gridright)) ||
392          (currlayer->algo->gridht > 0 &&
393             (bigy < currlayer->algo->gridtop || bigy > currlayer->algo->gridbottom)) ) {
394         return "Given position is outside grid boundary.";
395     }
396 
397     viewptr->SetPosMag(bigx, bigy, viewptr->GetMag());
398     DoAutoUpdate();
399 
400     return NULL;
401 }
402 
403 // -----------------------------------------------------------------------------
404 
GSF_setname(const wxString & name,int index)405 void GSF_setname(const wxString& name, int index)
406 {
407     if (name.length() == 0) return;
408 
409     // inscript should be true but play safe
410     if (allowundo && !currlayer->stayclean && inscript)
411         SavePendingChanges();
412 
413     if (index == currindex) {
414         // save old name for RememberNameChange
415         wxString oldname = currlayer->currname;
416 
417         // show new name in main window's title;
418         // also sets currlayer->currname and updates menu item
419         ChangeWindowTitle(name);
420 
421         if (allowundo && !currlayer->stayclean) {
422             // note that currfile and savestart/dirty flags don't change
423             currlayer->undoredo->RememberNameChange(oldname, currlayer->currfile,
424                                                     currlayer->savestart, currlayer->dirty);
425         }
426     } else {
427         // temporarily change currlayer (used in RememberNameChange)
428         Layer* savelayer = currlayer;
429         currlayer = GetLayer(index);
430         wxString oldname = currlayer->currname;
431 
432         currlayer->currname = name;
433 
434         if (allowundo && !currlayer->stayclean) {
435             // note that currfile and savestart/dirty flags don't change
436             currlayer->undoredo->RememberNameChange(oldname, currlayer->currfile,
437                                                     currlayer->savestart, currlayer->dirty);
438         }
439 
440         // restore currlayer
441         currlayer = savelayer;
442 
443         // show name in given layer's menu item
444         mainptr->UpdateLayerItem(index);
445     }
446 }
447 
448 // -----------------------------------------------------------------------------
449 
GSF_setcell(int x,int y,int newstate)450 const char* GSF_setcell(int x, int y, int newstate)
451 {
452     // check if x,y is outside bounded grid
453     if ( (currlayer->algo->gridwd > 0 &&
454             (x < currlayer->algo->gridleft.toint() ||
455              x > currlayer->algo->gridright.toint())) ||
456          (currlayer->algo->gridht > 0 &&
457             (y < currlayer->algo->gridtop.toint() ||
458              y > currlayer->algo->gridbottom.toint())) ) {
459         return "Given cell is outside grid boundary.";
460     }
461 
462     int oldstate = currlayer->algo->getcell(x, y);
463     if (newstate != oldstate) {
464         if (currlayer->algo->setcell(x, y, newstate) < 0) {
465             return "State value is out of range.";
466         }
467         currlayer->algo->endofpattern();
468         if (allowundo && !currlayer->stayclean) {
469             ChangeCell(x, y, oldstate, newstate);
470         }
471         MarkLayerDirty();
472         DoAutoUpdate();
473     }
474     return NULL;
475 }
476 
477 // -----------------------------------------------------------------------------
478 
GSF_paste(int x,int y,const char * mode)479 const char* GSF_paste(int x, int y, const char* mode)
480 {
481     // check if x,y is outside bounded grid
482     if ( (currlayer->algo->gridwd > 0 &&
483             (x < currlayer->algo->gridleft.toint() ||
484              x > currlayer->algo->gridright.toint())) ||
485          (currlayer->algo->gridht > 0 &&
486             (y < currlayer->algo->gridtop.toint() ||
487              y > currlayer->algo->gridbottom.toint())) ) {
488         return "Given cell is outside grid boundary.";
489     }
490 
491     if (!mainptr->ClipboardHasText())
492         return "No pattern in clipboard.";
493 
494     // temporarily change selection and paste mode
495     Selection oldsel = currlayer->currsel;
496     const char* oldmode = GetPasteMode();
497 
498     wxString modestr = wxString(mode, wxConvLocal);
499     if      (modestr.IsSameAs(wxT("and"), false))  SetPasteMode("And");
500     else if (modestr.IsSameAs(wxT("copy"), false)) SetPasteMode("Copy");
501     else if (modestr.IsSameAs(wxT("or"), false))   SetPasteMode("Or");
502     else if (modestr.IsSameAs(wxT("xor"), false))  SetPasteMode("Xor");
503     else return "Unknown mode.";
504 
505     // create huge selection rect so no possibility of error message
506     currlayer->currsel.SetRect(x, y, INT_MAX, INT_MAX);
507 
508     viewptr->PasteClipboard(true);      // true = paste to selection
509 
510     // restore selection and paste mode
511     currlayer->currsel = oldsel;
512     SetPasteMode(oldmode);
513 
514     DoAutoUpdate();
515     return NULL;
516 }
517 
518 // -----------------------------------------------------------------------------
519 
GSF_checkpos(lifealgo * algo,int x,int y)520 const char* GSF_checkpos(lifealgo* algo, int x, int y)
521 {
522     // check that x,y is within bounded grid
523     if ( (algo->gridwd > 0 &&
524             (x < algo->gridleft.toint() || x > algo->gridright.toint())) ||
525          (algo->gridht > 0 &&
526             (y < algo->gridtop.toint() || y > algo->gridbottom.toint())) ) {
527         return "Cell is outside grid boundary.";
528     }
529     return NULL;
530 }
531 
532 // -----------------------------------------------------------------------------
533 
GSF_checkrect(int x,int y,int wd,int ht)534 const char* GSF_checkrect(int x, int y, int wd, int ht)
535 {
536     if (wd <= 0) return "Rectangle width must be > 0.";
537     if (ht <= 0) return "Rectangle height must be > 0.";
538 
539     // check that rect is completely within bounded grid
540     if ( (currlayer->algo->gridwd > 0 &&
541             (x < currlayer->algo->gridleft.toint() ||
542              x > currlayer->algo->gridright.toint() ||
543              x+wd-1 < currlayer->algo->gridleft.toint() ||
544              x+wd-1 > currlayer->algo->gridright.toint())) ||
545          (currlayer->algo->gridht > 0 &&
546             (y < currlayer->algo->gridtop.toint() ||
547              y > currlayer->algo->gridbottom.toint() ||
548              y+ht-1 < currlayer->algo->gridtop.toint() ||
549              y+ht-1 > currlayer->algo->gridbottom.toint())) ) {
550         return "Rectangle is outside grid boundary.";
551     }
552     return NULL;
553 }
554 
555 // -----------------------------------------------------------------------------
556 
GSF_hash(int x,int y,int wd,int ht)557 int GSF_hash(int x, int y, int wd, int ht)
558 {
559     // calculate a hash value for pattern in given rect
560     int hash = 31415962;
561     int right = x + wd - 1;
562     int bottom = y + ht - 1;
563     int cx, cy;
564     int v = 0;
565     lifealgo* curralgo = currlayer->algo;
566     bool multistate = curralgo->NumCellStates() > 2;
567 
568     for ( cy=y; cy<=bottom; cy++ ) {
569         int yshift = cy - y;
570         for ( cx=x; cx<=right; cx++ ) {
571             int skip = curralgo->nextcell(cx, cy, v);
572             if (skip >= 0) {
573                 // found next live cell in this row (v is >= 1 if multistate)
574                 cx += skip;
575                 if (cx <= right) {
576                     // need to use a good hash function for patterns like AlienCounter.rle
577                     hash = (hash * 1000003) ^ yshift;
578                     hash = (hash * 1000003) ^ (cx - x);
579                     if (multistate) hash = (hash * 1000003) ^ v;
580                 }
581             } else {
582                 cx = right;  // done this row
583             }
584         }
585     }
586 
587     return hash;
588 }
589 
590 // -----------------------------------------------------------------------------
591 
GSF_select(int x,int y,int wd,int ht)592 void GSF_select(int x, int y, int wd, int ht)
593 {
594     if (wd < 1 || ht < 1) {
595         // remove any existing selection
596         viewptr->SaveCurrentSelection();
597         currlayer->currsel.Deselect();
598         viewptr->RememberNewSelection(_("Deselection"));
599     } else {
600         // set selection edges
601         viewptr->SaveCurrentSelection();
602         currlayer->currsel.SetRect(x, y, wd, ht);
603         viewptr->RememberNewSelection(_("Selection"));
604     }
605 }
606 
607 // -----------------------------------------------------------------------------
608 
GSF_setoption(const char * optname,int newval,int * oldval)609 bool GSF_setoption(const char* optname, int newval, int* oldval)
610 {
611     if (strcmp(optname, "autofit") == 0) {
612         *oldval = currlayer->autofit ? 1 : 0;
613         if (*oldval != newval) {
614             mainptr->ToggleAutoFit();
615             // autofit option only applies to a generating pattern
616             // DoAutoUpdate();
617         }
618 
619     } else if (strcmp(optname, "boldspacing") == 0) {
620         *oldval = boldspacing;
621         if (newval < 2) newval = 2;
622         if (newval > MAX_SPACING) newval = MAX_SPACING;
623         if (*oldval != newval) {
624             boldspacing = newval;
625             DoAutoUpdate();
626         }
627 
628     } else if (strcmp(optname, "drawingstate") == 0) {
629         *oldval = currlayer->drawingstate;
630         if (newval < 0) newval = 0;
631         if (newval >= currlayer->algo->NumCellStates())
632             newval = currlayer->algo->NumCellStates() - 1;
633         if (*oldval != newval) {
634             currlayer->drawingstate = newval;
635             if (autoupdate) {
636                 UpdateEditBar();
637                 updateedit = false;
638             } else {
639                 // update edit bar in next GSF_update call
640                 updateedit = true;
641             }
642         }
643 
644     } else if (strcmp(optname, "fullscreen") == 0) {
645         *oldval = mainptr->fullscreen ? 1 : 0;
646         if (*oldval != newval) {
647             mainptr->ToggleFullScreen();
648             DoAutoUpdate();
649         }
650 
651     } else if (strcmp(optname, "hyperspeed") == 0) {
652         *oldval = currlayer->hyperspeed ? 1 : 0;
653         if (*oldval != newval)
654             mainptr->ToggleHyperspeed();
655 
656     } else if (strcmp(optname, "mindelay") == 0) {
657         *oldval = mindelay;
658         if (newval < 0) newval = 0;
659         if (newval > MAX_DELAY) newval = MAX_DELAY;
660         if (*oldval != newval) {
661             mindelay = newval;
662             mainptr->UpdateStepExponent();
663             DoAutoUpdate();
664         }
665 
666     } else if (strcmp(optname, "maxdelay") == 0) {
667         *oldval = maxdelay;
668         if (newval < 0) newval = 0;
669         if (newval > MAX_DELAY) newval = MAX_DELAY;
670         if (*oldval != newval) {
671             maxdelay = newval;
672             mainptr->UpdateStepExponent();
673             DoAutoUpdate();
674         }
675 
676     } else if (strcmp(optname, "opacity") == 0) {
677         *oldval = opacity;
678         if (newval < 1) newval = 1;
679         if (newval > 100) newval = 100;
680         if (*oldval != newval) {
681             opacity = newval;
682             DoAutoUpdate();
683         }
684 
685     } else if (strcmp(optname, "restoreview") == 0) {
686         *oldval = restoreview ? 1 : 0;
687         if (*oldval != newval) {
688             restoreview = !restoreview;
689             // no need for DoAutoUpdate();
690         }
691 
692     } else if (strcmp(optname, "savexrle") == 0) {
693         *oldval = savexrle ? 1 : 0;
694         if (*oldval != newval) {
695             savexrle = !savexrle;
696             // no need for DoAutoUpdate();
697         }
698 
699     } else if (strcmp(optname, "showallstates") == 0) {
700         *oldval = showallstates ? 1 : 0;
701         if (*oldval != newval) {
702             ToggleAllStates();
703             DoAutoUpdate();
704         }
705 
706     } else if (strcmp(optname, "showboldlines") == 0) {
707         *oldval = showboldlines ? 1 : 0;
708         if (*oldval != newval) {
709             showboldlines = !showboldlines;
710             DoAutoUpdate();
711         }
712 
713     } else if (strcmp(optname, "showbuttons") == 0) {
714         *oldval = controlspos;
715         if (newval < 0) newval = 0;
716         if (newval > 4) newval = 4;
717         if (*oldval != newval) {
718             // update position of translucent buttons
719             controlspos = newval;
720             int wd, ht;
721             viewptr->GetClientSize(&wd, &ht);
722             viewptr->SetViewSize(wd, ht);
723             DoAutoUpdate();
724         }
725 
726     } else if (strcmp(optname, "showeditbar") == 0) {
727         *oldval = showedit ? 1 : 0;
728         if (*oldval != newval) {
729             ToggleEditBar();
730             DoAutoUpdate();
731         }
732 
733     } else if (strcmp(optname, "showexact") == 0) {
734         *oldval = showexact ? 1 : 0;
735         if (*oldval != newval) {
736             mainptr->ToggleExactNumbers();
737             DoAutoUpdate();
738         }
739 
740     } else if (strcmp(optname, "showgrid") == 0) {
741         *oldval = showgridlines ? 1 : 0;
742         if (*oldval != newval) {
743             showgridlines = !showgridlines;
744             DoAutoUpdate();
745         }
746 
747     } else if (strcmp(optname, "showhashinfo") == 0) {
748         *oldval = currlayer->showhashinfo ? 1 : 0;
749         if (*oldval != newval)
750             mainptr->ToggleHashInfo();
751 
752     } else if (strcmp(optname, "showpopulation") == 0) {
753         *oldval = showpopulation ? 1 : 0;
754         if (*oldval != newval) {
755             mainptr->ToggleShowPopulation();
756             DoAutoUpdate();
757         }
758 
759     } else if (strcmp(optname, "showicons") == 0) {
760         *oldval = showicons ? 1 : 0;
761         if (*oldval != newval) {
762             viewptr->ToggleCellIcons();
763             DoAutoUpdate();
764         }
765 
766     } else if (strcmp(optname, "showlayerbar") == 0) {
767         *oldval = showlayer ? 1 : 0;
768         if (*oldval != newval) {
769             ToggleLayerBar();
770             DoAutoUpdate();
771         }
772 
773     } else if (strcmp(optname, "showoverlay") == 0) {
774         *oldval = showoverlay ? 1 : 0;
775         if (*oldval != newval) {
776             showoverlay = !showoverlay;
777             DoAutoUpdate();
778         }
779 
780     } else if (strcmp(optname, "showprogress") == 0) {
781         *oldval = showprogress ? 1 : 0;
782         if (*oldval != newval) {
783             showprogress = !showprogress;
784             // no need for DoAutoUpdate();
785         }
786 
787     } else if (strcmp(optname, "showfiles") == 0 ||
788                strcmp(optname, "showpatterns") == 0) {      // deprecated
789         *oldval = showfiles ? 1 : 0;
790         if (*oldval != newval) {
791             mainptr->ToggleShowFiles();
792             DoAutoUpdate();
793         }
794 
795     } else if (strcmp(optname, "showscripts") == 0) {
796         *oldval = 0;
797         if (*oldval != newval) {
798             // deprecated so do nothing
799             DoAutoUpdate();
800         }
801 
802     } else if (strcmp(optname, "showscrollbars") == 0) {
803         *oldval = showscrollbars ? 1 : 0;
804         if (*oldval != newval) {
805             mainptr->ToggleScrollBars();
806             DoAutoUpdate();
807         }
808 
809     } else if (strcmp(optname, "showstatusbar") == 0) {
810         *oldval = showstatus ? 1 : 0;
811         if (*oldval != newval) {
812             mainptr->ToggleStatusBar();
813             DoAutoUpdate();
814         }
815 
816     } else if (strcmp(optname, "showtimeline") == 0) {
817         *oldval = showtimeline ? 1 : 0;
818         if (*oldval != newval) {
819             ToggleTimelineBar();
820             DoAutoUpdate();
821         }
822 
823     } else if (strcmp(optname, "showtoolbar") == 0) {
824         *oldval = showtool ? 1 : 0;
825         if (*oldval != newval) {
826             mainptr->ToggleToolBar();
827             DoAutoUpdate();
828         }
829 
830     } else if (strcmp(optname, "smartscale") == 0) {
831         *oldval = smartscale ? 1 : 0;
832         if (*oldval != newval) {
833             viewptr->ToggleSmarterScaling();
834             DoAutoUpdate();
835         }
836 
837     } else if (strcmp(optname, "swapcolors") == 0) {
838         *oldval = swapcolors ? 1 : 0;
839         if (*oldval != newval) {
840             viewptr->ToggleCellColors();
841             DoAutoUpdate();
842         }
843 
844     } else if (strcmp(optname, "synccursors") == 0) {
845         *oldval = synccursors ? 1 : 0;
846         if (*oldval != newval) {
847             ToggleSyncCursors();
848             DoAutoUpdate();
849         }
850 
851     } else if (strcmp(optname, "syncviews") == 0) {
852         *oldval = syncviews ? 1 : 0;
853         if (*oldval != newval) {
854             ToggleSyncViews();
855             DoAutoUpdate();
856         }
857 
858     } else if (strcmp(optname, "switchlayers") == 0) {
859         *oldval = canswitch ? 1 : 0;
860         if (*oldval != newval) {
861             canswitch = !canswitch;
862             // no need for DoAutoUpdate();
863         }
864 
865     } else if (strcmp(optname, "stacklayers") == 0) {
866         *oldval = stacklayers ? 1 : 0;
867         if (*oldval != newval) {
868             ToggleStackLayers();
869             DoAutoUpdate();
870         }
871 
872     } else if (strcmp(optname, "tilelayers") == 0) {
873         *oldval = tilelayers ? 1 : 0;
874         if (*oldval != newval) {
875             ToggleTileLayers();
876             DoAutoUpdate();
877         }
878 
879         // this option is deprecated (use setalgo command)
880     } else if (strcmp(optname, "hashing") == 0) {
881         *oldval = (currlayer->algtype == HLIFE_ALGO) ? 1 : 0;
882         if (*oldval != newval) {
883             mainptr->ChangeAlgorithm(newval ? HLIFE_ALGO : QLIFE_ALGO);
884             DoAutoUpdate();
885         }
886 
887     } else {
888         // unknown option
889         return false;
890     }
891 
892     if (*oldval != newval) {
893         mainptr->UpdateMenuItems();
894     }
895 
896     return true;
897 }
898 
899 // -----------------------------------------------------------------------------
900 
GSF_getoption(const char * optname,int * optval)901 bool GSF_getoption(const char* optname, int* optval)
902 {
903     if      (strcmp(optname, "autofit") == 0)           *optval = currlayer->autofit ? 1 : 0;
904     else if (strcmp(optname, "boldspacing") == 0)       *optval = boldspacing;
905     else if (strcmp(optname, "drawingstate") == 0)      *optval = currlayer->drawingstate;
906     else if (strcmp(optname, "fullscreen") == 0)        *optval = mainptr->fullscreen ? 1 : 0;
907     else if (strcmp(optname, "hyperspeed") == 0)        *optval = currlayer->hyperspeed ? 1 : 0;
908     else if (strcmp(optname, "mindelay") == 0)          *optval = mindelay;
909     else if (strcmp(optname, "maxdelay") == 0)          *optval = maxdelay;
910     else if (strcmp(optname, "opacity") == 0)           *optval = opacity;
911     else if (strcmp(optname, "restoreview") == 0)       *optval = restoreview ? 1 : 0;
912     else if (strcmp(optname, "savexrle") == 0)          *optval = savexrle ? 1 : 0;
913     else if (strcmp(optname, "showallstates") == 0)     *optval = showallstates ? 1 : 0;
914     else if (strcmp(optname, "showboldlines") == 0)     *optval = showboldlines ? 1 : 0;
915     else if (strcmp(optname, "showbuttons") == 0)       *optval = controlspos;
916     else if (strcmp(optname, "showeditbar") == 0)       *optval = showedit ? 1 : 0;
917     else if (strcmp(optname, "showexact") == 0)         *optval = showexact ? 1 : 0;
918     else if (strcmp(optname, "showgrid") == 0)          *optval = showgridlines ? 1 : 0;
919     else if (strcmp(optname, "showhashinfo") == 0)      *optval = currlayer->showhashinfo ? 1 : 0;
920     else if (strcmp(optname, "showpopulation") == 0)    *optval = showpopulation ? 1 : 0;
921     else if (strcmp(optname, "showicons") == 0)         *optval = showicons ? 1 : 0;
922     else if (strcmp(optname, "showlayerbar") == 0)      *optval = showlayer ? 1 : 0;
923     else if (strcmp(optname, "showoverlay") == 0)       *optval = showoverlay ? 1 : 0;
924     else if (strcmp(optname, "showprogress") == 0)      *optval = showprogress ? 1 : 0;
925     else if (strcmp(optname, "showfiles") == 0)         *optval = showfiles ? 1 : 0;
926     else if (strcmp(optname, "showpatterns") == 0)      *optval = showfiles ? 1 : 0;        // deprecated
927     else if (strcmp(optname, "showscripts") == 0)       *optval = 0;                        // ditto
928     else if (strcmp(optname, "showscrollbars") == 0)    *optval = showscrollbars ? 1 : 0;
929     else if (strcmp(optname, "showstatusbar") == 0)     *optval = showstatus ? 1 : 0;
930     else if (strcmp(optname, "showtimeline") == 0)      *optval = showtimeline ? 1 : 0;
931     else if (strcmp(optname, "showtoolbar") == 0)       *optval = showtool ? 1 : 0;
932     else if (strcmp(optname, "smartscale") == 0)        *optval = smartscale ? 1 : 0;
933     else if (strcmp(optname, "stacklayers") == 0)       *optval = stacklayers ? 1 : 0;
934     else if (strcmp(optname, "swapcolors") == 0)        *optval = swapcolors ? 1 : 0;
935     else if (strcmp(optname, "switchlayers") == 0)      *optval = canswitch ? 1 : 0;
936     else if (strcmp(optname, "synccursors") == 0)       *optval = synccursors ? 1 : 0;
937     else if (strcmp(optname, "syncviews") == 0)         *optval = syncviews ? 1 : 0;
938     else if (strcmp(optname, "tilelayers") == 0)        *optval = tilelayers ? 1 : 0;
939     // this option is deprecated (use getalgo command)
940     else if (strcmp(optname, "hashing") == 0)
941         *optval = (currlayer->algtype == HLIFE_ALGO) ? 1 : 0;
942     else {
943         // unknown option
944         return false;
945     }
946     return true;
947 }
948 
949 // -----------------------------------------------------------------------------
950 
GSF_setcolor(const char * colname,wxColor & newcol,wxColor & oldcol)951 bool GSF_setcolor(const char* colname, wxColor& newcol, wxColor& oldcol)
952 {
953     if (strncmp(colname, "livecells", 9) == 0) {
954         // livecells0..livecells9 are deprecated; get and set color of state 1
955         oldcol.Set(currlayer->cellr[1], currlayer->cellg[1], currlayer->cellb[1]);
956         if (oldcol != newcol) {
957             currlayer->cellr[1] = newcol.Red();
958             currlayer->cellg[1] = newcol.Green();
959             currlayer->cellb[1] = newcol.Blue();
960             UpdateIconColors();
961             UpdateCloneColors();
962             DoAutoUpdate();
963         }
964 
965     } else if (strcmp(colname, "deadcells") == 0) {
966         // deprecated; can now use setcolors([0,r,g,b])
967         oldcol.Set(currlayer->cellr[0], currlayer->cellg[0], currlayer->cellb[0]);
968         if (oldcol != newcol) {
969             currlayer->cellr[0] = newcol.Red();
970             currlayer->cellg[0] = newcol.Green();
971             currlayer->cellb[0] = newcol.Blue();
972             UpdateIconColors();
973             UpdateCloneColors();
974             DoAutoUpdate();
975         }
976 
977     } else if (strcmp(colname, "border") == 0) {
978         oldcol = *borderrgb;
979         if (oldcol != newcol) {
980             *borderrgb = newcol;
981             DoAutoUpdate();
982         }
983 
984     } else if (strcmp(colname, "paste") == 0) {
985         oldcol = *pastergb;
986         if (oldcol != newcol) {
987             *pastergb = newcol;
988             DoAutoUpdate();
989         }
990 
991     } else if (strcmp(colname, "select") == 0) {
992         oldcol = *selectrgb;
993         if (oldcol != newcol) {
994             *selectrgb = newcol;
995             DoAutoUpdate();
996         }
997 
998     } else if (strcmp(colname, "hashing") == 0) {      // deprecated
999         oldcol = algoinfo[HLIFE_ALGO]->statusrgb;
1000         if (oldcol != newcol) {
1001             algoinfo[HLIFE_ALGO]->statusrgb = newcol;
1002             UpdateStatusBrushes();
1003             DoAutoUpdate();
1004         }
1005 
1006     } else if (strcmp(colname, "nothashing") == 0) {   // deprecated
1007         oldcol = algoinfo[QLIFE_ALGO]->statusrgb;
1008         if (oldcol != newcol) {
1009             algoinfo[QLIFE_ALGO]->statusrgb = newcol;
1010             UpdateStatusBrushes();
1011             DoAutoUpdate();
1012         }
1013 
1014     } else {
1015         // look for algo name
1016         char* algoname = ReplaceDeprecatedAlgo((char*) colname);
1017         for (int i = 0; i < NumAlgos(); i++) {
1018             if (strcmp(algoname, GetAlgoName(i)) == 0) {
1019                 oldcol = algoinfo[i]->statusrgb;
1020                 if (oldcol != newcol) {
1021                     algoinfo[i]->statusrgb = newcol;
1022                     UpdateStatusBrushes();
1023                     DoAutoUpdate();
1024                 }
1025                 return true;
1026             }
1027         }
1028         // unknown color name
1029         return false;
1030     }
1031     return true;
1032 }
1033 
1034 // -----------------------------------------------------------------------------
1035 
GSF_getcolor(const char * colname,wxColor & color)1036 bool GSF_getcolor(const char* colname, wxColor& color)
1037 {
1038     if (strncmp(colname, "livecells", 9) == 0) {
1039         // livecells0..livecells9 are deprecated; return color of state 1
1040         color.Set(currlayer->cellr[1], currlayer->cellg[1], currlayer->cellb[1]);
1041     }
1042     else if (strcmp(colname, "deadcells") == 0) {
1043         // deprecated; can now use getcolors(0)
1044         color.Set(currlayer->cellr[0], currlayer->cellg[0], currlayer->cellb[0]);
1045     }
1046     else if (strcmp(colname, "border") == 0)     color = *borderrgb;
1047     else if (strcmp(colname, "paste") == 0)      color = *pastergb;
1048     else if (strcmp(colname, "select") == 0)     color = *selectrgb;
1049     // next two are deprecated
1050     else if (strcmp(colname, "hashing") == 0)    color = algoinfo[HLIFE_ALGO]->statusrgb;
1051     else if (strcmp(colname, "nothashing") == 0) color = algoinfo[QLIFE_ALGO]->statusrgb;
1052     else {
1053         // look for algo name
1054         char* algoname = ReplaceDeprecatedAlgo((char*) colname);
1055         for (int i = 0; i < NumAlgos(); i++) {
1056             if (strcmp(algoname, GetAlgoName(i)) == 0) {
1057                 color = algoinfo[i]->statusrgb;
1058                 return true;
1059             }
1060         }
1061         // unknown color name
1062         return false;
1063     }
1064     return true;
1065 }
1066 
1067 // -----------------------------------------------------------------------------
1068 
GSF_getevent(wxString & event,int get)1069 void GSF_getevent(wxString& event, int get)
1070 {
1071     if (get) {
1072         pass_key_events = true;     // future keyboard events will call PassKeyToScript
1073         pass_mouse_events = true;   // future mouse events will call PassClickToScript
1074         pass_file_events = true;    // future open file events will call PassFileToScript
1075 
1076         // rle3path is non-empty if Golly has just seen a .rle3 file and started up 3D.lua
1077         if (rle3path[0]) {
1078             event = wxT("file ") + rle3path;
1079             rle3path = wxEmptyString;
1080             return;
1081         }
1082 
1083     } else {
1084         // tell Golly to handle future keyboard/mouse/file events
1085         pass_key_events = false;
1086         pass_mouse_events = false;
1087         pass_file_events = false;
1088         // clear any pending events so event is set to empty string below
1089         eventqueue.Clear();
1090     }
1091 
1092     if (eventqueue.IsEmpty()) {
1093         event = wxEmptyString;
1094     } else {
1095         // get event at start of queue, then remove it
1096         event = eventqueue[0];
1097         eventqueue.RemoveAt(0);
1098     }
1099 }
1100 
1101 // -----------------------------------------------------------------------------
1102 
1103 #if defined(__WXMAC__) && wxCHECK_VERSION(2,9,0)
1104     // wxMOD_CONTROL has been changed to mean Command key down (sheesh!)
1105     #define wxMOD_CONTROL wxMOD_RAW_CONTROL
1106     #define ControlDown RawControlDown
1107 #endif
1108 
AppendModifiers(int modifiers,wxString & event)1109 static void AppendModifiers(int modifiers, wxString& event)
1110 {
1111     // reverse of GetModifiers
1112     if (modifiers == wxMOD_NONE) {
1113         event += wxT("none");
1114     } else {
1115         if (modifiers & wxMOD_ALT)       event += wxT("alt");
1116 #ifdef __WXMAC__
1117         if (modifiers & wxMOD_CMD)       event += wxT("cmd");
1118         if (modifiers & wxMOD_CONTROL)   event += wxT("ctrl");
1119 #else
1120         if (modifiers & wxMOD_CMD)       event += wxT("ctrl");
1121         if (modifiers & wxMOD_META)      event += wxT("meta");
1122 #endif
1123         if (modifiers & wxMOD_SHIFT)     event += wxT("shift");
1124     }
1125 }
1126 
1127 // -----------------------------------------------------------------------------
1128 
GetModifiers(const wxString & modstring)1129 static int GetModifiers(const wxString& modstring)
1130 {
1131     // reverse of AppendModifiers
1132     int modifiers = wxMOD_NONE;
1133     if (modstring != wxT("none")) {
1134         if (modstring.Contains(wxT("alt")))     modifiers |= wxMOD_ALT;
1135         if (modstring.Contains(wxT("cmd")))     modifiers |= wxMOD_CMD;
1136         if (modstring.Contains(wxT("ctrl")))    modifiers |= wxMOD_CONTROL;
1137         if (modstring.Contains(wxT("meta")))    modifiers |= wxMOD_META;
1138         if (modstring.Contains(wxT("shift")))   modifiers |= wxMOD_SHIFT;
1139     }
1140     return modifiers;
1141 }
1142 
1143 // -----------------------------------------------------------------------------
1144 
GSF_doevent(const wxString & event)1145 const char* GSF_doevent(const wxString& event)
1146 {
1147     if (event.length() > 0) {
1148         if (event.StartsWith(wxT("key ")) && event.length() > 7) {
1149             // parse event string like "key x altshift"
1150             int key = event[4];
1151             if (event[4] == 'f' && event[5] >= '1' && event[5] <= '9') {
1152                 // parse function key (f1 to f24)
1153                 if (event[6] == ' ') {
1154                     // f1 to f9
1155                     key = WXK_F1 + (event[5] - '1');
1156                 } else if (event[6] >= '0' && event[6] <= '9') {
1157                     // f10 to f24
1158                     key = WXK_F1 + 10 * (event[5] - '0') + (event[6] - '0') - 1;
1159                     if (key > WXK_F24)
1160                         return "Bad function key (must be f1 to f24).";
1161                 } else {
1162                     return "Bad function key (must be f1 to f24).";
1163                 }
1164             } else if (event[5] != ' ') {
1165                 // parse special char name like space, tab, etc
1166                 // must match reverse conversion in PassKeyToScript and PassKeyUpToScript
1167                 if (event.Contains(wxT("space")))      key = ' '; else
1168                 if (event.Contains(wxT("home")))       key = WXK_HOME; else
1169                 if (event.Contains(wxT("end")))        key = WXK_END; else
1170                 if (event.Contains(wxT("pageup")))     key = WXK_PAGEUP; else
1171                 if (event.Contains(wxT("pagedown")))   key = WXK_PAGEDOWN; else
1172                 if (event.Contains(wxT("help")))       key = WXK_HELP; else
1173                 if (event.Contains(wxT("insert")))     key = WXK_INSERT; else
1174                 if (event.Contains(wxT("delete")))     key = WXK_DELETE; else
1175                 if (event.Contains(wxT("tab")))        key = WXK_TAB; else
1176                 if (event.Contains(wxT("enter")))      key = WXK_RETURN; else
1177                 if (event.Contains(wxT("return")))     key = WXK_RETURN; else
1178                 if (event.Contains(wxT("left")))       key = WXK_LEFT; else
1179                 if (event.Contains(wxT("right")))      key = WXK_RIGHT; else
1180                 if (event.Contains(wxT("up")))         key = WXK_UP; else
1181                 if (event.Contains(wxT("down")))       key = WXK_DOWN; else
1182                 return "Unknown key.";
1183             }
1184 
1185             viewptr->ProcessKey(key, GetModifiers(event.AfterLast(' ')));
1186 
1187             if (showtitle) {
1188                 // update window title
1189                 inscript = false;
1190                 mainptr->SetWindowTitle(wxEmptyString);
1191                 inscript = true;
1192                 showtitle = false;
1193             }
1194 
1195         } else if (event.StartsWith(wxT("zoom"))) {
1196             // parse event string like "zoomin 10 20" or "zoomout 10 20"
1197             wxString xstr = event.AfterFirst(' ');
1198             wxString ystr = xstr.AfterFirst(' ');
1199             xstr = xstr.BeforeFirst(' ');
1200             if (!xstr.IsNumber()) return "Bad x value.";
1201             if (!ystr.IsNumber()) return "Bad y value.";
1202             int x = wxAtoi(xstr);
1203             int y = wxAtoi(ystr);
1204 
1205             // x,y is pixel position in viewport
1206             if (event.StartsWith(wxT("zoomin"))) {
1207                 viewptr->TestAutoFit();
1208                 if (currlayer->view->getmag() < MAX_MAG) {
1209                     currlayer->view->zoom(x, y);
1210                 }
1211             } else {
1212                 viewptr->TestAutoFit();
1213                 currlayer->view->unzoom(x, y);
1214             }
1215 
1216             inscript = false;
1217             mainptr->UpdatePatternAndStatus();
1218             bigview->UpdateScrollBars();
1219             inscript = true;
1220             mainptr->UpdateUserInterface();
1221 
1222         } else if (event.StartsWith(wxT("click "))) {
1223             // parse event string like "click 10 20 left altshift"
1224             wxString xstr = event.AfterFirst(' ');
1225             wxString ystr = xstr.AfterFirst(' ');
1226             xstr = xstr.BeforeFirst(' ');
1227             ystr = ystr.BeforeFirst(' ');
1228             if (!xstr.IsNumber()) return "Bad x value.";
1229             if (!ystr.IsNumber()) return "Bad y value.";
1230             bigint x(xstr.mb_str(wxConvLocal));
1231             bigint y(ystr.mb_str(wxConvLocal));
1232 
1233             int button;
1234             if (event.Contains(wxT(" left "))) button = wxMOUSE_BTN_LEFT; else
1235                 if (event.Contains(wxT(" middle "))) button = wxMOUSE_BTN_MIDDLE; else
1236                     if (event.Contains(wxT(" right "))) button = wxMOUSE_BTN_RIGHT; else
1237                         return "Unknown button.";
1238 
1239             if (viewptr->CellVisible(x, y) && viewptr->CellInGrid(x, y)) {
1240                 // convert x,y cell position to pixel position in viewport
1241                 pair<int,int> xy = currlayer->view->screenPosOf(x, y, currlayer->algo);
1242                 int mods = GetModifiers(event.AfterLast(' '));
1243 
1244                 viewptr->ProcessClick(xy.first, xy.second, button, mods);
1245 
1246                 if (showtitle) {
1247                     // update window title
1248                     inscript = false;
1249                     mainptr->SetWindowTitle(wxEmptyString);
1250                     inscript = true;
1251                     showtitle = false;
1252                 }
1253             } else {
1254                 // ignore click if x,y is outside viewport or grid
1255                 return NULL;
1256             }
1257 
1258         } else if (event.StartsWith(wxT("kup "))) {
1259             // ignore key up event
1260             return NULL;
1261 
1262         } else if (event.StartsWith(wxT("mup "))) {
1263             // ignore mouse up event
1264             return NULL;
1265 
1266         } else if (event.StartsWith(wxT("file "))) {
1267             // ignore file event (scripts can call GSF_open)
1268             return NULL;
1269 
1270         } else if (event.StartsWith(wxT("o"))) {
1271             // ignore oclick/ozoomin/ozoomout event in overlay
1272             return NULL;
1273 
1274         } else {
1275             return "Unknown event.";
1276         }
1277     }
1278     return NULL;
1279 }
1280 
1281 // -----------------------------------------------------------------------------
1282 
1283 // the following is deprecated (use GSF_getevent)
1284 
GSF_getkey()1285 char GSF_getkey()
1286 {
1287     pass_key_events = true;   // future keyboard events will call PassKeyToScript
1288 
1289     if (scriptchars.length() == 0) {
1290         // return empty string
1291         return '\0';
1292     } else {
1293         // return first char in scriptchars and then remove it
1294         char ch = scriptchars.GetChar(0);
1295         scriptchars = scriptchars.AfterFirst(ch);
1296         return ch;
1297     }
1298 }
1299 
1300 // -----------------------------------------------------------------------------
1301 
1302 // the following is deprecated (use GSF_doevent)
1303 
GSF_dokey(const char * ascii)1304 void GSF_dokey(const char* ascii)
1305 {
1306     if (*ascii) {
1307         // convert ascii char to corresponding wx key code;
1308         // note that PassKeyToScript does the reverse conversion
1309         int key;
1310         switch (*ascii) {
1311             case 127:   // treat delete like backspace
1312             case 8:     key = WXK_BACK;   break;
1313             case 9:     key = WXK_TAB;    break;
1314             case 10:    // treat linefeed like return
1315             case 13:    key = WXK_RETURN; break;
1316             case 28:    key = WXK_LEFT;   break;
1317             case 29:    key = WXK_RIGHT;  break;
1318             case 30:    key = WXK_UP;     break;
1319             case 31:    key = WXK_DOWN;   break;
1320             default:    key = *ascii;
1321         }
1322 
1323         // we can't handle modifiers here
1324         viewptr->ProcessKey(key, wxMOD_NONE);
1325 
1326         if (showtitle) {
1327             // update window title
1328             inscript = false;
1329             mainptr->SetWindowTitle(wxEmptyString);
1330             inscript = true;
1331             showtitle = false;
1332         }
1333     }
1334 }
1335 
1336 // -----------------------------------------------------------------------------
1337 
GSF_update()1338 void GSF_update()
1339 {
1340     if (mainptr->IsIconized()) return;
1341 
1342     // update viewport, status bar, and possibly other bars
1343     inscript = false;
1344 
1345     // pass in true so that Update() is called
1346     mainptr->UpdatePatternAndStatus(true);
1347 
1348     if (showtitle) {
1349         mainptr->SetWindowTitle(wxEmptyString);
1350         showtitle = false;
1351     }
1352 
1353     if (updateedit) {
1354         UpdateEditBar();
1355         updateedit = false;
1356     }
1357 
1358     inscript = true;
1359 
1360     #ifdef __WXGTK__
1361         // needed on Linux to see update immediately
1362         insideYield = true;
1363         wxGetApp().Yield(true);
1364         insideYield = false;
1365     #endif
1366 }
1367 
1368 // -----------------------------------------------------------------------------
1369 
GSF_exit(const wxString & errmsg)1370 void GSF_exit(const wxString& errmsg)
1371 {
1372     if (!errmsg.IsEmpty()) {
1373         // display given error message
1374         inscript = false;
1375         statusptr->ErrorMessage(errmsg);
1376         inscript = true;
1377         // make sure status bar is visible
1378         if (!showstatus) mainptr->ToggleStatusBar();
1379     }
1380 
1381     exitcalled = true;   // prevent CheckScriptError changing message
1382 }
1383 
1384 #ifdef __WXMAC__
1385     // convert path to decomposed UTF8 so fopen will work
1386     #define CURRFILE currlayer->currfile.fn_str()
1387 #else
1388     #define CURRFILE currlayer->currfile.mb_str(wxConvLocal)
1389 #endif
1390 
1391 // -----------------------------------------------------------------------------
1392 
GSF_getpath()1393 const char* GSF_getpath()
1394 {
1395     // need to be careful converting Unicode wxString to char*
1396     static wxCharBuffer path;
1397     path = CURRFILE;
1398     return (const char*) path;
1399 }
1400 
1401 // -----------------------------------------------------------------------------
1402 
GSF_getinfo()1403 const char* GSF_getinfo()
1404 {
1405     // comment buffer
1406     static char comments[maxcomments];
1407 
1408     // buffer for receiving comment data (allocated by readcomments)
1409     char *commptr = NULL;
1410 
1411     // read the comments in the pattern file
1412     const char* err = readcomments(CURRFILE, &commptr);
1413     if (err) {
1414         free(commptr);
1415         return "";
1416     }
1417 
1418     // copy the comments and truncate to buffer size if longer
1419     strncpy(comments, commptr, maxcomments);
1420     comments[maxcomments - 1] = '\0';
1421     free(commptr);
1422     return comments;
1423 }
1424 
1425 // =============================================================================
1426 
CheckScriptError(const wxString & ext)1427 void CheckScriptError(const wxString& ext)
1428 {
1429     if (scripterr.IsEmpty()) return;    // no error
1430 
1431     if (scripterr.Find(wxString(abortmsg,wxConvLocal)) >= 0) {
1432         // error was caused by AbortLuaScript/AbortPerlScript/AbortPythonScript
1433         // so don't display scripterr
1434     } else {
1435         wxString errtype;
1436         if (ext.IsSameAs(wxT("lua"), false)) {
1437             errtype = _("Lua error:");
1438         } else if (ext.IsSameAs(wxT("pl"), false)) {
1439             errtype = _("Perl error:");
1440             scripterr.Replace(wxT(". at "), wxT("\nat "));
1441         }
1442         Beep();
1443         wxMessageBox(scripterr, errtype, wxOK | wxICON_EXCLAMATION, wxGetActiveWindow());
1444     }
1445 
1446     // don't change status message if GSF_exit was used to stop script
1447     if (!exitcalled) statusptr->DisplayMessage(_("Script aborted."));
1448 }
1449 
1450 // -----------------------------------------------------------------------------
1451 
ChangeCell(int x,int y,int oldstate,int newstate)1452 void ChangeCell(int x, int y, int oldstate, int newstate)
1453 {
1454     // first check if there are any pending gen changes that need to be remembered
1455     if (currlayer->undoredo->savegenchanges) {
1456         currlayer->undoredo->savegenchanges = false;
1457         currlayer->undoredo->RememberGenFinish();
1458     }
1459 
1460     // setcell/putcells command is changing state of cell at x,y
1461     currlayer->undoredo->SaveCellChange(x, y, oldstate, newstate);
1462     if (!currlayer->undoredo->savecellchanges) {
1463         currlayer->undoredo->savecellchanges = true;
1464         // save layer's dirty state for next RememberCellChanges call
1465         currlayer->savedirty = currlayer->dirty;
1466     }
1467 }
1468 
1469 // -----------------------------------------------------------------------------
1470 
SavePendingChanges(bool checkgenchanges)1471 void SavePendingChanges(bool checkgenchanges)
1472 {
1473     // this should only be called if inscript && allowundo && !currlayer->stayclean
1474     if ( !(inscript && allowundo && !currlayer->stayclean) )
1475         Warning(_("Bug detected in SavePendingChanges!"));
1476 
1477     if (currlayer->undoredo->savecellchanges) {
1478         currlayer->undoredo->savecellchanges = false;
1479         // remember accumulated cell changes
1480         currlayer->undoredo->RememberCellChanges(_("bug1"), currlayer->savedirty);
1481         // action string should never be seen
1482     }
1483 
1484     if (checkgenchanges && currlayer->undoredo->savegenchanges) {
1485         currlayer->undoredo->savegenchanges = false;
1486         // remember accumulated gen changes
1487         currlayer->undoredo->RememberGenFinish();
1488     }
1489 }
1490 
1491 // -----------------------------------------------------------------------------
1492 
RunScript(const wxString & filename)1493 void RunScript(const wxString& filename)
1494 {
1495     if (TimelineExists()) {
1496         statusptr->ErrorMessage(_("You can't run a script if there is a timeline."));
1497         return;
1498     }
1499 
1500     // use these flags to allow re-entrancy
1501     bool already_inscript = inscript;
1502     bool in_luascript = luascript;
1503     bool in_plscript = plscript;
1504     wxString savecwd;
1505 
1506     if (!wxFileName::FileExists(filename)) {
1507         Warning(_("The script file does not exist:\n") + filename);
1508         return;
1509     }
1510 
1511     if (already_inscript) {
1512         // save current directory so we can restore it below
1513         savecwd = scriptloc;
1514     } else {
1515         mainptr->showbanner = false;
1516         statusptr->ClearMessage();
1517         scripttitle.Clear();
1518         scripterr.Clear();
1519         scriptchars.Clear();
1520         eventqueue.Clear();
1521         canswitch = false;
1522         stop_after_script = false;
1523         autoupdate = false;
1524         exitcalled = false;
1525         allowcheck = true;
1526         showprogress = true;
1527         showtitle = false;
1528         updateedit = false;
1529         pass_key_events = false;
1530         pass_mouse_events = false;
1531         pass_file_events = false;
1532         wxGetApp().PollerReset();
1533     }
1534 
1535     // temporarily change current directory to location of script
1536     wxFileName fullname(filename);
1537     fullname.Normalize();
1538     scriptloc = fullname.GetPath();
1539     if ( scriptloc.Last() != wxFILE_SEP_PATH ) scriptloc += wxFILE_SEP_PATH;
1540     wxSetWorkingDirectory(scriptloc);
1541 
1542     wxString fpath = fullname.GetFullPath();
1543     #ifdef __WXMAC__
1544         // use decomposed UTF8 so interpreter can open names with non-ASCII chars
1545         fpath = wxString(fpath.fn_str(),wxConvLocal);
1546     #endif
1547 
1548     if (!already_inscript) {
1549         if (allowundo) {
1550             // save each layer's dirty state for use by next RememberCellChanges call
1551             for ( int i = 0; i < numlayers; i++ ) {
1552                 Layer* layer = GetLayer(i);
1553                 layer->savedirty = layer->dirty;
1554                 // at start of script there are no pending cell/gen changes
1555                 layer->undoredo->savecellchanges = false;
1556                 layer->undoredo->savegenchanges = false;
1557                 // add a special node to indicate that the script is about to start so
1558                 // that all changes made by the script can be undone/redone in one go;
1559                 // note that the UndoRedo ctor calls RememberScriptStart if the script
1560                 // creates a new non-cloned layer, and we let RememberScriptStart handle
1561                 // multiple calls if this layer is a clone
1562                 layer->undoredo->RememberScriptStart();
1563             }
1564         }
1565 
1566         inscript = true;
1567 
1568         mainptr->UpdateUserInterface();
1569 
1570         // temporarily remove accelerators from all menu items
1571         // so keyboard shortcuts can be passed to script
1572         mainptr->UpdateMenuAccelerators();
1573     }
1574 
1575     wxString ext = filename.AfterLast('.');
1576     if (ext.IsSameAs(wxT("lua"), false)) {
1577         luascript = true;
1578         RunLuaScript(fpath);
1579     } else if (ext.IsSameAs(wxT("pl"), false)) {
1580         plscript = true;
1581         RunPerlScript(fpath);
1582     } else {
1583         // should never happen
1584         luascript = false;
1585         plscript = false;
1586         Warning(_("Unexpected extension in script file:\n") + filename);
1587     }
1588 
1589     if (already_inscript) {
1590         // restore current directory saved above
1591         scriptloc = savecwd;
1592         wxSetWorkingDirectory(scriptloc);
1593 
1594         // display any Lua/Perl/Python error message
1595         CheckScriptError(ext);
1596         if (!scripterr.IsEmpty()) {
1597             if (in_luascript) {
1598                 // abort the calling Lua script
1599                 AbortLuaScript();
1600             } else if (in_plscript) {
1601                 // abort the calling Perl script
1602                 AbortPerlScript();
1603             }
1604         }
1605 
1606         luascript = in_luascript;
1607         plscript = in_plscript;
1608 
1609     } else {
1610         // already_inscript is false
1611 
1612         // tidy up the undo/redo history for each layer; note that some calls
1613         // use currlayer (eg. RememberGenFinish) so we temporarily set currlayer
1614         // to each layer -- this is a bit yukky but should be safe as long as we
1615         // synchronize clone info, especially currlayer->algo ptrs because they
1616         // can change if the script called new()
1617         SyncClones();
1618         Layer* savelayer = currlayer;
1619         for ( int i = 0; i < numlayers; i++ ) {
1620             currlayer = GetLayer(i);
1621             if (allowundo) {
1622                 if (currlayer->undoredo->savecellchanges) {
1623                     currlayer->undoredo->savecellchanges = false;
1624                     // remember pending cell change(s)
1625                     if (currlayer->stayclean)
1626                         currlayer->undoredo->ForgetCellChanges();
1627                     else
1628                         currlayer->undoredo->RememberCellChanges(_("bug2"), currlayer->savedirty);
1629                     // action string should never be seen
1630                 }
1631                 if (currlayer->undoredo->savegenchanges) {
1632                     currlayer->undoredo->savegenchanges = false;
1633                     // remember pending gen change(s); no need to test stayclean flag
1634                     // (if it's true then NextGeneration called RememberGenStart)
1635                     currlayer->undoredo->RememberGenFinish();
1636                 }
1637                 // add special node to indicate that the script has finished;
1638                 // we let RememberScriptFinish handle multiple calls if this
1639                 // layer is a clone
1640                 currlayer->undoredo->RememberScriptFinish();
1641             }
1642             // reset the stayclean flag in case it was set by MarkLayerClean
1643             currlayer->stayclean = false;
1644         }
1645         currlayer = savelayer;
1646 
1647         // must reset inscript AFTER RememberGenFinish
1648         inscript = false;
1649 
1650         // restore current directory to location of Golly app
1651         wxSetWorkingDirectory(gollydir);
1652 
1653         luascript = false;
1654         plscript = false;
1655 
1656         // update Undo/Redo items based on current layer's history
1657         if (allowundo) currlayer->undoredo->UpdateUndoRedoItems();
1658 
1659         // display any error message
1660         CheckScriptError(ext);
1661 
1662         if (!scripttitle.IsEmpty()) {
1663             scripttitle.Clear();
1664             showtitle = true;
1665         }
1666 
1667         // update title, menu bar, cursor, viewport, status bar, tool bar, etc
1668         if (showtitle) mainptr->SetWindowTitle(wxEmptyString);
1669         mainptr->UpdateEverything();
1670 
1671         // restore accelerators that were cleared above
1672         mainptr->UpdateMenuAccelerators();
1673     }
1674 }
1675 
1676 // -----------------------------------------------------------------------------
1677 
PassOverlayClickToScript(int ox,int oy,int button,int modifiers)1678 void PassOverlayClickToScript(int ox, int oy, int button, int modifiers)
1679 {
1680     // build a string like "oclick 30 50 left none" and add to event queue
1681     // for possible consumption by GSF_getevent
1682     wxString clickinfo;
1683     clickinfo.Printf(wxT("oclick %d %d"), ox, oy);
1684     if (button == wxMOUSE_BTN_LEFT)     clickinfo += wxT(" left ");
1685     if (button == wxMOUSE_BTN_MIDDLE)   clickinfo += wxT(" middle ");
1686     if (button == wxMOUSE_BTN_RIGHT)    clickinfo += wxT(" right ");
1687     AppendModifiers(modifiers, clickinfo);
1688     eventqueue.Add(clickinfo);
1689 }
1690 
1691 // -----------------------------------------------------------------------------
1692 
PassClickToScript(const bigint & x,const bigint & y,int button,int modifiers)1693 void PassClickToScript(const bigint& x, const bigint& y, int button, int modifiers)
1694 {
1695     // build a string like "click 10 20 left altshift" and add to event queue
1696     // for possible consumption by GSF_getevent
1697     wxString clickinfo = wxT("click ");
1698     clickinfo += wxString(x.tostring('\0'),wxConvLocal);
1699     clickinfo += wxT(" ");
1700     clickinfo += wxString(y.tostring('\0'),wxConvLocal);
1701     if (button == wxMOUSE_BTN_LEFT)     clickinfo += wxT(" left ");
1702     if (button == wxMOUSE_BTN_MIDDLE)   clickinfo += wxT(" middle ");
1703     if (button == wxMOUSE_BTN_RIGHT)    clickinfo += wxT(" right ");
1704     AppendModifiers(modifiers, clickinfo);
1705     eventqueue.Add(clickinfo);
1706 }
1707 
1708 // -----------------------------------------------------------------------------
1709 
PassMouseUpToScript(int button)1710 void PassMouseUpToScript(int button)
1711 {
1712     // build a string like "mup left" and add to event queue
1713     // for possible consumption by GSF_getevent
1714     wxString minfo = wxT("mup ");
1715     if (button == wxMOUSE_BTN_LEFT)     minfo += wxT("left");
1716     if (button == wxMOUSE_BTN_MIDDLE)   minfo += wxT("middle");
1717     if (button == wxMOUSE_BTN_RIGHT)    minfo += wxT("right");
1718     eventqueue.Add(minfo);
1719 }
1720 
1721 // -----------------------------------------------------------------------------
1722 
PassZoomInToScript(int x,int y)1723 void PassZoomInToScript(int x, int y)
1724 {
1725     int ox, oy;
1726     if (showoverlay && curroverlay->PointInOverlay(x, y, &ox, &oy)
1727                     && !curroverlay->TransparentPixel(ox, oy)) {
1728         // zoom in to the overlay pixel at ox,oy
1729         wxString zinfo;
1730         zinfo.Printf(wxT("ozoomin %d %d"), ox, oy);
1731         eventqueue.Add(zinfo);
1732 
1733     } else {
1734         // zoom in to the viewport pixel at x,y (note that it's best not to
1735         // pass the corresponding cell position because a doevent call will result
1736         // in unwanted drifting due to conversion back to a pixel position)
1737         wxString zinfo;
1738         zinfo.Printf(wxT("zoomin %d %d"), x, y);
1739         eventqueue.Add(zinfo);
1740     }
1741 }
1742 
1743 // -----------------------------------------------------------------------------
1744 
PassZoomOutToScript(int x,int y)1745 void PassZoomOutToScript(int x, int y)
1746 {
1747     int ox, oy;
1748     if (showoverlay && curroverlay->PointInOverlay(x, y, &ox, &oy)
1749                     && !curroverlay->TransparentPixel(ox, oy)) {
1750         // zoom out from the overlay pixel at ox,oy
1751         wxString zinfo;
1752         zinfo.Printf(wxT("ozoomout %d %d"), ox, oy);
1753         eventqueue.Add(zinfo);
1754 
1755     } else {
1756         // zoom out from the viewport pixel at x,y
1757         wxString zinfo;
1758         zinfo.Printf(wxT("zoomout %d %d"), x, y);
1759         eventqueue.Add(zinfo);
1760     }
1761 }
1762 
1763 // -----------------------------------------------------------------------------
1764 
PassKeyUpToScript(int key)1765 void PassKeyUpToScript(int key)
1766 {
1767     // build a string like "kup x" and add to event queue
1768     // for possible consumption by GSF_getevent
1769     wxString keyinfo = wxT("kup ");
1770     if (key > ' ' && key <= '~') {
1771         // displayable ASCII
1772         if (key >= 'A' && key <= 'Z') key += 32;  // convert A..Z to a..z to match case in key event
1773         keyinfo += wxChar(key);
1774     } else if (key >= WXK_F1 && key <= WXK_F24) {
1775         // function key
1776         keyinfo += wxString::Format(wxT("f%d"), key - WXK_F1 + 1);
1777     } else {
1778         // convert some special key codes to names like space, tab, delete, etc
1779         // (must match reverse conversion in GSF_doevent)
1780         switch (key) {
1781             case ' ':               keyinfo += wxT("space");      break;
1782             case WXK_HOME:          keyinfo += wxT("home");       break;
1783             case WXK_END:           keyinfo += wxT("end");        break;
1784             case WXK_PAGEUP:        keyinfo += wxT("pageup");     break;
1785             case WXK_PAGEDOWN:      keyinfo += wxT("pagedown");   break;
1786             case WXK_HELP:          keyinfo += wxT("help");       break;
1787             case WXK_INSERT:        keyinfo += wxT("insert");     break;
1788             case WXK_BACK:          // treat backspace like delete
1789             case WXK_DELETE:        keyinfo += wxT("delete");     break;
1790             case WXK_TAB:           keyinfo += wxT("tab");        break;
1791             case WXK_NUMPAD_ENTER:  // treat enter like return
1792             case WXK_RETURN:        keyinfo += wxT("return");     break;
1793             case WXK_LEFT:          keyinfo += wxT("left");       break;
1794             case WXK_RIGHT:         keyinfo += wxT("right");      break;
1795             case WXK_UP:            keyinfo += wxT("up");         break;
1796             case WXK_DOWN:          keyinfo += wxT("down");       break;
1797             case WXK_ADD:           keyinfo += wxT("+");          break;
1798             case WXK_SUBTRACT:      keyinfo += wxT("-");          break;
1799             case WXK_DIVIDE:        keyinfo += wxT("/");          break;
1800             case WXK_MULTIPLY:      keyinfo += wxT("*");          break;
1801             default:                return;  // ignore all other key codes
1802         }
1803     }
1804     eventqueue.Add(keyinfo);
1805 }
1806 
1807 // -----------------------------------------------------------------------------
1808 
PassKeyToScript(int key,int modifiers)1809 void PassKeyToScript(int key, int modifiers)
1810 {
1811     if (key == WXK_ESCAPE) {
1812         if (mainptr->generating) {
1813             // interrupt a run() or step() command
1814             wxGetApp().PollerInterrupt();
1815         }
1816         if (luascript) AbortLuaScript();
1817         if (plscript) AbortPerlScript();
1818     } else {
1819         // build a string like "key x altshift" and add to event queue
1820         // for possible consumption by GSF_getevent
1821         wxString keyinfo = wxT("key ");
1822         if (key > ' ' && key <= '~') {
1823             // displayable ASCII
1824             keyinfo += wxChar(key);
1825         } else if (key >= WXK_F1 && key <= WXK_F24) {
1826             // function key
1827             keyinfo += wxString::Format(wxT("f%d"), key - WXK_F1 + 1);
1828         } else {
1829             // convert some special key codes to names like space, tab, delete, etc
1830             // (must match reverse conversion in GSF_doevent)
1831             switch (key) {
1832                 case ' ':               keyinfo += wxT("space");      break;
1833                 case WXK_HOME:          keyinfo += wxT("home");       break;
1834                 case WXK_END:           keyinfo += wxT("end");        break;
1835                 case WXK_PAGEUP:        keyinfo += wxT("pageup");     break;
1836                 case WXK_PAGEDOWN:      keyinfo += wxT("pagedown");   break;
1837                 case WXK_HELP:          keyinfo += wxT("help");       break;
1838                 case WXK_INSERT:        keyinfo += wxT("insert");     break;
1839                 case WXK_BACK:          // treat backspace like delete
1840                 case WXK_DELETE:        keyinfo += wxT("delete");     break;
1841                 case WXK_TAB:           keyinfo += wxT("tab");        break;
1842                 case WXK_NUMPAD_ENTER:  // treat enter like return
1843                 case WXK_RETURN:        keyinfo += wxT("return");     break;
1844                 case WXK_LEFT:          keyinfo += wxT("left");       break;
1845                 case WXK_RIGHT:         keyinfo += wxT("right");      break;
1846                 case WXK_UP:            keyinfo += wxT("up");         break;
1847                 case WXK_DOWN:          keyinfo += wxT("down");       break;
1848                 case WXK_ADD:           keyinfo += wxT("+");          break;
1849                 case WXK_SUBTRACT:      keyinfo += wxT("-");          break;
1850                 case WXK_DIVIDE:        keyinfo += wxT("/");          break;
1851                 case WXK_MULTIPLY:      keyinfo += wxT("*");          break;
1852                 default:                return;  // ignore all other key codes
1853             }
1854         }
1855         keyinfo += wxT(" ");
1856         AppendModifiers(modifiers, keyinfo);
1857         eventqueue.Add(keyinfo);
1858 
1859         // NOTE: following code is for deprecated getkey() command
1860 
1861         // convert wx key code to corresponding ascii char (if possible)
1862         // so that scripts can be platform-independent;
1863         // note that GSF_dokey does the reverse conversion
1864         char ascii;
1865         if (key >= ' ' && key <= '~') {
1866             if (modifiers == wxMOD_SHIFT && key >= 'a' && key <= 'z') {
1867                 // let script see A..Z
1868                 ascii = key - 32;
1869             } else {
1870                 ascii = key;
1871             }
1872         } else {
1873             switch (key) {
1874                 case WXK_DELETE:     // treat delete like backspace
1875                 case WXK_BACK:       ascii = 8;     break;
1876                 case WXK_TAB:        ascii = 9;     break;
1877                 case WXK_NUMPAD_ENTER: // treat enter like return
1878                 case WXK_RETURN:     ascii = 13;    break;
1879                 case WXK_LEFT:       ascii = 28;    break;
1880                 case WXK_RIGHT:      ascii = 29;    break;
1881                 case WXK_UP:         ascii = 30;    break;
1882                 case WXK_DOWN:       ascii = 31;    break;
1883                 case WXK_ADD:        ascii = '+';   break;
1884                 case WXK_SUBTRACT:   ascii = '-';   break;
1885                 case WXK_DIVIDE:     ascii = '/';   break;
1886                 case WXK_MULTIPLY:   ascii = '*';   break;
1887                 default:             return;  // ignore all other key codes
1888             }
1889         }
1890         // save ascii char for possible consumption by GSF_getkey
1891         scriptchars += ascii;
1892     }
1893 }
1894 
1895 // -----------------------------------------------------------------------------
1896 
PassFileToScript(const wxString & filepath)1897 void PassFileToScript(const wxString& filepath)
1898 {
1899     wxString fileinfo = wxT("file ");
1900     fileinfo += filepath;
1901     eventqueue.Add(fileinfo);
1902 }
1903 
1904 // -----------------------------------------------------------------------------
1905 
FinishScripting()1906 void FinishScripting()
1907 {
1908     // called when main window is closing (ie. app is quitting)
1909     if (inscript) {
1910         if (mainptr->generating) {
1911             // interrupt a run() or step() command
1912             wxGetApp().PollerInterrupt();
1913         }
1914         if (luascript) AbortLuaScript();
1915         if (plscript) AbortPerlScript();
1916         wxSetWorkingDirectory(gollydir);
1917         inscript = false;
1918     }
1919 
1920     FinishLuaScripting();
1921     FinishPerlScripting();
1922 }
1923