]> git.saurik.com Git - wxWidgets.git/blob - demos/life/life.cpp
Use wxID_EXIT for the "Quit" item in the exec sample.
[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 // 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 #ifndef wxHAS_IMAGES_IN_RESOURCES
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.
68 enum
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
100 BEGIN_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)
125 END_EVENT_TABLE()
126
127 BEGIN_EVENT_TABLE(LifeNavigator, wxMiniFrame)
128 EVT_CLOSE ( LifeNavigator::OnClose)
129 END_EVENT_TABLE()
130
131 BEGIN_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)
140 END_EVENT_TABLE()
141
142
143 // Create a new application object
144 IMPLEMENT_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
161 bool 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
183 LifeFrame::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
347 LifeFrame::~LifeFrame()
348 {
349 delete m_timer;
350 }
351
352 void 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.
366 void 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
387 void 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
451 void 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
482 void 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
505 void 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
521 void 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
541 void 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
554 void LifeFrame::OnTimer(wxTimerEvent& WXUNUSED(event))
555 {
556 OnStep();
557 }
558
559 void 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
569 void LifeFrame::OnStart()
570 {
571 if (!m_running)
572 {
573 m_timer->Start(m_interval);
574 m_running = true;
575 UpdateUI();
576 }
577 }
578
579 void 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
590 void 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
606 LifeNavigator::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
678 void 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
693 LifeCanvas::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
719 LifeCanvas::~LifeCanvas()
720 {
721 delete m_life;
722 }
723
724 // recenter at the given position
725 void 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
735 void 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
766 void 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
776 void 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
796 void 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
831 void 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
895 void 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
1005 void 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
1034 void 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
1123 void LifeCanvas::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
1124 {
1125 // do nothing. I just don't want the background to be erased, you know.
1126 }