1 // This file is part of Golly.
2 // See docs/License.html for the copyright notice.
3 
4 // A GUI for Golly, implemented in wxWidgets (www.wxwidgets.org).
5 // Unfinished code is flagged by "!!!".
6 // Uncertain code is flagged by "???".
7 
8 #include "wx/wxprec.h"     // for compilers that support precompilation
9 #ifndef WX_PRECOMP
10     #include "wx/wx.h"     // for all others include the necessary headers
11 #endif
12 
13 #include "wx/image.h"      // for wxImage
14 #include "wx/stdpaths.h"   // for wxStandardPaths
15 #include "wx/sysopt.h"     // for wxSystemOptions
16 #include "wx/filename.h"   // for wxFileName
17 #include "wx/fs_inet.h"    // for wxInternetFSHandler
18 #include "wx/fs_zip.h"     // for wxZipFSHandler
19 
20 #include "lifepoll.h"
21 #include "util.h"          // for lifeerrors
22 
23 #include "wxgolly.h"       // defines GollyApp class
24 #include "wxmain.h"        // defines MainFrame class
25 #include "wxstatus.h"      // defines StatusBar class
26 #include "wxview.h"        // defines PatternView class
27 #include "wxutils.h"       // for Warning, Fatal, BeginProgress, etc
28 #include "wxprefs.h"       // for GetPrefs, gollydir, rulesdir, userrules
29 
30 #ifdef __WXMSW__
31     // app icons are loaded via .rc file
32 #else
33     #include "icons/appicon.xpm"
34 #endif
35 
36 // -----------------------------------------------------------------------------
37 
38 // Create a new application object: this macro will allow wxWidgets to create
39 // the application object during program execution and also implements the
40 // accessor function wxGetApp() which will return the reference of the correct
41 // type (ie. GollyApp and not wxApp).
42 
43 IMPLEMENT_APP(GollyApp)
44 
45 // -----------------------------------------------------------------------------
46 
47 #define STRINGIFY(arg) STR2(arg)
48 #define STR2(arg) #arg
49 
50 MainFrame* mainptr = NULL;       // main window
51 PatternView* viewptr = NULL;     // current viewport window (possibly a tile)
52 PatternView* bigview = NULL;     // main viewport window
53 StatusBar* statusptr = NULL;     // status bar window
54 wxStopWatch* stopwatch;          // global stopwatch
55 bool insideYield = false;        // processing an event via Yield()?
56 
57 // -----------------------------------------------------------------------------
58 
59 // let non-wx modules call Fatal, Warning, BeginProgress, etc
60 
61 class wx_errors : public lifeerrors
62 {
63 public:
fatal(const char * s)64     virtual void fatal(const char* s) {
65         Fatal(wxString(s,wxConvLocal));
66     }
67 
warning(const char * s)68     virtual void warning(const char* s) {
69         Warning(wxString(s,wxConvLocal));
70     }
71 
status(const char * s)72     virtual void status(const char* s) {
73         statusptr->DisplayMessage(wxString(s,wxConvLocal));
74     }
75 
beginprogress(const char * s)76     virtual void beginprogress(const char* s) {
77         BeginProgress(wxString(s,wxConvLocal));
78         // init flag for isaborted() calls in non-wx modules
79         aborted = false;
80     }
81 
abortprogress(double f,const char * s)82     virtual bool abortprogress(double f, const char* s) {
83         return AbortProgress(f, wxString(s,wxConvLocal));
84     }
85 
endprogress()86     virtual void endprogress() {
87         EndProgress();
88     }
89 
getuserrules()90     virtual const char* getuserrules() {
91         // need to be careful converting Unicode wxString to char*
92         #ifdef __WXMAC__
93             // we need to convert path to decomposed UTF8 so fopen will work
94             dirbuff = userrules.fn_str();
95         #else
96             dirbuff = userrules.mb_str(wxConvLocal);
97         #endif
98         return (const char*) dirbuff;
99     }
100 
getrulesdir()101     virtual const char* getrulesdir() {
102         // need to be careful converting Unicode wxString to char*
103         #ifdef __WXMAC__
104             // we need to convert path to decomposed UTF8 so fopen will work
105             dirbuff = rulesdir.fn_str();
106         #else
107             dirbuff = rulesdir.mb_str(wxConvLocal);
108         #endif
109         return (const char*) dirbuff;
110     }
111 
112 private:
113     wxCharBuffer dirbuff;
114 };
115 
116 wx_errors wxerrhandler;    // create instance
117 
118 // -----------------------------------------------------------------------------
119 
120 // let non-wx modules process events
121 
122 class wx_poll : public lifepoll
123 {
124 public:
125     virtual int checkevents();
126     virtual void updatePop();
127     long nextcheck;
128 };
129 
checkevents()130 int wx_poll::checkevents()
131 {
132     // avoid calling Yield too often
133     long t = stopwatch->Time();
134     if (t > nextcheck) {
135         nextcheck = t + 100;        // call 10 times per sec
136         if (mainptr->infront) {
137             // make sure viewport window keeps keyboard focus
138             viewptr->SetFocus();
139         }
140         insideYield = true;
141         wxGetApp().Yield(true);
142         insideYield = false;
143     }
144     return isInterrupted();
145 }
146 
updatePop()147 void wx_poll::updatePop()
148 {
149     if (showstatus && !mainptr->IsIconized()) {
150         statusptr->Refresh(false);
151     }
152 }
153 
154 wx_poll wxpoller;    // create instance
155 
Poller()156 lifepoll* GollyApp::Poller()
157 {
158     return &wxpoller;
159 }
160 
PollerReset()161 void GollyApp::PollerReset()
162 {
163     wxpoller.resetInterrupted();
164     wxpoller.nextcheck = 0;
165 }
166 
PollerInterrupt()167 void GollyApp::PollerInterrupt()
168 {
169     wxpoller.setInterrupted();
170     wxpoller.nextcheck = 0;
171 }
172 
173 // -----------------------------------------------------------------------------
174 
SetAppDirectory(const char * argv0)175 void SetAppDirectory(const char* argv0)
176 {
177 #ifdef __WXMSW__
178     // on Windows we need to reset current directory to app directory if user
179     // dropped file from somewhere else onto app to start it up (otherwise we
180     // can't find Help files)
181     wxString appdir = wxStandardPaths::Get().GetDataDir();
182     wxString currdir = wxGetCwd();
183     if ( currdir.CmpNoCase(appdir) != 0 )
184         wxSetWorkingDirectory(appdir);
185     // avoid VC++ warning
186     wxUnusedVar(argv0);
187 #elif defined(__WXMAC__)
188     // wxMac has set current directory to location of .app bundle so no need
189     // to do anything
190 #else // assume Unix
191     // first, try to switch to GOLLYDIR if that is set to a sensible value:
192     static const char *gd = STRINGIFY(GOLLYDIR);
193     if ( *gd == '/' && wxSetWorkingDirectory(wxString(gd,wxConvLocal)) ) {
194         return;
195     }
196     // otherwise, use the executable directory as the application directory.
197     // user might have started app from a different directory so find
198     // last "/" in argv0 and change cwd if "/" isn't part of "./" prefix
199     unsigned int pos = strlen(argv0);
200     while (pos > 0) {
201         pos--;
202         if (argv0[pos] == '/') break;
203     }
204     if ( pos > 0 && !(pos == 1 && argv0[0] == '.') ) {
205         char appdir[2048];
206         if (pos < sizeof(appdir)) {
207             strncpy(appdir, argv0, pos);
208             appdir[pos] = 0;
209             wxSetWorkingDirectory(wxString(appdir,wxConvLocal));
210         }
211     }
212 #endif
213 }
214 
215 // -----------------------------------------------------------------------------
216 
SetFrameIcon(wxFrame * frame)217 void GollyApp::SetFrameIcon(wxFrame* frame)
218 {
219     // set frame icon
220 #ifdef __WXMSW__
221     // create a bundle with 32x32 and 16x16 icons
222     wxIconBundle icb(wxICON(appicon0));
223     icb.AddIcon(wxICON(appicon1));
224     frame->SetIcons(icb);
225 #else
226     // use appicon.xpm on other platforms (ignored on Mac)
227     frame->SetIcon(wxICON(appicon));
228 #endif
229 }
230 
231 // -----------------------------------------------------------------------------
232 
233 static wxString initdir;    // set to current working directory when app starts
234 
235 #ifdef __WXMAC__
236 
237 // open file double-clicked or dropped onto Golly icon
238 
MacOpenFile(const wxString & path)239 void GollyApp::MacOpenFile(const wxString& path)
240 {
241     mainptr->Raise();
242     wxFileName filename(path);
243     // convert given path to a full path if not one already
244     if (!filename.IsAbsolute()) filename = initdir + path;
245     mainptr->pendingfiles.Add(filename.GetFullPath());
246 
247     // next OnIdle will call OpenFile (if we call OpenFile here with a script
248     // that opens a modal dialog then dialog can't be closed!)
249 }
250 
251 #endif
252 
253 // -----------------------------------------------------------------------------
254 
255 // app execution starts here
256 
OnInit()257 bool GollyApp::OnInit()
258 {
259     SetAppName(_("Golly"));    // for use in Warning/Fatal dialogs
260 
261     // create a stopwatch so we can use Time() to get elapsed millisecs
262     stopwatch = new wxStopWatch();
263 
264     // set variable seed for later rand() calls
265     srand(time(0));
266 
267 #if defined(__WXMAC__) && !wxCHECK_VERSION(2,7,2)
268     // prevent rectangle animation when windows open/close
269     wxSystemOptions::SetOption(wxMAC_WINDOW_PLAIN_TRANSITION, 1);
270     // prevent position problem in wxTextCtrl with wxTE_DONTWRAP style
271     // (but doesn't fix problem with I-beam cursor over scroll bars)
272     wxSystemOptions::SetOption(wxMAC_TEXTCONTROL_USE_MLTE, 1);
273 #endif
274 
275     // get current working directory before calling SetAppDirectory
276     initdir = wxFileName::GetCwd();
277     if (initdir.Last() != wxFILE_SEP_PATH) initdir += wxFILE_SEP_PATH;
278 
279     // make sure current working directory contains application otherwise
280     // we can't open Help files
281     SetAppDirectory( wxString(argv[0]).mb_str(wxConvLocal) );
282 
283     // now set global gollydir for use in GetPrefs and elsewhere
284     gollydir = wxFileName::GetCwd();
285     if (gollydir.Last() != wxFILE_SEP_PATH) gollydir += wxFILE_SEP_PATH;
286 
287     // let non-wx modules call Fatal, Warning, BeginProgress, etc
288     lifeerrors::seterrorhandler(&wxerrhandler);
289 
290     // allow .html files to include common graphic formats,
291     // and .icons files to be in any of these formats;
292     // note that wxBMPHandler is always installed, so it needs not be added,
293     // and we can assume that if HAVE_WX_BMP_HANDLER is not defined, then
294     // the handlers have not been auto-detected (and we just install them all).
295 #if !defined(HAVE_WX_BMP_HANDLER) || defined(HAVE_WX_GIF_HANDLER)
296     wxImage::AddHandler(new wxGIFHandler);
297 #endif
298 #if !defined(HAVE_WX_BMP_HANDLER) || defined(HAVE_WX_PNG_HANDLER)
299     wxImage::AddHandler(new wxPNGHandler);
300 #endif
301 #if !defined(HAVE_WX_BMP_HANDLER) || defined(HAVE_WX_TIFF_HANDLER)
302     wxImage::AddHandler(new wxTIFFHandler);
303 #endif
304 
305     // wxInternetFSHandler is needed to allow downloading files
306     wxFileSystem::AddHandler(new wxInternetFSHandler);
307     wxFileSystem::AddHandler(new wxZipFSHandler);
308 
309     // get main window location and other user preferences
310     GetPrefs();
311 
312     // create main window (also initializes viewptr, bigview, statusptr)
313     mainptr = new MainFrame();
314     if (mainptr == NULL) Fatal(_("Failed to create main window!"));
315 
316     // initialize some stuff before showing main window
317     mainptr->SetRandomFillPercentage();
318     mainptr->SetMinimumStepExponent();
319 
320     wxString banner = _("This is Golly version ");
321     banner +=         _(STRINGIFY(VERSION));
322     banner +=         _(" (");
323 #ifdef GOLLY64BIT
324     banner +=         _("64-bit");
325 #else
326     banner +=         _("32-bit");
327 #endif
328 #ifdef ENABLE_SOUND
329     banner +=         _(", Sound");
330 #endif
331     banner +=         _(").  Copyright 2005-2019 The Golly Gang.");
332     if (debuglevel > 0) {
333         banner += wxString::Format(_("  *** debuglevel = %d ***"), debuglevel);
334     }
335     statusptr->SetMessage(banner);
336 
337     mainptr->NewPattern();
338 
339     // script/pattern files are stored in the pendingfiles array for later processing
340     // in OnIdle; this avoids a crash in Win app if a script is run before showing
341     // the main window, and also avoids event problems in Win app with a long-running
342     // script (eg. user can't hit escape to abort script)
343     const wxString START_LUA = wxT("golly-start.lua");
344     const wxString START_PYTHON = wxT("golly-start.py");
345     wxString startscript = gollydir + START_LUA;
346     if (wxFileExists(startscript)) {
347         mainptr->pendingfiles.Add(startscript);
348     } else {
349         // look in user-specific data directory
350         startscript = datadir + START_LUA;
351         if (wxFileExists(startscript)) {
352             mainptr->pendingfiles.Add(startscript);
353         }
354     }
355     startscript = gollydir + START_PYTHON;
356     if (wxFileExists(startscript)) {
357         mainptr->pendingfiles.Add(startscript);
358     } else {
359         // look in user-specific data directory
360         startscript = datadir + START_PYTHON;
361         if (wxFileExists(startscript)) {
362             mainptr->pendingfiles.Add(startscript);
363         }
364     }
365 
366     // argc is > 1 if command line has one or more script/pattern files
367     for (int n = 1; n < argc; n++) {
368         wxFileName filename(argv[n]);
369         // convert given path to a full path if not one already
370         if (!filename.IsAbsolute()) filename = initdir + argv[n];
371         mainptr->pendingfiles.Add(filename.GetFullPath());
372     }
373 
374     // show main window
375     if (maximize) mainptr->Maximize(true);
376     mainptr->Show(true);
377     SetTopWindow(mainptr);
378 
379     // true means call wxApp::OnRun() which will enter the main event loop;
380     // false means exit immediately
381     return true;
382 }
383