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/wxhtml.h"          // for wxHtmlWindow
10 #include "wx/file.h"            // for wxFile
11 #include "wx/protocol/http.h"   // for wxHTTP
12 #include "wx/wfstream.h"        // for wxFileOutputStream, wxFileInputStream
13 #include "wx/zipstrm.h"         // for wxZipInputStream
14 
15 #include "lifealgo.h"           // for lifealgo class
16 #include "ruleloaderalgo.h"     // for noTABLEorTREE
17 
18 #include "wxgolly.h"       // for wxGetApp, mainptr, viewptr
19 #include "wxmain.h"        // for mainptr->...
20 #include "wxview.h"        // for viewptr->...
21 #include "wxutils.h"       // for Warning, BeginProgress, etc
22 #include "wxprefs.h"       // for GetShortcutTable, helpfontsize, gollydir, etc
23 #include "wxscript.h"      // for inscript, pass_file_events, etc
24 #include "wxlayer.h"       // for numlayers, GetLayer, etc
25 #include "wxalgos.h"       // for QLIFE_ALGO
26 #include "wxhelp.h"
27 
28 // -----------------------------------------------------------------------------
29 
30 // define a modeless help window:
31 
32 class HelpFrame : public wxFrame
33 {
34 public:
35     HelpFrame();
36 
SetStatus(const wxString & text)37     void SetStatus(const wxString& text) {
38         status->SetLabel(text);
39     }
40 
41     bool infront;     // help window is active?
42 
43 private:
44     // ids for buttons in help window (see also wxID_CLOSE)
45     enum {
46         ID_BACK_BUTT = wxID_HIGHEST + 1,
47         ID_FORWARD_BUTT,
48         ID_CONTENTS_BUTT
49     };
50 
51     // event handlers
52     void OnActivate(wxActivateEvent& event);
53     void OnBackButton(wxCommandEvent& event);
54     void OnForwardButton(wxCommandEvent& event);
55     void OnContentsButton(wxCommandEvent& event);
56     void OnCloseButton(wxCommandEvent& event);
57     void OnClose(wxCloseEvent& event);
58 
59     wxStaticText* status;   // status line at bottom of help window
60 
61     // any class wishing to process wxWidgets events must use this macro
62     DECLARE_EVENT_TABLE()
63 };
64 
65 BEGIN_EVENT_TABLE(HelpFrame, wxFrame)
66 EVT_ACTIVATE   (                    HelpFrame::OnActivate)
67 EVT_BUTTON     (ID_BACK_BUTT,       HelpFrame::OnBackButton)
68 EVT_BUTTON     (ID_FORWARD_BUTT,    HelpFrame::OnForwardButton)
69 EVT_BUTTON     (ID_CONTENTS_BUTT,   HelpFrame::OnContentsButton)
70 EVT_BUTTON     (wxID_CLOSE,         HelpFrame::OnCloseButton)
71 EVT_CLOSE      (                    HelpFrame::OnClose)
72 END_EVENT_TABLE()
73 
74 // -----------------------------------------------------------------------------
75 
76 // define a child window for displaying html info:
77 
78 class HtmlView : public wxHtmlWindow
79 {
80 public:
HtmlView(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,long style)81     HtmlView(wxWindow* parent, wxWindowID id, const wxPoint& pos,
82              const wxSize& size, long style)
83     : wxHtmlWindow(parent, id, pos, size, style) {
84         editlink = false;
85         linkrect = wxRect(0,0,0,0);
86     }
87 
88     virtual void OnLinkClicked(const wxHtmlLinkInfo& link);
89     virtual void OnCellMouseHover(wxHtmlCell* cell, wxCoord x, wxCoord y);
90 
91     void ClearStatus();  // clear help window's status line
92 
93     void SetFontSizes(int size);
94     void ChangeFontSizes(int size);
95 
96     void CheckAndLoad(const wxString& filepath);
97 
StartTimer()98     void StartTimer() {
99         htmltimer = new wxTimer(this, wxID_ANY);
100         // call OnTimer 10 times per sec
101         htmltimer->Start(100, wxTIMER_CONTINUOUS);
102     }
103 
StopTimer()104     void StopTimer() {
105         htmltimer->Stop();
106         delete htmltimer;
107     }
108 
109     bool editlink;       // open clicked file in editor?
110     bool canreload;      // can OnSize call CheckAndLoad?
111 
112 private:
113 #ifdef __WXMSW__
114     // see HtmlView::OnKeyUp for why we do this
115     void OnKeyUp(wxKeyEvent& event);
116 #else
117     void OnKeyDown(wxKeyEvent& event);
118 #endif
119 
120     void OnChar(wxKeyEvent& event);
121     void OnSize(wxSizeEvent& event);
122     void OnMouseMotion(wxMouseEvent& event);
123     void OnMouseLeave(wxMouseEvent& event);
124     void OnMouseDown(wxMouseEvent& event);
125     void OnTimer(wxTimerEvent& event);
126 
127     wxTimer* htmltimer;
128     wxRect linkrect;     // rect for cell containing link
129 
130     // any class wishing to process wxWidgets events must use this macro
131     DECLARE_EVENT_TABLE()
132 };
133 
134 BEGIN_EVENT_TABLE(HtmlView, wxHtmlWindow)
135 #ifdef __WXMSW__
136 // see HtmlView::OnKeyUp for why we do this
137 EVT_KEY_UP        (HtmlView::OnKeyUp)
138 #else
139 EVT_KEY_DOWN      (HtmlView::OnKeyDown)
140 #endif
141 EVT_CHAR          (HtmlView::OnChar)
142 EVT_SIZE          (HtmlView::OnSize)
143 EVT_MOTION        (HtmlView::OnMouseMotion)
144 EVT_ENTER_WINDOW  (HtmlView::OnMouseMotion)
145 EVT_LEAVE_WINDOW  (HtmlView::OnMouseLeave)
146 EVT_LEFT_DOWN     (HtmlView::OnMouseDown)
147 EVT_RIGHT_DOWN    (HtmlView::OnMouseDown)
148 EVT_TIMER         (wxID_ANY, HtmlView::OnTimer)
149 END_EVENT_TABLE()
150 
151 // -----------------------------------------------------------------------------
152 
153 HelpFrame* helpptr = NULL;    // help window
154 HtmlView* htmlwin = NULL;     // html child window
155 
156 wxButton* backbutt;           // back button
157 wxButton* forwbutt;           // forwards button
158 wxButton* contbutt;           // Contents button
159 
160 long whenactive;              // when help window became active (elapsed millisecs)
161 
162 const wxString helphome = _("Help/index.html");    // contents page
163 wxString currhelp = helphome;                      // current help file
164 const wxString lexicon_name = _("lexicon");        // name of lexicon layer
165 
166 int lexlayer;                 // index of existing lexicon layer (-ve if not present)
167 wxString lexpattern;          // lexicon pattern data
168 
169 // prefix of most recent full URL in a html get ("get:http://.../foo.html")
170 // to allow later relative gets ("get:foo.rle")
171 wxString urlprefix = wxEmptyString;
172 const wxString HTML_PREFIX = _("GET---");          // prepended to html filename
173 
174 // -----------------------------------------------------------------------------
175 
GetHelpFrame()176 wxFrame* GetHelpFrame() {
177     return helpptr;
178 }
179 
180 // -----------------------------------------------------------------------------
181 
182 // create the help window
HelpFrame()183 HelpFrame::HelpFrame()
184 : wxFrame(NULL, wxID_ANY, _(""), wxPoint(helpx,helpy), wxSize(helpwd,helpht))
185 {
186     wxGetApp().SetFrameIcon(this);
187 
188 #ifdef __WXMSW__
189     // use current theme's background colour
190     SetBackgroundColour(wxNullColour);
191 #endif
192 
193     htmlwin = new HtmlView(this, wxID_ANY,
194                            // specify small size to avoid clipping scroll bar on resize
195                            wxDefaultPosition, wxSize(30,30),
196                            wxHW_DEFAULT_STYLE | wxSUNKEN_BORDER);
197     htmlwin->StartTimer();
198     htmlwin->SetBorders(4);
199     htmlwin->SetFontSizes(helpfontsize);
200 
201     wxBoxSizer* vbox = new wxBoxSizer(wxVERTICAL);
202     wxBoxSizer* hbox = new wxBoxSizer(wxHORIZONTAL);
203 
204     backbutt = new wxButton(this, ID_BACK_BUTT, _("<"),
205                             wxDefaultPosition, wxSize(40,wxDefaultCoord));
206     hbox->Add(backbutt, 0, wxALL | wxALIGN_LEFT, 10);
207 
208     forwbutt = new wxButton(this, ID_FORWARD_BUTT, _(">"),
209                             wxDefaultPosition, wxSize(40,wxDefaultCoord));
210     hbox->Add(forwbutt, 0, wxTOP | wxBOTTOM | wxALIGN_LEFT, 10);
211 
212     contbutt = new wxButton(this, ID_CONTENTS_BUTT, _("Contents"));
213     hbox->Add(contbutt, 0, wxALL | wxALIGN_LEFT, 10);
214 
215     hbox->AddStretchSpacer(1);
216 
217     wxButton* closebutt = new wxButton(this, wxID_CLOSE, _("Close"));
218     closebutt->SetDefault();
219     hbox->Add(closebutt, 0, wxALL, 10);
220 
221     vbox->Add(hbox, 0, wxALL | wxEXPAND | wxALIGN_TOP, 0);
222 
223     vbox->Add(htmlwin, 1, wxLEFT | wxRIGHT | wxEXPAND | wxALIGN_TOP, 10);
224 
225     status = new wxStaticText(this, wxID_STATIC, wxEmptyString);
226 #ifdef __WXMAC__
227     status->SetWindowVariant(wxWINDOW_VARIANT_SMALL);
228 #endif
229     wxBoxSizer* statbox = new wxBoxSizer(wxHORIZONTAL);
230     statbox->Add(status);
231     vbox->AddSpacer(2);
232     vbox->Add(statbox, 0, wxLEFT | wxALIGN_LEFT, 10);
233     vbox->AddSpacer(4);
234 
235     SetMinSize(wxSize(minhelpwd, minhelpht));
236     SetSizer(vbox);
237 
238     // expand sizer now to avoid seeing small htmlwin and buttons in top left corner
239     vbox->SetDimension(0, 0, helpwd, helpht);
240 }
241 
242 // -----------------------------------------------------------------------------
243 
UpdateHelpButtons()244 void UpdateHelpButtons()
245 {
246     backbutt->Enable( htmlwin->HistoryCanBack() );
247     forwbutt->Enable( htmlwin->HistoryCanForward() );
248     // check for title used in Help/index.html
249     contbutt->Enable( htmlwin->GetOpenedPageTitle() != _("Golly Help: Contents") );
250 
251     wxString location = htmlwin->GetOpenedPage();
252     if ( !location.IsEmpty() ) {
253 
254         if (location.StartsWith(wxT("file:"))) {
255             // this happens in wx 3.1.0, so convert location from URL to file path
256             wxFileName fname = wxFileSystem::URLToFileName(location);
257             location = fname.GetFullPath();
258             #ifdef __WXMSW__
259                 location.Replace(wxT("\\"), wxT("/"));
260             #endif
261         }
262 
263         // avoid bug in wx 3.1.0
264         location.Replace(wxT("%20"), wxT(" "));
265         location.Replace(wxT("%23"), wxT("#"));
266 
267         // set currhelp so user can close help window and then open same page
268         currhelp = location;
269 
270         // if filename starts with HTML_PREFIX then set urlprefix to corresponding
271         // url so any later relative "get:foo.rle" links will work
272         wxString filename = location.AfterLast('/');
273         if (filename.StartsWith(HTML_PREFIX)) {
274             // replace HTML_PREFIX with "http://" and convert spaces to '/'
275             // (ie. reverse what we did in GetURL)
276             urlprefix = filename;
277             urlprefix.Replace(HTML_PREFIX, wxT("http://"), false);   // do once
278             urlprefix.Replace(wxT(" "), wxT("/"));
279             urlprefix = urlprefix.BeforeLast('/');
280             urlprefix += wxT("/");     // must end in slash
281         }
282     }
283 
284     htmlwin->ClearStatus();
285     htmlwin->SetFocus();       // for keyboard shortcuts
286 }
287 
288 // -----------------------------------------------------------------------------
289 
ShowHelp(const wxString & filepath)290 void ShowHelp(const wxString& filepath)
291 {
292     // display given html file in help window
293     if (helpptr) {
294         // help window exists so bring it to front and display given file
295         if (!filepath.IsEmpty()) {
296             htmlwin->CheckAndLoad(filepath);
297             UpdateHelpButtons();
298         }
299         helpptr->Raise();
300 
301     } else {
302         helpptr = new HelpFrame();
303         if (helpptr == NULL) {
304             Warning(_("Could not create help window!"));
305             return;
306         }
307 
308         // assume our .html files contain a <title> tag
309         htmlwin->SetRelatedFrame(helpptr, _("%s"));
310 
311         if (!filepath.IsEmpty()) {
312             htmlwin->CheckAndLoad(filepath);
313         } else {
314             htmlwin->CheckAndLoad(currhelp);
315         }
316 
317         // prevent HtmlView::OnSize calling CheckAndLoad twice
318         htmlwin->canreload = false;
319 
320         helpptr->Show(true);
321         UpdateHelpButtons();    // must be after Show to avoid scroll bar appearing on Mac
322 
323         // allow HtmlView::OnSize to call CheckAndLoad if window is resized
324         htmlwin->canreload = true;
325     }
326     whenactive = 0;
327 }
328 
329 // -----------------------------------------------------------------------------
330 
OnActivate(wxActivateEvent & event)331 void HelpFrame::OnActivate(wxActivateEvent& event)
332 {
333     // IsActive() is not always reliable so we set infront flag
334     infront = event.GetActive();
335     if (infront) {
336         // help window is being activated
337         whenactive = stopwatch->Time();
338 
339         // ensure correct menu items, esp after help window
340         // is clicked while app is in background
341         mainptr->UpdateMenuItems();
342     }
343     event.Skip();
344 }
345 
346 // -----------------------------------------------------------------------------
347 
OnBackButton(wxCommandEvent & WXUNUSED (event))348 void HelpFrame::OnBackButton(wxCommandEvent& WXUNUSED(event))
349 {
350     if ( htmlwin->HistoryBack() ) {
351         UpdateHelpButtons();
352     } else {
353         Beep();
354     }
355 }
356 
357 // -----------------------------------------------------------------------------
358 
OnForwardButton(wxCommandEvent & WXUNUSED (event))359 void HelpFrame::OnForwardButton(wxCommandEvent& WXUNUSED(event))
360 {
361     if ( htmlwin->HistoryForward() ) {
362         UpdateHelpButtons();
363     } else {
364         Beep();
365     }
366 }
367 
368 // -----------------------------------------------------------------------------
369 
OnContentsButton(wxCommandEvent & WXUNUSED (event))370 void HelpFrame::OnContentsButton(wxCommandEvent& WXUNUSED(event))
371 {
372     ShowHelp(helphome);
373 }
374 
375 // -----------------------------------------------------------------------------
376 
OnCloseButton(wxCommandEvent & WXUNUSED (event))377 void HelpFrame::OnCloseButton(wxCommandEvent& WXUNUSED(event))
378 {
379     Close(true);
380 }
381 
382 // -----------------------------------------------------------------------------
383 
OnClose(wxCloseEvent & WXUNUSED (event))384 void HelpFrame::OnClose(wxCloseEvent& WXUNUSED(event))
385 {
386 #ifdef __WXMSW__
387     if (!IsIconized()) {
388 #endif
389         // save current location and size for later use in SavePrefs
390         wxRect r = GetRect();
391         helpx = r.x;
392         helpy = r.y;
393         helpwd = r.width;
394         helpht = r.height;
395 #ifdef __WXMSW__
396     }
397 #endif
398 
399     // stop htmltimer immediately (if we do it in ~HtmlView dtor then timer
400     // only stops when app becomes idle)
401     htmlwin->StopTimer();
402 
403     Destroy();        // also deletes all child windows (buttons, etc)
404     helpptr = NULL;
405 }
406 
407 // -----------------------------------------------------------------------------
408 
LoadRule(const wxString & rulestring,bool fromfile)409 void LoadRule(const wxString& rulestring, bool fromfile)
410 {
411     wxString oldrule = wxString(currlayer->algo->getrule(),wxConvLocal);
412     int oldmaxstate = currlayer->algo->NumCellStates() - 1;
413     const char* err;
414 
415     // selection might change if grid becomes smaller,
416     // so save current selection for RememberRuleChange/RememberAlgoChange
417     viewptr->SaveCurrentSelection();
418 
419     mainptr->Raise();
420 
421     if (mainptr->generating) {
422         Warning(_("Cannot change rule while generating a pattern."));
423         return;
424     } else if (inscript) {
425         Warning(_("Cannot change rule while a script is running."));
426         return;
427     }
428 
429     if (fromfile) {
430         // load given rule from a .rule file
431 
432         // InitAlgorithms ensures the RuleLoader algo is the last algo
433         int rule_loader_algo = NumAlgos() - 1;
434 
435         if (currlayer->algtype == rule_loader_algo) {
436             // RuleLoader is current algo so no need to switch
437             err = currlayer->algo->setrule( rulestring.mb_str(wxConvLocal) );
438         } else {
439             // switch to RuleLoader algo
440             lifealgo* tempalgo = CreateNewUniverse(rule_loader_algo);
441             err = tempalgo->setrule( rulestring.mb_str(wxConvLocal) );
442             delete tempalgo;
443             if (!err) {
444                 // change the current algorithm and switch to the new rule
445                 mainptr->ChangeAlgorithm(rule_loader_algo, rulestring);
446                 if (rule_loader_algo != currlayer->algtype) {
447                     RestoreRule(oldrule);
448                     Warning(_("Algorithm could not be changed (pattern is too big to convert)."));
449                 } else {
450                     mainptr->SetWindowTitle(wxEmptyString);
451                     mainptr->UpdateEverything();
452                 }
453                 return;
454             }
455         }
456 
457         if (err) {
458             // RuleLoader algo found some sort of error
459             if (strcmp(err, noTABLEorTREE) == 0) {
460                 // .rule file has no TABLE or TREE section but it might be used
461                 // to override a built-in rule, so try each algo
462                 wxString temprule = rulestring;
463                 temprule.Replace(wxT("_"), wxT("/"));   // eg. convert B3_S23 to B3/S23
464                 for (int i = 0; i < NumAlgos(); i++) {
465                     lifealgo* tempalgo = CreateNewUniverse(i);
466                     err = tempalgo->setrule( temprule.mb_str(wxConvLocal) );
467                     delete tempalgo;
468                     if (!err) {
469                         // change the current algorithm and switch to the new rule
470                         mainptr->ChangeAlgorithm(i, temprule);
471                         if (i != currlayer->algtype) {
472                             RestoreRule(oldrule);
473                             Warning(_("Algorithm could not be changed (pattern is too big to convert)."));
474                         } else {
475                             mainptr->SetWindowTitle(wxEmptyString);
476                             mainptr->UpdateEverything();
477                         }
478                         return;
479                     }
480                 }
481             }
482             RestoreRule(oldrule);
483             wxString msg = _("The rule file is not valid:\n") + rulestring;
484             msg += _("\n\nThe error message:\n") + wxString(err,wxConvLocal);
485             Warning(msg);
486             return;
487         }
488 
489     } else {
490         // fromfile is false, so switch to rule given in a "rule:" link
491 
492         err = currlayer->algo->setrule( rulestring.mb_str(wxConvLocal) );
493         if (err) {
494             // try to find another algorithm that supports the given rule
495             for (int i = 0; i < NumAlgos(); i++) {
496                 if (i != currlayer->algtype) {
497                     lifealgo* tempalgo = CreateNewUniverse(i);
498                     err = tempalgo->setrule( rulestring.mb_str(wxConvLocal) );
499                     delete tempalgo;
500                     if (!err) {
501                         // change the current algorithm and switch to the new rule
502                         mainptr->ChangeAlgorithm(i, rulestring);
503                         if (i != currlayer->algtype) {
504                             RestoreRule(oldrule);
505                             Warning(_("Algorithm could not be changed (pattern is too big to convert)."));
506                         } else {
507                             mainptr->SetWindowTitle(wxEmptyString);
508                             mainptr->UpdateEverything();
509                         }
510                         return;
511                     }
512                 }
513             }
514             RestoreRule(oldrule);
515             Warning(_("Given rule is not valid in any algorithm:\n") + rulestring);
516             return;
517         }
518     }
519 
520     wxString newrule = wxString(currlayer->algo->getrule(),wxConvLocal);
521     int newmaxstate = currlayer->algo->NumCellStates() - 1;
522     if (oldrule != newrule || oldmaxstate != newmaxstate) {
523         // show new rule in main window's title but don't change name
524         mainptr->SetWindowTitle(wxEmptyString);
525 
526         // if pattern exists and is at starting gen then ensure savestart is true
527         // so that SaveStartingPattern will save pattern to suitable file
528         // (and thus undo/reset will work correctly)
529         if (currlayer->algo->getGeneration() == currlayer->startgen && !currlayer->algo->isEmpty()) {
530             currlayer->savestart = true;
531         }
532 
533         // if grid is bounded then remove any live cells outside grid edges
534         if (currlayer->algo->gridwd > 0 || currlayer->algo->gridht > 0) {
535             mainptr->ClearOutsideGrid();
536         }
537 
538         // new rule might have changed the number of cell states;
539         // if there are fewer states then pattern might change
540         if (newmaxstate < oldmaxstate && !currlayer->algo->isEmpty()) {
541             mainptr->ReduceCellStates(newmaxstate);
542         }
543 
544         if (allowundo && !currlayer->stayclean) {
545             currlayer->undoredo->RememberRuleChange(oldrule);
546         }
547     }
548 
549     // update colors and/or icons for the new rule
550     UpdateLayerColors();
551 
552     // pattern might have changed or colors/icons might have changed
553     mainptr->UpdateEverything();
554 }
555 
556 // -----------------------------------------------------------------------------
557 
DownloadFile(const wxString & url,const wxString & filepath)558 bool DownloadFile(const wxString& url, const wxString& filepath)
559 {
560     bool result = false;
561 
562     wxHTTP http;
563     http.SetTimeout(5);                                // secs
564     http.SetHeader(wxT("Accept") , wxT("*/*"));        // any file type
565     http.SetHeader(wxT("User-Agent"), wxT("Golly"));
566 
567     // Connect() wants a server address (eg. "www.foo.com"), not a full URL
568     wxString temp = url.AfterFirst('/');
569     while (temp[0] == '/') temp = temp.Mid(1);
570     size_t slashpos = temp.Find('/');
571     wxString server = temp.Left(slashpos);
572     if (http.Connect(server, 80)) {
573         // GetInputStream() wants everything after the server address
574         wxString respath = temp.Right(temp.length() - slashpos);
575         wxInputStream* instream = http.GetInputStream(respath);
576         if (instream) {
577 
578             wxFileOutputStream outstream(filepath);
579             if (outstream.Ok()) {
580                 // read and write in chunks so we can show a progress dialog
581                 const int BUFFER_SIZE = 4000;             // seems ok (on Mac at least)
582                 char buf[BUFFER_SIZE];
583                 size_t incount = 0;
584                 size_t outcount = 0;
585                 size_t lastread, lastwrite;
586                 double filesize = (double) instream->GetSize();
587                 if (filesize <= 0.0) filesize = -1.0;     // show indeterminate progress
588 
589                 BeginProgress(_("Downloading file"));
590                 while (true) {
591                     instream->Read(buf, BUFFER_SIZE);
592                     lastread = instream->LastRead();
593                     if (lastread == 0) break;
594                     outstream.Write(buf, lastread);
595                     lastwrite = outstream.LastWrite();
596                     incount += lastread;
597                     outcount += lastwrite;
598                     if (incount != outcount) {
599                         Warning(_("Error occurred while writing file:\n") + filepath);
600                         break;
601                     }
602                     char msg[128];
603                     sprintf(msg, "File size: %.2f MB", double(incount) / 1048576.0);
604                     if (AbortProgress((double)incount / filesize, wxString(msg,wxConvLocal))) {
605                         // force false result
606                         outcount = 0;
607                         break;
608                     }
609                 }
610                 EndProgress();
611 
612                 result = (incount == outcount);
613                 if (!result) {
614                     // delete incomplete filepath
615                     if (wxFileExists(filepath)) wxRemoveFile(filepath);
616                 }
617             } else {
618                 Warning(_("Could not open output stream for file:\n") + filepath);
619             }
620             delete instream;
621 
622         } else {
623             int err = http.GetError();
624             if (err == wxPROTO_NOFILE) {
625                 Warning(_("Remote file does not exist:\n") + url);
626             } else {
627                 // we get wxPROTO_NETERR (generic network error) with some naughty servers
628                 // that use LF rather than CRLF to terminate HTTP header messages
629                 // eg: http://fano.ics.uci.edu/ca/rules/b0135s014/g1.lif
630                 // (wxProtocol::ReadLine needs to be made more tolerant)
631                 Warning(wxString::Format(_("Could not download file (error %d):\n"),err) + url);
632             }
633         }
634     } else {
635         Warning(_("Could not connect to server:\n") + server);
636     }
637 
638     http.Close();
639     return result;
640 }
641 
642 // -----------------------------------------------------------------------------
643 
GetURL(const wxString & url)644 void GetURL(const wxString& url)
645 {
646     wxString fullurl;
647     if (url.StartsWith(wxT("http:"))) {
648         fullurl = url;
649     } else {
650         // relative get, so prepend prefix set earlier in UpdateHelpButtons
651         fullurl = urlprefix + url;
652     }
653 
654     wxString filename = fullurl.AfterLast('/');
655 
656     // remove ugly stuff at start of file names downloaded from ConwayLife.com
657     if (filename.StartsWith(wxT("download.php?f=")) ||
658         filename.StartsWith(wxT("pattern.asp?p=")) ||
659         filename.StartsWith(wxT("script.asp?s="))) {
660         filename = filename.AfterFirst('=');
661     }
662 
663     // create full path for downloaded file based on given url;
664     // first remove initial "http://"
665     wxString filepath = fullurl.AfterFirst('/');
666     while (filepath[0] == '/') filepath = filepath.Mid(1);
667     if (IsHTMLFile(filename)) {
668         // create special name for html file so UpdateHelpButtons can detect it
669         // and set urlprefix
670         filepath.Replace(wxT("/"), wxT(" "));     // assume url has no spaces
671         filepath = HTML_PREFIX + filepath;
672     } else {
673         // no need for url info in file name
674         filepath = filename;
675     }
676 #ifdef __WXMSW__
677     // replace chars that can appear in URLs but are not allowed in filenames on Windows
678     filepath.Replace(wxT("*"), wxT("_"));
679     filepath.Replace(wxT("?"), wxT("_"));
680 #endif
681 
682     if (IsRuleFile(filename)) {
683         // create file in user's rules directory (rulesdir might be read-only)
684         filepath = userrules + filename;
685     } else if (IsHTMLFile(filename)) {
686         // nicer to store html files in temporary directory
687         filepath = tempdir + filepath;
688     } else {
689         // all other files are stored in user's download directory
690         filepath = downloaddir + filepath;
691     }
692 
693     // try to download the file
694     if ( !DownloadFile(fullurl, filepath) ) return;
695 
696     if (htmlwin->editlink) {
697         if (IsRuleFile(filename) && filename.Lower().EndsWith(wxT(".icons"))) {
698             // let user see b&w image in .icons file
699             mainptr->Raise();
700             mainptr->OpenFile(filepath);
701         } else {
702             mainptr->EditFile(filepath);
703         }
704         return;
705     }
706 
707     if (IsHTMLFile(filename)) {
708         // display html file in help window
709         htmlwin->LoadPage(filepath);
710 
711     } else if (IsRuleFile(filename)) {
712         // load corresponding .rule file
713         LoadRule(filename.BeforeLast('.'));
714 
715     } else if (IsTextFile(filename)) {
716         // open text file in user's text editor
717         mainptr->EditFile(filepath);
718 
719     } else if (IsZipFile(filename)) {
720         // open zip file (don't raise main window here)
721         mainptr->OpenFile(filepath);
722 
723     } else if (IsScriptFile(filename)) {
724         // run script depending on safety check; if it is allowed to run
725         // then we remember script in the Run Recent submenu
726         mainptr->CheckBeforeRunning(filepath, true, wxEmptyString);
727 
728     } else {
729         // assume pattern file, so try to load it
730         mainptr->Raise();
731         mainptr->OpenFile(filepath);
732     }
733 
734     if (helpptr && helpptr->infront) UpdateHelpButtons();
735 }
736 
737 // -----------------------------------------------------------------------------
738 
UnzipFile(const wxString & zippath,const wxString & entry)739 void UnzipFile(const wxString& zippath, const wxString& entry)
740 {
741     wxString filename = entry.AfterLast(wxFILE_SEP_PATH);
742     wxString tempfile = tempdir + filename;
743 
744     if ( IsRuleFile(filename) ) {
745         // rule-related file should have already been extracted and installed
746         // into userrules, so check that file exists and load rule
747         wxString rulefile = userrules + filename;
748         if (wxFileExists(rulefile)) {
749             if (htmlwin->editlink) {
750                 if (filename.Lower().EndsWith(wxT(".icons"))) {
751                     // let user see b&w image in .icons file
752                     mainptr->Raise();
753                     mainptr->OpenFile(rulefile);
754                 } else {
755                     mainptr->EditFile(rulefile);
756                 }
757             } else {
758                 // load corresponding .rule file
759                 LoadRule(filename.BeforeLast('.'));
760             }
761         } else {
762             Warning(_("Rule-related file was not installed:\n") + rulefile);
763         }
764 
765     } else if ( mainptr->ExtractZipEntry(zippath, entry, tempfile) ) {
766         if (htmlwin->editlink) {
767             mainptr->EditFile(tempfile);
768 
769         } else if ( IsHTMLFile(filename) ) {
770             // display html file
771             htmlwin->LoadPage(tempfile);
772             if (helpptr && helpptr->infront) UpdateHelpButtons();
773 
774         } else if ( IsTextFile(filename) ) {
775             // open text file in user's text editor
776             mainptr->EditFile(tempfile);
777 
778         } else if ( IsScriptFile(filename) ) {
779             // run script depending on safety check; note that because the script is
780             // included in a zip file we don't remember it in the Run Recent submenu
781             mainptr->CheckBeforeRunning(tempfile, false, zippath);
782 
783         } else {
784             // open pattern but don't remember in Open Recent menu
785             mainptr->Raise();
786             mainptr->OpenFile(tempfile, false);
787         }
788     }
789 }
790 
791 // -----------------------------------------------------------------------------
792 
ClickLexiconPattern(const wxHtmlCell * htmlcell)793 void ClickLexiconPattern(const wxHtmlCell* htmlcell)
794 {
795     if (htmlcell) {
796         wxHtmlContainerCell* parent = htmlcell->GetParent();
797         if (parent) {
798             parent = parent->GetParent();
799             if (parent) {
800                 wxHtmlCell* container = parent->GetFirstChild();
801 
802                 // extract pattern data and store in lexpattern
803                 lexpattern.Clear();
804                 while (container) {
805                     wxHtmlCell* cell = container->GetFirstChild();
806                     while (cell) {
807                         wxString celltext = cell->ConvertToText(NULL);
808                         if (celltext.IsEmpty()) {
809                             // probably a formatting cell
810                         } else {
811                             lexpattern += celltext;
812                             // append eol char(s)
813 #ifdef __WXMSW__
814                             // use DOS line ending (CR+LF) on Windows
815                             lexpattern += '\r';
816                             lexpattern += '\n';
817 #else
818                             // use LF on Linux or Mac
819                             lexpattern += '\n';
820 #endif
821                         }
822                         cell = cell->GetNext();
823                     }
824                     container = container->GetNext();
825                 }
826 
827                 if (!lexpattern.IsEmpty()) {
828                     mainptr->Raise();
829                     // look for existing lexicon layer
830                     lexlayer = -1;
831                     for (int i = 0; i < numlayers; i++) {
832                         if (GetLayer(i)->currname == lexicon_name) {
833                             lexlayer = i;
834                             break;
835                         }
836                     }
837                     if (lexlayer < 0 && numlayers == MAX_LAYERS) {
838                         Warning(_("Cannot create new layer for lexicon pattern."));
839                         return;
840                     }
841 
842                     if (mainptr->generating) {
843                         mainptr->command_pending = true;
844                         mainptr->cmdevent.SetId(ID_LOAD_LEXICON);
845                         mainptr->Stop();
846                         return;
847                     }
848 
849                     LoadLexiconPattern();
850                 }
851             }
852         }
853     }
854 }
855 
856 // -----------------------------------------------------------------------------
857 
LoadLexiconPattern()858 void LoadLexiconPattern()
859 {
860     // switch to existing lexicon layer or create a new such layer
861     if (lexlayer >= 0) {
862         SetLayer(lexlayer);
863     } else {
864         AddLayer();
865         mainptr->SetWindowTitle(lexicon_name);
866     }
867 
868     // copy lexpattern data to tempstart file so we can handle
869     // all formats supported by readpattern
870     wxFile outfile(currlayer->tempstart, wxFile::write);
871     if ( outfile.IsOpened() ) {
872         outfile.Write(lexpattern);
873         outfile.Close();
874 
875         // all Life Lexicon patterns assume we're using Conway's Life so try
876         // switching to B3/S23 or Life; if that fails then switch to QuickLife
877         const char* err = currlayer->algo->setrule("B3/S23");
878         if (err) {
879             // try "Life" in case current algo is RuleLoader and Life.rule exists
880             // (also had to make a similar change to the loadpattern code in readpattern.cpp)
881             err = currlayer->algo->setrule("Life");
882         }
883         if (err) {
884             mainptr->ChangeAlgorithm(QLIFE_ALGO, wxString("B3/S23",wxConvLocal));
885         }
886 
887         // load lexicon pattern into current layer
888         mainptr->LoadPattern(currlayer->tempstart, lexicon_name);
889     } else {
890         Warning(_("Could not create tempstart file!"));
891     }
892 }
893 
894 // -----------------------------------------------------------------------------
895 
OnLinkClicked(const wxHtmlLinkInfo & link)896 void HtmlView::OnLinkClicked(const wxHtmlLinkInfo& link)
897 {
898 #ifdef __WXMAC__
899     if ( stopwatch->Time() - whenactive < 500 ) {
900         // avoid problem on Mac:
901         // ignore click in link if the help window was in the background;
902         // this isn't fail safe because OnLinkClicked is only called AFTER
903         // the mouse button is released (better soln would be to set an
904         // ignoreclick flag in OnMouseDown handler if click occurred very
905         // soon after activate)
906         return;
907     }
908 #endif
909 
910     wxString url = link.GetHref();
911     if ( url.StartsWith(wxT("http:")) || url.StartsWith(wxT("mailto:")) ) {
912         // pass http/mailto URL to user's preferred browser/emailer
913         if ( !wxLaunchDefaultBrowser(url) )
914             Warning(_("Could not open URL in browser!"));
915 
916     } else if ( url.StartsWith(wxT("get:")) ) {
917         if (mainptr->generating) {
918             Warning(_("Cannot download file while generating a pattern."));
919         } else if (inscript) {
920             Warning(_("Cannot download file while a script is running."));
921         } else if (editlink && IsZipFile(url)) {
922             Warning(_("Opening a zip file in a text editor is not a good idea."));
923         } else {
924             // download clicked file
925             GetURL( url.AfterFirst(':') );
926         }
927 
928     } else if ( url.StartsWith(wxT("unzip:")) ) {
929         if (inscript) {
930             Warning(_("Cannot extract zip entry while a script is running."));
931         } else {
932             // extract clicked entry from zip file
933             wxString zippath = url.AfterFirst(':');
934             wxString entry = url.AfterLast(':');
935             zippath = zippath.BeforeLast(':');
936             UnzipFile(zippath, entry);
937         }
938 
939     } else if ( url.StartsWith(wxT("edit:")) ) {
940         // open clicked file in user's preferred text editor
941         wxString path = url.AfterFirst(':');
942 #ifdef __WXMSW__
943         path.Replace(wxT("/"), wxT("\\"));
944 #endif
945         wxFileName fname(path);
946         if (!fname.IsAbsolute()) path = gollydir + path;
947         mainptr->EditFile(path);
948 
949     } else if ( url.StartsWith(wxT("lexpatt:")) ) {
950         if (inscript) {
951             Warning(_("Cannot load lexicon pattern while a script is running."));
952         } else {
953             // user clicked on pattern in Life Lexicon
954             ClickLexiconPattern( link.GetHtmlCell() );
955         }
956 
957     } else if ( url.StartsWith(wxT("prefs:")) ) {
958         // user clicked on link to Preferences dialog
959         mainptr->ShowPrefsDialog( url.AfterFirst(':') );
960 
961     } else if ( url.StartsWith(wxT("open:")) ) {
962         wxString path = url.AfterFirst(':');
963 #ifdef __WXMSW__
964         path.Replace(wxT("/"), wxT("\\"));
965 #endif
966         wxFileName fname(path);
967         if (!fname.IsAbsolute()) path = gollydir + path;
968         if (inscript) {
969             if (pass_file_events) {
970                 PassFileToScript(path);
971             }
972         } else {
973             // open clicked file
974             if (editlink) {
975                 mainptr->EditFile(path);
976             } else {
977                 mainptr->Raise();
978                 mainptr->OpenFile(path);
979             }
980         }
981 
982     } else if ( url.StartsWith(wxT("rule:")) ) {
983         // switch to given rule (false = not necessarily in a .rule file)
984         LoadRule( url.AfterFirst(':'), false );
985 
986     } else {
987         // assume it's a link to a local target or another help file
988         CheckAndLoad(url);
989         if (helpptr && helpptr->infront) UpdateHelpButtons();
990     }
991 }
992 
993 // -----------------------------------------------------------------------------
994 
OnCellMouseHover(wxHtmlCell * cell,wxCoord x,wxCoord y)995 void HtmlView::OnCellMouseHover(wxHtmlCell* cell, wxCoord x, wxCoord y)
996 {
997     if (helpptr && helpptr->infront && cell) {
998         wxHtmlLinkInfo* link = cell->GetLink(x,y);
999         if (link) {
1000             wxString href = link->GetHref();
1001             href.Replace(wxT("&"), wxT("&&"));
1002             helpptr->SetStatus(href);
1003             wxPoint pt = ScreenToClient( wxGetMousePosition() );
1004             linkrect = wxRect(pt.x-x, pt.y-y, cell->GetWidth(), cell->GetHeight());
1005         } else {
1006             ClearStatus();
1007         }
1008     }
1009 }
1010 
1011 // -----------------------------------------------------------------------------
1012 
OnMouseMotion(wxMouseEvent & event)1013 void HtmlView::OnMouseMotion(wxMouseEvent& event)
1014 {
1015     if (helpptr && helpptr->infront && !linkrect.IsEmpty()) {
1016         int x = event.GetX();
1017         int y = event.GetY();
1018         if (!linkrect.Contains(x,y)) ClearStatus();
1019     }
1020     event.Skip();
1021 }
1022 
1023 // -----------------------------------------------------------------------------
1024 
OnMouseLeave(wxMouseEvent & event)1025 void HtmlView::OnMouseLeave(wxMouseEvent& event)
1026 {
1027     if (helpptr && helpptr->infront) {
1028         ClearStatus();
1029     }
1030     event.Skip();
1031 }
1032 
1033 // -----------------------------------------------------------------------------
1034 
ClearStatus()1035 void HtmlView::ClearStatus()
1036 {
1037     if (helpptr) {
1038         helpptr->SetStatus(wxEmptyString);
1039         linkrect = wxRect(0,0,0,0);
1040     }
1041 }
1042 
1043 // -----------------------------------------------------------------------------
1044 
1045 #if defined(__WXMAC__) && wxCHECK_VERSION(2,9,0)
1046     // wxMOD_CONTROL has been changed to mean Command key down (sheesh!)
1047     #define wxMOD_CONTROL wxMOD_RAW_CONTROL
1048     #define ControlDown RawControlDown
1049 #endif
1050 
OnMouseDown(wxMouseEvent & event)1051 void HtmlView::OnMouseDown(wxMouseEvent& event)
1052 {
1053     // set flag so ctrl/right-clicked file can be opened in editor
1054     // (this is consistent with how we handle clicks in the file pane)
1055     editlink = event.ControlDown() || event.RightDown();
1056     event.Skip();
1057 }
1058 
1059 // -----------------------------------------------------------------------------
1060 
CheckAndLoad(const wxString & filepath)1061 void HtmlView::CheckAndLoad(const wxString& filepath)
1062 {
1063     if (filepath == SHOW_KEYBOARD_SHORTCUTS) {
1064         // build HTML string to display current keyboard shortcuts
1065         wxString contents = GetShortcutTable();
1066 
1067         // write contents to file and call LoadPage so that back/forwards buttons work
1068         wxString htmlfile = tempdir + SHOW_KEYBOARD_SHORTCUTS;
1069         wxFile outfile(htmlfile, wxFile::write);
1070         if (outfile.IsOpened()) {
1071             outfile.Write(contents);
1072             outfile.Close();
1073             LoadPage(htmlfile);
1074         } else {
1075             Warning(_("Could not create file:\n") + htmlfile);
1076             // might as well show contents
1077             SetPage(contents);
1078             currhelp = SHOW_KEYBOARD_SHORTCUTS;
1079         }
1080 
1081     } else if ( filepath.StartsWith(_("Help/")) ) {
1082         // prepend location of Golly so user can open help while running a script
1083         wxString fullpath = gollydir + filepath;
1084         LoadPage(fullpath);
1085 
1086     } else {
1087         // assume full path or local link
1088         #if defined(__WXMSW__) && wxCHECK_VERSION(3,1,0)
1089             wxFileName fname(filepath);
1090             if (fname.IsAbsolute()) {
1091                 // call LoadFile rather than LoadPage to avoid bug in wx 3.1.0 on Windows 10
1092                 LoadFile(fname);
1093             } else {
1094                 LoadPage(filepath);
1095             }
1096         #else
1097             LoadPage(filepath);
1098         #endif
1099     }
1100 }
1101 
1102 // -----------------------------------------------------------------------------
1103 
1104 #ifdef __WXMSW__
1105 // we have to use OnKeyUp handler on Windows otherwise wxHtmlWindow's OnKeyUp
1106 // gets called which detects ctrl-C and clobbers our clipboard fix
OnKeyUp(wxKeyEvent & event)1107 void HtmlView::OnKeyUp(wxKeyEvent& event)
1108 #else
1109 // we have to use OnKeyDown handler on Mac -- if OnKeyUp handler is used and
1110 // cmd-C is pressed quickly then key code is 400!!!
1111 void HtmlView::OnKeyDown(wxKeyEvent& event)
1112 #endif
1113 {
1114     int key = event.GetKeyCode();
1115     if (event.CmdDown()) {
1116         // let cmd-A select all text
1117         if (key == 'A') {
1118             SelectAll();
1119             return;
1120         }
1121 #ifdef __WXMAC__
1122         // let cmd-W close help window or about box
1123         if (key == 'W') {
1124             GetParent()->Close(true);
1125             return;
1126         }
1127 #endif
1128     }
1129     if ( event.CmdDown() || event.AltDown() ) {
1130         if ( key == 'C' ) {
1131             // copy any selected text to the clipboard
1132             wxString text = SelectionToText();
1133             if ( text.Length() > 0 ) {
1134                 if ( helpptr && helpptr->infront &&
1135                     GetOpenedPageTitle().StartsWith(wxT("Life Lexicon")) ) {
1136                     // avoid wxHTML bug when copying text inside <pre>...</pre>!!!
1137                     // if there are at least 2 lines and the 1st line is twice
1138                     // the size of the 2nd line then insert \n in middle of 1st line
1139                     if ( text.Freq('\n') > 0 ) {
1140                         wxString line1 = text.BeforeFirst('\n');
1141                         wxString aftern = text.AfterFirst('\n');
1142                         wxString line2 = aftern.BeforeFirst('\n');
1143                         size_t line1len = line1.Length();
1144                         size_t line2len = line2.Length();
1145                         if ( line1len == 2 * line2len ) {
1146                             wxString left = text.Left(line2len);
1147                             wxString right = text.Mid(line2len);
1148                             text = left;
1149                             text += '\n';
1150                             text += right;
1151                         }
1152                     }
1153                 }
1154                 mainptr->CopyTextToClipboard(text);
1155             }
1156         } else {
1157             event.Skip();
1158         }
1159     } else {
1160         // this handler is also called from ShowAboutBox
1161         if ( helpptr == NULL || !helpptr->infront ) {
1162             if ( key == WXK_NUMPAD_ENTER || key == WXK_RETURN ) {
1163                 // allow enter key or return key to close about box
1164                 GetParent()->Close(true);
1165                 return;
1166             }
1167             event.Skip();
1168             return;
1169         }
1170         // let escape/return/enter key close help window
1171         if ( key == WXK_ESCAPE || key == WXK_RETURN || key == WXK_NUMPAD_ENTER ) {
1172             helpptr->Close(true);
1173         } else if ( key == WXK_HOME ) {
1174             ShowHelp(helphome);
1175         } else {
1176             event.Skip();
1177         }
1178     }
1179 }
1180 
1181 // -----------------------------------------------------------------------------
1182 
OnChar(wxKeyEvent & event)1183 void HtmlView::OnChar(wxKeyEvent& event)
1184 {
1185     // this handler is also called from ShowAboutBox
1186     if ( helpptr == NULL || !helpptr->infront ) {
1187         event.Skip();
1188         return;
1189     }
1190     int key = event.GetKeyCode();
1191     if ( key == '+' || key == '=' || key == WXK_ADD ) {
1192         if ( helpfontsize < maxfontsize ) {
1193             helpfontsize++;
1194             ChangeFontSizes(helpfontsize);
1195         }
1196     } else if ( key == '-' || key == WXK_SUBTRACT ) {
1197         if ( helpfontsize > minfontsize ) {
1198             helpfontsize--;
1199             ChangeFontSizes(helpfontsize);
1200         }
1201     } else if ( key == '[' || key == WXK_LEFT ) {
1202         if ( HistoryBack() ) {
1203             UpdateHelpButtons();
1204         }
1205     } else if ( key == ']' || key == WXK_RIGHT ) {
1206         if ( HistoryForward() ) {
1207             UpdateHelpButtons();
1208         }
1209     } else {
1210         event.Skip();     // so up/down arrows and pg up/down keys work
1211     }
1212 }
1213 
1214 // -----------------------------------------------------------------------------
1215 
1216 // avoid scroll position being reset to top when wxHtmlWindow is resized
OnSize(wxSizeEvent & event)1217 void HtmlView::OnSize(wxSizeEvent& event)
1218 {
1219     int x, y;
1220     GetViewStart(&x, &y);         // save current position
1221 
1222     wxHtmlWindow::OnSize(event);
1223 
1224     wxString location = GetOpenedPage();
1225     if ( !location.IsEmpty() && canreload ) {
1226 
1227         if (location.StartsWith(wxT("file:"))) {
1228             // this happens in wx 3.1.0, so convert location from URL to file path
1229             wxFileName fname = wxFileSystem::URLToFileName(location);
1230             location = fname.GetFullPath();
1231             #ifdef __WXMSW__
1232                 location.Replace(wxT("\\"), wxT("/"));
1233             #endif
1234         }
1235 
1236         // avoid bug in wx 3.1.0
1237         location.Replace(wxT("%20"), wxT(" "));
1238         location.Replace(wxT("%23"), wxT("#"));
1239 
1240         CheckAndLoad(location);    // reload page
1241         Scroll(x, y);              // scroll to old position
1242     }
1243 
1244     // prevent wxHtmlWindow::OnSize being called again
1245     event.Skip(false);
1246 }
1247 
1248 // -----------------------------------------------------------------------------
1249 
OnTimer(wxTimerEvent & WXUNUSED (event))1250 void HtmlView::OnTimer(wxTimerEvent& WXUNUSED(event))
1251 {
1252     if (helpptr && helpptr->infront) {
1253         // send idle event to html window so cursor gets updated
1254         // even while app is busy doing something else (eg. generating)
1255         wxIdleEvent idleevent;
1256 #if wxCHECK_VERSION(2,9,2)
1257         // SendIdleEvents is now in wxWindow
1258         SendIdleEvents(idleevent);
1259 #else
1260         wxGetApp().SendIdleEvents(this, idleevent);
1261 #endif
1262     }
1263 }
1264 
1265 // -----------------------------------------------------------------------------
1266 
SetFontSizes(int size)1267 void HtmlView::SetFontSizes(int size)
1268 {
1269     // set font sizes for <FONT SIZE=-2> to <FONT SIZE=+4>
1270     int f_sizes[7];
1271     f_sizes[0] = int(size * 0.6);
1272     f_sizes[1] = int(size * 0.8);
1273     f_sizes[2] = size;
1274     f_sizes[3] = int(size * 1.2);
1275     f_sizes[4] = int(size * 1.4);
1276     f_sizes[5] = int(size * 1.6);
1277     f_sizes[6] = int(size * 1.8);
1278 #ifdef __WXOSX_COCOA__
1279     SetFonts(wxT("Lucida Grande"), wxT("Monaco"), f_sizes);
1280 #else
1281     SetFonts(wxEmptyString, wxEmptyString, f_sizes);
1282 #endif
1283 }
1284 
1285 // -----------------------------------------------------------------------------
1286 
ChangeFontSizes(int size)1287 void HtmlView::ChangeFontSizes(int size)
1288 {
1289     // changing font sizes resets pos to top, so save and restore pos
1290     int x, y;
1291     GetViewStart(&x, &y);
1292     SetFontSizes(size);
1293     if (y > 0) Scroll(-1, y);
1294 }
1295 
1296 // -----------------------------------------------------------------------------
1297 
ShowAboutBox()1298 void ShowAboutBox()
1299 {
1300     if (viewptr->waitingforclick) return;
1301 
1302     wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
1303     wxDialog dlg(mainptr, wxID_ANY, wxString(_("About Golly")));
1304 
1305     HtmlView* html = new HtmlView(&dlg, wxID_ANY, wxDefaultPosition,
1306 #if wxCHECK_VERSION(2,9,0)
1307                                   // work around SetSize bug below!!!
1308                                   wxSize(400, 320),
1309 #elif defined(__WXGTK__)
1310                                   wxSize(460, 220),
1311 #else
1312                                   wxSize(386, 220),
1313 #endif
1314                                   wxHW_SCROLLBAR_NEVER | wxSUNKEN_BORDER);
1315     html->SetBorders(0);
1316 #ifdef __WXOSX_COCOA__
1317     html->SetFontSizes(helpfontsize);
1318 #endif
1319     html->CheckAndLoad(_("Help/about.html"));
1320 
1321     // avoid HtmlView::OnSize calling CheckAndLoad again
1322     html->canreload = false;
1323 
1324     // this call seems to be ignored in __WXOSX_COCOA__!!!
1325     html->SetSize(html->GetInternalRepresentation()->GetWidth(),
1326                   html->GetInternalRepresentation()->GetHeight());
1327 
1328     topsizer->Add(html, 1, wxALL, 10);
1329 
1330     wxButton* okbutt = new wxButton(&dlg, wxID_OK, _("OK"));
1331     okbutt->SetDefault();
1332     topsizer->Add(okbutt, 0, wxBOTTOM | wxALIGN_CENTER, 10);
1333 
1334     dlg.SetSizer(topsizer);
1335     topsizer->Fit(&dlg);
1336     dlg.Centre();
1337     dlg.ShowModal();
1338     viewptr->ResetMouseDown();
1339 }
1340