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