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