]> git.saurik.com Git - wxWidgets.git/blame - demos/life/life.cpp
fixed fatal bug in wxStatusBar::SetFieldsCount(), added demo of it to the sample
[wxWidgets.git] / demos / life / life.cpp
CommitLineData
5a1dca12
GRG
1/////////////////////////////////////////////////////////////////////////////
2// Name: life.cpp
3// Purpose: The game of life, created by J. H. Conway
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
5a1dca12 16#ifdef __GNUG__
2480be69 17 #pragma implementation "life.h"
5a1dca12
GRG
18#endif
19
5a1dca12 20#include "wx/statline.h"
2480be69
GRG
21
22#include "life.h"
23#include "game.h"
24#include "dialogs.h"
5a1dca12
GRG
25
26// --------------------------------------------------------------------------
27// resources
28// --------------------------------------------------------------------------
29
30#if defined(__WXGTK__) || defined(__WXMOTIF__)
31 // the application icon
32 #include "mondrian.xpm"
33
34 // bitmap buttons for the toolbar
35 #include "bitmaps/reset.xpm"
36 #include "bitmaps/play.xpm"
37 #include "bitmaps/stop.xpm"
e0a40292
GRG
38 #include "bitmaps/zoomin.xpm"
39 #include "bitmaps/zoomout.xpm"
5a1dca12
GRG
40#endif
41
5a1dca12
GRG
42// --------------------------------------------------------------------------
43// constants
44// --------------------------------------------------------------------------
45
46// IDs for the controls and the menu commands
47enum
48{
49 // menu items and toolbar buttons
e0a40292 50 ID_RESET = 1001,
087e4f4a
GRG
51 ID_SAMPLES,
52 ID_ABOUT,
53 ID_EXIT,
e0a40292 54 ID_CENTER,
5a1dca12 55 ID_START,
087e4f4a 56 ID_STEP,
5a1dca12 57 ID_STOP,
e0a40292
GRG
58 ID_ZOOMIN,
59 ID_ZOOMOUT,
60 ID_TOPSPEED,
5a1dca12 61
087e4f4a 62 // speed selection slider
2480be69 63 ID_SLIDER
5a1dca12
GRG
64};
65
66// --------------------------------------------------------------------------
67// event tables and other macros for wxWindows
68// --------------------------------------------------------------------------
69
70// Event tables
5a1dca12 71BEGIN_EVENT_TABLE(LifeFrame, wxFrame)
e0a40292
GRG
72 EVT_MENU (ID_SAMPLES, LifeFrame::OnSamples)
73 EVT_MENU (ID_RESET, LifeFrame::OnMenu)
74 EVT_MENU (ID_ABOUT, LifeFrame::OnMenu)
75 EVT_MENU (ID_EXIT, LifeFrame::OnMenu)
76 EVT_MENU (ID_CENTER, LifeFrame::OnMenu)
77 EVT_MENU (ID_START, LifeFrame::OnMenu)
78 EVT_MENU (ID_STEP, LifeFrame::OnMenu)
79 EVT_MENU (ID_STOP, LifeFrame::OnMenu)
80 EVT_MENU (ID_ZOOMIN, LifeFrame::OnMenu)
81 EVT_MENU (ID_ZOOMOUT, LifeFrame::OnMenu)
82 EVT_MENU (ID_TOPSPEED, LifeFrame::OnMenu)
83 EVT_COMMAND_SCROLL (ID_SLIDER, LifeFrame::OnSlider)
84 EVT_CLOSE ( LifeFrame::OnClose)
ecbdd409 85END_EVENT_TABLE()
5a1dca12 86
e0a40292
GRG
87BEGIN_EVENT_TABLE(LifeCanvas, wxWindow)
88 EVT_PAINT ( LifeCanvas::OnPaint)
89 EVT_SCROLLWIN ( LifeCanvas::OnScroll)
90 EVT_SIZE ( LifeCanvas::OnSize)
91 EVT_MOUSE_EVENTS ( LifeCanvas::OnMouse)
92 EVT_ERASE_BACKGROUND( LifeCanvas::OnEraseBackground)
ecbdd409 93END_EVENT_TABLE()
5a1dca12 94
5a1dca12
GRG
95
96// Create a new application object
97IMPLEMENT_APP(LifeApp)
98
e0a40292 99
5a1dca12
GRG
100// ==========================================================================
101// implementation
102// ==========================================================================
103
2480be69 104// some shortcuts
e0a40292
GRG
105#define ADD_TOOL(id, bmp, tooltip, help) \
106 toolBar->AddTool(id, bmp, wxNullBitmap, FALSE, -1, -1, (wxObject *)0, tooltip, help)
2480be69 107
e0a40292 108#define GET_FRAME() ((LifeFrame *) wxGetApp().GetTopWindow())
2480be69 109
5a1dca12
GRG
110// --------------------------------------------------------------------------
111// LifeApp
112// --------------------------------------------------------------------------
113
e0a40292 114// 'Main program' equivalent: the program execution "starts" here
5a1dca12
GRG
115bool LifeApp::OnInit()
116{
117 // create the main application window
118 LifeFrame *frame = new LifeFrame();
119
120 // show it and tell the application that it's our main window
121 frame->Show(TRUE);
122 SetTopWindow(frame);
123
124 // enter the main message loop and run the app
125 return TRUE;
126}
127
128// --------------------------------------------------------------------------
129// LifeFrame
130// --------------------------------------------------------------------------
131
132// frame constructor
133LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(50, 50))
134{
135 // frame icon
136 SetIcon(wxICON(mondrian));
137
138 // menu bar
139 wxMenu *menuFile = new wxMenu("", wxMENU_TEAROFF);
087e4f4a 140 wxMenu *menuGame = new wxMenu("", wxMENU_TEAROFF);
5a1dca12 141
e0a40292 142 menuFile->Append(ID_RESET, _("Reset"), _("Start a new game"));
087e4f4a 143 menuFile->Append(ID_SAMPLES, _("Sample game..."), _("Select a sample configuration"));
5a1dca12
GRG
144 menuFile->AppendSeparator();
145 menuFile->Append(ID_ABOUT, _("&About...\tCtrl-A"), _("Show about dialog"));
146 menuFile->AppendSeparator();
147 menuFile->Append(ID_EXIT, _("E&xit\tAlt-X"), _("Quit this program"));
087e4f4a 148
e0a40292 149 menuGame->Append(ID_CENTER, _("Re&center\tCtrl-C"), _("Go to (0, 0)"));
087e4f4a
GRG
150 menuGame->Append(ID_START, _("&Start\tCtrl-S"), _("Start"));
151 menuGame->Append(ID_STEP, _("&Next\tCtrl-N"), _("Single step"));
152 menuGame->Append(ID_STOP, _("S&top\tCtrl-T"), _("Stop"));
153 menuGame->Enable(ID_STOP, FALSE);
154 menuGame->AppendSeparator();
e0a40292
GRG
155 menuGame->Append(ID_TOPSPEED, _("Top speed!"), _("Go as fast as possible"));
156 menuGame->AppendSeparator();
157 menuGame->Append(ID_ZOOMIN, _("Zoom &in\tCtrl-I"));
158 menuGame->Append(ID_ZOOMOUT, _("Zoom &out\tCtrl-O"));
087e4f4a 159
5a1dca12
GRG
160 wxMenuBar *menuBar = new wxMenuBar();
161 menuBar->Append(menuFile, _("&File"));
087e4f4a 162 menuBar->Append(menuGame, _("&Game"));
5a1dca12
GRG
163 SetMenuBar(menuBar);
164
165 // tool bar
e0a40292 166 wxBitmap tbBitmaps[5];
471ed537 167
5a1dca12
GRG
168 tbBitmaps[0] = wxBITMAP(reset);
169 tbBitmaps[1] = wxBITMAP(play);
170 tbBitmaps[2] = wxBITMAP(stop);
e0a40292
GRG
171 tbBitmaps[3] = wxBITMAP(zoomin);
172 tbBitmaps[4] = wxBITMAP(zoomout);
5a1dca12
GRG
173
174 wxToolBar *toolBar = CreateToolBar();
175 toolBar->SetMargins(5, 5);
176 toolBar->SetToolBitmapSize(wxSize(16, 16));
e0a40292
GRG
177 ADD_TOOL(ID_RESET, tbBitmaps[0], _("Reset"), _("Start a new game"));
178 ADD_TOOL(ID_START, tbBitmaps[1], _("Start"), _("Start"));
179 ADD_TOOL(ID_STOP, tbBitmaps[2], _("Stop"), _("Stop"));
180 toolBar->AddSeparator();
181 ADD_TOOL(ID_ZOOMIN, tbBitmaps[3], _("Zoom in"), _("Zoom in"));
182 ADD_TOOL(ID_ZOOMOUT, tbBitmaps[4], _("Zoom out"), _("Zoom out"));
5a1dca12 183 toolBar->Realize();
e0a40292 184 toolBar->EnableTool(ID_STOP, FALSE); // must be after Realize() !
5a1dca12
GRG
185
186 // status bar
187 CreateStatusBar(2);
188 SetStatusText(_("Welcome to Life!"));
189
e0a40292 190 // game and canvas
2480be69 191 wxPanel *panel = new wxPanel(this, -1);
e0a40292 192 m_life = new Life();
2480be69
GRG
193 m_canvas = new LifeCanvas(panel, m_life);
194 m_timer = new LifeTimer();
e0a40292
GRG
195 m_running = FALSE;
196 m_topspeed = FALSE;
2480be69
GRG
197 m_interval = 500;
198 m_tics = 0;
199 m_text = new wxStaticText(panel, -1, "");
5a1dca12
GRG
200 UpdateInfoText();
201
e0a40292 202 // speed selection slider
2480be69
GRG
203 wxSlider *slider = new wxSlider(panel, ID_SLIDER,
204 5, 1, 10,
205 wxDefaultPosition,
206 wxSize(200, -1),
207 wxSL_HORIZONTAL | wxSL_AUTOTICKS);
5a1dca12
GRG
208
209 // component layout
210 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
087e4f4a 211 sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
5a1dca12
GRG
212 sizer->Add(m_canvas, 1, wxGROW | wxCENTRE | wxALL, 5);
213 sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
087e4f4a 214 sizer->Add(m_text, 0, wxCENTRE | wxTOP, 5);
5a1dca12
GRG
215 sizer->Add(slider, 0, wxCENTRE | wxALL, 5);
216 panel->SetSizer(sizer);
217 panel->SetAutoLayout(TRUE);
218 sizer->Fit(this);
219 sizer->SetSizeHints(this);
220}
221
222LifeFrame::~LifeFrame()
223{
224 delete m_timer;
5a1dca12
GRG
225}
226
227void LifeFrame::UpdateInfoText()
228{
229 wxString msg;
230
e0a40292
GRG
231 msg.Printf(_(" Generation: %u (T: %u ms), Population: %u "),
232 m_tics,
233 m_topspeed? 0 : m_interval,
234 m_life->GetNumCells());
5a1dca12
GRG
235 m_text->SetLabel(msg);
236}
237
e0a40292
GRG
238// Enable or disable tools and menu entries according to the current
239// state. See also wxEVT_UPDATE_UI events for a slightly different
240// way to do this.
241void LifeFrame::UpdateUI()
242{
243 GetToolBar()->EnableTool(ID_START, !m_running);
244 GetToolBar()->EnableTool(ID_STOP, m_running);
245 GetMenuBar()->GetMenu(1)->Enable(ID_START, !m_running);
246 GetMenuBar()->GetMenu(1)->Enable(ID_STEP, !m_running);
247 GetMenuBar()->GetMenu(1)->Enable(ID_STOP, m_running);
248}
249
5a1dca12
GRG
250// event handlers
251void LifeFrame::OnMenu(wxCommandEvent& event)
252{
253 switch (event.GetId())
254 {
e0a40292 255 case ID_CENTER : m_canvas->Recenter(0, 0); break;
5a1dca12 256 case ID_START : OnStart(); break;
087e4f4a 257 case ID_STEP : OnTimer(); break;
5a1dca12 258 case ID_STOP : OnStop(); break;
e0a40292 259 case ID_ZOOMIN :
087e4f4a 260 {
e0a40292
GRG
261 int cellsize = m_canvas->GetCellSize();
262 if (cellsize < 32)
263 m_canvas->SetCellSize(cellsize * 2);
087e4f4a
GRG
264 break;
265 }
e0a40292 266 case ID_ZOOMOUT :
5a1dca12 267 {
e0a40292
GRG
268 int cellsize = m_canvas->GetCellSize();
269 if (cellsize > 1)
270 m_canvas->SetCellSize(cellsize / 2);
271 break;
272 }
273 case ID_TOPSPEED:
274 {
275 m_running = TRUE;
276 m_topspeed = TRUE;
277 UpdateUI();
278 while (m_running && m_topspeed)
279 {
280 OnTimer();
281 wxYield();
282 }
283 break;
284 }
285 case ID_RESET:
286 {
287 // stop if it was running
5a1dca12
GRG
288 OnStop();
289 m_life->Clear();
e0a40292 290 m_canvas->Recenter(0, 0);
5a1dca12
GRG
291 m_tics = 0;
292 UpdateInfoText();
293 break;
294 }
e0a40292 295 case ID_ABOUT:
5a1dca12 296 {
e0a40292
GRG
297 LifeAboutDialog dialog(this);
298 dialog.ShowModal();
5a1dca12
GRG
299 break;
300 }
301 case ID_EXIT :
302 {
303 // TRUE is to force the frame to close
304 Close(TRUE);
305 break;
306 }
307 }
308}
309
e0a40292 310void LifeFrame::OnClose(wxCloseEvent& WXUNUSED(event))
5a1dca12 311{
e0a40292
GRG
312 // Stop if it was running; this is absolutely needed because
313 // the frame won't be actually destroyed until there are no
314 // more pending events, and this in turn won't ever happen
315 // if the timer is running faster than the window can redraw.
5a1dca12 316 OnStop();
e0a40292 317 Destroy();
5a1dca12
GRG
318}
319
087e4f4a
GRG
320void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event))
321{
322 // stop if it was running
323 OnStop();
324
471ed537 325 // dialog box
087e4f4a
GRG
326 LifeSamplesDialog dialog(this);
327
328 // new game?
329 if (dialog.ShowModal() == wxID_OK)
330 {
e0a40292 331 const LifeShape shape = dialog.GetShape();
087e4f4a
GRG
332
333 // put the shape
e0a40292
GRG
334 m_life->Clear();
335 m_life->SetShape(shape);
087e4f4a 336
e0a40292
GRG
337 // recenter canvas
338 m_canvas->Recenter(0, 0);
087e4f4a
GRG
339 m_tics = 0;
340 UpdateInfoText();
341 }
342}
343
5a1dca12
GRG
344void LifeFrame::OnStart()
345{
087e4f4a
GRG
346 if (!m_running)
347 {
087e4f4a
GRG
348 m_timer->Start(m_interval);
349 m_running = TRUE;
e0a40292 350 UpdateUI();
087e4f4a 351 }
5a1dca12
GRG
352}
353
354void LifeFrame::OnStop()
355{
087e4f4a
GRG
356 if (m_running)
357 {
087e4f4a
GRG
358 m_timer->Stop();
359 m_running = FALSE;
e0a40292
GRG
360 m_topspeed = FALSE;
361 UpdateUI();
087e4f4a 362 }
5a1dca12
GRG
363}
364
365void LifeFrame::OnTimer()
366{
a36f0f83
GRG
367 if (m_life->NextTic())
368 m_tics++;
369 else
370 OnStop();
5a1dca12 371
e0a40292 372 m_canvas->DrawChanged();
a36f0f83 373 UpdateInfoText();
5a1dca12
GRG
374}
375
a36f0f83
GRG
376void LifeFrame::OnSlider(wxScrollEvent& event)
377{
378 m_interval = event.GetPosition() * 100;
379
a36f0f83
GRG
380 if (m_running)
381 {
e0a40292
GRG
382 OnStop();
383 OnStart();
a36f0f83 384 }
e0a40292 385
a36f0f83
GRG
386 UpdateInfoText();
387}
388
5a1dca12
GRG
389// --------------------------------------------------------------------------
390// LifeTimer
391// --------------------------------------------------------------------------
392
5a1dca12
GRG
393void LifeTimer::Notify()
394{
a36f0f83 395 GET_FRAME()->OnTimer();
087e4f4a 396};
5a1dca12
GRG
397
398// --------------------------------------------------------------------------
087e4f4a 399// LifeCanvas
5a1dca12
GRG
400// --------------------------------------------------------------------------
401
402// canvas constructor
087e4f4a 403LifeCanvas::LifeCanvas(wxWindow *parent, Life *life, bool interactive)
e0a40292
GRG
404 : wxWindow(parent, -1, wxPoint(0, 0), wxSize(100, 100),
405 wxSUNKEN_BORDER)
5a1dca12 406{
087e4f4a
GRG
407 m_life = life;
408 m_interactive = interactive;
409 m_cellsize = 8;
e0a40292
GRG
410 m_status = MOUSE_NOACTION;
411 m_viewportX = 0;
412 m_viewportY = 0;
413 m_viewportH = 0;
414 m_viewportW = 0;
415
416 if (m_interactive)
417 SetCursor(*wxCROSS_CURSOR);
418
419 // reduce flicker if wxEVT_ERASE_BACKGROUND is not available
420 SetBackgroundColour(*wxWHITE);
5a1dca12
GRG
421}
422
423LifeCanvas::~LifeCanvas()
424{
e0a40292 425 delete m_life;
5a1dca12
GRG
426}
427
e0a40292
GRG
428// recenter at the given position
429void LifeCanvas::Recenter(wxInt32 i, wxInt32 j)
5a1dca12 430{
e0a40292
GRG
431 m_viewportX = i - m_viewportW / 2;
432 m_viewportY = j - m_viewportH / 2;
5a1dca12 433
087e4f4a 434 // redraw everything
e0a40292 435 Refresh(FALSE);
5a1dca12
GRG
436}
437
e0a40292
GRG
438// set the cell size and refresh display
439void LifeCanvas::SetCellSize(int cellsize)
5a1dca12 440{
e0a40292
GRG
441 m_cellsize = cellsize;
442
443 // find current center
444 wxInt32 cx = m_viewportX + m_viewportW / 2;
445 wxInt32 cy = m_viewportY + m_viewportH / 2;
446
447 // get current canvas size and adjust viewport accordingly
448 wxCoord w, h;
449 GetClientSize(&w, &h);
450 m_viewportW = (w + m_cellsize - 1) / m_cellsize;
451 m_viewportH = (h + m_cellsize - 1) / m_cellsize;
452
453 // recenter
454 m_viewportX = cx - m_viewportW / 2;
455 m_viewportY = cy - m_viewportH / 2;
456
457 // adjust scrollbars
458 if (m_interactive)
459 {
460 SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
461 SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
462 m_thumbX = m_viewportW;
463 m_thumbY = m_viewportH;
464 }
465
466 Refresh(FALSE);
467}
2480be69 468
e0a40292
GRG
469// draw a cell
470void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, bool alive)
471{
472 wxClientDC dc(this);
5a1dca12 473
e0a40292
GRG
474 dc.SetPen(alive? *wxBLACK_PEN : *wxWHITE_PEN);
475 dc.SetBrush(alive? *wxBLACK_BRUSH : *wxWHITE_BRUSH);
5a1dca12 476
e0a40292
GRG
477 dc.BeginDrawing();
478 DrawCell(i, j, dc);
5a1dca12 479 dc.EndDrawing();
5a1dca12
GRG
480}
481
e0a40292 482void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, wxDC &dc)
5a1dca12 483{
e0a40292
GRG
484 wxCoord x = CellToX(i);
485 wxCoord y = CellToY(j);
5a1dca12 486
e0a40292
GRG
487 // if cellsize is 1 or 2, there will be no grid
488 switch (m_cellsize)
489 {
490 case 1:
491 dc.DrawPoint(x, y);
492 break;
493 case 2:
494 dc.DrawRectangle(x, y, 2, 2);
495 break;
496 default:
497 dc.DrawRectangle(x + 1, y + 1, m_cellsize - 1, m_cellsize - 1);
498 }
5a1dca12
GRG
499}
500
e0a40292
GRG
501// draw all changed cells
502void LifeCanvas::DrawChanged()
5a1dca12 503{
e0a40292
GRG
504 wxClientDC dc(this);
505
506 size_t ncells;
507 Cell *cells;
508 bool done = FALSE;
509
510 m_life->BeginFind(m_viewportX,
511 m_viewportY,
512 m_viewportX + m_viewportW,
513 m_viewportY + m_viewportH,
514 TRUE);
515
516 dc.BeginDrawing();
517 dc.SetLogicalFunction(wxINVERT);
518
519 if (m_cellsize == 1)
5a1dca12 520 {
e0a40292 521 // drawn using DrawPoint
5a1dca12 522 dc.SetPen(*wxBLACK_PEN);
5a1dca12
GRG
523 }
524 else
525 {
e0a40292
GRG
526 // drawn using DrawRectangle
527 dc.SetPen(*wxTRANSPARENT_PEN);
528 dc.SetBrush(*wxBLACK_BRUSH);
529 }
530
531 while (!done)
532 {
533 done = m_life->FindMore(&cells, &ncells);
534
535 for (size_t m = 0; m < ncells; m++)
536 DrawCell(cells[m].i, cells[m].j, dc);
5a1dca12 537 }
e0a40292 538 dc.EndDrawing();
5a1dca12 539}
ecbdd409 540
5a1dca12
GRG
541// event handlers
542void LifeCanvas::OnPaint(wxPaintEvent& event)
543{
544 wxPaintDC dc(this);
e0a40292
GRG
545 wxRect rect = GetUpdateRegion().GetBox();
546 wxCoord x, y, w, h;
547 wxInt32 i0, j0, i1, j1;
548
549 // find damaged area
550 x = rect.GetX();
551 y = rect.GetY();
552 w = rect.GetWidth();
553 h = rect.GetHeight();
554
555 i0 = XToCell(x);
556 j0 = YToCell(y);
557 i1 = XToCell(x + w - 1);
558 j1 = YToCell(y + h - 1);
559
560 size_t ncells;
561 Cell *cells;
562 bool done = FALSE;
5a1dca12 563
e0a40292
GRG
564 m_life->BeginFind(i0, j0, i1, j1, FALSE);
565 done = m_life->FindMore(&cells, &ncells);
5a1dca12 566
e0a40292 567 // erase all damaged cells and draw the grid
5a1dca12 568 dc.BeginDrawing();
e0a40292 569 dc.SetBrush(*wxWHITE_BRUSH);
5a1dca12 570
e0a40292 571 if (m_cellsize <= 2)
5a1dca12 572 {
e0a40292
GRG
573 // no grid
574 dc.SetPen(*wxWHITE_PEN);
575 dc.DrawRectangle(x, y, w, h);
5a1dca12 576 }
e0a40292
GRG
577 else
578 {
579 x = CellToX(i0);
580 y = CellToY(j0);
581 w = CellToX(i1 + 1) - x + 1;
582 h = CellToY(j1 + 1) - y + 1;
583
584 dc.SetPen(*wxLIGHT_GREY_PEN);
585 for (wxInt32 yy = y; yy <= (y + h - m_cellsize); yy += m_cellsize)
586 dc.DrawRectangle(x, yy, w, m_cellsize + 1);
587 for (wxInt32 xx = x; xx <= (x + w - m_cellsize); xx += m_cellsize)
588 dc.DrawLine(xx, y, xx, y + h);
589 }
590
591 // draw all alive cells
592 dc.SetPen(*wxBLACK_PEN);
593 dc.SetBrush(*wxBLACK_BRUSH);
594
595 while (!done)
596 {
597 for (size_t m = 0; m < ncells; m++)
598 DrawCell(cells[m].i, cells[m].j, dc);
599
600 done = m_life->FindMore(&cells, &ncells);
601 }
602
603 // last set
604 for (size_t m = 0; m < ncells; m++)
605 DrawCell(cells[m].i, cells[m].j, dc);
5a1dca12 606
5a1dca12
GRG
607 dc.EndDrawing();
608}
609
610void LifeCanvas::OnMouse(wxMouseEvent& event)
611{
087e4f4a
GRG
612 if (!m_interactive)
613 return;
614
5a1dca12 615 // which cell are we pointing at?
e0a40292
GRG
616 wxInt32 i = XToCell( event.GetX() );
617 wxInt32 j = YToCell( event.GetY() );
618
619 // set statusbar text
620 wxString msg;
621 msg.Printf(_("Cell: (%d, %d)"), i, j);
622 GET_FRAME()->SetStatusText(msg, 1);
5a1dca12
GRG
623
624 // button pressed?
625 if (!event.LeftIsDown())
626 {
627 m_status = MOUSE_NOACTION;
628 }
e0a40292 629 else
5a1dca12
GRG
630 {
631 bool alive = m_life->IsAlive(i, j);
632
633 // if just pressed, update status
634 if (m_status == MOUSE_NOACTION)
635 m_status = (alive? MOUSE_ERASING : MOUSE_DRAWING);
636
637 // toggle cell and refresh if needed
638 if (((m_status == MOUSE_ERASING) && alive) ||
639 ((m_status == MOUSE_DRAWING) && !alive))
640 {
e0a40292
GRG
641 m_life->SetCell(i, j, !alive);
642 DrawCell(i, j, !alive);
643 GET_FRAME()->UpdateInfoText();
5a1dca12
GRG
644 }
645 }
646}
647
648void LifeCanvas::OnSize(wxSizeEvent& event)
649{
e0a40292
GRG
650 // find center
651 wxInt32 cx = m_viewportX + m_viewportW / 2;
652 wxInt32 cy = m_viewportY + m_viewportH / 2;
653
654 // get new size
5a1dca12
GRG
655 wxCoord w = event.GetSize().GetX();
656 wxCoord h = event.GetSize().GetY();
e0a40292
GRG
657 m_viewportW = (w + m_cellsize - 1) / m_cellsize;
658 m_viewportH = (h + m_cellsize - 1) / m_cellsize;
659
660 // recenter
661 m_viewportX = cx - m_viewportW / 2;
662 m_viewportY = cy - m_viewportH / 2;
663
664 // scrollbars
665 if (m_interactive)
666 {
667 SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
668 SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
669 m_thumbX = m_viewportW;
670 m_thumbY = m_viewportH;
671 }
5a1dca12
GRG
672
673 // allow default processing
674 event.Skip();
675}
e0a40292
GRG
676
677void LifeCanvas::OnScroll(wxScrollWinEvent& event)
678{
679 WXTYPE type = event.GetEventType();
680 int pos = event.GetPosition();
681 int orient = event.GetOrientation();
682 bool scrolling = event.IsScrolling();
683 int scrollinc = 0;
684
685 // calculate scroll increment
686 switch (type)
687 {
688 case wxEVT_SCROLLWIN_TOP:
689 {
690 if (orient == wxHORIZONTAL)
691 scrollinc = -m_viewportW;
692 else
693 scrollinc = -m_viewportH;
694 break;
695 }
696 case wxEVT_SCROLLWIN_BOTTOM:
697 {
698 if (orient == wxHORIZONTAL)
699 scrollinc = m_viewportW;
700 else
701 scrollinc = m_viewportH;
702 break;
703 }
704 case wxEVT_SCROLLWIN_LINEUP: scrollinc = -1; break;
705 case wxEVT_SCROLLWIN_LINEDOWN: scrollinc = +1; break;
706 case wxEVT_SCROLLWIN_PAGEUP: scrollinc = -10; break;
707 case wxEVT_SCROLLWIN_PAGEDOWN: scrollinc = +10; break;
708 case wxEVT_SCROLLWIN_THUMBTRACK:
709 {
710 if (scrolling)
711 {
712 // user is dragging the thumb in the scrollbar
713 if (orient == wxHORIZONTAL)
714 {
715 scrollinc = pos - m_thumbX;
716 m_thumbX = pos;
717 }
718 else
719 {
720 scrollinc = pos - m_thumbY;
721 m_thumbY = pos;
722 }
723 }
724 else
725 {
726 // user released the thumb after dragging
727 m_thumbX = m_viewportW;
728 m_thumbY = m_viewportH;
729 }
730 break;
731 }
732 }
733
734#ifdef __WXGTK__ // what about Motif?
735 // wxGTK updates the thumb automatically (wxMSW doesn't); reset it back
736 if ((type != wxEVT_SCROLLWIN_THUMBTRACK) || !scrolling)
737 {
738 SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
739 SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
740 }
741#endif
742
743 if (scrollinc == 0) return;
744
745 // scroll the window and adjust the viewport
746 if (orient == wxHORIZONTAL)
747 {
748 m_viewportX += scrollinc;
749 ScrollWindow( -m_cellsize * scrollinc, 0, (const wxRect *) NULL);
750 }
751 else
752 {
753 m_viewportY += scrollinc;
754 ScrollWindow( 0, -m_cellsize * scrollinc, (const wxRect *) NULL);
755 }
756}
757
758void LifeCanvas::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
759{
760 // do nothing. I just don't want the background to be erased, you know.
761}