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