]> git.saurik.com Git - wxWidgets.git/blame - demos/life/life.cpp
Add new sample project files.
[wxWidgets.git] / demos / life / life.cpp
CommitLineData
5a1dca12
GRG
1/////////////////////////////////////////////////////////////////////////////
2// Name: life.cpp
29b07a38 3// Purpose: The game of Life, created by J. H. Conway
5a1dca12
GRG
4// Author: Guillermo Rodriguez Garcia, <guille@iies.es>
5// Modified by:
6// Created: Jan/2000
7// RCS-ID: $Id$
8// Copyright: (c) 2000, Guillermo Rodriguez Garcia
9// Licence: wxWindows licence
10/////////////////////////////////////////////////////////////////////////////
11
12// ==========================================================================
e0a40292 13// headers, declarations, constants
5a1dca12
GRG
14// ==========================================================================
15
2fa7c206
JS
16// For compilers that support precompilation, includes "wx/wx.h".
17#include "wx/wxprec.h"
18
19#ifdef __BORLANDC__
29b07a38 20 #pragma hdrstop
2fa7c206
JS
21#endif
22
23#ifndef WX_PRECOMP
29b07a38 24 #include "wx/wx.h"
2fa7c206
JS
25#endif
26
5a1dca12 27#include "wx/statline.h"
f6bcfd97
BP
28#include "wx/wfstream.h"
29#include "wx/filedlg.h"
5d2ac6b8 30#include "wx/stockitem.h"
2480be69
GRG
31
32#include "life.h"
33#include "game.h"
34#include "dialogs.h"
f6bcfd97 35#include "reader.h"
5a1dca12
GRG
36
37// --------------------------------------------------------------------------
38// resources
39// --------------------------------------------------------------------------
40
e334d0ea 41#if defined(__WXGTK__) || defined(__WXMOTIF__) || defined(__WXMAC__) || defined(__WXMGL__) || defined(__WXX11__)
29b07a38 42 // application icon
5a1dca12
GRG
43 #include "mondrian.xpm"
44
45 // bitmap buttons for the toolbar
46 #include "bitmaps/reset.xpm"
f6bcfd97 47 #include "bitmaps/open.xpm"
5a1dca12
GRG
48 #include "bitmaps/play.xpm"
49 #include "bitmaps/stop.xpm"
e0a40292
GRG
50 #include "bitmaps/zoomin.xpm"
51 #include "bitmaps/zoomout.xpm"
f6bcfd97 52 #include "bitmaps/info.xpm"
29b07a38
GRG
53
54 // navigator
55 #include "bitmaps/north.xpm"
56 #include "bitmaps/south.xpm"
57 #include "bitmaps/east.xpm"
58 #include "bitmaps/west.xpm"
59 #include "bitmaps/center.xpm"
5a1dca12
GRG
60#endif
61
5a1dca12
GRG
62// --------------------------------------------------------------------------
63// constants
64// --------------------------------------------------------------------------
65
22c1c01b 66// IDs for the controls and the menu commands. Exluding those already defined
be5a51fb 67// by wxWidgets, such as wxID_NEW.
5a1dca12
GRG
68enum
69{
29b07a38 70 // timer
22c1c01b 71 ID_TIMER = wxID_HIGHEST,
29b07a38 72
f6bcfd97 73 // file menu
087e4f4a 74 ID_SAMPLES,
29b07a38 75
f6bcfd97 76 // view menu
29b07a38
GRG
77 ID_SHOWNAV,
78 ID_ORIGIN,
79 ID_CENTER,
80 ID_NORTH,
81 ID_SOUTH,
82 ID_EAST,
83 ID_WEST,
f6bcfd97
BP
84 ID_INFO,
85
86 // game menu
87 ID_START,
88 ID_STEP,
f6bcfd97
BP
89 ID_TOPSPEED,
90
91 // speed selection slider
22c1c01b 92 ID_SLIDER
5a1dca12
GRG
93};
94
95// --------------------------------------------------------------------------
be5a51fb 96// event tables and other macros for wxWidgets
5a1dca12
GRG
97// --------------------------------------------------------------------------
98
99// Event tables
5a1dca12 100BEGIN_EVENT_TABLE(LifeFrame, wxFrame)
5d2ac6b8 101 EVT_MENU (wxID_NEW, LifeFrame::OnMenu)
5f4d35b8 102#if wxUSE_FILEDLG
5d2ac6b8 103 EVT_MENU (wxID_OPEN, LifeFrame::OnOpen)
5f4d35b8 104#endif
5d2ac6b8
WS
105 EVT_MENU (ID_SAMPLES, LifeFrame::OnSamples)
106 EVT_MENU (wxID_ABOUT, LifeFrame::OnMenu)
107 EVT_MENU (wxID_EXIT, LifeFrame::OnMenu)
108 EVT_MENU (ID_SHOWNAV, LifeFrame::OnMenu)
109 EVT_MENU (ID_ORIGIN, LifeFrame::OnNavigate)
110 EVT_BUTTON (ID_CENTER, LifeFrame::OnNavigate)
111 EVT_BUTTON (ID_NORTH, LifeFrame::OnNavigate)
112 EVT_BUTTON (ID_SOUTH, LifeFrame::OnNavigate)
113 EVT_BUTTON (ID_EAST, LifeFrame::OnNavigate)
114 EVT_BUTTON (ID_WEST, LifeFrame::OnNavigate)
115 EVT_MENU (wxID_ZOOM_IN, LifeFrame::OnZoom)
116 EVT_MENU (wxID_ZOOM_OUT,LifeFrame::OnZoom)
117 EVT_MENU (ID_INFO, LifeFrame::OnMenu)
118 EVT_MENU (ID_START, LifeFrame::OnMenu)
119 EVT_MENU (ID_STEP, LifeFrame::OnMenu)
120 EVT_MENU (wxID_STOP, LifeFrame::OnMenu)
121 EVT_MENU (ID_TOPSPEED, LifeFrame::OnMenu)
122 EVT_COMMAND_SCROLL (ID_SLIDER, LifeFrame::OnSlider)
123 EVT_TIMER (ID_TIMER, LifeFrame::OnTimer)
124 EVT_CLOSE ( LifeFrame::OnClose)
ecbdd409 125END_EVENT_TABLE()
5a1dca12 126
29b07a38
GRG
127BEGIN_EVENT_TABLE(LifeNavigator, wxMiniFrame)
128 EVT_CLOSE ( LifeNavigator::OnClose)
129END_EVENT_TABLE()
130
e0a40292
GRG
131BEGIN_EVENT_TABLE(LifeCanvas, wxWindow)
132 EVT_PAINT ( LifeCanvas::OnPaint)
133 EVT_SCROLLWIN ( LifeCanvas::OnScroll)
134 EVT_SIZE ( LifeCanvas::OnSize)
f6bcfd97
BP
135 EVT_MOTION ( LifeCanvas::OnMouse)
136 EVT_LEFT_DOWN ( LifeCanvas::OnMouse)
137 EVT_LEFT_UP ( LifeCanvas::OnMouse)
138 EVT_LEFT_DCLICK ( LifeCanvas::OnMouse)
e0a40292 139 EVT_ERASE_BACKGROUND( LifeCanvas::OnEraseBackground)
ecbdd409 140END_EVENT_TABLE()
5a1dca12 141
5a1dca12
GRG
142
143// Create a new application object
144IMPLEMENT_APP(LifeApp)
145
e0a40292 146
5a1dca12
GRG
147// ==========================================================================
148// implementation
149// ==========================================================================
150
2480be69 151// some shortcuts
f6bcfd97 152#define ADD_TOOL(id, bmp, tooltip, help) \
05e0b047 153 toolBar->AddTool(id, wxEmptyString, bmp, wxNullBitmap, wxITEM_NORMAL, tooltip, help)
2480be69 154
2480be69 155
5a1dca12
GRG
156// --------------------------------------------------------------------------
157// LifeApp
158// --------------------------------------------------------------------------
159
e0a40292 160// 'Main program' equivalent: the program execution "starts" here
5a1dca12
GRG
161bool LifeApp::OnInit()
162{
163 // create the main application window
164 LifeFrame *frame = new LifeFrame();
165
18f42b94 166 // show it
22c1c01b 167 frame->Show(true);
5a1dca12 168
dbf75be7
GRG
169 // just for Motif
170#ifdef __WXMOTIF__
171 frame->UpdateInfoText();
172#endif
173
5a1dca12 174 // enter the main message loop and run the app
22c1c01b 175 return true;
5a1dca12
GRG
176}
177
178// --------------------------------------------------------------------------
179// LifeFrame
180// --------------------------------------------------------------------------
181
182// frame constructor
5f4d35b8 183LifeFrame::LifeFrame() :
e5c990b3
JS
184 wxFrame( (wxFrame *) NULL, wxID_ANY, _("Life!"), wxDefaultPosition ),
185 m_navigator(NULL)
5a1dca12
GRG
186{
187 // frame icon
188 SetIcon(wxICON(mondrian));
189
190 // menu bar
22c1c01b
DS
191 wxMenu *menuFile = new wxMenu(wxMENU_TEAROFF);
192 wxMenu *menuView = new wxMenu(wxMENU_TEAROFF);
193 wxMenu *menuGame = new wxMenu(wxMENU_TEAROFF);
194 wxMenu *menuHelp = new wxMenu(wxMENU_TEAROFF);
5a1dca12 195
cbd88f22 196 menuFile->Append(wxID_NEW, wxEmptyString, _("Start a new game"));
5f4d35b8 197#if wxUSE_FILEDLG
cbd88f22 198 menuFile->Append(wxID_OPEN, wxEmptyString, _("Open an existing Life pattern"));
5f4d35b8 199#endif
f6bcfd97 200 menuFile->Append(ID_SAMPLES, _("&Sample game..."), _("Select a sample configuration"));
a9102b36 201#if ! (defined(__SMARTPHONE__) || defined(__POCKETPC__))
5a1dca12 202 menuFile->AppendSeparator();
cbd88f22 203 menuFile->Append(wxID_EXIT);
087e4f4a 204
22c1c01b
DS
205 menuView->Append(ID_SHOWNAV, _("Navigation &toolbox"), _("Show or hide toolbox"), wxITEM_CHECK);
206 menuView->Check(ID_SHOWNAV, true);
29b07a38 207 menuView->AppendSeparator();
e5c990b3
JS
208#endif
209
f6bcfd97
BP
210 menuView->Append(ID_ORIGIN, _("&Absolute origin"), _("Go to (0, 0)"));
211 menuView->Append(ID_CENTER, _("&Center of mass"), _("Find center of mass"));
212 menuView->Append(ID_NORTH, _("&North"), _("Find northernmost cell"));
213 menuView->Append(ID_SOUTH, _("&South"), _("Find southernmost cell"));
214 menuView->Append(ID_EAST, _("&East"), _("Find easternmost cell"));
215 menuView->Append(ID_WEST, _("&West"), _("Find westernmost cell"));
29b07a38 216 menuView->AppendSeparator();
cbd88f22
VZ
217 menuView->Append(wxID_ZOOM_IN, wxEmptyString, _("Zoom in"));
218 menuView->Append(wxID_ZOOM_OUT, wxEmptyString, _("Zoom out"));
22c1c01b 219 menuView->Append(ID_INFO, _("&Description\tCtrl-D"), _("View pattern description"));
29b07a38 220
087e4f4a
GRG
221 menuGame->Append(ID_START, _("&Start\tCtrl-S"), _("Start"));
222 menuGame->Append(ID_STEP, _("&Next\tCtrl-N"), _("Single step"));
cbd88f22 223 menuGame->Append(wxID_STOP, wxEmptyString, _("Stop"));
5d2ac6b8 224 menuGame->Enable(wxID_STOP, false);
087e4f4a 225 menuGame->AppendSeparator();
f6bcfd97 226 menuGame->Append(ID_TOPSPEED, _("T&op speed!"), _("Go as fast as possible"));
087e4f4a 227
22c1c01b
DS
228 menuHelp->Append(wxID_ABOUT, _("&About\tCtrl-A"), _("Show about dialog"));
229
5a1dca12
GRG
230 wxMenuBar *menuBar = new wxMenuBar();
231 menuBar->Append(menuFile, _("&File"));
29b07a38 232 menuBar->Append(menuView, _("&View"));
087e4f4a 233 menuBar->Append(menuGame, _("&Game"));
22c1c01b 234 menuBar->Append(menuHelp, _("&Help"));
5a1dca12
GRG
235 SetMenuBar(menuBar);
236
237 // tool bar
f6bcfd97 238 wxBitmap tbBitmaps[7];
471ed537 239
5a1dca12 240 tbBitmaps[0] = wxBITMAP(reset);
f6bcfd97
BP
241 tbBitmaps[1] = wxBITMAP(open);
242 tbBitmaps[2] = wxBITMAP(zoomin);
243 tbBitmaps[3] = wxBITMAP(zoomout);
244 tbBitmaps[4] = wxBITMAP(info);
245 tbBitmaps[5] = wxBITMAP(play);
246 tbBitmaps[6] = wxBITMAP(stop);
5a1dca12
GRG
247
248 wxToolBar *toolBar = CreateToolBar();
249 toolBar->SetMargins(5, 5);
250 toolBar->SetToolBitmapSize(wxSize(16, 16));
f6bcfd97 251
cbd88f22 252 ADD_TOOL(wxID_NEW, tbBitmaps[0], wxGetStockLabel(wxID_NEW, wxSTOCK_NOFLAGS), _("Start a new game"));
5292c055 253#ifndef __POCKETPC__
5f4d35b8 254#if wxUSE_FILEDLG
cbd88f22 255 ADD_TOOL(wxID_OPEN, tbBitmaps[1], wxGetStockLabel(wxID_OPEN, wxSTOCK_NOFLAGS), _("Open an existing Life pattern"));
5f4d35b8 256#endif // wxUSE_FILEDLG
e4535381 257
f6bcfd97 258 toolBar->AddSeparator();
cbd88f22
VZ
259 ADD_TOOL(wxID_ZOOM_IN, tbBitmaps[2], wxGetStockLabel(wxID_ZOOM_IN, wxSTOCK_NOFLAGS), _("Zoom in"));
260 ADD_TOOL(wxID_ZOOM_OUT, tbBitmaps[3], wxGetStockLabel(wxID_ZOOM_OUT, wxSTOCK_NOFLAGS), _("Zoom out"));
f6bcfd97 261 ADD_TOOL(ID_INFO, tbBitmaps[4], _("Description"), _("Show description"));
e0a40292 262 toolBar->AddSeparator();
5f4d35b8 263#endif // __POCKETPC__
f6bcfd97 264 ADD_TOOL(ID_START, tbBitmaps[5], _("Start"), _("Start"));
53d90250 265 ADD_TOOL(wxID_STOP, tbBitmaps[6], _("Stop"), _("Stop"));
f6bcfd97 266
5a1dca12 267 toolBar->Realize();
5d2ac6b8 268 toolBar->EnableTool(wxID_STOP, false); // must be after Realize() !
5a1dca12 269
67a99992 270#if wxUSE_STATUSBAR
5a1dca12
GRG
271 // status bar
272 CreateStatusBar(2);
273 SetStatusText(_("Welcome to Life!"));
67a99992 274#endif // wxUSE_STATUSBAR
5a1dca12 275
29b07a38
GRG
276 // game and timer
277 m_life = new Life();
278 m_timer = new wxTimer(this, ID_TIMER);
22c1c01b
DS
279 m_running = false;
280 m_topspeed = false;
29b07a38
GRG
281 m_interval = 500;
282 m_tics = 0;
5a1dca12 283
29b07a38
GRG
284 // We use two different panels to reduce flicker in wxGTK, because
285 // some widgets (like wxStaticText) don't have their own X11 window,
286 // and thus updating the text would result in a refresh of the canvas
287 // if they belong to the same parent.
288
22c1c01b
DS
289 wxPanel *panel1 = new wxPanel(this, wxID_ANY);
290 wxPanel *panel2 = new wxPanel(this, wxID_ANY);
29b07a38
GRG
291
292 // canvas
293 m_canvas = new LifeCanvas(panel1, m_life);
294
295 // info panel
22c1c01b 296 m_text = new wxStaticText(panel2, wxID_ANY,
29b07a38
GRG
297 wxEmptyString,
298 wxDefaultPosition,
299 wxDefaultSize,
300 wxALIGN_CENTER | wxST_NO_AUTORESIZE);
301
302 wxSlider *slider = new wxSlider(panel2, ID_SLIDER,
2480be69
GRG
303 5, 1, 10,
304 wxDefaultPosition,
422d0ff0 305 wxSize(200, wxDefaultCoord),
2480be69 306 wxSL_HORIZONTAL | wxSL_AUTOTICKS);
5a1dca12 307
29b07a38
GRG
308 UpdateInfoText();
309
5a1dca12 310 // component layout
29b07a38
GRG
311 wxBoxSizer *sizer1 = new wxBoxSizer(wxVERTICAL);
312 wxBoxSizer *sizer2 = new wxBoxSizer(wxVERTICAL);
313 wxBoxSizer *sizer3 = new wxBoxSizer(wxVERTICAL);
314
27dc5184 315#if wxUSE_STATLINE
22c1c01b 316 sizer1->Add( new wxStaticLine(panel1, wxID_ANY), 0, wxGROW );
27dc5184 317#endif // wxUSE_STATLINE
29b07a38 318 sizer1->Add( m_canvas, 1, wxGROW | wxALL, 2 );
27dc5184 319#if wxUSE_STATLINE
22c1c01b 320 sizer1->Add( new wxStaticLine(panel1, wxID_ANY), 0, wxGROW );
27dc5184 321#endif // wxUSE_STATLINE
29b07a38 322 panel1->SetSizer( sizer1 );
29b07a38
GRG
323 sizer1->Fit( panel1 );
324
325 sizer2->Add( m_text, 0, wxGROW | wxTOP, 4 );
326 sizer2->Add( slider, 0, wxCENTRE | wxALL, 4 );
281b0186 327
29b07a38 328 panel2->SetSizer( sizer2 );
29b07a38
GRG
329 sizer2->Fit( panel2 );
330
331 sizer3->Add( panel1, 1, wxGROW );
332 sizer3->Add( panel2, 0, wxGROW );
333 SetSizer( sizer3 );
e5c990b3 334
e4535381 335#ifndef __WXWINCE__
29b07a38
GRG
336 sizer3->Fit( this );
337
338 // set minimum frame size
339 sizer3->SetSizeHints( this );
340
e5c990b3 341 // navigator frame - not appropriate for small devices
29b07a38 342 m_navigator = new LifeNavigator(this);
e5c990b3
JS
343#endif
344
5a1dca12
GRG
345}
346
347LifeFrame::~LifeFrame()
348{
349 delete m_timer;
5a1dca12
GRG
350}
351
352void LifeFrame::UpdateInfoText()
353{
354 wxString msg;
355
9bc95da0 356 msg.Printf(_(" Generation: %lu (T: %lu ms), Population: %lu "),
e0a40292
GRG
357 m_tics,
358 m_topspeed? 0 : m_interval,
9bc95da0 359 static_cast<unsigned long>(m_life->GetNumCells()));
5a1dca12
GRG
360 m_text->SetLabel(msg);
361}
362
e0a40292
GRG
363// Enable or disable tools and menu entries according to the current
364// state. See also wxEVT_UPDATE_UI events for a slightly different
365// way to do this.
366void LifeFrame::UpdateUI()
367{
a9cf4097 368 // start / stop
e0a40292 369 GetToolBar()->EnableTool(ID_START, !m_running);
5d2ac6b8 370 GetToolBar()->EnableTool(wxID_STOP, m_running);
4efbec35
JS
371 GetMenuBar()->Enable(ID_START, !m_running);
372 GetMenuBar()->Enable(ID_STEP, !m_running);
5d2ac6b8 373 GetMenuBar()->Enable(wxID_STOP, m_running);
4efbec35 374 GetMenuBar()->Enable(ID_TOPSPEED, !m_topspeed);
a9cf4097
GRG
375
376 // zooming
377 int cellsize = m_canvas->GetCellSize();
5d2ac6b8
WS
378 GetToolBar()->EnableTool(wxID_ZOOM_IN, cellsize < 32);
379 GetToolBar()->EnableTool(wxID_ZOOM_OUT, cellsize > 1);
380 GetMenuBar()->Enable(wxID_ZOOM_IN, cellsize < 32);
381 GetMenuBar()->Enable(wxID_ZOOM_OUT, cellsize > 1);
e0a40292
GRG
382}
383
f6bcfd97
BP
384// Event handlers -----------------------------------------------------------
385
386// OnMenu handles all events which don't have their own event handler
5a1dca12
GRG
387void LifeFrame::OnMenu(wxCommandEvent& event)
388{
389 switch (event.GetId())
390 {
22c1c01b 391 case wxID_NEW:
f6bcfd97
BP
392 {
393 // stop if it was running
394 OnStop();
395 m_life->Clear();
396 m_canvas->Recenter(0, 0);
397 m_tics = 0;
398 UpdateInfoText();
399 break;
400 }
22c1c01b 401 case wxID_ABOUT:
f6bcfd97
BP
402 {
403 LifeAboutDialog dialog(this);
404 dialog.ShowModal();
405 break;
406 }
22c1c01b 407 case wxID_EXIT:
f6bcfd97 408 {
22c1c01b
DS
409 // true is to force the frame to close
410 Close(true);
f6bcfd97
BP
411 break;
412 }
22c1c01b 413 case ID_SHOWNAV:
087e4f4a 414 {
29b07a38 415 bool checked = GetMenuBar()->GetMenu(1)->IsChecked(ID_SHOWNAV);
e5c990b3
JS
416 if (m_navigator)
417 m_navigator->Show(checked);
e0a40292
GRG
418 break;
419 }
f6bcfd97
BP
420 case ID_INFO:
421 {
422 wxString desc = m_life->GetDescription();
22c1c01b 423
5d2ac6b8 424 if ( desc.empty() )
f6bcfd97
BP
425 desc = _("Not available");
426
427 // should we make the description editable here?
428 wxMessageBox(desc, _("Description"), wxOK | wxICON_INFORMATION);
429
430 break;
431 }
432 case ID_START : OnStart(); break;
433 case ID_STEP : OnStep(); break;
5d2ac6b8 434 case wxID_STOP : OnStop(); break;
e0a40292
GRG
435 case ID_TOPSPEED:
436 {
22c1c01b
DS
437 m_running = true;
438 m_topspeed = true;
e0a40292 439 UpdateUI();
22c1c01b 440 while (m_running && m_topspeed)
e0a40292 441 {
29b07a38 442 OnStep();
e0a40292
GRG
443 wxYield();
444 }
445 break;
446 }
f6bcfd97
BP
447 }
448}
449
5f4d35b8 450#if wxUSE_FILEDLG
f6bcfd97
BP
451void LifeFrame::OnOpen(wxCommandEvent& WXUNUSED(event))
452{
453 wxFileDialog filedlg(this,
454 _("Choose a file to open"),
22c1c01b
DS
455 wxEmptyString,
456 wxEmptyString,
f6bcfd97 457 _("Life patterns (*.lif)|*.lif|All files (*.*)|*.*"),
55325d01 458 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
f6bcfd97
BP
459
460 if (filedlg.ShowModal() == wxID_OK)
461 {
2b15fdfc 462 wxFileInputStream stream(filedlg.GetPath());
f6bcfd97
BP
463 LifeReader reader(stream);
464
465 // the reader handles errors itself, no need to do anything here
466 if (reader.IsOk())
e0a40292 467 {
f6bcfd97 468 // stop if running and put the pattern
5a1dca12
GRG
469 OnStop();
470 m_life->Clear();
f6bcfd97
BP
471 m_life->SetPattern(reader.GetPattern());
472
473 // recenter canvas
e0a40292 474 m_canvas->Recenter(0, 0);
5a1dca12
GRG
475 m_tics = 0;
476 UpdateInfoText();
5a1dca12
GRG
477 }
478 }
479}
5f4d35b8 480#endif
5a1dca12 481
087e4f4a
GRG
482void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event))
483{
484 // stop if it was running
485 OnStop();
486
471ed537 487 // dialog box
087e4f4a
GRG
488 LifeSamplesDialog dialog(this);
489
087e4f4a
GRG
490 if (dialog.ShowModal() == wxID_OK)
491 {
f6bcfd97 492 const LifePattern pattern = dialog.GetPattern();
087e4f4a 493
f6bcfd97 494 // put the pattern
e0a40292 495 m_life->Clear();
f6bcfd97 496 m_life->SetPattern(pattern);
087e4f4a 497
e0a40292
GRG
498 // recenter canvas
499 m_canvas->Recenter(0, 0);
087e4f4a
GRG
500 m_tics = 0;
501 UpdateInfoText();
502 }
503}
504
29b07a38
GRG
505void LifeFrame::OnZoom(wxCommandEvent& event)
506{
507 int cellsize = m_canvas->GetCellSize();
508
5d2ac6b8 509 if ((event.GetId() == wxID_ZOOM_IN) && cellsize < 32)
29b07a38
GRG
510 {
511 m_canvas->SetCellSize(cellsize * 2);
512 UpdateUI();
513 }
5d2ac6b8 514 else if ((event.GetId() == wxID_ZOOM_OUT) && cellsize > 1)
29b07a38
GRG
515 {
516 m_canvas->SetCellSize(cellsize / 2);
517 UpdateUI();
518 }
519}
520
521void LifeFrame::OnNavigate(wxCommandEvent& event)
522{
764835a5 523 LifeCell c;
29b07a38
GRG
524
525 switch (event.GetId())
526 {
22c1c01b 527 case ID_NORTH: c = m_life->FindNorth(); break;
29b07a38
GRG
528 case ID_SOUTH: c = m_life->FindSouth(); break;
529 case ID_WEST: c = m_life->FindWest(); break;
530 case ID_EAST: c = m_life->FindEast(); break;
531 case ID_CENTER: c = m_life->FindCenter(); break;
22c1c01b
DS
532 default :
533 wxFAIL;
534 // Fall through!
29b07a38
GRG
535 case ID_ORIGIN: c.i = c.j = 0; break;
536 }
537
538 m_canvas->Recenter(c.i, c.j);
539}
540
541void LifeFrame::OnSlider(wxScrollEvent& event)
542{
543 m_interval = event.GetPosition() * 100;
544
545 if (m_running)
546 {
547 OnStop();
548 OnStart();
549 }
22c1c01b 550
29b07a38
GRG
551 UpdateInfoText();
552}
553
554void LifeFrame::OnTimer(wxTimerEvent& WXUNUSED(event))
555{
556 OnStep();
557}
558
f6bcfd97
BP
559void LifeFrame::OnClose(wxCloseEvent& WXUNUSED(event))
560{
561 // Stop if it was running; this is absolutely needed because
562 // the frame won't be actually destroyed until there are no
563 // more pending events, and this in turn won't ever happen
564 // if the timer is running faster than the window can redraw.
565 OnStop();
22c1c01b 566 Destroy();
f6bcfd97
BP
567}
568
5a1dca12
GRG
569void LifeFrame::OnStart()
570{
087e4f4a
GRG
571 if (!m_running)
572 {
087e4f4a 573 m_timer->Start(m_interval);
22c1c01b 574 m_running = true;
e0a40292 575 UpdateUI();
087e4f4a 576 }
5a1dca12
GRG
577}
578
579void LifeFrame::OnStop()
580{
087e4f4a
GRG
581 if (m_running)
582 {
087e4f4a 583 m_timer->Stop();
22c1c01b
DS
584 m_running = false;
585 m_topspeed = false;
e0a40292 586 UpdateUI();
087e4f4a 587 }
5a1dca12
GRG
588}
589
29b07a38 590void LifeFrame::OnStep()
5a1dca12 591{
a36f0f83
GRG
592 if (m_life->NextTic())
593 m_tics++;
594 else
595 OnStop();
5a1dca12 596
e0a40292 597 m_canvas->DrawChanged();
a36f0f83 598 UpdateInfoText();
5a1dca12
GRG
599}
600
a36f0f83 601
5a1dca12 602// --------------------------------------------------------------------------
29b07a38 603// LifeNavigator miniframe
5a1dca12
GRG
604// --------------------------------------------------------------------------
605
29b07a38 606LifeNavigator::LifeNavigator(wxWindow *parent)
22c1c01b 607 : wxMiniFrame(parent, wxID_ANY,
29b07a38
GRG
608 _("Navigation"),
609 wxDefaultPosition,
610 wxDefaultSize,
611 wxCAPTION | wxSIMPLE_BORDER)
5a1dca12 612{
22c1c01b 613 wxPanel *panel = new wxPanel(this, wxID_ANY);
29b07a38
GRG
614 wxBoxSizer *sizer1 = new wxBoxSizer(wxVERTICAL);
615 wxBoxSizer *sizer2 = new wxBoxSizer(wxHORIZONTAL);
616
617 // create bitmaps and masks for the buttons
618 wxBitmap
619 bmpn = wxBITMAP(north),
22c1c01b 620 bmpw = wxBITMAP(west),
29b07a38 621 bmpc = wxBITMAP(center),
22c1c01b 622 bmpe = wxBITMAP(east),
29b07a38
GRG
623 bmps = wxBITMAP(south);
624
a99c96b0 625#if !defined(__WXGTK__) && !defined(__WXMOTIF__) && !defined(__WXMAC__)
29b07a38
GRG
626 bmpn.SetMask(new wxMask(bmpn, *wxLIGHT_GREY));
627 bmpw.SetMask(new wxMask(bmpw, *wxLIGHT_GREY));
628 bmpc.SetMask(new wxMask(bmpc, *wxLIGHT_GREY));
22c1c01b 629 bmpe.SetMask(new wxMask(bmpe, *wxLIGHT_GREY));
29b07a38
GRG
630 bmps.SetMask(new wxMask(bmps, *wxLIGHT_GREY));
631#endif
632
633 // create the buttons and attach tooltips to them
634 wxBitmapButton
635 *bn = new wxBitmapButton(panel, ID_NORTH, bmpn),
636 *bw = new wxBitmapButton(panel, ID_WEST , bmpw),
637 *bc = new wxBitmapButton(panel, ID_CENTER, bmpc),
638 *be = new wxBitmapButton(panel, ID_EAST , bmpe),
639 *bs = new wxBitmapButton(panel, ID_SOUTH, bmps);
640
7a85f3f9 641#if wxUSE_TOOLTIPS
29b07a38
GRG
642 bn->SetToolTip(_("Find northernmost cell"));
643 bw->SetToolTip(_("Find westernmost cell"));
644 bc->SetToolTip(_("Find center of mass"));
645 be->SetToolTip(_("Find easternmost cell"));
646 bs->SetToolTip(_("Find southernmost cell"));
7a85f3f9 647#endif
29b07a38
GRG
648
649 // add buttons to sizers
650 sizer2->Add( bw, 0, wxCENTRE | wxWEST, 4 );
651 sizer2->Add( bc, 0, wxCENTRE);
652 sizer2->Add( be, 0, wxCENTRE | wxEAST, 4 );
653 sizer1->Add( bn, 0, wxCENTRE | wxNORTH, 4 );
654 sizer1->Add( sizer2 );
655 sizer1->Add( bs, 0, wxCENTRE | wxSOUTH, 4 );
656
b538bf7e 657 // set the panel and miniframe size
29b07a38 658 panel->SetSizer(sizer1);
b538bf7e
JS
659
660 sizer1->Fit(panel);
661 SetClientSize(panel->GetSize());
662 wxSize sz = GetSize();
663 SetSizeHints(sz.x, sz.y, sz.x, sz.y);
29b07a38
GRG
664
665 // move it to a sensible position
666 wxRect parentRect = parent->GetRect();
667 wxSize childSize = GetSize();
668 int x = parentRect.GetX() +
669 parentRect.GetWidth();
670 int y = parentRect.GetY() +
671 (parentRect.GetHeight() - childSize.GetHeight()) / 4;
672 Move(x, y);
673
674 // done
22c1c01b 675 Show(true);
29b07a38
GRG
676}
677
678void LifeNavigator::OnClose(wxCloseEvent& event)
679{
dbf75be7 680 // avoid if we can
29b07a38
GRG
681 if (event.CanVeto())
682 event.Veto();
683 else
684 Destroy();
685}
686
5a1dca12
GRG
687
688// --------------------------------------------------------------------------
087e4f4a 689// LifeCanvas
5a1dca12
GRG
690// --------------------------------------------------------------------------
691
692// canvas constructor
087e4f4a 693LifeCanvas::LifeCanvas(wxWindow *parent, Life *life, bool interactive)
22c1c01b 694 : wxWindow(parent, wxID_ANY, wxDefaultPosition, wxSize(100, 100),
64b9776c 695 wxFULL_REPAINT_ON_RESIZE | wxHSCROLL | wxVSCROLL
e6f85dee
JS
696#if !defined(__SMARTPHONE__) && !defined(__POCKETPC__)
697 |wxSUNKEN_BORDER
698#else
699 |wxSIMPLE_BORDER
700#endif
701 )
5a1dca12 702{
087e4f4a
GRG
703 m_life = life;
704 m_interactive = interactive;
705 m_cellsize = 8;
e0a40292
GRG
706 m_status = MOUSE_NOACTION;
707 m_viewportX = 0;
708 m_viewportY = 0;
709 m_viewportH = 0;
710 m_viewportW = 0;
711
712 if (m_interactive)
713 SetCursor(*wxCROSS_CURSOR);
714
715 // reduce flicker if wxEVT_ERASE_BACKGROUND is not available
22c1c01b 716 SetBackgroundColour(*wxWHITE);
5a1dca12
GRG
717}
718
719LifeCanvas::~LifeCanvas()
720{
22c1c01b 721 delete m_life;
5a1dca12
GRG
722}
723
e0a40292
GRG
724// recenter at the given position
725void LifeCanvas::Recenter(wxInt32 i, wxInt32 j)
5a1dca12 726{
e0a40292
GRG
727 m_viewportX = i - m_viewportW / 2;
728 m_viewportY = j - m_viewportH / 2;
5a1dca12 729
087e4f4a 730 // redraw everything
22c1c01b 731 Refresh(false);
5a1dca12
GRG
732}
733
e0a40292
GRG
734// set the cell size and refresh display
735void LifeCanvas::SetCellSize(int cellsize)
5a1dca12 736{
e0a40292
GRG
737 m_cellsize = cellsize;
738
739 // find current center
740 wxInt32 cx = m_viewportX + m_viewportW / 2;
741 wxInt32 cy = m_viewportY + m_viewportH / 2;
742
743 // get current canvas size and adjust viewport accordingly
2fa7c206 744 int w, h;
e0a40292
GRG
745 GetClientSize(&w, &h);
746 m_viewportW = (w + m_cellsize - 1) / m_cellsize;
747 m_viewportH = (h + m_cellsize - 1) / m_cellsize;
22c1c01b 748
e0a40292
GRG
749 // recenter
750 m_viewportX = cx - m_viewportW / 2;
751 m_viewportY = cy - m_viewportH / 2;
752
753 // adjust scrollbars
754 if (m_interactive)
755 {
756 SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
757 SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
758 m_thumbX = m_viewportW;
759 m_thumbY = m_viewportH;
760 }
22c1c01b
DS
761
762 Refresh(false);
e0a40292 763}
2480be69 764
e0a40292
GRG
765// draw a cell
766void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, bool alive)
767{
768 wxClientDC dc(this);
5a1dca12 769
e0a40292
GRG
770 dc.SetPen(alive? *wxBLACK_PEN : *wxWHITE_PEN);
771 dc.SetBrush(alive? *wxBLACK_BRUSH : *wxWHITE_BRUSH);
5a1dca12 772
e0a40292 773 DrawCell(i, j, dc);
5a1dca12
GRG
774}
775
e0a40292 776void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, wxDC &dc)
5a1dca12 777{
e0a40292
GRG
778 wxCoord x = CellToX(i);
779 wxCoord y = CellToY(j);
5a1dca12 780
e0a40292
GRG
781 // if cellsize is 1 or 2, there will be no grid
782 switch (m_cellsize)
783 {
784 case 1:
785 dc.DrawPoint(x, y);
786 break;
787 case 2:
788 dc.DrawRectangle(x, y, 2, 2);
789 break;
790 default:
791 dc.DrawRectangle(x + 1, y + 1, m_cellsize - 1, m_cellsize - 1);
792 }
5a1dca12
GRG
793}
794
e0a40292
GRG
795// draw all changed cells
796void LifeCanvas::DrawChanged()
5a1dca12 797{
e0a40292
GRG
798 wxClientDC dc(this);
799
800 size_t ncells;
764835a5 801 LifeCell *cells;
22c1c01b 802 bool done = false;
e0a40292
GRG
803
804 m_life->BeginFind(m_viewportX,
805 m_viewportY,
806 m_viewportX + m_viewportW,
807 m_viewportY + m_viewportH,
22c1c01b
DS
808 true);
809
e0a40292 810 if (m_cellsize == 1)
5a1dca12
GRG
811 {
812 dc.SetPen(*wxBLACK_PEN);
5a1dca12
GRG
813 }
814 else
815 {
e0a40292
GRG
816 dc.SetPen(*wxTRANSPARENT_PEN);
817 dc.SetBrush(*wxBLACK_BRUSH);
818 }
030d06e1 819 dc.SetLogicalFunction(wxINVERT);
e0a40292
GRG
820
821 while (!done)
822 {
823 done = m_life->FindMore(&cells, &ncells);
824
825 for (size_t m = 0; m < ncells; m++)
826 DrawCell(cells[m].i, cells[m].j, dc);
5a1dca12
GRG
827 }
828}
ecbdd409 829
5a1dca12 830// event handlers
babd36de 831void LifeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event))
5a1dca12
GRG
832{
833 wxPaintDC dc(this);
e0a40292
GRG
834 wxRect rect = GetUpdateRegion().GetBox();
835 wxCoord x, y, w, h;
836 wxInt32 i0, j0, i1, j1;
837
838 // find damaged area
839 x = rect.GetX();
840 y = rect.GetY();
841 w = rect.GetWidth();
842 h = rect.GetHeight();
843
844 i0 = XToCell(x);
845 j0 = YToCell(y);
846 i1 = XToCell(x + w - 1);
847 j1 = YToCell(y + h - 1);
848
849 size_t ncells;
764835a5 850 LifeCell *cells;
5a1dca12 851
22c1c01b
DS
852 m_life->BeginFind(i0, j0, i1, j1, false);
853 bool done = m_life->FindMore(&cells, &ncells);
5a1dca12 854
e0a40292 855 // erase all damaged cells and draw the grid
e0a40292 856 dc.SetBrush(*wxWHITE_BRUSH);
5a1dca12 857
e0a40292 858 if (m_cellsize <= 2)
5a1dca12 859 {
e0a40292
GRG
860 // no grid
861 dc.SetPen(*wxWHITE_PEN);
862 dc.DrawRectangle(x, y, w, h);
5a1dca12 863 }
e0a40292
GRG
864 else
865 {
866 x = CellToX(i0);
867 y = CellToY(j0);
868 w = CellToX(i1 + 1) - x + 1;
869 h = CellToY(j1 + 1) - y + 1;
870
871 dc.SetPen(*wxLIGHT_GREY_PEN);
872 for (wxInt32 yy = y; yy <= (y + h - m_cellsize); yy += m_cellsize)
873 dc.DrawRectangle(x, yy, w, m_cellsize + 1);
874 for (wxInt32 xx = x; xx <= (x + w - m_cellsize); xx += m_cellsize)
875 dc.DrawLine(xx, y, xx, y + h);
876 }
877
878 // draw all alive cells
879 dc.SetPen(*wxBLACK_PEN);
880 dc.SetBrush(*wxBLACK_BRUSH);
881
882 while (!done)
883 {
884 for (size_t m = 0; m < ncells; m++)
885 DrawCell(cells[m].i, cells[m].j, dc);
886
887 done = m_life->FindMore(&cells, &ncells);
888 }
889
890 // last set
891 for (size_t m = 0; m < ncells; m++)
892 DrawCell(cells[m].i, cells[m].j, dc);
5a1dca12
GRG
893}
894
895void LifeCanvas::OnMouse(wxMouseEvent& event)
896{
087e4f4a
GRG
897 if (!m_interactive)
898 return;
899
5a1dca12 900 // which cell are we pointing at?
e0a40292
GRG
901 wxInt32 i = XToCell( event.GetX() );
902 wxInt32 j = YToCell( event.GetY() );
903
67a99992 904#if wxUSE_STATUSBAR
e0a40292
GRG
905 // set statusbar text
906 wxString msg;
907 msg.Printf(_("Cell: (%d, %d)"), i, j);
29b07a38 908 ((LifeFrame *) wxGetApp().GetTopWindow())->SetStatusText(msg, 1);
67a99992 909#endif // wxUSE_STATUSBAR
5a1dca12 910
f6bcfd97
BP
911 // NOTE that wxMouseEvent::LeftDown() and wxMouseEvent::LeftIsDown()
912 // have different semantics. The first one is used to signal that the
913 // button was just pressed (i.e., in "button down" events); the second
914 // one just describes the current status of the button, independently
915 // of the mouse event type. LeftIsDown is typically used in "mouse
916 // move" events, to test if the button is _still_ pressed.
917
918 // is the button down?
5a1dca12
GRG
919 if (!event.LeftIsDown())
920 {
921 m_status = MOUSE_NOACTION;
7989fb37 922 return;
5a1dca12 923 }
5a1dca12 924
f6bcfd97
BP
925 // was it pressed just now?
926 if (event.LeftDown())
7989fb37 927 {
f6bcfd97 928 // yes: start a new action and toggle this cell
7989fb37 929 m_status = (m_life->IsAlive(i, j)? MOUSE_ERASING : MOUSE_DRAWING);
22c1c01b 930
7989fb37
GRG
931 m_mi = i;
932 m_mj = j;
933 m_life->SetCell(i, j, m_status == MOUSE_DRAWING);
934 DrawCell(i, j, m_status == MOUSE_DRAWING);
935 }
936 else if ((m_mi != i) || (m_mj != j))
937 {
f6bcfd97 938 // no: continue ongoing action
5e9ff6ad
GRG
939 bool alive = (m_status == MOUSE_DRAWING);
940
941 // prepare DC and pen + brush to optimize drawing
942 wxClientDC dc(this);
943 dc.SetPen(alive? *wxBLACK_PEN : *wxWHITE_PEN);
944 dc.SetBrush(alive? *wxBLACK_BRUSH : *wxWHITE_BRUSH);
5e9ff6ad 945
7989fb37
GRG
946 // draw a line of cells using Bresenham's algorithm
947 wxInt32 d, ii, jj, di, ai, si, dj, aj, sj;
948 di = i - m_mi;
949 ai = abs(di) << 1;
950 si = (di < 0)? -1 : 1;
951 dj = j - m_mj;
952 aj = abs(dj) << 1;
953 sj = (dj < 0)? -1 : 1;
954
955 ii = m_mi;
956 jj = m_mj;
22c1c01b 957
7989fb37
GRG
958 if (ai > aj)
959 {
960 // iterate over i
22c1c01b
DS
961 d = aj - (ai >> 1);
962
7989fb37
GRG
963 while (ii != i)
964 {
5e9ff6ad
GRG
965 m_life->SetCell(ii, jj, alive);
966 DrawCell(ii, jj, dc);
7989fb37
GRG
967 if (d >= 0)
968 {
969 jj += sj;
22c1c01b 970 d -= ai;
7989fb37
GRG
971 }
972 ii += si;
973 d += aj;
974 }
975 }
976 else
5a1dca12 977 {
7989fb37
GRG
978 // iterate over j
979 d = ai - (aj >> 1);
980
981 while (jj != j)
982 {
5e9ff6ad
GRG
983 m_life->SetCell(ii, jj, alive);
984 DrawCell(ii, jj, dc);
7989fb37
GRG
985 if (d >= 0)
986 {
987 ii += si;
22c1c01b 988 d -= aj;
7989fb37
GRG
989 }
990 jj += sj;
991 d += ai;
992 }
5a1dca12 993 }
7989fb37
GRG
994
995 // last cell
5e9ff6ad
GRG
996 m_life->SetCell(ii, jj, alive);
997 DrawCell(ii, jj, dc);
7989fb37
GRG
998 m_mi = ii;
999 m_mj = jj;
5a1dca12 1000 }
7989fb37 1001
29b07a38 1002 ((LifeFrame *) wxGetApp().GetTopWindow())->UpdateInfoText();
5a1dca12
GRG
1003}
1004
1005void LifeCanvas::OnSize(wxSizeEvent& event)
1006{
e0a40292
GRG
1007 // find center
1008 wxInt32 cx = m_viewportX + m_viewportW / 2;
1009 wxInt32 cy = m_viewportY + m_viewportH / 2;
1010
1011 // get new size
5a1dca12
GRG
1012 wxCoord w = event.GetSize().GetX();
1013 wxCoord h = event.GetSize().GetY();
e0a40292
GRG
1014 m_viewportW = (w + m_cellsize - 1) / m_cellsize;
1015 m_viewportH = (h + m_cellsize - 1) / m_cellsize;
1016
1017 // recenter
1018 m_viewportX = cx - m_viewportW / 2;
1019 m_viewportY = cy - m_viewportH / 2;
1020
1021 // scrollbars
1022 if (m_interactive)
1023 {
1024 SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
1025 SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
1026 m_thumbX = m_viewportW;
1027 m_thumbY = m_viewportH;
1028 }
5a1dca12
GRG
1029
1030 // allow default processing
1031 event.Skip();
1032}
e0a40292
GRG
1033
1034void LifeCanvas::OnScroll(wxScrollWinEvent& event)
1035{
254a2129 1036 WXTYPE type = (WXTYPE)event.GetEventType();
e0a40292
GRG
1037 int pos = event.GetPosition();
1038 int orient = event.GetOrientation();
e0a40292
GRG
1039
1040 // calculate scroll increment
33e39147 1041 int scrollinc = 0;
764835a5 1042 if (type == wxEVT_SCROLLWIN_TOP)
e0a40292 1043 {
764835a5
GD
1044 if (orient == wxHORIZONTAL)
1045 scrollinc = -m_viewportW;
1046 else
1047 scrollinc = -m_viewportH;
1048 }
1049 else
1050 if (type == wxEVT_SCROLLWIN_BOTTOM)
1051 {
1052 if (orient == wxHORIZONTAL)
1053 scrollinc = m_viewportW;
1054 else
1055 scrollinc = m_viewportH;
1056 }
1057 else
1058 if (type == wxEVT_SCROLLWIN_LINEUP)
1059 {
1060 scrollinc = -1;
1061 }
1062 else
1063 if (type == wxEVT_SCROLLWIN_LINEDOWN)
1064 {
1065 scrollinc = +1;
1066 }
1067 else
1068 if (type == wxEVT_SCROLLWIN_PAGEUP)
1069 {
1070 scrollinc = -10;
1071 }
1072 else
1073 if (type == wxEVT_SCROLLWIN_PAGEDOWN)
1074 {
b1823f8b 1075 scrollinc = +10;
764835a5
GD
1076 }
1077 else
1078 if (type == wxEVT_SCROLLWIN_THUMBTRACK)
1079 {
1080 if (orient == wxHORIZONTAL)
e0a40292 1081 {
764835a5
GD
1082 scrollinc = pos - m_thumbX;
1083 m_thumbX = pos;
e0a40292 1084 }
764835a5 1085 else
33e39147 1086 {
764835a5
GD
1087 scrollinc = pos - m_thumbY;
1088 m_thumbY = pos;
33e39147 1089 }
e0a40292 1090 }
764835a5
GD
1091 else
1092 if (type == wxEVT_SCROLLWIN_THUMBRELEASE)
1093 {
1094 m_thumbX = m_viewportW;
1095 m_thumbY = m_viewportH;
1096 }
e0a40292 1097
dbf75be7
GRG
1098#if defined(__WXGTK__) || defined(__WXMOTIF__)
1099 // wxGTK and wxMotif update the thumb automatically (wxMSW doesn't);
1100 // so reset it back as we always want it to be in the same position.
33e39147 1101 if (type != wxEVT_SCROLLWIN_THUMBTRACK)
e0a40292
GRG
1102 {
1103 SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
1104 SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
1105 }
1106#endif
1107
1108 if (scrollinc == 0) return;
22c1c01b 1109
e0a40292
GRG
1110 // scroll the window and adjust the viewport
1111 if (orient == wxHORIZONTAL)
1112 {
1113 m_viewportX += scrollinc;
1114 ScrollWindow( -m_cellsize * scrollinc, 0, (const wxRect *) NULL);
1115 }
1116 else
1117 {
22c1c01b 1118 m_viewportY += scrollinc;
e0a40292
GRG
1119 ScrollWindow( 0, -m_cellsize * scrollinc, (const wxRect *) NULL);
1120 }
1121}
1122
1123void LifeCanvas::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
1124{
1125 // do nothing. I just don't want the background to be erased, you know.
1126}