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