1 // This file is part of Golly.
2 // See docs/License.html for the copyright notice.
3 
4 #include "wx/wxprec.h"      // for compilers that support precompilation
5 #ifndef WX_PRECOMP
6     #include "wx/wx.h"      // for all others include the necessary headers
7 #endif
8 
9 #include "wx/dir.h"         // for wxDir
10 #include "wx/file.h"        // for wxFile
11 #include "wx/filename.h"    // for wxFileName
12 
13 #include "bigint.h"
14 #include "lifealgo.h"
15 #include "qlifealgo.h"
16 #include "hlifealgo.h"
17 #include "util.h"           // for linereader
18 
19 #include "wxgolly.h"        // for wxGetApp, statusptr, viewptr, bigview
20 #include "wxutils.h"        // for BeginProgress, GetString, etc
21 #include "wxprefs.h"        // for allowundo, etc
22 #include "wxrule.h"         // for ChangeRule
23 #include "wxhelp.h"         // for LoadLexiconPattern
24 #include "wxstatus.h"       // for statusptr->...
25 #include "wxselect.h"       // for Selection
26 #include "wxview.h"         // for viewptr->...
27 #include "wxscript.h"       // for inscript, PassKeyToScript
28 #include "wxmain.h"         // for MainFrame
29 #include "wxundo.h"         // for undoredo->...
30 #include "wxalgos.h"        // for *_ALGO, algo_type, CreateNewUniverse, etc
31 #include "wxlayer.h"        // for currlayer, etc
32 #include "wxtimeline.h"     // for TimelineExists, UpdateTimelineBar, etc
33 
34 #include <stdexcept>        // for std::runtime_error and std::exception
35 #include <sstream>          // for std::ostringstream
36 
37 #ifdef __WXMAC__
38     // we need to convert filepath to decomposed UTF8 so fopen will work
39     #define OPENFILE(filepath) fopen(filepath.fn_str(),"r")
40 #else
41     #define OPENFILE(filepath) fopen(filepath.mb_str(wxConvLocal),"r")
42 #endif
43 
44 // This module implements Control menu functions.
45 
46 // -----------------------------------------------------------------------------
47 
SaveStartingPattern()48 bool MainFrame::SaveStartingPattern()
49 {
50     if ( currlayer->algo->getGeneration() > currlayer->startgen ) {
51         // don't do anything if current gen count > starting gen
52         return true;
53     }
54 
55     // save current name, rule, dirty flag, scale, location, etc
56     currlayer->startname = currlayer->currname;
57     currlayer->startrule = wxString(currlayer->algo->getrule(), wxConvLocal);
58     currlayer->startdirty = currlayer->dirty;
59     currlayer->startmag = viewptr->GetMag();
60     viewptr->GetPos(currlayer->startx, currlayer->starty);
61     currlayer->startbase = currlayer->currbase;
62     currlayer->startexpo = currlayer->currexpo;
63     currlayer->startalgo = currlayer->algtype;
64 
65     // if this layer is a clone then save some settings in other clones
66     if (currlayer->cloneid > 0) {
67         for ( int i = 0; i < numlayers; i++ ) {
68             Layer* cloneptr = GetLayer(i);
69             if (cloneptr != currlayer && cloneptr->cloneid == currlayer->cloneid) {
70                 cloneptr->startname = cloneptr->currname;
71                 cloneptr->startx = cloneptr->view->x;
72                 cloneptr->starty = cloneptr->view->y;
73                 cloneptr->startmag = cloneptr->view->getmag();
74                 cloneptr->startbase = cloneptr->currbase;
75                 cloneptr->startexpo = cloneptr->currexpo;
76             }
77         }
78     }
79 
80     // save current selection
81     currlayer->startsel = currlayer->currsel;
82 
83     if ( !currlayer->savestart ) {
84         // no need to save pattern (use currlayer->currfile as the starting pattern)
85         if (currlayer->currfile.IsEmpty())
86             Warning(_("Bug in SaveStartingPattern: currfile is empty!"));
87         return true;
88     }
89 
90     currlayer->currfile = currlayer->tempstart;     // ResetPattern will load tempstart
91 
92     // save starting pattern in tempstart file
93     if ( currlayer->algo->hyperCapable() ) {
94         // much faster to save pattern in a macrocell file
95         const char* err = WritePattern(currlayer->tempstart, MC_format,
96                                        no_compression, 0, 0, 0, 0);
97         if (err) {
98             statusptr->ErrorMessage(wxString(err,wxConvLocal));
99             // don't allow user to continue generating
100             return false;
101         }
102     } else {
103         // can only save as RLE if edges are within getcell/setcell limits
104         bigint top, left, bottom, right;
105         currlayer->algo->findedges(&top, &left, &bottom, &right);
106         if ( viewptr->OutsideLimits(top, left, bottom, right) ) {
107             statusptr->ErrorMessage(_("Starting pattern is outside +/- 10^9 boundary."));
108             // don't allow user to continue generating
109             return false;
110         }
111         int itop = top.toint();
112         int ileft = left.toint();
113         int ibottom = bottom.toint();
114         int iright = right.toint();
115         // use XRLE format so the pattern's top left location and the current
116         // generation count are stored in the file
117         const char* err = WritePattern(currlayer->tempstart, XRLE_format, no_compression,
118                                        itop, ileft, ibottom, iright);
119         if (err) {
120             statusptr->ErrorMessage(wxString(err,wxConvLocal));
121             // don't allow user to continue generating
122             return false;
123         }
124     }
125 
126     return true;
127 }
128 
129 // -----------------------------------------------------------------------------
130 
ResetPattern(bool resetundo)131 void MainFrame::ResetPattern(bool resetundo)
132 {
133     if (currlayer->algo->getGeneration() == currlayer->startgen) return;
134 
135     if (generating) {
136         command_pending = true;
137         cmdevent.SetId(ID_RESET);
138         Stop();
139         return;
140     }
141 
142     if (inscript) stop_after_script = true;
143 
144     if (currlayer->algo->getGeneration() < currlayer->startgen) {
145         // if this happens then startgen logic is wrong
146         Warning(_("Current gen < starting gen!"));
147         return;
148     }
149 
150     if (currlayer->currfile.IsEmpty()) {
151         // if this happens then savestart or currfile logic is wrong
152         Warning(_("Starting pattern cannot be restored!"));
153         return;
154     }
155 
156     if (allowundo && !currlayer->stayclean && inscript) {
157         // script called reset()
158         SavePendingChanges();
159         currlayer->undoredo->RememberGenStart();
160     }
161 
162     // save current algo and rule
163     algo_type oldalgo = currlayer->algtype;
164     wxString oldrule = wxString(currlayer->algo->getrule(), wxConvLocal);
165 
166     // restore pattern and settings saved by SaveStartingPattern;
167     // first restore algorithm
168     currlayer->algtype = currlayer->startalgo;
169 
170     // restore starting pattern
171     LoadPattern(currlayer->currfile, wxEmptyString);
172 
173     if (currlayer->algo->getGeneration() != currlayer->startgen) {
174         // LoadPattern failed to reset the gen count to startgen
175         // (probably because the user deleted the starting pattern)
176         // so best to clear the pattern and reset the gen count
177         CreateUniverse();
178         currlayer->algo->setGeneration(currlayer->startgen);
179         Warning(_("Failed to reset pattern from this file:\n") + currlayer->currfile);
180     }
181 
182     // restore settings saved by SaveStartingPattern
183     RestoreRule(currlayer->startrule);
184     currlayer->currname = currlayer->startname;
185     currlayer->dirty = currlayer->startdirty;
186     if (restoreview) {
187         viewptr->SetPosMag(currlayer->startx, currlayer->starty, currlayer->startmag);
188     }
189 
190     // restore step size and set increment
191     currlayer->currbase = currlayer->startbase;
192     currlayer->currexpo = currlayer->startexpo;
193     SetGenIncrement();
194 
195     // if this layer is a clone then restore some settings in other clones
196     if (currlayer->cloneid > 0) {
197         for ( int i = 0; i < numlayers; i++ ) {
198             Layer* cloneptr = GetLayer(i);
199             if (cloneptr != currlayer && cloneptr->cloneid == currlayer->cloneid) {
200                 cloneptr->currname = cloneptr->startname;
201                 if (restoreview) {
202                     cloneptr->view->setpositionmag(cloneptr->startx, cloneptr->starty,
203                                                    cloneptr->startmag);
204                 }
205                 cloneptr->currbase = cloneptr->startbase;
206                 cloneptr->currexpo = cloneptr->startexpo;
207                 // also synchronize dirty flags and update items in Layer menu
208                 cloneptr->dirty = currlayer->dirty;
209                 UpdateLayerItem(i);
210             }
211         }
212     }
213 
214     // restore selection
215     currlayer->currsel = currlayer->startsel;
216 
217     // switch to default colors if algo/rule changed
218     wxString newrule = wxString(currlayer->algo->getrule(), wxConvLocal);
219     if (oldalgo != currlayer->algtype || oldrule != newrule) {
220         UpdateLayerColors();
221     }
222 
223     // update window title in case currname, rule or dirty flag changed;
224     // note that UpdateLayerItem(currindex) gets called
225     SetWindowTitle(currlayer->currname);
226     UpdateEverything();
227 
228     if (allowundo && !currlayer->stayclean) {
229         if (inscript) {
230             // script called reset() so remember gen change (RememberGenStart was called above)
231             currlayer->undoredo->RememberGenFinish();
232         } else if (resetundo) {
233             // wind back the undo history to the starting pattern
234             currlayer->undoredo->SyncUndoHistory();
235         }
236     }
237 }
238 
239 // -----------------------------------------------------------------------------
240 
RestorePattern(bigint & gen,const wxString & filename,bigint & x,bigint & y,int mag,int base,int expo)241 void MainFrame::RestorePattern(bigint& gen, const wxString& filename,
242                                bigint& x, bigint& y, int mag, int base, int expo)
243 {
244     // called to undo/redo a generating change
245     if (gen == currlayer->startgen) {
246         // restore starting pattern (false means don't call SyncUndoHistory)
247         ResetPattern(false);
248     } else {
249         // restore pattern in given filename;
250         // false means don't update status bar (algorithm should NOT change)
251         LoadPattern(filename, wxEmptyString, false);
252 
253         if (currlayer->algo->getGeneration() != gen) {
254             // best to clear the pattern and set the expected gen count
255             CreateUniverse();
256             currlayer->algo->setGeneration(gen);
257             Warning(_("Could not restore pattern from this file:\n") + filename);
258         }
259 
260         // restore step size and set increment
261         currlayer->currbase = base;
262         currlayer->currexpo = expo;
263         SetGenIncrement();
264 
265         // restore position and scale, if allowed
266         if (restoreview) viewptr->SetPosMag(x, y, mag);
267 
268         UpdatePatternAndStatus();
269     }
270 }
271 
272 // -----------------------------------------------------------------------------
273 
ChangeGenCount(const char * genstring,bool inundoredo)274 const char* MainFrame::ChangeGenCount(const char* genstring, bool inundoredo)
275 {
276     // disallow alphabetic chars in genstring
277     for (unsigned int i = 0; i < strlen(genstring); i++)
278         if ( (genstring[i] >= 'a' && genstring[i] <= 'z') ||
279              (genstring[i] >= 'A' && genstring[i] <= 'Z') )
280             return "Alphabetic character is not allowed in generation string.";
281 
282     bigint oldgen = currlayer->algo->getGeneration();
283     bigint newgen(genstring);
284 
285     if (genstring[0] == '+' || genstring[0] == '-') {
286         // leading +/- sign so make newgen relative to oldgen
287         bigint relgen = newgen;
288         newgen = oldgen;
289         newgen += relgen;
290         if (newgen < bigint::zero) newgen = bigint::zero;
291     }
292 
293     // set stop_after_script BEFORE testing newgen == oldgen so scripts
294     // can call setgen("+0") to prevent further generating
295     if (inscript) stop_after_script = true;
296 
297     if (newgen == oldgen) return NULL;
298 
299     if (!inundoredo && allowundo && !currlayer->stayclean && inscript) {
300         // script called setgen()
301         SavePendingChanges();
302     }
303 
304     // need IsParityShifted() method???
305     if (currlayer->algtype == QLIFE_ALGO && newgen.odd() != oldgen.odd()) {
306         // qlife stores pattern in different bits depending on gen parity,
307         // so we need to create a new qlife universe, set its gen, copy the
308         // current pattern to the new universe, then switch to that universe
309         bigint top, left, bottom, right;
310         currlayer->algo->findedges(&top, &left, &bottom, &right);
311         if ( viewptr->OutsideLimits(top, left, bottom, right) ) {
312             return "Pattern is too big to copy.";
313         }
314         // create a new universe of same type and same rule
315         lifealgo* newalgo = CreateNewUniverse(currlayer->algtype);
316         const char* err = newalgo->setrule(currlayer->algo->getrule());
317         if (err) {
318             delete newalgo;
319             return "Current rule is no longer valid!";
320         }
321         newalgo->setGeneration(newgen);
322         // copy pattern
323         if ( !viewptr->CopyRect(top.toint(), left.toint(), bottom.toint(), right.toint(),
324                                 currlayer->algo, newalgo, false, _("Copying pattern")) ) {
325             delete newalgo;
326             return "Failed to copy pattern.";
327         }
328         // switch to new universe
329         delete currlayer->algo;
330         currlayer->algo = newalgo;
331         SetGenIncrement();
332     } else {
333         currlayer->algo->setGeneration(newgen);
334     }
335 
336     if (!inundoredo) {
337         // save some settings for RememberSetGen below
338         bigint oldstartgen = currlayer->startgen;
339         bool oldsave = currlayer->savestart;
340 
341         // may need to change startgen and savestart
342         if (oldgen == currlayer->startgen || newgen <= currlayer->startgen) {
343             currlayer->startgen = newgen;
344             currlayer->savestart = true;
345         }
346 
347         if (allowundo && !currlayer->stayclean) {
348             currlayer->undoredo->RememberSetGen(oldgen, newgen, oldstartgen, oldsave);
349         }
350     }
351 
352     UpdateStatus();
353     return NULL;
354 }
355 
356 // -----------------------------------------------------------------------------
357 
SetGeneration()358 void MainFrame::SetGeneration()
359 {
360     if (generating) {
361         command_pending = true;
362         cmdevent.SetId(ID_SETGEN);
363         Stop();
364         return;
365     }
366 
367     bigint oldgen = currlayer->algo->getGeneration();
368     wxString result;
369     wxString prompt = _("Enter a new generation count:");
370     prompt += _("\n(+n/-n is relative to current count)");
371     if ( GetString(_("Set Generation"), prompt,
372                    wxString(oldgen.tostring(), wxConvLocal), result) ) {
373 
374         const char* err = ChangeGenCount(result.mb_str(wxConvLocal));
375 
376         if (err) {
377             Warning(wxString(err,wxConvLocal));
378         } else {
379             // Reset/Undo/Redo items might become enabled or disabled
380             // (we need to do this if user clicked "Generation=..." text)
381             UpdateMenuItems();
382         }
383     }
384 }
385 
386 // -----------------------------------------------------------------------------
387 
SetGenIncrement()388 void MainFrame::SetGenIncrement()
389 {
390     if (currlayer->currexpo > 0) {
391         bigint inc = 1;
392         int maxexpo = 1 ;
393         if (currlayer->currbase <= 10000) {
394            int mantissa = currlayer->currbase ;
395            int himantissa = 0x7fffffff ;
396            while (mantissa > 1 && 0 == (mantissa & 1))
397               mantissa >>= 1 ;
398            if (mantissa == 1) {
399               maxexpo = 0x7fffffff ;
400            } else {
401               int p = mantissa ;
402               while (p <= himantissa / mantissa) {
403                  p *= mantissa ;
404                  maxexpo++ ;
405               }
406            }
407         }
408         if (currlayer->currexpo > maxexpo)
409            currlayer->currexpo = maxexpo ;
410         // set increment to currbase^currexpo
411         int i = currlayer->currexpo;
412         while (i > 0) {
413             if (currlayer->currbase > 10000) {
414                inc = currlayer->currbase ;
415             } else {
416                inc.mul_smallint(currlayer->currbase);
417             }
418             i--;
419         }
420         currlayer->algo->setIncrement(inc);
421     } else {
422         currlayer->algo->setIncrement(1);
423     }
424 }
425 
426 // -----------------------------------------------------------------------------
427 
StartGenTimer()428 void MainFrame::StartGenTimer()
429 {
430     int interval = SIXTY_HERTZ;     // do ~60 calls of OnGenTimer per sec
431 
432     // increase interval if user wants a delay
433     if (currlayer->currexpo < 0) {
434         interval = statusptr->GetCurrentDelay();
435         if (interval < SIXTY_HERTZ) interval += SIXTY_HERTZ;
436     }
437 
438     if (gentimer->IsRunning()) gentimer->Stop();
439     gentimer->Start(interval, wxTIMER_CONTINUOUS);
440 }
441 
442 // -----------------------------------------------------------------------------
443 
GoFaster()444 void MainFrame::GoFaster()
445 {
446     if (TimelineExists()) {
447         PlayTimelineFaster();
448     } else {
449         currlayer->currexpo++;
450         SetGenIncrement();
451         // only need to refresh status bar
452         UpdateStatus();
453         if (generating && currlayer->currexpo <= 0) {
454             // decrease gentimer interval
455             StartGenTimer();
456         }
457     }
458 }
459 
460 // -----------------------------------------------------------------------------
461 
GoSlower()462 void MainFrame::GoSlower()
463 {
464     if (TimelineExists()) {
465         PlayTimelineSlower();
466     } else {
467         if (currlayer->currexpo > minexpo) {
468             currlayer->currexpo--;
469             SetGenIncrement();
470             // only need to refresh status bar
471             UpdateStatus();
472             if (generating && currlayer->currexpo < 0) {
473                 // increase gentimer interval
474                 StartGenTimer();
475             }
476         } else {
477             Beep();
478         }
479     }
480 }
481 
482 // -----------------------------------------------------------------------------
483 
SetMinimumStepExponent()484 void MainFrame::SetMinimumStepExponent()
485 {
486     // set minexpo depending on mindelay and maxdelay
487     minexpo = 0;
488     if (mindelay > 0) {
489         int d = mindelay;
490         minexpo--;
491         while (d < maxdelay) {
492             d *= 2;
493             minexpo--;
494         }
495     }
496 }
497 
498 // -----------------------------------------------------------------------------
499 
UpdateStepExponent()500 void MainFrame::UpdateStepExponent()
501 {
502     SetMinimumStepExponent();
503     if (currlayer->currexpo < minexpo) currlayer->currexpo = minexpo;
504     SetGenIncrement();
505 
506     if (generating && currlayer->currexpo <= 0) {
507         // update gentimer interval
508         StartGenTimer();
509     }
510 }
511 
512 // -----------------------------------------------------------------------------
513 
SetStepExponent(int newexpo)514 void MainFrame::SetStepExponent(int newexpo)
515 {
516     currlayer->currexpo = newexpo;
517     if (currlayer->currexpo < minexpo) currlayer->currexpo = minexpo;
518     SetGenIncrement();
519 
520     if (generating && currlayer->currexpo <= 0) {
521         // update gentimer interval
522         StartGenTimer();
523     }
524 }
525 
526 // -----------------------------------------------------------------------------
527 
SetBaseStep()528 void MainFrame::SetBaseStep()
529 {
530     int i;
531     if ( GetInteger(_("Set Base Step"),
532                     _("Temporarily change the current base step:"),
533                     currlayer->currbase, 2, MAX_BASESTEP, &i) ) {
534         currlayer->currbase = i;
535         SetGenIncrement();
536         UpdateStatus();
537     }
538 }
539 
540 // -----------------------------------------------------------------------------
541 
DisplayPattern()542 void MainFrame::DisplayPattern()
543 {
544     // this routine is similar to UpdatePatternAndStatus() but if tiled windows
545     // exist it only updates the current tile if possible; ie. it's not a clone
546     // and tile views aren't synchronized
547 
548     if (IsIconized()) return;
549 
550     if (tilelayers && numlayers > 1 && !syncviews && currlayer->cloneid == 0) {
551         // only update the current tile
552         viewptr->Refresh(false);
553     } else {
554         // update main viewport window, possibly including all tile windows
555         // (tile windows are children of bigview)
556         if (numlayers > 1 && (stacklayers || tilelayers)) {
557             bigview->Refresh(false);
558         } else {
559             viewptr->Refresh(false);
560         }
561     }
562 
563     if (showstatus) {
564         statusptr->CheckMouseLocation(infront);
565         statusptr->Refresh(false);
566     }
567 }
568 
569 // -----------------------------------------------------------------------------
570 
StepPattern()571 bool MainFrame::StepPattern()
572 {
573     lifealgo* curralgo = currlayer->algo;
574     if (curralgo->unbounded && (curralgo->gridwd > 0 || curralgo->gridht > 0)) {
575         // bounded grid, so temporarily set the increment to 1 so we can call
576         // CreateBorderCells() and DeleteBorderCells() around each step()
577         int savebase = currlayer->currbase;
578         int saveexpo = currlayer->currexpo;
579         bigint inc = curralgo->getIncrement();
580         curralgo->setIncrement(1);
581         while (inc > 0) {
582             if (wxGetApp().Poller()->checkevents()) {
583                 SetGenIncrement();         // restore correct increment
584                 return false;
585             }
586             if (savebase != currlayer->currbase || saveexpo != currlayer->currexpo) {
587                 // user changed step base/exponent, so best to simply exit loop
588                 break;
589             }
590             if (!curralgo->CreateBorderCells()) {
591                 SetGenIncrement();         // restore correct increment
592                 return false;
593             }
594             curralgo->step();
595             if (!curralgo->DeleteBorderCells()) {
596                 SetGenIncrement();         // restore correct increment
597                 return false;
598             }
599             if (curralgo->isrecording()) curralgo->extendtimeline();
600             inc -= 1;
601         }
602         // safe way to restore correct increment in case user altered step base/exponent
603         SetGenIncrement();
604     } else {
605         if (wxGetApp().Poller()->checkevents()) return false;
606         curralgo->step();
607         if (curralgo->isrecording()) curralgo->extendtimeline();
608     }
609 
610     if (currlayer->autofit) viewptr->FitInView(0);
611 
612     if (!IsIconized()) DisplayPattern();
613 
614     /* enable this code if we ever implement isPeriodic()
615     if (autostop) {
616         int period = curralgo->isPeriodic();
617         if (period > 0) {
618             if (period == 1) {
619                 if (curralgo->isEmpty()) {
620                     statusptr->DisplayMessage(_("Pattern is empty."));
621                 } else {
622                     statusptr->DisplayMessage(_("Pattern is stable."));
623                 }
624             } else {
625                 wxString s;
626                 s.Printf(_("Pattern is oscillating (period = %d)."), period);
627                 statusptr->DisplayMessage(s);
628             }
629             return false;
630         }
631     }
632     */
633 
634     return true;
635 }
636 
637 // -----------------------------------------------------------------------------
638 
DoPendingAction(bool restart)639 void MainFrame::DoPendingAction(bool restart)
640 {
641     if (command_pending) {
642         command_pending = false;
643 
644         int id = cmdevent.GetId();
645         switch (id) {
646             // don't restart the generating loop after some commands
647             case wxID_NEW:          NewPattern(); break;
648             case wxID_OPEN:         OpenPattern(); break;
649             case ID_OPEN_CLIP:      OpenClipboard(); break;
650             case ID_RESET:          ResetPattern(); break;
651             case ID_SETGEN:         SetGeneration(); break;
652             case ID_UNDO:           currlayer->undoredo->UndoChange(); break;
653             case ID_ADD_LAYER:      AddLayer(); break;
654             case ID_DUPLICATE:      DuplicateLayer(); break;
655             case ID_LOAD_LEXICON:   LoadLexiconPattern(); break;
656             default:
657                 if ( id > ID_OPEN_RECENT && id <= ID_OPEN_RECENT + numpatterns ) {
658                     OpenRecentPattern(id);
659 
660                 } else if ( id > ID_RUN_RECENT && id <= ID_RUN_RECENT + numscripts ) {
661                     OpenRecentScript(id);
662                     if (restart && !stop_after_script) {
663                         wxCommandEvent goevt(wxEVT_COMMAND_MENU_SELECTED, ID_START);
664                         wxPostEvent(this->GetEventHandler(), goevt);
665                         // avoid clearing status message due to script like density.py
666                         keepmessage = true;
667                     }
668 
669                 } else if ( id == ID_RUN_SCRIPT ) {
670                     OpenScript();
671                     if (restart && !stop_after_script) {
672                         wxCommandEvent goevt(wxEVT_COMMAND_MENU_SELECTED, ID_START);
673                         wxPostEvent(this->GetEventHandler(), goevt);
674                         // avoid clearing status message due to script like density.py
675                         keepmessage = true;
676                     }
677 
678                 } else if ( id == ID_RUN_CLIP ) {
679                     RunClipboard();
680                     if (restart && !stop_after_script) {
681                         wxCommandEvent goevt(wxEVT_COMMAND_MENU_SELECTED, ID_START);
682                         wxPostEvent(this->GetEventHandler(), goevt);
683                         // avoid clearing status message due to script like density.py
684                         keepmessage = true;
685                     }
686 
687                 } else if ( id >= ID_LAYER0 && id <= ID_LAYERMAX ) {
688                     int oldcloneid = currlayer->cloneid;
689                     SetLayer(id - ID_LAYER0);
690                     // continue generating if new layer is a clone of old layer
691                     if (restart && currlayer->cloneid > 0 && currlayer->cloneid == oldcloneid) {
692                         wxCommandEvent goevt(wxEVT_COMMAND_MENU_SELECTED, ID_START);
693                         wxPostEvent(this->GetEventHandler(), goevt);
694                     }
695 
696                 } else if ( id == ID_DEL_LAYER ) {
697                     int wasclone = currlayer->cloneid > 0 &&
698                     ((currindex == 0 && currlayer->cloneid == GetLayer(1)->cloneid) ||
699                      (currindex > 0 && currlayer->cloneid == GetLayer(currindex-1)->cloneid));
700                     DeleteLayer();
701                     // continue generating if new layer is/was a clone of old layer
702                     if (restart && wasclone) {
703                         wxCommandEvent goevt(wxEVT_COMMAND_MENU_SELECTED, ID_START);
704                         wxPostEvent(this->GetEventHandler(), goevt);
705                     }
706 
707                 } else {
708                     // temporarily pretend the tool/layer/edit bars are not showing
709                     // to avoid Update[Tool/Layer/Edit]Bar changing button states
710                     bool saveshowtool = showtool;    showtool = false;
711                     bool saveshowlayer = showlayer;  showlayer = false;
712                     bool saveshowedit = showedit;    showedit = false;
713 
714                     // process the pending command
715                     cmdevent.SetEventType(wxEVT_COMMAND_MENU_SELECTED);
716                     cmdevent.SetEventObject(this);
717                     this->GetEventHandler()->ProcessEvent(cmdevent);
718 
719                     // restore tool/layer/edit bar flags
720                     showtool = saveshowtool;
721                     showlayer = saveshowlayer;
722                     showedit = saveshowedit;
723 
724                     if (restart) {
725                         // call StartGenerating again
726                         wxCommandEvent goevt(wxEVT_COMMAND_MENU_SELECTED, ID_START);
727                         wxPostEvent(this->GetEventHandler(), goevt);
728                     }
729                 }
730         }
731     }
732 
733     if (draw_pending) {
734         draw_pending = false;
735 
736         // temporarily pretend the tool/layer/edit bars are not showing
737         // to avoid Update[Tool/Layer/Edit]Bar changing button states
738         bool saveshowtool = showtool;    showtool = false;
739         bool saveshowlayer = showlayer;  showlayer = false;
740         bool saveshowedit = showedit;    showedit = false;
741 
742         UpdateEverything();
743 
744         // do the drawing by creating a mouse down event so PatternView::OnMouseDown is called again,
745         // but we need to reset mouseisdown flag which is currently true
746         viewptr->mouseisdown = false;
747         mouseevent.SetEventType(wxEVT_LEFT_DOWN);
748         mouseevent.SetEventObject(viewptr);
749         viewptr->GetEventHandler()->ProcessEvent(mouseevent);
750         while (viewptr->drawingcells) {
751             insideYield = true;
752             wxGetApp().Yield(true);
753             insideYield = false;
754             wxMilliSleep(5);             // don't hog CPU
755         }
756 
757         // restore tool/layer/edit bar flags
758         showtool = saveshowtool;
759         showlayer = saveshowlayer;
760         showedit = saveshowedit;
761 
762         if (restart) {
763             // call StartGenerating again
764             wxCommandEvent goevt(wxEVT_COMMAND_MENU_SELECTED, ID_START);
765             wxPostEvent(this->GetEventHandler(), goevt);
766         }
767     }
768 }
769 
770 // -----------------------------------------------------------------------------
771 
DisplayTimingInfo()772 void MainFrame::DisplayTimingInfo()
773 {
774     if (viewptr->waitingforclick) return;
775     if (generating) {
776         endtime = stopwatch->Time();
777         endgen = currlayer->algo->getGeneration().todouble();
778     }
779     if (endtime > begintime) {
780         double secs = (double)(endtime - begintime) / 1000.0;
781         double gens = endgen - begingen;
782         wxString s;
783         s.Printf(_("%g gens in %g secs (%g gens/sec)."), gens, secs, gens/secs);
784         statusptr->DisplayMessage(s);
785     }
786 }
787 
788 // -----------------------------------------------------------------------------
789 
StartGenerating()790 void MainFrame::StartGenerating()
791 {
792     if (insideYield || viewptr->drawingcells || viewptr->waitingforclick) {
793         return;
794     }
795 
796     if (currlayer->algo->isEmpty()) {
797         statusptr->ErrorMessage(empty_pattern);
798         return;
799     }
800 
801     if (currlayer->algo->isrecording()) {
802         // don't attempt to save starting pattern here (let DeleteTimeline do it)
803     } else if (!SaveStartingPattern()) {
804         return;
805     }
806 
807     // no need to test inscript or currlayer->stayclean
808     if (allowundo && !currlayer->algo->isrecording()) currlayer->undoredo->RememberGenStart();
809 
810     // for DisplayTimingInfo
811     begintime = stopwatch->Time();
812     begingen = currlayer->algo->getGeneration().todouble();
813 
814     // for hyperspeed
815     hypdown = 64;
816 
817     generating = true;
818     wxGetApp().PollerReset();
819     UpdateUserInterface();
820 
821     // only show hashing info while generating
822     lifealgo::setVerbose(currlayer->showhashinfo);
823 
824     StartGenTimer();
825 }
826 
827 // -----------------------------------------------------------------------------
828 
FinishUp()829 void MainFrame::FinishUp()
830 {
831     // display the final pattern
832     if (currlayer->autofit) viewptr->FitInView(0);
833     if (command_pending || draw_pending) {
834         // let the pending command/draw do the update below
835     } else {
836         UpdateEverything();
837     }
838 
839     // note that we must call RememberGenFinish BEFORE processing any pending command
840     if (allowundo && !currlayer->algo->isrecording()) currlayer->undoredo->RememberGenFinish();
841 
842     // stop recording any timeline before processing any pending command
843     if (currlayer->algo->isrecording()) {
844         currlayer->algo->stoprecording();
845         if (currlayer->algo->getframecount() > 0) {
846             // probably best to go to last frame
847             currlayer->currframe = currlayer->algo->getframecount() - 1;
848             currlayer->autoplay = 0;
849             currlayer->tlspeed = 0;
850             currlayer->algo->gotoframe(currlayer->currframe);
851             if (currlayer->autofit) viewptr->FitInView(1);
852         }
853         if (!showtimeline) ToggleTimelineBar();
854         UpdateUserInterface();
855     }
856 
857     DoPendingAction(true);     // true means we can restart generating
858 }
859 
860 // -----------------------------------------------------------------------------
861 
StopGenerating()862 void MainFrame::StopGenerating()
863 {
864     if (gentimer->IsRunning()) gentimer->Stop();
865     generating = false;
866     wxGetApp().PollerInterrupt();
867     lifealgo::setVerbose(0);
868 
869     // for DisplayTimingInfo
870     endtime = stopwatch->Time();
871     endgen = currlayer->algo->getGeneration().todouble();
872 
873     if (insideYield) {
874         // we're currently in the event poller somewhere inside step(), so we must let
875         // step() complete and only call FinishUp after StepPattern has finished
876     } else {
877         FinishUp();
878     }
879 }
880 
881 // -----------------------------------------------------------------------------
882 
883 // this flag is used to avoid re-entrancy in OnGenTimer (note that on Windows
884 // the timer can fire while a wxMessageBox dialog is open)
885 static bool in_timer = false;
886 
OnGenTimer(wxTimerEvent & WXUNUSED (event))887 void MainFrame::OnGenTimer(wxTimerEvent& WXUNUSED(event))
888 {
889     if (in_timer) return;
890     in_timer = true;
891 
892     if (!StepPattern()) {
893         if (generating) {
894             // call StopGenerating() to stop gentimer
895             Stop();
896         } else {
897             // StopGenerating() was called while insideYield
898             FinishUp();
899         }
900         in_timer = false;
901         return;
902     }
903 
904     if (currlayer->algo->isrecording()) {
905         if (showtimeline) UpdateTimelineBar();
906         if (currlayer->algo->getframecount() == MAX_FRAME_COUNT) {
907             if (generating) {
908                 // call StopGenerating() to stop gentimer
909                 Stop();
910             } else {
911                 // StopGenerating() was called while insideYield
912                 FinishUp();
913             }
914             wxString msg;
915             msg.Printf(_("No more frames can be recorded (maximum = %d)."), MAX_FRAME_COUNT);
916             Warning(msg);
917             in_timer = false;
918             return;
919         }
920     } else if (currlayer->hyperspeed && currlayer->algo->hyperCapable()) {
921         hypdown--;
922         if (hypdown == 0) {
923             hypdown = 64;
924             GoFaster();
925         }
926     }
927 
928     if (!generating) {
929         // StopGenerating() was called while insideYield
930         FinishUp();
931     }
932 
933     in_timer = false;
934 }
935 
936 // -----------------------------------------------------------------------------
937 
Stop()938 void MainFrame::Stop()
939 {
940     if (inscript) {
941         PassKeyToScript(WXK_ESCAPE);
942     } else if (generating) {
943         StopGenerating();
944     }
945 }
946 
947 // -----------------------------------------------------------------------------
948 
StartOrStop()949 void MainFrame::StartOrStop()
950 {
951     if (inscript || generating) {
952         Stop();
953     } else if (TimelineExists()) {
954         if (currlayer->algo->isrecording()) {
955             // should never happen if generating is false
956             Warning(_("Bug: recording but not generating!"));
957         } else {
958             PlayTimeline(1);        // play forwards or stop if already playing
959         }
960     } else {
961         StartGenerating();
962     }
963 }
964 
965 // -----------------------------------------------------------------------------
966 
967 // this global flag is used to avoid re-entrancy in NextGeneration()
968 // due to holding down the space/tab key
969 static bool inNextGen = false;
970 
NextGeneration(bool useinc)971 void MainFrame::NextGeneration(bool useinc)
972 {
973     if (inNextGen) return;
974     inNextGen = true;
975 
976     if (!inscript && generating) {
977         Stop();
978         inNextGen = false;
979         return;
980     }
981 
982     if (insideYield) {
983         // avoid calling step() recursively
984         inNextGen = false;
985         return;
986     }
987 
988     if (viewptr->drawingcells || viewptr->waitingforclick) {
989         Beep();
990         inNextGen = false;
991         return;
992     }
993 
994     // best if generating stops after running a script like oscar.py or goto.py
995     if (inscript) stop_after_script = true;
996 
997     lifealgo* curralgo = currlayer->algo;
998     if (curralgo->isEmpty()) {
999         statusptr->ErrorMessage(empty_pattern);
1000         inNextGen = false;
1001         return;
1002     }
1003 
1004     if (!SaveStartingPattern()) {
1005         inNextGen = false;
1006         return;
1007     }
1008 
1009     if (allowundo) {
1010         if (currlayer->stayclean) {
1011             // script has called run/step after a new/open command has set
1012             // stayclean true by calling MarkLayerClean
1013             if (curralgo->getGeneration() == currlayer->startgen) {
1014                 // starting pattern has just been saved so we need to remember
1015                 // this gen change in case user does a Reset after script ends
1016                 // (RememberGenFinish will be called at the end of RunScript)
1017                 if (currlayer->undoredo->savegenchanges) {
1018                     // script must have called reset command, so we need to call
1019                     // RememberGenFinish to match earlier RememberGenStart
1020                     currlayer->undoredo->savegenchanges = false;
1021                     currlayer->undoredo->RememberGenFinish();
1022                 }
1023                 currlayer->undoredo->RememberGenStart();
1024             }
1025         } else {
1026             // !currlayer->stayclean
1027             if (inscript) {
1028                 // pass in false so we don't test savegenchanges flag;
1029                 // ie. we only want to save pending cell changes here
1030                 SavePendingChanges(false);
1031             }
1032             currlayer->undoredo->RememberGenStart();
1033         }
1034     }
1035 
1036     // curralgo->step() calls checkevents() so set generating flag
1037     generating = true;
1038 
1039     // only show hashing info while generating
1040     lifealgo::setVerbose( currlayer->showhashinfo );
1041 
1042     // avoid doing some things if NextGeneration is called from a script;
1043     // ie. by a run/step command
1044     if (!inscript) {
1045         if (useinc) {
1046             // for DisplayTimingInfo
1047             begintime = stopwatch->Time();
1048             begingen = curralgo->getGeneration().todouble();
1049         }
1050         wxGetApp().PollerReset();
1051         viewptr->CheckCursor(infront);
1052     }
1053 
1054     bool boundedgrid = curralgo->unbounded && (curralgo->gridwd > 0 || curralgo->gridht > 0);
1055 
1056     if (useinc) {
1057         // step by current increment
1058         if (curralgo->getIncrement() > bigint::one && !inscript) {
1059             UpdateToolBar();
1060             UpdateMenuItems();
1061         }
1062         if (boundedgrid) {
1063             // temporarily set the increment to 1 so we can call CreateBorderCells()
1064             // and DeleteBorderCells() around each step()
1065             int savebase = currlayer->currbase;
1066             int saveexpo = currlayer->currexpo;
1067             bigint inc = curralgo->getIncrement();
1068             curralgo->setIncrement(1);
1069             while (inc > 0) {
1070                 if (wxGetApp().Poller()->checkevents()) break;
1071                 if (savebase != currlayer->currbase || saveexpo != currlayer->currexpo) {
1072                     // user changed step base/exponent, so reset increment to 1
1073                     inc = curralgo->getIncrement();
1074                     curralgo->setIncrement(1);
1075                 }
1076                 if (!curralgo->CreateBorderCells()) break;
1077                 curralgo->step();
1078                 if (!curralgo->DeleteBorderCells()) break;
1079                 inc -= 1;
1080             }
1081             // safe way to restore correct increment in case user altered base/expo in above loop
1082             SetGenIncrement();
1083         } else {
1084             curralgo->step();
1085         }
1086     } else {
1087         // step by 1 gen
1088         bigint saveinc = curralgo->getIncrement();
1089         curralgo->setIncrement(1);
1090         if (boundedgrid) curralgo->CreateBorderCells();
1091         curralgo->step();
1092         if (boundedgrid) curralgo->DeleteBorderCells();
1093         curralgo->setIncrement(saveinc);
1094     }
1095 
1096     generating = false;
1097 
1098     lifealgo::setVerbose(0);
1099 
1100     if (!inscript) {
1101         if (useinc) {
1102             // for DisplayTimingInfo (we add 1 millisec here in case it took < 1 millisec)
1103             endtime = stopwatch->Time() + 1;
1104             endgen = curralgo->getGeneration().todouble();
1105         }
1106         // autofit is only used when doing many gens
1107         if (currlayer->autofit && useinc && curralgo->getIncrement() > bigint::one)
1108             viewptr->FitInView(0);
1109         UpdateEverything();
1110     }
1111 
1112     // we must call RememberGenFinish BEFORE processing any pending command
1113     if (allowundo && !currlayer->stayclean)
1114         currlayer->undoredo->RememberGenFinish();
1115 
1116     // process any pending command seen via checkevents() in curralgo->step()
1117     if (!inscript)
1118         DoPendingAction(false);     // false means don't restart generating loop
1119 
1120     inNextGen = false;
1121 }
1122 
1123 // -----------------------------------------------------------------------------
1124 
ToggleAutoFit()1125 void MainFrame::ToggleAutoFit()
1126 {
1127     currlayer->autofit = !currlayer->autofit;
1128 
1129     // we only use autofit when generating; that's why the Auto Fit item
1130     // is in the Control menu and not in the View menu
1131     if (generating && currlayer->autofit) {
1132         viewptr->FitInView(0);
1133         UpdateEverything();
1134     }
1135 }
1136 
1137 // -----------------------------------------------------------------------------
1138 
ToggleHyperspeed()1139 void MainFrame::ToggleHyperspeed()
1140 {
1141     currlayer->hyperspeed = !currlayer->hyperspeed;
1142 }
1143 
1144 // -----------------------------------------------------------------------------
1145 
ToggleHashInfo()1146 void MainFrame::ToggleHashInfo()
1147 {
1148     currlayer->showhashinfo = !currlayer->showhashinfo;
1149 
1150     // only show hashing info while generating
1151     if (generating) lifealgo::setVerbose( currlayer->showhashinfo );
1152 }
1153 
1154 // -----------------------------------------------------------------------------
1155 
ToggleShowPopulation()1156 void MainFrame::ToggleShowPopulation()
1157 {
1158     showpopulation = !showpopulation;
1159 
1160     if (generating && showstatus && !IsIconized()) statusptr->Refresh(false);
1161 }
1162 
1163 // -----------------------------------------------------------------------------
1164 
ClearOutsideGrid()1165 void MainFrame::ClearOutsideGrid()
1166 {
1167     // check current pattern and clear any live cells outside bounded grid
1168     bool patternchanged = false;
1169     bool savechanges = allowundo && !currlayer->stayclean;
1170 
1171     // might also need to truncate selection
1172     currlayer->currsel.CheckGridEdges();
1173 
1174     if (!currlayer->algo->unbounded) {
1175         if (currlayer->algo->clipped_cells.size() > 0) {
1176             // cells outside the grid were clipped
1177             if (savechanges) {
1178                 for (size_t i = 0; i < currlayer->algo->clipped_cells.size(); i += 3) {
1179                     int x = currlayer->algo->clipped_cells[i];
1180                     int y = currlayer->algo->clipped_cells[i+1];
1181                     int s = currlayer->algo->clipped_cells[i+2];
1182                     currlayer->undoredo->SaveCellChange(x, y, s, 0);
1183                 }
1184             }
1185             currlayer->algo->clipped_cells.clear();
1186             patternchanged = true;
1187         }
1188 
1189     } else {
1190         // algo uses an unbounded grid
1191         if (currlayer->algo->isEmpty()) return;
1192 
1193         // check if current pattern is too big to use nextcell/setcell
1194         bigint top, left, bottom, right;
1195         currlayer->algo->findedges(&top, &left, &bottom, &right);
1196         if ( viewptr->OutsideLimits(top, left, bottom, right) ) {
1197             statusptr->ErrorMessage(_("Pattern too big to check (outside +/- 10^9 boundary)."));
1198             return;
1199         }
1200 
1201         int itop = top.toint();
1202         int ileft = left.toint();
1203         int ibottom = bottom.toint();
1204         int iright = right.toint();
1205 
1206         // no need to do anything if pattern is entirely within grid
1207         int gtop = currlayer->algo->gridtop.toint();
1208         int gleft = currlayer->algo->gridleft.toint();
1209         int gbottom = currlayer->algo->gridbottom.toint();
1210         int gright = currlayer->algo->gridright.toint();
1211         if (currlayer->algo->gridwd == 0) {
1212             // grid has infinite width
1213             gleft = INT_MIN;
1214             gright = INT_MAX;
1215         }
1216         if (currlayer->algo->gridht == 0) {
1217             // grid has infinite height
1218             gtop = INT_MIN;
1219             gbottom = INT_MAX;
1220         }
1221         if (itop >= gtop && ileft >= gleft && ibottom <= gbottom && iright <= gright) {
1222             return;
1223         }
1224 
1225         int ht = ibottom - itop + 1;
1226         int cx, cy;
1227 
1228         // for showing accurate progress we need to add pattern height to pop count
1229         // in case this is a huge pattern with many blank rows
1230         double maxcount = currlayer->algo->getPopulation().todouble() + ht;
1231         double accumcount = 0;
1232         int currcount = 0;
1233         bool abort = false;
1234         int v = 0;
1235         BeginProgress(_("Checking cells outside grid"));
1236 
1237         lifealgo* curralgo = currlayer->algo;
1238         for ( cy=itop; cy<=ibottom; cy++ ) {
1239             currcount++;
1240             for ( cx=ileft; cx<=iright; cx++ ) {
1241                 int skip = curralgo->nextcell(cx, cy, v);
1242                 if (skip >= 0) {
1243                     // found next live cell in this row
1244                     cx += skip;
1245                     if (cx < gleft || cx > gright || cy < gtop || cy > gbottom) {
1246                         // clear cell outside grid
1247                         if (savechanges) currlayer->undoredo->SaveCellChange(cx, cy, v, 0);
1248                         curralgo->setcell(cx, cy, 0);
1249                         patternchanged = true;
1250                     }
1251                     currcount++;
1252                 } else {
1253                     cx = iright;  // done this row
1254                 }
1255                 if (currcount > 1024) {
1256                     accumcount += currcount;
1257                     currcount = 0;
1258                     abort = AbortProgress(accumcount / maxcount, wxEmptyString);
1259                     if (abort) break;
1260                 }
1261             }
1262             if (abort) break;
1263         }
1264 
1265         curralgo->endofpattern();
1266         EndProgress();
1267     }
1268 
1269     if (patternchanged) {
1270         statusptr->ErrorMessage(_("Pattern was truncated (live cells were outside grid)."));
1271     }
1272 }
1273 
1274 // -----------------------------------------------------------------------------
1275 
ReduceCellStates(int newmaxstate)1276 void MainFrame::ReduceCellStates(int newmaxstate)
1277 {
1278     // check current pattern and reduce any cell states > newmaxstate
1279     bool patternchanged = false;
1280     bool savechanges = allowundo && !currlayer->stayclean;
1281 
1282     // check if current pattern is too big to use nextcell/setcell
1283     bigint top, left, bottom, right;
1284     currlayer->algo->findedges(&top, &left, &bottom, &right);
1285     if ( viewptr->OutsideLimits(top, left, bottom, right) ) {
1286         statusptr->ErrorMessage(_("Pattern too big to check (outside +/- 10^9 boundary)."));
1287         return;
1288     }
1289 
1290     int itop = top.toint();
1291     int ileft = left.toint();
1292     int ibottom = bottom.toint();
1293     int iright = right.toint();
1294     int ht = ibottom - itop + 1;
1295     int cx, cy;
1296 
1297     // for showing accurate progress we need to add pattern height to pop count
1298     // in case this is a huge pattern with many blank rows
1299     double maxcount = currlayer->algo->getPopulation().todouble() + ht;
1300     double accumcount = 0;
1301     int currcount = 0;
1302     bool abort = false;
1303     int v = 0;
1304     BeginProgress(_("Checking cell states"));
1305 
1306     lifealgo* curralgo = currlayer->algo;
1307     for ( cy=itop; cy<=ibottom; cy++ ) {
1308         currcount++;
1309         for ( cx=ileft; cx<=iright; cx++ ) {
1310             int skip = curralgo->nextcell(cx, cy, v);
1311             if (skip >= 0) {
1312                 // found next live cell in this row
1313                 cx += skip;
1314                 if (v > newmaxstate) {
1315                     // reduce cell's current state to largest state
1316                     if (savechanges) currlayer->undoredo->SaveCellChange(cx, cy, v, newmaxstate);
1317                     curralgo->setcell(cx, cy, newmaxstate);
1318                     patternchanged = true;
1319                 }
1320                 currcount++;
1321             } else {
1322                 cx = iright;  // done this row
1323             }
1324             if (currcount > 1024) {
1325                 accumcount += currcount;
1326                 currcount = 0;
1327                 abort = AbortProgress(accumcount / maxcount, wxEmptyString);
1328                 if (abort) break;
1329             }
1330         }
1331         if (abort) break;
1332     }
1333 
1334     curralgo->endofpattern();
1335     EndProgress();
1336 
1337     if (patternchanged) {
1338         statusptr->ErrorMessage(_("Pattern has changed (new rule has fewer states)."));
1339     }
1340 }
1341 
1342 // -----------------------------------------------------------------------------
1343 
ShowRuleDialog()1344 void MainFrame::ShowRuleDialog()
1345 {
1346     if (inscript || viewptr->waitingforclick) return;
1347 
1348     if (generating) {
1349         command_pending = true;
1350         cmdevent.SetId(ID_SETRULE);
1351         Stop();
1352         return;
1353     }
1354 
1355     algo_type oldalgo = currlayer->algtype;
1356     wxString oldrule = wxString(currlayer->algo->getrule(), wxConvLocal);
1357     int oldmaxstate = currlayer->algo->NumCellStates() - 1;
1358 
1359     // selection might change if grid becomes smaller,
1360     // so save current selection for RememberRuleChange/RememberAlgoChange
1361     viewptr->SaveCurrentSelection();
1362 
1363     if (ChangeRule()) {
1364         if (currlayer->algtype != oldalgo) {
1365         	// ChangeAlgorithm was called so we're almost done;
1366             // we just have to call UpdateEverything now that the main window is active
1367             UpdateEverything();
1368             return;
1369         }
1370 
1371         // show new rule in window title (but don't change file name);
1372         // even if the rule didn't change we still need to do this because
1373         // the user might have simply added/deleted a named rule
1374         SetWindowTitle(wxEmptyString);
1375 
1376         // check if the rule string changed, or the number of states changed
1377         // (the latter might happen if user modified .rule file)
1378         wxString newrule = wxString(currlayer->algo->getrule(), wxConvLocal);
1379         int newmaxstate = currlayer->algo->NumCellStates() - 1;
1380         if (oldrule != newrule || oldmaxstate != newmaxstate) {
1381 
1382             // if pattern exists and is at starting gen then ensure savestart is true
1383             // so that SaveStartingPattern will save pattern to suitable file
1384             // (and thus undo/reset will work correctly)
1385             if (currlayer->algo->getGeneration() == currlayer->startgen && !currlayer->algo->isEmpty()) {
1386                 currlayer->savestart = true;
1387             }
1388 
1389             // if grid is bounded then remove any live cells outside grid edges
1390             if (currlayer->algo->gridwd > 0 || currlayer->algo->gridht > 0) {
1391                 ClearOutsideGrid();
1392             }
1393 
1394             // rule change might have changed the number of cell states;
1395             // if there are fewer states then pattern might change
1396             if (newmaxstate < oldmaxstate && !currlayer->algo->isEmpty()) {
1397                 ReduceCellStates(newmaxstate);
1398             }
1399 
1400             if (allowundo) {
1401                 currlayer->undoredo->RememberRuleChange(oldrule);
1402             }
1403         }
1404 
1405         // switch to default colors and icons for new rule (we need to do this even if
1406         // oldrule == newrule in case colors or icons changed)
1407         UpdateLayerColors();
1408 
1409         // pattern or colors or icons might have changed
1410         UpdateEverything();
1411     }
1412 }
1413 
1414 // -----------------------------------------------------------------------------
1415 
ChangeAlgorithm(algo_type newalgotype,const wxString & newrule,bool inundoredo)1416 void MainFrame::ChangeAlgorithm(algo_type newalgotype, const wxString& newrule, bool inundoredo)
1417 {
1418     if (newalgotype == currlayer->algtype) return;
1419 
1420     // check if current pattern is too big to use nextcell/setcell
1421     bigint top, left, bottom, right;
1422     if ( !currlayer->algo->isEmpty() ) {
1423         currlayer->algo->findedges(&top, &left, &bottom, &right);
1424         if ( viewptr->OutsideLimits(top, left, bottom, right) ) {
1425             statusptr->ErrorMessage(_("Pattern cannot be converted (outside +/- 10^9 boundary)."));
1426             return;
1427         }
1428     }
1429 
1430     if (generating) {
1431         command_pending = true;
1432         cmdevent.SetId(ID_ALGO0 + newalgotype);
1433         Stop();
1434         return;
1435     }
1436 
1437     // save changes if undo/redo is enabled and script isn't constructing a pattern
1438     // and we're not undoing/redoing an earlier algo change
1439     bool savechanges = allowundo && !currlayer->stayclean && !inundoredo;
1440     if (savechanges && inscript) {
1441         // note that we must save pending gen changes BEFORE changing algo type
1442         // otherwise temporary files won't be the correct type (mc or rle)
1443         SavePendingChanges();
1444     }
1445 
1446     // selection might change if grid becomes smaller,
1447     // so save current selection for RememberAlgoChange
1448     if (savechanges) viewptr->SaveCurrentSelection();
1449 
1450     bool rulechanged = false;
1451     wxString oldrule = wxString(currlayer->algo->getrule(), wxConvLocal);
1452 
1453     // change algorithm type, reset step size, and update status bar immediately
1454     algo_type oldalgo = currlayer->algtype;
1455     currlayer->algtype = newalgotype;
1456     currlayer->currbase = algoinfo[newalgotype]->defbase;
1457     currlayer->currexpo = 0;
1458     UpdateStatus();
1459 
1460     // create a new universe of the requested flavor
1461     lifealgo* newalgo = CreateNewUniverse(newalgotype);
1462 
1463     if (inundoredo) {
1464         // switch to given newrule
1465         const char* err = newalgo->setrule( newrule.mb_str(wxConvLocal) );
1466         if (err) newalgo->setrule( newalgo->DefaultRule() );
1467     } else {
1468         const char* err;
1469         if (newrule.IsEmpty()) {
1470             // try to use same rule
1471             err = newalgo->setrule( currlayer->algo->getrule() );
1472         } else {
1473             // switch to newrule
1474             err = newalgo->setrule( newrule.mb_str(wxConvLocal) );
1475             rulechanged = true;
1476         }
1477         if (err) {
1478             wxString defrule = wxString(newalgo->DefaultRule(), wxConvLocal);
1479             if (newrule.IsEmpty() && oldrule.Find(':') >= 0) {
1480                 // switch to new algo's default rule, but preserve the topology in oldrule
1481                 // so we can do things like switch from "LifeHistory:T30,20" in RuleLoader
1482                 // to "B3/S23:T30,20" in QuickLife
1483                 if (defrule.Find(':') >= 0) {
1484                     // default rule shouldn't have a suffix but play safe and remove it
1485                     defrule = defrule.BeforeFirst(':');
1486                 }
1487                 defrule += wxT(":");
1488                 defrule += oldrule.AfterFirst(':');
1489             }
1490             err = newalgo->setrule( defrule.mb_str(wxConvLocal) );
1491             // shouldn't ever fail but play safe
1492             if (err) newalgo->setrule( newalgo->DefaultRule() );
1493             rulechanged = true;
1494         }
1495     }
1496 
1497     // set same gen count
1498     newalgo->setGeneration( currlayer->algo->getGeneration() );
1499 
1500     bool patternchanged = false;
1501     if ( !currlayer->algo->isEmpty() ) {
1502         // copy pattern in current universe to new universe
1503         int itop = top.toint();
1504         int ileft = left.toint();
1505         int ibottom = bottom.toint();
1506         int iright = right.toint();
1507         int ht = ibottom - itop + 1;
1508         int cx, cy;
1509 
1510         // for showing accurate progress we need to add pattern height to pop count
1511         // in case this is a huge pattern with many blank rows
1512         double maxcount = currlayer->algo->getPopulation().todouble() + ht;
1513         double accumcount = 0;
1514         int currcount = 0;
1515         bool abort = false;
1516         int v = 0;
1517         BeginProgress(_("Converting pattern"));
1518 
1519         // set newalgo's grid edges so we can save cells that are outside grid
1520         int gtop = newalgo->gridtop.toint();
1521         int gleft = newalgo->gridleft.toint();
1522         int gbottom = newalgo->gridbottom.toint();
1523         int gright = newalgo->gridright.toint();
1524         if (newalgo->gridwd == 0) {
1525             // grid has infinite width
1526             gleft = INT_MIN;
1527             gright = INT_MAX;
1528         }
1529         if (newalgo->gridht == 0) {
1530             // grid has infinite height
1531             gtop = INT_MIN;
1532             gbottom = INT_MAX;
1533         }
1534 
1535         // need to check for state change if new algo has fewer states than old algo
1536         int newmaxstate = newalgo->NumCellStates() - 1;
1537 
1538         lifealgo* curralgo = currlayer->algo;
1539         for ( cy=itop; cy<=ibottom; cy++ ) {
1540             currcount++;
1541             for ( cx=ileft; cx<=iright; cx++ ) {
1542                 int skip = curralgo->nextcell(cx, cy, v);
1543                 if (skip >= 0) {
1544                     // found next live cell in this row
1545                     cx += skip;
1546                     if (cx < gleft || cx > gright || cy < gtop || cy > gbottom) {
1547                         // cx,cy is outside grid
1548                         if (savechanges) currlayer->undoredo->SaveCellChange(cx, cy, v, 0);
1549                         // no need to clear cell from curralgo (that universe will soon be deleted)
1550                         patternchanged = true;
1551                     } else {
1552                         if (v > newmaxstate) {
1553                             // reduce v to largest state in new algo
1554                             if (savechanges) currlayer->undoredo->SaveCellChange(cx, cy, v, newmaxstate);
1555                             v = newmaxstate;
1556                             patternchanged = true;
1557                         }
1558                         newalgo->setcell(cx, cy, v);
1559                     }
1560                     currcount++;
1561                 } else {
1562                     cx = iright;  // done this row
1563                 }
1564                 if (currcount > 1024) {
1565                     accumcount += currcount;
1566                     currcount = 0;
1567                     abort = AbortProgress(accumcount / maxcount, wxEmptyString);
1568                     if (abort) break;
1569                 }
1570             }
1571             if (abort) break;
1572         }
1573 
1574         newalgo->endofpattern();
1575         EndProgress();
1576     }
1577 
1578     // delete old universe and point current universe to new universe
1579     delete currlayer->algo;
1580     currlayer->algo = newalgo;
1581     SetGenIncrement();
1582 
1583     // if new grid is bounded then we might need to truncate the selection
1584     if (currlayer->algo->gridwd > 0 || currlayer->algo->gridht > 0) {
1585         currlayer->currsel.CheckGridEdges();
1586     }
1587 
1588     // switch to default colors for new algo+rule
1589     UpdateLayerColors();
1590 
1591     if (!inundoredo) {
1592         // if pattern exists and is at starting gen then set savestart true
1593         // so that SaveStartingPattern will save pattern to suitable file
1594         // (and thus ResetPattern will work correctly)
1595         if (currlayer->algo->getGeneration() == currlayer->startgen && !currlayer->algo->isEmpty()) {
1596             currlayer->savestart = true;
1597         }
1598 
1599         if (rulechanged) {
1600             // show new rule in window title (but don't change file name)
1601             SetWindowTitle(wxEmptyString);
1602             if (newrule.IsEmpty()) {
1603                 if (patternchanged) {
1604                     statusptr->ErrorMessage(_("Rule has changed and pattern has changed."));
1605                 } else {
1606                     // don't beep
1607                     statusptr->DisplayMessage(_("Rule has changed."));
1608                 }
1609             } else {
1610                 if (patternchanged) {
1611                     statusptr->ErrorMessage(_("Algorithm has changed and pattern has changed."));
1612                 } else {
1613                     // don't beep
1614                     statusptr->DisplayMessage(_("Algorithm has changed."));
1615                 }
1616             }
1617         } else if (patternchanged) {
1618             statusptr->ErrorMessage(_("Pattern has changed."));
1619         }
1620 
1621         if (!inscript) {
1622             UpdateEverything();
1623         }
1624     }
1625 
1626     if (savechanges) {
1627         currlayer->undoredo->RememberAlgoChange(oldalgo, oldrule);
1628     }
1629 }
1630 
1631 // -----------------------------------------------------------------------------
1632 
CreateTABLE(const wxString & tablepath)1633 static wxString CreateTABLE(const wxString& tablepath)
1634 {
1635     wxString contents = wxT("\n@TABLE\n\n");
1636     // append contents of .table file
1637     FILE* f = OPENFILE(tablepath);
1638     if (f) {
1639         const int MAXLINELEN = 4095;
1640         char linebuf[MAXLINELEN + 1];
1641         linereader reader(f);
1642         while (true) {
1643             if (reader.fgets(linebuf, MAXLINELEN) == 0) break;
1644             contents += wxString(linebuf, wxConvLocal);
1645             contents += wxT("\n");
1646         }
1647         reader.close();
1648     } else {
1649         std::ostringstream oss;
1650         oss << "Could not read .table file:\n" << tablepath.mb_str(wxConvLocal);
1651         throw std::runtime_error(oss.str().c_str());
1652     }
1653     return contents;
1654 }
1655 
1656 // -----------------------------------------------------------------------------
1657 
CreateEmptyTABLE(const wxString & folder,const wxString & prefix,const wxSortedArrayString & allfiles)1658 static wxString CreateEmptyTABLE(const wxString& folder, const wxString& prefix,
1659                                  const wxSortedArrayString& allfiles)
1660 {
1661     // create a valid table that does nothing
1662     wxString contents = wxT("\nThis file contains colors and/or icons shared by ");
1663     contents += prefix;
1664     contents += wxT("-* rules.\n");
1665     contents += wxT("\n@TABLE\n\n");
1666 
1667     // search allfiles for 1st prefix-*.table/tree file and extract numstates and neighborhood
1668     wxString numstates, neighborhood;
1669     for (size_t n = 0; n < allfiles.GetCount(); n++) {
1670         wxString filename = allfiles[n];
1671         if (filename.EndsWith(wxT(".table")) || filename.EndsWith(wxT(".tree"))) {
1672             if (prefix == filename.BeforeLast('-')) {
1673                 wxString filepath = folder + filename;
1674                 FILE* f = OPENFILE(filepath);
1675                 if (f) {
1676                     const int MAXLINELEN = 4095;
1677                     char linebuf[MAXLINELEN + 1];
1678                     linereader reader(f);
1679                     while (true) {
1680                         if (reader.fgets(linebuf, MAXLINELEN) == 0) break;
1681                         if (strncmp(linebuf, "n_states:", 9) == 0) {
1682                             numstates = wxString(linebuf,wxConvLocal) + wxT("\n");
1683                         } else if (strncmp(linebuf, "num_states=", 11) == 0) {
1684                             // convert to table syntax
1685                             numstates = wxT("n_states:") + wxString(linebuf+11,wxConvLocal);
1686                             numstates += wxT("\n");
1687                         } else if (strncmp(linebuf, "neighborhood:", 13) == 0) {
1688                             neighborhood = wxString(linebuf,wxConvLocal) + wxT("\n");
1689                             break;
1690                         } else if (strncmp(linebuf, "num_neighbors=", 14) == 0) {
1691                             // convert to table syntax
1692                             neighborhood = wxT("neighborhood:");
1693                             if (linebuf[14] == '4')
1694                                 neighborhood += wxT("vonNeumann\n");
1695                             else
1696                                 neighborhood += wxT("Moore\n");
1697                             break;
1698                         }
1699                     }
1700                     reader.close();
1701                 } else {
1702                     std::ostringstream oss;
1703                     oss << "Could not read .table/tree file:\n" << filepath.mb_str(wxConvLocal);
1704                     throw std::runtime_error(oss.str().c_str());
1705                 }
1706             }
1707         }
1708     }
1709 
1710     if (numstates.length() == 0) {
1711         numstates = wxT("n_states:256\n");
1712         wxString msg = _("Could not find ") + prefix;
1713         msg += _("-*.table/tree to set n_states in ");
1714         msg += prefix;
1715         msg += _("-shared.rule.");
1716         Warning(msg);
1717     }
1718 
1719     contents += numstates;
1720     contents += neighborhood;
1721     contents += wxT("symmetries:none\n");   // anything valid would do
1722     contents += wxT("# do nothing\n");      // no transitions
1723 
1724     return contents;
1725 }
1726 
1727 // -----------------------------------------------------------------------------
1728 
CreateTREE(const wxString & treepath)1729 static wxString CreateTREE(const wxString& treepath)
1730 {
1731     wxString contents = wxT("\n@TREE\n\n");
1732     // append contents of .tree file
1733     FILE* f = OPENFILE(treepath);
1734     if (f) {
1735         const int MAXLINELEN = 4095;
1736         char linebuf[MAXLINELEN + 1];
1737         linereader reader(f);
1738         while (true) {
1739             if (reader.fgets(linebuf, MAXLINELEN) == 0) break;
1740             contents += wxString(linebuf, wxConvLocal);
1741             contents += wxT("\n");
1742         }
1743         reader.close();
1744     } else {
1745         std::ostringstream oss;
1746         oss << "Could not read .tree file:\n" << treepath.mb_str(wxConvLocal);
1747         throw std::runtime_error(oss.str().c_str());
1748     }
1749     return contents;
1750 }
1751 
1752 // -----------------------------------------------------------------------------
1753 
CreateCOLORS(const wxString & colorspath)1754 static wxString CreateCOLORS(const wxString& colorspath)
1755 {
1756     wxString contents = wxT("\n@COLORS\n\n");
1757     FILE* f = OPENFILE(colorspath);
1758     if (f) {
1759         const int MAXLINELEN = 4095;
1760         char linebuf[MAXLINELEN + 1];
1761         linereader reader(f);
1762         while (true) {
1763             if (reader.fgets(linebuf, MAXLINELEN) == 0) break;
1764             int skip = 0;
1765             if (strncmp(linebuf, "color", 5) == 0 ||
1766                 strncmp(linebuf, "gradient", 8) == 0) {
1767                 // strip off everything before 1st digit
1768                 while (linebuf[skip] && (linebuf[skip] < '0' || linebuf[skip] > '9')) {
1769                     skip++;
1770                 }
1771             }
1772             contents += wxString(linebuf + skip, wxConvLocal);
1773             contents += wxT("\n");
1774         }
1775         reader.close();
1776     } else {
1777         std::ostringstream oss;
1778         oss << "Could not read .colors file:\n" << colorspath.mb_str(wxConvLocal);
1779         throw std::runtime_error(oss.str().c_str());
1780     }
1781     return contents;
1782 }
1783 
1784 // -----------------------------------------------------------------------------
1785 
CreateStateColors(wxImage image,int numicons)1786 static wxString CreateStateColors(wxImage image, int numicons)
1787 {
1788     wxString contents = wxT("\n@COLORS\n\n");
1789 
1790     // if the last icon has only 1 color then assume it is the extra 15x15 icon
1791     // supplied to set the color of state 0
1792     if (numicons > 1) {
1793         wxImage icon = image.GetSubImage(wxRect((numicons-1)*15, 0, 15, 15));
1794         if (icon.CountColours(1) == 1) {
1795             unsigned char* idata = icon.GetData();
1796             unsigned char R = idata[0];
1797             unsigned char G = idata[1];
1798             unsigned char B = idata[2];
1799             contents += wxString::Format(wxT("0 %d %d %d\n"), R, G, B);
1800             numicons--;
1801         }
1802     }
1803 
1804     // set non-icon colors for each live state to the average of the non-black pixels
1805     // in each 15x15 icon (note we've skipped the extra icon detected above)
1806     for (int i = 0; i < numicons; i++) {
1807         wxImage icon = image.GetSubImage(wxRect(i*15, 0, 15, 15));
1808         int nbcount = 0;   // non-black pixels
1809         int totalR = 0;
1810         int totalG = 0;
1811         int totalB = 0;
1812         unsigned char* idata = icon.GetData();
1813         for (int y = 0; y < 15; y++) {
1814             for (int x = 0; x < 15; x++) {
1815                 long pos = (y * 15 + x) * 3;
1816                 unsigned char R = idata[pos];
1817                 unsigned char G = idata[pos+1];
1818                 unsigned char B = idata[pos+2];
1819                 if (R > 0 || G > 0 || B > 0) {
1820                     // non-black pixel
1821                     nbcount++;
1822                     totalR += R;
1823                     totalG += G;
1824                     totalB += B;
1825                 }
1826             }
1827         }
1828         if (nbcount > 0) {
1829             contents += wxString::Format(wxT("%d %d %d %d\n"), i+1, totalR/nbcount, totalG/nbcount, totalB/nbcount);
1830         } else {
1831             // unlikely, but avoid div by zero
1832             contents += wxString::Format(wxT("%d 0 0 0\n"), i+1);
1833         }
1834     }
1835 
1836     return contents;
1837 }
1838 
1839 // -----------------------------------------------------------------------------
1840 
hex2(int i)1841 static wxString hex2(int i)
1842 {
1843     // convert given number from 0..255 into 2 hex digits
1844     wxString result = wxT("xx");
1845     const char* hexdigit = "0123456789ABCDEF";
1846     result[0] = hexdigit[i / 16];
1847     result[1] = hexdigit[i % 16];
1848     return result;
1849 }
1850 
1851 // -----------------------------------------------------------------------------
1852 
CreateXPM(const wxString & iconspath,wxImage image,int size,int numicons)1853 static wxString CreateXPM(const wxString& iconspath, wxImage image, int size, int numicons)
1854 {
1855     // create XPM data for given set of icons
1856     wxString contents = wxT("\nXPM\n");
1857 
1858     int charsperpixel = 1;
1859     const char* cindex = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1860     wxImageHistogram histogram;
1861     int numcolors = image.ComputeHistogram(histogram);
1862     if (numcolors > 256) {
1863         std::ostringstream oss;
1864         oss << "Image in " << iconspath.mb_str(wxConvLocal) << " has more than 256 colors.";
1865         throw std::runtime_error(oss.str().c_str());
1866     }
1867     if (numcolors > 26) charsperpixel = 2;   // AABA..PA, ABBB..PB, ... , APBP..PP
1868 
1869     contents += wxT("/* width height num_colors chars_per_pixel */\n");
1870     contents += wxString::Format(wxT("\"%d %d %d %d\"\n"), size, size*numicons, numcolors, charsperpixel);
1871 
1872     contents += wxT("/* colors */\n");
1873     int n = 0;
1874     for (wxImageHistogram::iterator entry = histogram.begin(); entry != histogram.end(); ++entry) {
1875         unsigned long key = entry->first;
1876         unsigned char R = (key & 0xFF0000) >> 16;
1877         unsigned char G = (key & 0x00FF00) >> 8;
1878         unsigned char B = (key & 0x0000FF);
1879         if (R == 0 && G == 0 && B == 0) {
1880             // nicer to show . or .. for black pixels
1881             contents += wxT("\".");
1882             if (charsperpixel == 2) contents += wxT(".");
1883             contents += wxT(" c #000000\"\n");
1884         } else {
1885             wxString hexcolor = wxT("#");
1886             hexcolor += hex2(R);
1887             hexcolor += hex2(G);
1888             hexcolor += hex2(B);
1889             contents += wxT("\"");
1890             if (charsperpixel == 1) {
1891                 contents += cindex[n];
1892             } else {
1893                 contents += cindex[n % 16];
1894                 contents += cindex[n / 16];
1895             }
1896             contents += wxT(" c ");
1897             contents += hexcolor;
1898             contents += wxT("\"\n");
1899         }
1900         n++;
1901     }
1902 
1903     for (int i = 0; i < numicons; i++) {
1904         contents += wxString::Format(wxT("/* icon for state %d */\n"), i+1);
1905         wxImage icon = image.GetSubImage(wxRect(i*15, 0, size, size));
1906         unsigned char* idata = icon.GetData();
1907         for (int y = 0; y < size; y++) {
1908             contents += wxT("\"");
1909             for (int x = 0; x < size; x++) {
1910                 long pos = (y * size + x) * 3;
1911                 unsigned char R = idata[pos];
1912                 unsigned char G = idata[pos+1];
1913                 unsigned char B = idata[pos+2];
1914                 if (R == 0 && G == 0 && B == 0) {
1915                     // nicer to show . or .. for black pixels
1916                     contents += wxT(".");
1917                     if (charsperpixel == 2) contents += wxT(".");
1918                 } else {
1919                     n = 0;
1920                     unsigned long thisRGB = wxImageHistogram::MakeKey(R,G,B);
1921                     for (wxImageHistogram::iterator entry = histogram.begin(); entry != histogram.end(); ++entry) {
1922                         if (thisRGB == entry->first) break;
1923                         n++;
1924                     }
1925                     if (charsperpixel == 1) {
1926                         contents += cindex[n];
1927                     } else {
1928                         contents += cindex[n % 16];
1929                         contents += cindex[n / 16];
1930                     }
1931                 }
1932             }
1933             contents += wxT("\"\n");
1934         }
1935     }
1936 
1937     return contents;
1938 }
1939 
1940 // -----------------------------------------------------------------------------
1941 
CreateICONS(const wxString & iconspath,bool nocolors)1942 static wxString CreateICONS(const wxString& iconspath, bool nocolors)
1943 {
1944     wxString contents = wxT("\n@ICONS\n");
1945     wxImage image;
1946     if (image.LoadFile(iconspath)) {
1947         int wd = image.GetWidth();
1948         int ht = image.GetHeight();
1949         if (ht != 15 && ht != 22) {
1950             std::ostringstream oss;
1951             oss << "Image in " << iconspath.mb_str(wxConvLocal) <<
1952                 " has incorrect height (should be 15 or 22).";
1953             throw std::runtime_error(oss.str().c_str());
1954         }
1955         if (wd % 15 > 0) {
1956             std::ostringstream oss;
1957             oss << "Image in " << iconspath.mb_str(wxConvLocal) <<
1958                 " has incorrect width (should be multiple of 15).";
1959             throw std::runtime_error(oss.str().c_str());
1960         }
1961         int numicons = wd / 15;
1962 
1963         // WARNING: MultiColorImage must be called 1st in the next test because
1964         // we want image to be converted to black-and-white if it only uses 2 colors
1965         // (for compatibility with Golly 2.4 and older)
1966         if (MultiColorImage(image) && nocolors) {
1967             // the .icons file is multi-color and there was no .colors file,
1968             // so prepend a @COLORS section that sets non-icon colors
1969             contents = CreateStateColors(image.GetSubImage(wxRect(0,0,wd,15)), numicons) + contents;
1970         }
1971 
1972         if (ht == 15) {
1973             contents += CreateXPM(iconspath, image, 15, numicons);
1974         } else {
1975             contents += CreateXPM(iconspath, image.GetSubImage(wxRect(0,0,wd,15)), 15, numicons);
1976             contents += CreateXPM(iconspath, image.GetSubImage(wxRect(0,15,wd,7)), 7, numicons);
1977         }
1978     } else {
1979         std::ostringstream oss;
1980         oss << "Could not load image from .icons file:\n" << iconspath.mb_str(wxConvLocal);
1981         throw std::runtime_error(oss.str().c_str());
1982     }
1983     return contents;
1984 }
1985 
1986 // -----------------------------------------------------------------------------
1987 
CreateOneRule(const wxString & rulefile,const wxString & folder,const wxSortedArrayString & allfiles,wxString & htmlinfo)1988 static void CreateOneRule(const wxString& rulefile, const wxString& folder,
1989                           const wxSortedArrayString& allfiles, wxString& htmlinfo)
1990 {
1991     wxString tabledata, treedata, colordata, icondata;
1992     wxString rulename = rulefile.BeforeLast('.');
1993 
1994     if (rulename.EndsWith(wxT("-shared"))) {
1995         // create a .rule file with colors and/or icons shared by other .rule files
1996         wxString prefix = rulename.BeforeLast('-');
1997 
1998         tabledata = CreateEmptyTABLE(folder, prefix, allfiles);
1999 
2000         wxString sharedcolors = prefix + wxT(".colors");
2001         if (allfiles.Index(sharedcolors) != wxNOT_FOUND)
2002             colordata = CreateCOLORS(folder + sharedcolors);
2003 
2004         wxString sharedicons = prefix + wxT(".icons");
2005         if (allfiles.Index(sharedicons) != wxNOT_FOUND)
2006             icondata = CreateICONS(folder + sharedicons, colordata.length() == 0);
2007 
2008     } else {
2009         wxString tablefile = rulename + wxT(".table");
2010         wxString treefile = rulename + wxT(".tree");
2011         wxString colorsfile = rulename + wxT(".colors");
2012         wxString iconsfile = rulename + wxT(".icons");
2013 
2014         if (allfiles.Index(tablefile) != wxNOT_FOUND)
2015             tabledata = CreateTABLE(folder + tablefile);
2016 
2017         if (allfiles.Index(treefile) != wxNOT_FOUND)
2018             treedata = CreateTREE(folder + treefile);
2019 
2020         if (allfiles.Index(colorsfile) != wxNOT_FOUND)
2021             colordata = CreateCOLORS(folder + colorsfile);
2022 
2023         if (allfiles.Index(iconsfile) != wxNOT_FOUND)
2024             icondata = CreateICONS(folder + iconsfile, colordata.length() == 0);
2025     }
2026 
2027     wxString contents = wxT("@RULE ") + rulename + wxT("\n");
2028     contents += tabledata;
2029     contents += treedata;
2030     contents += colordata;
2031     contents += icondata;
2032 
2033     // write contents to .rule file
2034     wxString rulepath = folder + rulefile;
2035     wxFile outfile(rulepath, wxFile::write);
2036     if (outfile.IsOpened()) {
2037         outfile.Write(contents);
2038         outfile.Close();
2039     } else {
2040         std::ostringstream oss;
2041         oss << "Could not create rule file:\n" << rulepath.mb_str(wxConvLocal);
2042         throw std::runtime_error(oss.str().c_str());
2043     }
2044 
2045     // append created file to htmlinfo
2046     htmlinfo += _("<a href=\"open:");
2047     htmlinfo += folder;
2048     htmlinfo += rulefile;
2049     htmlinfo += _("\">");
2050     htmlinfo += rulefile;
2051     htmlinfo += _("</a><br>\n");
2052 }
2053 
2054 // -----------------------------------------------------------------------------
2055 
SharedColorsIcons(const wxString & prefix,const wxSortedArrayString & allfiles)2056 static bool SharedColorsIcons(const wxString& prefix, const wxSortedArrayString& allfiles)
2057 {
2058     // return true if prefix.colors/icons is shared by at least one prefix-*.table/tree file
2059     for (size_t n = 0; n < allfiles.GetCount(); n++) {
2060         wxString filename = allfiles[n];
2061         if (filename.EndsWith(wxT(".table")) || filename.EndsWith(wxT(".tree"))) {
2062             if (prefix == filename.BeforeLast('-')) {
2063                 return true;
2064             }
2065         }
2066     }
2067 
2068     // prefix-*.table/tree does not exist in allfiles
2069     return false;
2070 }
2071 
2072 // -----------------------------------------------------------------------------
2073 
ConvertRules(const wxString & folder,bool supplied,wxString & htmlinfo)2074 static int ConvertRules(const wxString& folder, bool supplied, wxString& htmlinfo)
2075 {
2076     wxString filename, rulefile, rulename;
2077     int depcount = 0;
2078 
2079     if (!wxFileName::DirExists(folder)) {
2080         // this might happen if user deleted/renamed folder while Golly is running
2081         std::ostringstream oss;
2082         oss << "Directory does not exist:\n" << folder.mb_str(wxConvLocal);
2083         throw std::runtime_error(oss.str().c_str());
2084     }
2085 
2086     wxDir dir(folder);
2087     if (!dir.IsOpened()) {
2088         std::ostringstream oss;
2089         oss << "Failed to open directory:\n" << folder.mb_str(wxConvLocal);
2090         throw std::runtime_error(oss.str().c_str());
2091     }
2092 
2093     htmlinfo += wxT("<p>\n");
2094     if (supplied) {
2095         htmlinfo += _("New .rule files created in the supplied Rules folder:<br>\n(");
2096     } else {
2097         htmlinfo += _("New .rule files created in your rules folder:<br>\n(");
2098     }
2099     htmlinfo += folder;
2100     htmlinfo += _(")<p>\n");
2101 
2102     // build an array of all files in the given folder (using a sorted array speeds up Index calls)
2103     wxSortedArrayString allfiles;
2104     bool found = dir.GetFirst(&filename, wxEmptyString, wxDIR_FILES | wxDIR_HIDDEN);
2105     while (found) {
2106         allfiles.Add(filename);
2107         found = dir.GetNext(&filename);
2108     }
2109 
2110     // create an array of candidate .rule files to be created
2111     wxSortedArrayString candidates;
2112     for (size_t n = 0; n < allfiles.GetCount(); n++) {
2113         filename = allfiles[n];
2114         rulename = filename.BeforeLast('.');
2115         bool colorsicons = filename.EndsWith(wxT(".colors")) || filename.EndsWith(wxT(".icons"));
2116         bool tabletree = filename.EndsWith(wxT(".table")) || filename.EndsWith(wxT(".tree"));
2117         if (colorsicons || tabletree) {
2118             depcount++;
2119             if (colorsicons) {
2120                 if (SharedColorsIcons(rulename, allfiles)) {
2121                     // colors and/or icons will be shared by one or more rulename-*.rule files
2122                     rulefile = rulename + wxT("-shared.rule");
2123                 } else {
2124                     rulefile = rulename + wxT(".rule");
2125                 }
2126             } else {
2127                 // .table/tree file
2128                 rulefile = rulename + wxT(".rule");
2129             }
2130             // add .rule file to candidates if it hasn't been added yet
2131             // and if it doesn't already exist
2132             if (candidates.Index(rulefile) == wxNOT_FOUND &&
2133                 allfiles.Index(rulefile) == wxNOT_FOUND) {
2134                 candidates.Add(rulefile);
2135             }
2136         }
2137     }
2138 
2139     // create the new .rule files
2140     for (size_t n = 0; n < candidates.GetCount(); n++) {
2141         CreateOneRule(candidates[n], folder, allfiles, htmlinfo);
2142     }
2143 
2144     if (candidates.GetCount() == 0) {
2145         htmlinfo += _("None.\n");
2146     }
2147 
2148     return depcount;
2149 }
2150 
2151 // -----------------------------------------------------------------------------
2152 
ShowCreatedRules(wxString & htmlinfo)2153 static void ShowCreatedRules(wxString& htmlinfo)
2154 {
2155     wxString header = _("<html><title>Converted Rules</title>\n");
2156     header += _("<body bgcolor=\"#FFFFCE\">\n");
2157     htmlinfo = header + htmlinfo;
2158     htmlinfo += _("\n</body></html>");
2159 
2160     wxString htmlfile = tempdir + _("converted-rules.html");
2161     wxFile outfile(htmlfile, wxFile::write);
2162     if (outfile.IsOpened()) {
2163         outfile.Write(htmlinfo);
2164         outfile.Close();
2165         ShowHelp(htmlfile);
2166     } else {
2167         Warning(_("Could not create html file:\n") + htmlfile);
2168     }
2169 }
2170 
2171 // -----------------------------------------------------------------------------
2172 
DeleteOldRules(const wxString & folder)2173 static void DeleteOldRules(const wxString& folder)
2174 {
2175     if (wxFileName::DirExists(folder)) {
2176         wxDir dir(folder);
2177         if (dir.IsOpened()) {
2178             // build an array of all files in the given folder
2179             wxArrayString allfiles;
2180             wxString filename;
2181             bool found = dir.GetFirst(&filename, wxEmptyString, wxDIR_FILES | wxDIR_HIDDEN);
2182             while (found) {
2183                 allfiles.Add(filename);
2184                 found = dir.GetNext(&filename);
2185             }
2186             // delete all the .table/tree/colors/icons files
2187             for (size_t n = 0; n < allfiles.GetCount(); n++) {
2188                 filename = allfiles[n];
2189                 if ( filename.EndsWith(wxT(".colors")) || filename.EndsWith(wxT(".icons")) ||
2190                      filename.EndsWith(wxT(".table")) || filename.EndsWith(wxT(".tree")) ) {
2191                     wxRemoveFile(folder + filename);
2192                 }
2193             }
2194         }
2195     }
2196 }
2197 
2198 // -----------------------------------------------------------------------------
2199 
ConvertOldRules()2200 void MainFrame::ConvertOldRules()
2201 {
2202     if (inscript || viewptr->waitingforclick) return;
2203 
2204     if (generating) {
2205         command_pending = true;
2206         cmdevent.SetId(ID_CONVERT);
2207         Stop();
2208         return;
2209     }
2210 
2211     // look for deprecated .table/tree/colors/icons files and create corresponding .rule files
2212 
2213     wxString htmlinfo;
2214     bool aborted = false;
2215     int depcount = 0;       // number of deprecated files
2216     try {
2217         // look in the supplied Rules folder first, then in the user's rules folder
2218         depcount += ConvertRules(rulesdir, true, htmlinfo);
2219         depcount += ConvertRules(userrules, false, htmlinfo);
2220     }
2221     catch(const std::exception& e) {
2222         // display message set by throw std::runtime_error(...)
2223         Warning(wxString(e.what(),wxConvLocal));
2224         aborted = true;
2225         // nice to also show error message in help window
2226         htmlinfo += _("\n<p>*** CONVERSION ABORTED DUE TO ERROR ***\n<p>");
2227         htmlinfo += wxString(e.what(),wxConvLocal);
2228     }
2229 
2230     // display the results in the help window
2231     ShowCreatedRules(htmlinfo);
2232 
2233     if (!aborted && depcount > 0) {
2234         // ask user if it's ok to delete all the deprecated files
2235         int answer = wxMessageBox(_("Do you want to delete all the old .table/tree/colors/icons files?"),
2236                                   _("Delete deprecated files?"),
2237                                   wxICON_QUESTION | wxYES_NO, wxGetActiveWindow());
2238         if (answer == wxYES) {
2239             DeleteOldRules(rulesdir);
2240             DeleteOldRules(userrules);
2241         }
2242     }
2243 }
2244 
2245 // -----------------------------------------------------------------------------
2246 
CreateRuleFiles(const wxSortedArrayString & deprecated,const wxSortedArrayString & ziprules)2247 wxString MainFrame::CreateRuleFiles(const wxSortedArrayString& deprecated,
2248                                     const wxSortedArrayString& ziprules)
2249 {
2250     // use the given list of deprecated .table/tree/colors/icons files
2251     // (recently extracted from a .zip file and installed in userrules)
2252     // to create new .rule files, except those in ziprules (they were in
2253     // the .zip file and have already been installed)
2254     wxString htmlinfo;
2255     bool aborted = false;
2256     try {
2257         // create an array of candidate .rule files to be created
2258         wxString rulefile, filename, rulename;
2259         wxSortedArrayString candidates;
2260         for (size_t n = 0; n < deprecated.GetCount(); n++) {
2261             filename = deprecated[n];
2262             rulename = filename.BeforeLast('.');
2263             if (filename.EndsWith(wxT(".colors")) || filename.EndsWith(wxT(".icons"))) {
2264                 if (SharedColorsIcons(rulename, deprecated)) {
2265                     // colors and/or icons will be shared by one or more rulename-*.rule files
2266                     rulefile = rulename + wxT("-shared.rule");
2267                 } else {
2268                     rulefile = rulename + wxT(".rule");
2269                 }
2270             } else {
2271                 // .table/tree file
2272                 rulefile = rulename + wxT(".rule");
2273             }
2274             // add .rule file to candidates if it hasn't been added yet
2275             // and if it isn't in the zip file (ie. already installed)
2276             if (candidates.Index(rulefile) == wxNOT_FOUND &&
2277                 ziprules.Index(rulefile) == wxNOT_FOUND) {
2278                 candidates.Add(rulefile);
2279             }
2280         }
2281 
2282         // create the new .rule files (unlike ConvertRules, here we overwrite any
2283         // existing .rule files in case the zip file's contents have changed)
2284         for (size_t n = 0; n < candidates.GetCount(); n++) {
2285             CreateOneRule(candidates[n], userrules, deprecated, htmlinfo);
2286         }
2287     }
2288     catch(const std::exception& e) {
2289         // display message set by throw std::runtime_error(...)
2290         Warning(wxString(e.what(),wxConvLocal));
2291         aborted = true;
2292         // nice to also show error message in help window
2293         htmlinfo += _("\n<p>*** CONVERSION ABORTED DUE TO ERROR ***\n<p>");
2294         htmlinfo += wxString(e.what(),wxConvLocal);
2295     }
2296 
2297     if (!aborted) {
2298         // delete all the deprecated files
2299         for (size_t n = 0; n < deprecated.GetCount(); n++) {
2300             wxRemoveFile(userrules + deprecated[n]);
2301         }
2302     }
2303 
2304     return htmlinfo;
2305 }
2306