]> git.saurik.com Git - wxWidgets.git/blame_incremental - demos/life/life.cpp
Add backend descriptions for all backends to the documentation.
[wxWidgets.git] / demos / life / life.cpp
... / ...
CommitLineData
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// For compilers that support precompilation, includes "wx/wx.h".
17#include "wx/wxprec.h"
18
19#ifdef __BORLANDC__
20 #pragma hdrstop
21#endif
22
23#ifndef WX_PRECOMP
24 #include "wx/wx.h"
25#endif
26
27#include "wx/statline.h"
28#include "wx/wfstream.h"
29#include "wx/filedlg.h"
30#include "wx/stockitem.h"
31
32#include "life.h"
33#include "game.h"
34#include "dialogs.h"
35#include "reader.h"
36
37// --------------------------------------------------------------------------
38// resources
39// --------------------------------------------------------------------------
40
41#if defined(__WXGTK__) || defined(__WXMOTIF__) || defined(__WXMAC__) || defined(__WXMGL__) || defined(__WXX11__)
42 // application icon
43 #include "mondrian.xpm"
44
45 // bitmap buttons for the toolbar
46 #include "bitmaps/reset.xpm"
47 #include "bitmaps/open.xpm"
48 #include "bitmaps/play.xpm"
49 #include "bitmaps/stop.xpm"
50 #include "bitmaps/zoomin.xpm"
51 #include "bitmaps/zoomout.xpm"
52 #include "bitmaps/info.xpm"
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"
60#endif
61
62// --------------------------------------------------------------------------
63// constants
64// --------------------------------------------------------------------------
65
66// IDs for the controls and the menu commands. Exluding those already defined
67// by wxWidgets, such as wxID_NEW.
68enum
69{
70 // timer
71 ID_TIMER = wxID_HIGHEST,
72
73 // file menu
74 ID_SAMPLES,
75
76 // view menu
77 ID_SHOWNAV,
78 ID_ORIGIN,
79 ID_CENTER,
80 ID_NORTH,
81 ID_SOUTH,
82 ID_EAST,
83 ID_WEST,
84 ID_INFO,
85
86 // game menu
87 ID_START,
88 ID_STEP,
89 ID_TOPSPEED,
90
91 // speed selection slider
92 ID_SLIDER
93};
94
95// --------------------------------------------------------------------------
96// event tables and other macros for wxWidgets
97// --------------------------------------------------------------------------
98
99// Event tables
100BEGIN_EVENT_TABLE(LifeFrame, wxFrame)
101 EVT_MENU (wxID_NEW, LifeFrame::OnMenu)
102#if wxUSE_FILEDLG
103 EVT_MENU (wxID_OPEN, LifeFrame::OnOpen)
104#endif
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)
125END_EVENT_TABLE()
126
127BEGIN_EVENT_TABLE(LifeNavigator, wxMiniFrame)
128 EVT_CLOSE ( LifeNavigator::OnClose)
129END_EVENT_TABLE()
130
131BEGIN_EVENT_TABLE(LifeCanvas, wxWindow)
132 EVT_PAINT ( LifeCanvas::OnPaint)
133 EVT_SCROLLWIN ( LifeCanvas::OnScroll)
134 EVT_SIZE ( LifeCanvas::OnSize)
135 EVT_MOTION ( LifeCanvas::OnMouse)
136 EVT_LEFT_DOWN ( LifeCanvas::OnMouse)
137 EVT_LEFT_UP ( LifeCanvas::OnMouse)
138 EVT_LEFT_DCLICK ( LifeCanvas::OnMouse)
139 EVT_ERASE_BACKGROUND( LifeCanvas::OnEraseBackground)
140END_EVENT_TABLE()
141
142
143// Create a new application object
144IMPLEMENT_APP(LifeApp)
145
146
147// ==========================================================================
148// implementation
149// ==========================================================================
150
151// some shortcuts
152#define ADD_TOOL(id, bmp, tooltip, help) \
153 toolBar->AddTool(id, wxEmptyString, bmp, wxNullBitmap, wxITEM_NORMAL, tooltip, help)
154
155
156// --------------------------------------------------------------------------
157// LifeApp
158// --------------------------------------------------------------------------
159
160// 'Main program' equivalent: the program execution "starts" here
161bool LifeApp::OnInit()
162{
163 // create the main application window
164 LifeFrame *frame = new LifeFrame();
165
166 // show it
167 frame->Show(true);
168
169 // just for Motif
170#ifdef __WXMOTIF__
171 frame->UpdateInfoText();
172#endif
173
174 // enter the main message loop and run the app
175 return true;
176}
177
178// --------------------------------------------------------------------------
179// LifeFrame
180// --------------------------------------------------------------------------
181
182// frame constructor
183LifeFrame::LifeFrame() :
184 wxFrame( (wxFrame *) NULL, wxID_ANY, _("Life!"), wxDefaultPosition ),
185 m_navigator(NULL)
186{
187 // frame icon
188 SetIcon(wxICON(mondrian));
189
190 // menu bar
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);
195
196 menuFile->Append(wxID_NEW, wxEmptyString, _("Start a new game"));
197#if wxUSE_FILEDLG
198 menuFile->Append(wxID_OPEN, wxEmptyString, _("Open an existing Life pattern"));
199#endif
200 menuFile->Append(ID_SAMPLES, _("&Sample game..."), _("Select a sample configuration"));
201#if ! (defined(__SMARTPHONE__) || defined(__POCKETPC__))
202 menuFile->AppendSeparator();
203 menuFile->Append(wxID_EXIT);
204
205 menuView->Append(ID_SHOWNAV, _("Navigation &toolbox"), _("Show or hide toolbox"), wxITEM_CHECK);
206 menuView->Check(ID_SHOWNAV, true);
207 menuView->AppendSeparator();
208#endif
209
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"));
216 menuView->AppendSeparator();
217 menuView->Append(wxID_ZOOM_IN, wxEmptyString, _("Zoom in"));
218 menuView->Append(wxID_ZOOM_OUT, wxEmptyString, _("Zoom out"));
219 menuView->Append(ID_INFO, _("&Description\tCtrl-D"), _("View pattern description"));
220
221 menuGame->Append(ID_START, _("&Start\tCtrl-S"), _("Start"));
222 menuGame->Append(ID_STEP, _("&Next\tCtrl-N"), _("Single step"));
223 menuGame->Append(wxID_STOP, wxEmptyString, _("Stop"));
224 menuGame->Enable(wxID_STOP, false);
225 menuGame->AppendSeparator();
226 menuGame->Append(ID_TOPSPEED, _("T&op speed!"), _("Go as fast as possible"));
227
228 menuHelp->Append(wxID_ABOUT, _("&About\tCtrl-A"), _("Show about dialog"));
229
230 wxMenuBar *menuBar = new wxMenuBar();
231 menuBar->Append(menuFile, _("&File"));
232 menuBar->Append(menuView, _("&View"));
233 menuBar->Append(menuGame, _("&Game"));
234 menuBar->Append(menuHelp, _("&Help"));
235 SetMenuBar(menuBar);
236
237 // tool bar
238 wxBitmap tbBitmaps[7];
239
240 tbBitmaps[0] = wxBITMAP(reset);
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);
247
248 wxToolBar *toolBar = CreateToolBar();
249 toolBar->SetMargins(5, 5);
250 toolBar->SetToolBitmapSize(wxSize(16, 16));
251
252 ADD_TOOL(wxID_NEW, tbBitmaps[0], wxGetStockLabel(wxID_NEW, wxSTOCK_NOFLAGS), _("Start a new game"));
253#ifndef __POCKETPC__
254#if wxUSE_FILEDLG
255 ADD_TOOL(wxID_OPEN, tbBitmaps[1], wxGetStockLabel(wxID_OPEN, wxSTOCK_NOFLAGS), _("Open an existing Life pattern"));
256#endif // wxUSE_FILEDLG
257
258 toolBar->AddSeparator();
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"));
261 ADD_TOOL(ID_INFO, tbBitmaps[4], _("Description"), _("Show description"));
262 toolBar->AddSeparator();
263#endif // __POCKETPC__
264 ADD_TOOL(ID_START, tbBitmaps[5], _("Start"), _("Start"));
265 ADD_TOOL(wxID_STOP, tbBitmaps[6], _("Stop"), _("Stop"));
266
267 toolBar->Realize();
268 toolBar->EnableTool(wxID_STOP, false); // must be after Realize() !
269
270#if wxUSE_STATUSBAR
271 // status bar
272 CreateStatusBar(2);
273 SetStatusText(_("Welcome to Life!"));
274#endif // wxUSE_STATUSBAR
275
276 // game and timer
277 m_life = new Life();
278 m_timer = new wxTimer(this, ID_TIMER);
279 m_running = false;
280 m_topspeed = false;
281 m_interval = 500;
282 m_tics = 0;
283
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
289 wxPanel *panel1 = new wxPanel(this, wxID_ANY);
290 wxPanel *panel2 = new wxPanel(this, wxID_ANY);
291
292 // canvas
293 m_canvas = new LifeCanvas(panel1, m_life);
294
295 // info panel
296 m_text = new wxStaticText(panel2, wxID_ANY,
297 wxEmptyString,
298 wxDefaultPosition,
299 wxDefaultSize,
300 wxALIGN_CENTER | wxST_NO_AUTORESIZE);
301
302 wxSlider *slider = new wxSlider(panel2, ID_SLIDER,
303 5, 1, 10,
304 wxDefaultPosition,
305 wxSize(200, wxDefaultCoord),
306 wxSL_HORIZONTAL | wxSL_AUTOTICKS);
307
308 UpdateInfoText();
309
310 // component layout
311 wxBoxSizer *sizer1 = new wxBoxSizer(wxVERTICAL);
312 wxBoxSizer *sizer2 = new wxBoxSizer(wxVERTICAL);
313 wxBoxSizer *sizer3 = new wxBoxSizer(wxVERTICAL);
314
315#if wxUSE_STATLINE
316 sizer1->Add( new wxStaticLine(panel1, wxID_ANY), 0, wxGROW );
317#endif // wxUSE_STATLINE
318 sizer1->Add( m_canvas, 1, wxGROW | wxALL, 2 );
319#if wxUSE_STATLINE
320 sizer1->Add( new wxStaticLine(panel1, wxID_ANY), 0, wxGROW );
321#endif // wxUSE_STATLINE
322 panel1->SetSizer( sizer1 );
323 sizer1->Fit( panel1 );
324
325 sizer2->Add( m_text, 0, wxGROW | wxTOP, 4 );
326 sizer2->Add( slider, 0, wxCENTRE | wxALL, 4 );
327
328 panel2->SetSizer( sizer2 );
329 sizer2->Fit( panel2 );
330
331 sizer3->Add( panel1, 1, wxGROW );
332 sizer3->Add( panel2, 0, wxGROW );
333 SetSizer( sizer3 );
334
335#ifndef __WXWINCE__
336 sizer3->Fit( this );
337
338 // set minimum frame size
339 sizer3->SetSizeHints( this );
340
341 // navigator frame - not appropriate for small devices
342 m_navigator = new LifeNavigator(this);
343#endif
344
345}
346
347LifeFrame::~LifeFrame()
348{
349 delete m_timer;
350}
351
352void LifeFrame::UpdateInfoText()
353{
354 wxString msg;
355
356 msg.Printf(_(" Generation: %lu (T: %lu ms), Population: %lu "),
357 m_tics,
358 m_topspeed? 0 : m_interval,
359 static_cast<unsigned long>(m_life->GetNumCells()));
360 m_text->SetLabel(msg);
361}
362
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{
368 // start / stop
369 GetToolBar()->EnableTool(ID_START, !m_running);
370 GetToolBar()->EnableTool(wxID_STOP, m_running);
371 GetMenuBar()->Enable(ID_START, !m_running);
372 GetMenuBar()->Enable(ID_STEP, !m_running);
373 GetMenuBar()->Enable(wxID_STOP, m_running);
374 GetMenuBar()->Enable(ID_TOPSPEED, !m_topspeed);
375
376 // zooming
377 int cellsize = m_canvas->GetCellSize();
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);
382}
383
384// Event handlers -----------------------------------------------------------
385
386// OnMenu handles all events which don't have their own event handler
387void LifeFrame::OnMenu(wxCommandEvent& event)
388{
389 switch (event.GetId())
390 {
391 case wxID_NEW:
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 }
401 case wxID_ABOUT:
402 {
403 LifeAboutDialog dialog(this);
404 dialog.ShowModal();
405 break;
406 }
407 case wxID_EXIT:
408 {
409 // true is to force the frame to close
410 Close(true);
411 break;
412 }
413 case ID_SHOWNAV:
414 {
415 bool checked = GetMenuBar()->GetMenu(1)->IsChecked(ID_SHOWNAV);
416 if (m_navigator)
417 m_navigator->Show(checked);
418 break;
419 }
420 case ID_INFO:
421 {
422 wxString desc = m_life->GetDescription();
423
424 if ( desc.empty() )
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;
434 case wxID_STOP : OnStop(); break;
435 case ID_TOPSPEED:
436 {
437 m_running = true;
438 m_topspeed = true;
439 UpdateUI();
440 while (m_running && m_topspeed)
441 {
442 OnStep();
443 wxYield();
444 }
445 break;
446 }
447 }
448}
449
450#if wxUSE_FILEDLG
451void LifeFrame::OnOpen(wxCommandEvent& WXUNUSED(event))
452{
453 wxFileDialog filedlg(this,
454 _("Choose a file to open"),
455 wxEmptyString,
456 wxEmptyString,
457 _("Life patterns (*.lif)|*.lif|All files (*.*)|*.*"),
458 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
459
460 if (filedlg.ShowModal() == wxID_OK)
461 {
462 wxFileInputStream stream(filedlg.GetPath());
463 LifeReader reader(stream);
464
465 // the reader handles errors itself, no need to do anything here
466 if (reader.IsOk())
467 {
468 // stop if running and put the pattern
469 OnStop();
470 m_life->Clear();
471 m_life->SetPattern(reader.GetPattern());
472
473 // recenter canvas
474 m_canvas->Recenter(0, 0);
475 m_tics = 0;
476 UpdateInfoText();
477 }
478 }
479}
480#endif
481
482void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event))
483{
484 // stop if it was running
485 OnStop();
486
487 // dialog box
488 LifeSamplesDialog dialog(this);
489
490 if (dialog.ShowModal() == wxID_OK)
491 {
492 const LifePattern pattern = dialog.GetPattern();
493
494 // put the pattern
495 m_life->Clear();
496 m_life->SetPattern(pattern);
497
498 // recenter canvas
499 m_canvas->Recenter(0, 0);
500 m_tics = 0;
501 UpdateInfoText();
502 }
503}
504
505void LifeFrame::OnZoom(wxCommandEvent& event)
506{
507 int cellsize = m_canvas->GetCellSize();
508
509 if ((event.GetId() == wxID_ZOOM_IN) && cellsize < 32)
510 {
511 m_canvas->SetCellSize(cellsize * 2);
512 UpdateUI();
513 }
514 else if ((event.GetId() == wxID_ZOOM_OUT) && cellsize > 1)
515 {
516 m_canvas->SetCellSize(cellsize / 2);
517 UpdateUI();
518 }
519}
520
521void LifeFrame::OnNavigate(wxCommandEvent& event)
522{
523 LifeCell c;
524
525 switch (event.GetId())
526 {
527 case ID_NORTH: c = m_life->FindNorth(); break;
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;
532 default :
533 wxFAIL;
534 // Fall through!
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 }
550
551 UpdateInfoText();
552}
553
554void LifeFrame::OnTimer(wxTimerEvent& WXUNUSED(event))
555{
556 OnStep();
557}
558
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();
566 Destroy();
567}
568
569void LifeFrame::OnStart()
570{
571 if (!m_running)
572 {
573 m_timer->Start(m_interval);
574 m_running = true;
575 UpdateUI();
576 }
577}
578
579void LifeFrame::OnStop()
580{
581 if (m_running)
582 {
583 m_timer->Stop();
584 m_running = false;
585 m_topspeed = false;
586 UpdateUI();
587 }
588}
589
590void LifeFrame::OnStep()
591{
592 if (m_life->NextTic())
593 m_tics++;
594 else
595 OnStop();
596
597 m_canvas->DrawChanged();
598 UpdateInfoText();
599}
600
601
602// --------------------------------------------------------------------------
603// LifeNavigator miniframe
604// --------------------------------------------------------------------------
605
606LifeNavigator::LifeNavigator(wxWindow *parent)
607 : wxMiniFrame(parent, wxID_ANY,
608 _("Navigation"),
609 wxDefaultPosition,
610 wxDefaultSize,
611 wxCAPTION | wxSIMPLE_BORDER)
612{
613 wxPanel *panel = new wxPanel(this, wxID_ANY);
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),
620 bmpw = wxBITMAP(west),
621 bmpc = wxBITMAP(center),
622 bmpe = wxBITMAP(east),
623 bmps = wxBITMAP(south);
624
625#if !defined(__WXGTK__) && !defined(__WXMOTIF__) && !defined(__WXMAC__)
626 bmpn.SetMask(new wxMask(bmpn, *wxLIGHT_GREY));
627 bmpw.SetMask(new wxMask(bmpw, *wxLIGHT_GREY));
628 bmpc.SetMask(new wxMask(bmpc, *wxLIGHT_GREY));
629 bmpe.SetMask(new wxMask(bmpe, *wxLIGHT_GREY));
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
641#if wxUSE_TOOLTIPS
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"));
647#endif
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
657 // set the panel and miniframe size
658 panel->SetSizer(sizer1);
659
660 sizer1->Fit(panel);
661 SetClientSize(panel->GetSize());
662 wxSize sz = GetSize();
663 SetSizeHints(sz.x, sz.y, sz.x, sz.y);
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
675 Show(true);
676}
677
678void LifeNavigator::OnClose(wxCloseEvent& event)
679{
680 // avoid if we can
681 if (event.CanVeto())
682 event.Veto();
683 else
684 Destroy();
685}
686
687
688// --------------------------------------------------------------------------
689// LifeCanvas
690// --------------------------------------------------------------------------
691
692// canvas constructor
693LifeCanvas::LifeCanvas(wxWindow *parent, Life *life, bool interactive)
694 : wxWindow(parent, wxID_ANY, wxDefaultPosition, wxSize(100, 100),
695 wxFULL_REPAINT_ON_RESIZE | wxHSCROLL | wxVSCROLL
696#if !defined(__SMARTPHONE__) && !defined(__POCKETPC__)
697 |wxSUNKEN_BORDER
698#else
699 |wxSIMPLE_BORDER
700#endif
701 )
702{
703 m_life = life;
704 m_interactive = interactive;
705 m_cellsize = 8;
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
716 SetBackgroundColour(*wxWHITE);
717}
718
719LifeCanvas::~LifeCanvas()
720{
721 delete m_life;
722}
723
724// recenter at the given position
725void LifeCanvas::Recenter(wxInt32 i, wxInt32 j)
726{
727 m_viewportX = i - m_viewportW / 2;
728 m_viewportY = j - m_viewportH / 2;
729
730 // redraw everything
731 Refresh(false);
732}
733
734// set the cell size and refresh display
735void LifeCanvas::SetCellSize(int cellsize)
736{
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
744 int w, h;
745 GetClientSize(&w, &h);
746 m_viewportW = (w + m_cellsize - 1) / m_cellsize;
747 m_viewportH = (h + m_cellsize - 1) / m_cellsize;
748
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 }
761
762 Refresh(false);
763}
764
765// draw a cell
766void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, bool alive)
767{
768 wxClientDC dc(this);
769
770 dc.SetPen(alive? *wxBLACK_PEN : *wxWHITE_PEN);
771 dc.SetBrush(alive? *wxBLACK_BRUSH : *wxWHITE_BRUSH);
772
773 DrawCell(i, j, dc);
774}
775
776void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, wxDC &dc)
777{
778 wxCoord x = CellToX(i);
779 wxCoord y = CellToY(j);
780
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 }
793}
794
795// draw all changed cells
796void LifeCanvas::DrawChanged()
797{
798 wxClientDC dc(this);
799
800 size_t ncells;
801 LifeCell *cells;
802 bool done = false;
803
804 m_life->BeginFind(m_viewportX,
805 m_viewportY,
806 m_viewportX + m_viewportW,
807 m_viewportY + m_viewportH,
808 true);
809
810 if (m_cellsize == 1)
811 {
812 dc.SetPen(*wxBLACK_PEN);
813 }
814 else
815 {
816 dc.SetPen(*wxTRANSPARENT_PEN);
817 dc.SetBrush(*wxBLACK_BRUSH);
818 }
819 dc.SetLogicalFunction(wxINVERT);
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);
827 }
828}
829
830// event handlers
831void LifeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event))
832{
833 wxPaintDC dc(this);
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;
850 LifeCell *cells;
851
852 m_life->BeginFind(i0, j0, i1, j1, false);
853 bool done = m_life->FindMore(&cells, &ncells);
854
855 // erase all damaged cells and draw the grid
856 dc.SetBrush(*wxWHITE_BRUSH);
857
858 if (m_cellsize <= 2)
859 {
860 // no grid
861 dc.SetPen(*wxWHITE_PEN);
862 dc.DrawRectangle(x, y, w, h);
863 }
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);
893}
894
895void LifeCanvas::OnMouse(wxMouseEvent& event)
896{
897 if (!m_interactive)
898 return;
899
900 // which cell are we pointing at?
901 wxInt32 i = XToCell( event.GetX() );
902 wxInt32 j = YToCell( event.GetY() );
903
904#if wxUSE_STATUSBAR
905 // set statusbar text
906 wxString msg;
907 msg.Printf(_("Cell: (%d, %d)"), i, j);
908 ((LifeFrame *) wxGetApp().GetTopWindow())->SetStatusText(msg, 1);
909#endif // wxUSE_STATUSBAR
910
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?
919 if (!event.LeftIsDown())
920 {
921 m_status = MOUSE_NOACTION;
922 return;
923 }
924
925 // was it pressed just now?
926 if (event.LeftDown())
927 {
928 // yes: start a new action and toggle this cell
929 m_status = (m_life->IsAlive(i, j)? MOUSE_ERASING : MOUSE_DRAWING);
930
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 {
938 // no: continue ongoing action
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);
945
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;
957
958 if (ai > aj)
959 {
960 // iterate over i
961 d = aj - (ai >> 1);
962
963 while (ii != i)
964 {
965 m_life->SetCell(ii, jj, alive);
966 DrawCell(ii, jj, dc);
967 if (d >= 0)
968 {
969 jj += sj;
970 d -= ai;
971 }
972 ii += si;
973 d += aj;
974 }
975 }
976 else
977 {
978 // iterate over j
979 d = ai - (aj >> 1);
980
981 while (jj != j)
982 {
983 m_life->SetCell(ii, jj, alive);
984 DrawCell(ii, jj, dc);
985 if (d >= 0)
986 {
987 ii += si;
988 d -= aj;
989 }
990 jj += sj;
991 d += ai;
992 }
993 }
994
995 // last cell
996 m_life->SetCell(ii, jj, alive);
997 DrawCell(ii, jj, dc);
998 m_mi = ii;
999 m_mj = jj;
1000 }
1001
1002 ((LifeFrame *) wxGetApp().GetTopWindow())->UpdateInfoText();
1003}
1004
1005void LifeCanvas::OnSize(wxSizeEvent& event)
1006{
1007 // find center
1008 wxInt32 cx = m_viewportX + m_viewportW / 2;
1009 wxInt32 cy = m_viewportY + m_viewportH / 2;
1010
1011 // get new size
1012 wxCoord w = event.GetSize().GetX();
1013 wxCoord h = event.GetSize().GetY();
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 }
1029
1030 // allow default processing
1031 event.Skip();
1032}
1033
1034void LifeCanvas::OnScroll(wxScrollWinEvent& event)
1035{
1036 WXTYPE type = (WXTYPE)event.GetEventType();
1037 int pos = event.GetPosition();
1038 int orient = event.GetOrientation();
1039
1040 // calculate scroll increment
1041 int scrollinc = 0;
1042 if (type == wxEVT_SCROLLWIN_TOP)
1043 {
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 {
1075 scrollinc = +10;
1076 }
1077 else
1078 if (type == wxEVT_SCROLLWIN_THUMBTRACK)
1079 {
1080 if (orient == wxHORIZONTAL)
1081 {
1082 scrollinc = pos - m_thumbX;
1083 m_thumbX = pos;
1084 }
1085 else
1086 {
1087 scrollinc = pos - m_thumbY;
1088 m_thumbY = pos;
1089 }
1090 }
1091 else
1092 if (type == wxEVT_SCROLLWIN_THUMBRELEASE)
1093 {
1094 m_thumbX = m_viewportW;
1095 m_thumbY = m_viewportH;
1096 }
1097
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.
1101 if (type != wxEVT_SCROLLWIN_THUMBTRACK)
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;
1109
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 {
1118 m_viewportY += scrollinc;
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}