1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: The game of Life, created by J. H. Conway
4 // Author: Guillermo Rodriguez Garcia, <guille@iies.es>
7 // Copyright: (c) 2000, Guillermo Rodriguez Garcia
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
11 // ==========================================================================
12 // headers, declarations, constants
13 // ==========================================================================
15 // For compilers that support precompilation, includes "wx/wx.h".
16 #include "wx/wxprec.h"
26 #include "wx/statline.h"
27 #include "wx/wfstream.h"
28 #include "wx/filedlg.h"
29 #include "wx/stockitem.h"
36 // --------------------------------------------------------------------------
38 // --------------------------------------------------------------------------
40 #ifndef wxHAS_IMAGES_IN_RESOURCES
42 #include "mondrian.xpm"
44 // bitmap buttons for the toolbar
45 #include "bitmaps/reset.xpm"
46 #include "bitmaps/open.xpm"
47 #include "bitmaps/play.xpm"
48 #include "bitmaps/stop.xpm"
49 #include "bitmaps/zoomin.xpm"
50 #include "bitmaps/zoomout.xpm"
51 #include "bitmaps/info.xpm"
54 #include "bitmaps/north.xpm"
55 #include "bitmaps/south.xpm"
56 #include "bitmaps/east.xpm"
57 #include "bitmaps/west.xpm"
58 #include "bitmaps/center.xpm"
61 // --------------------------------------------------------------------------
63 // --------------------------------------------------------------------------
65 // IDs for the controls and the menu commands. Exluding those already defined
66 // by wxWidgets, such as wxID_NEW.
70 ID_TIMER
= wxID_HIGHEST
,
90 // speed selection slider
94 // --------------------------------------------------------------------------
95 // event tables and other macros for wxWidgets
96 // --------------------------------------------------------------------------
99 BEGIN_EVENT_TABLE(LifeFrame
, wxFrame
)
100 EVT_MENU (wxID_NEW
, LifeFrame::OnMenu
)
102 EVT_MENU (wxID_OPEN
, LifeFrame::OnOpen
)
104 EVT_MENU (ID_SAMPLES
, LifeFrame::OnSamples
)
105 EVT_MENU (wxID_ABOUT
, LifeFrame::OnMenu
)
106 EVT_MENU (wxID_EXIT
, LifeFrame::OnMenu
)
107 EVT_MENU (ID_SHOWNAV
, LifeFrame::OnMenu
)
108 EVT_MENU (ID_ORIGIN
, LifeFrame::OnNavigate
)
109 EVT_BUTTON (ID_CENTER
, LifeFrame::OnNavigate
)
110 EVT_BUTTON (ID_NORTH
, LifeFrame::OnNavigate
)
111 EVT_BUTTON (ID_SOUTH
, LifeFrame::OnNavigate
)
112 EVT_BUTTON (ID_EAST
, LifeFrame::OnNavigate
)
113 EVT_BUTTON (ID_WEST
, LifeFrame::OnNavigate
)
114 EVT_MENU (wxID_ZOOM_IN
, LifeFrame::OnZoom
)
115 EVT_MENU (wxID_ZOOM_OUT
,LifeFrame::OnZoom
)
116 EVT_MENU (ID_INFO
, LifeFrame::OnMenu
)
117 EVT_MENU (ID_START
, LifeFrame::OnMenu
)
118 EVT_MENU (ID_STEP
, LifeFrame::OnMenu
)
119 EVT_MENU (wxID_STOP
, LifeFrame::OnMenu
)
120 EVT_MENU (ID_TOPSPEED
, LifeFrame::OnMenu
)
121 EVT_COMMAND_SCROLL (ID_SLIDER
, LifeFrame::OnSlider
)
122 EVT_TIMER (ID_TIMER
, LifeFrame::OnTimer
)
123 EVT_CLOSE ( LifeFrame::OnClose
)
126 BEGIN_EVENT_TABLE(LifeNavigator
, wxMiniFrame
)
127 EVT_CLOSE ( LifeNavigator::OnClose
)
130 BEGIN_EVENT_TABLE(LifeCanvas
, wxWindow
)
131 EVT_PAINT ( LifeCanvas::OnPaint
)
132 EVT_SCROLLWIN ( LifeCanvas::OnScroll
)
133 EVT_SIZE ( LifeCanvas::OnSize
)
134 EVT_MOTION ( LifeCanvas::OnMouse
)
135 EVT_LEFT_DOWN ( LifeCanvas::OnMouse
)
136 EVT_LEFT_UP ( LifeCanvas::OnMouse
)
137 EVT_LEFT_DCLICK ( LifeCanvas::OnMouse
)
138 EVT_ERASE_BACKGROUND( LifeCanvas::OnEraseBackground
)
142 // Create a new application object
143 IMPLEMENT_APP(LifeApp
)
146 // ==========================================================================
148 // ==========================================================================
151 #define ADD_TOOL(id, bmp, tooltip, help) \
152 toolBar->AddTool(id, wxEmptyString, bmp, wxNullBitmap, wxITEM_NORMAL, tooltip, help)
155 // --------------------------------------------------------------------------
157 // --------------------------------------------------------------------------
159 // 'Main program' equivalent: the program execution "starts" here
160 bool LifeApp::OnInit()
162 // create the main application window
163 LifeFrame
*frame
= new LifeFrame();
170 frame
->UpdateInfoText();
173 // enter the main message loop and run the app
177 // --------------------------------------------------------------------------
179 // --------------------------------------------------------------------------
182 LifeFrame::LifeFrame() :
183 wxFrame( (wxFrame
*) NULL
, wxID_ANY
, _("Life!"), wxDefaultPosition
),
187 SetIcon(wxICON(mondrian
));
190 wxMenu
*menuFile
= new wxMenu(wxMENU_TEAROFF
);
191 wxMenu
*menuView
= new wxMenu(wxMENU_TEAROFF
);
192 wxMenu
*menuGame
= new wxMenu(wxMENU_TEAROFF
);
193 wxMenu
*menuHelp
= new wxMenu(wxMENU_TEAROFF
);
195 menuFile
->Append(wxID_NEW
, wxEmptyString
, _("Start a new game"));
197 menuFile
->Append(wxID_OPEN
, wxEmptyString
, _("Open an existing Life pattern"));
199 menuFile
->Append(ID_SAMPLES
, _("&Sample game..."), _("Select a sample configuration"));
200 #if ! (defined(__SMARTPHONE__) || defined(__POCKETPC__))
201 menuFile
->AppendSeparator();
202 menuFile
->Append(wxID_EXIT
);
204 menuView
->Append(ID_SHOWNAV
, _("Navigation &toolbox"), _("Show or hide toolbox"), wxITEM_CHECK
);
205 menuView
->Check(ID_SHOWNAV
, true);
206 menuView
->AppendSeparator();
209 menuView
->Append(ID_ORIGIN
, _("&Absolute origin"), _("Go to (0, 0)"));
210 menuView
->Append(ID_CENTER
, _("&Center of mass"), _("Find center of mass"));
211 menuView
->Append(ID_NORTH
, _("&North"), _("Find northernmost cell"));
212 menuView
->Append(ID_SOUTH
, _("&South"), _("Find southernmost cell"));
213 menuView
->Append(ID_EAST
, _("&East"), _("Find easternmost cell"));
214 menuView
->Append(ID_WEST
, _("&West"), _("Find westernmost cell"));
215 menuView
->AppendSeparator();
216 menuView
->Append(wxID_ZOOM_IN
, wxEmptyString
, _("Zoom in"));
217 menuView
->Append(wxID_ZOOM_OUT
, wxEmptyString
, _("Zoom out"));
218 menuView
->Append(ID_INFO
, _("&Description\tCtrl-D"), _("View pattern description"));
220 menuGame
->Append(ID_START
, _("&Start\tCtrl-S"), _("Start"));
221 menuGame
->Append(ID_STEP
, _("&Next\tCtrl-N"), _("Single step"));
222 menuGame
->Append(wxID_STOP
, wxEmptyString
, _("Stop"));
223 menuGame
->Enable(wxID_STOP
, false);
224 menuGame
->AppendSeparator();
225 menuGame
->Append(ID_TOPSPEED
, _("T&op speed!"), _("Go as fast as possible"));
227 menuHelp
->Append(wxID_ABOUT
, _("&About\tCtrl-A"), _("Show about dialog"));
229 wxMenuBar
*menuBar
= new wxMenuBar();
230 menuBar
->Append(menuFile
, _("&File"));
231 menuBar
->Append(menuView
, _("&View"));
232 menuBar
->Append(menuGame
, _("&Game"));
233 menuBar
->Append(menuHelp
, _("&Help"));
237 wxBitmap tbBitmaps
[7];
239 tbBitmaps
[0] = wxBITMAP(reset
);
240 tbBitmaps
[1] = wxBITMAP(open
);
241 tbBitmaps
[2] = wxBITMAP(zoomin
);
242 tbBitmaps
[3] = wxBITMAP(zoomout
);
243 tbBitmaps
[4] = wxBITMAP(info
);
244 tbBitmaps
[5] = wxBITMAP(play
);
245 tbBitmaps
[6] = wxBITMAP(stop
);
247 wxToolBar
*toolBar
= CreateToolBar();
248 toolBar
->SetMargins(5, 5);
249 toolBar
->SetToolBitmapSize(wxSize(16, 16));
251 ADD_TOOL(wxID_NEW
, tbBitmaps
[0], wxGetStockLabel(wxID_NEW
, wxSTOCK_NOFLAGS
), _("Start a new game"));
254 ADD_TOOL(wxID_OPEN
, tbBitmaps
[1], wxGetStockLabel(wxID_OPEN
, wxSTOCK_NOFLAGS
), _("Open an existing Life pattern"));
255 #endif // wxUSE_FILEDLG
257 toolBar
->AddSeparator();
258 ADD_TOOL(wxID_ZOOM_IN
, tbBitmaps
[2], wxGetStockLabel(wxID_ZOOM_IN
, wxSTOCK_NOFLAGS
), _("Zoom in"));
259 ADD_TOOL(wxID_ZOOM_OUT
, tbBitmaps
[3], wxGetStockLabel(wxID_ZOOM_OUT
, wxSTOCK_NOFLAGS
), _("Zoom out"));
260 ADD_TOOL(ID_INFO
, tbBitmaps
[4], _("Description"), _("Show description"));
261 toolBar
->AddSeparator();
262 #endif // __POCKETPC__
263 ADD_TOOL(ID_START
, tbBitmaps
[5], _("Start"), _("Start"));
264 ADD_TOOL(wxID_STOP
, tbBitmaps
[6], _("Stop"), _("Stop"));
267 toolBar
->EnableTool(wxID_STOP
, false); // must be after Realize() !
272 SetStatusText(_("Welcome to Life!"));
273 #endif // wxUSE_STATUSBAR
277 m_timer
= new wxTimer(this, ID_TIMER
);
283 // We use two different panels to reduce flicker in wxGTK, because
284 // some widgets (like wxStaticText) don't have their own X11 window,
285 // and thus updating the text would result in a refresh of the canvas
286 // if they belong to the same parent.
288 wxPanel
*panel1
= new wxPanel(this, wxID_ANY
);
289 wxPanel
*panel2
= new wxPanel(this, wxID_ANY
);
292 m_canvas
= new LifeCanvas(panel1
, m_life
);
295 m_text
= new wxStaticText(panel2
, wxID_ANY
,
299 wxALIGN_CENTER
| wxST_NO_AUTORESIZE
);
301 wxSlider
*slider
= new wxSlider(panel2
, ID_SLIDER
,
304 wxSize(200, wxDefaultCoord
),
305 wxSL_HORIZONTAL
| wxSL_AUTOTICKS
);
310 wxBoxSizer
*sizer1
= new wxBoxSizer(wxVERTICAL
);
311 wxBoxSizer
*sizer2
= new wxBoxSizer(wxVERTICAL
);
312 wxBoxSizer
*sizer3
= new wxBoxSizer(wxVERTICAL
);
315 sizer1
->Add( new wxStaticLine(panel1
, wxID_ANY
), 0, wxGROW
);
316 #endif // wxUSE_STATLINE
317 sizer1
->Add( m_canvas
, 1, wxGROW
| wxALL
, 2 );
319 sizer1
->Add( new wxStaticLine(panel1
, wxID_ANY
), 0, wxGROW
);
320 #endif // wxUSE_STATLINE
321 panel1
->SetSizer( sizer1
);
322 sizer1
->Fit( panel1
);
324 sizer2
->Add( m_text
, 0, wxGROW
| wxTOP
, 4 );
325 sizer2
->Add( slider
, 0, wxCENTRE
| wxALL
, 4 );
327 panel2
->SetSizer( sizer2
);
328 sizer2
->Fit( panel2
);
330 sizer3
->Add( panel1
, 1, wxGROW
);
331 sizer3
->Add( panel2
, 0, wxGROW
);
337 // set minimum frame size
338 sizer3
->SetSizeHints( this );
340 // navigator frame - not appropriate for small devices
341 m_navigator
= new LifeNavigator(this);
346 LifeFrame::~LifeFrame()
351 void LifeFrame::UpdateInfoText()
355 msg
.Printf(_(" Generation: %lu (T: %lu ms), Population: %lu "),
357 m_topspeed
? 0 : m_interval
,
358 static_cast<unsigned long>(m_life
->GetNumCells()));
359 m_text
->SetLabel(msg
);
362 // Enable or disable tools and menu entries according to the current
363 // state. See also wxEVT_UPDATE_UI events for a slightly different
365 void LifeFrame::UpdateUI()
368 GetToolBar()->EnableTool(ID_START
, !m_running
);
369 GetToolBar()->EnableTool(wxID_STOP
, m_running
);
370 GetMenuBar()->Enable(ID_START
, !m_running
);
371 GetMenuBar()->Enable(ID_STEP
, !m_running
);
372 GetMenuBar()->Enable(wxID_STOP
, m_running
);
373 GetMenuBar()->Enable(ID_TOPSPEED
, !m_topspeed
);
376 int cellsize
= m_canvas
->GetCellSize();
377 GetToolBar()->EnableTool(wxID_ZOOM_IN
, cellsize
< 32);
378 GetToolBar()->EnableTool(wxID_ZOOM_OUT
, cellsize
> 1);
379 GetMenuBar()->Enable(wxID_ZOOM_IN
, cellsize
< 32);
380 GetMenuBar()->Enable(wxID_ZOOM_OUT
, cellsize
> 1);
383 // Event handlers -----------------------------------------------------------
385 // OnMenu handles all events which don't have their own event handler
386 void LifeFrame::OnMenu(wxCommandEvent
& event
)
388 switch (event
.GetId())
392 // stop if it was running
395 m_canvas
->Recenter(0, 0);
402 LifeAboutDialog
dialog(this);
408 // true is to force the frame to close
414 bool checked
= GetMenuBar()->GetMenu(1)->IsChecked(ID_SHOWNAV
);
416 m_navigator
->Show(checked
);
421 wxString desc
= m_life
->GetDescription();
424 desc
= _("Not available");
426 // should we make the description editable here?
427 wxMessageBox(desc
, _("Description"), wxOK
| wxICON_INFORMATION
);
431 case ID_START
: OnStart(); break;
432 case ID_STEP
: OnStep(); break;
433 case wxID_STOP
: OnStop(); break;
439 while (m_running
&& m_topspeed
)
450 void LifeFrame::OnOpen(wxCommandEvent
& WXUNUSED(event
))
452 wxFileDialog
filedlg(this,
453 _("Choose a file to open"),
456 _("Life patterns (*.lif)|*.lif|All files (*.*)|*.*"),
457 wxFD_OPEN
| wxFD_FILE_MUST_EXIST
);
459 if (filedlg
.ShowModal() == wxID_OK
)
461 wxFileInputStream
stream(filedlg
.GetPath());
462 LifeReader
reader(stream
);
464 // the reader handles errors itself, no need to do anything here
467 // stop if running and put the pattern
470 m_life
->SetPattern(reader
.GetPattern());
473 m_canvas
->Recenter(0, 0);
481 void LifeFrame::OnSamples(wxCommandEvent
& WXUNUSED(event
))
483 // stop if it was running
487 LifeSamplesDialog
dialog(this);
489 if (dialog
.ShowModal() == wxID_OK
)
491 const LifePattern pattern
= dialog
.GetPattern();
495 m_life
->SetPattern(pattern
);
498 m_canvas
->Recenter(0, 0);
504 void LifeFrame::OnZoom(wxCommandEvent
& event
)
506 int cellsize
= m_canvas
->GetCellSize();
508 if ((event
.GetId() == wxID_ZOOM_IN
) && cellsize
< 32)
510 m_canvas
->SetCellSize(cellsize
* 2);
513 else if ((event
.GetId() == wxID_ZOOM_OUT
) && cellsize
> 1)
515 m_canvas
->SetCellSize(cellsize
/ 2);
520 void LifeFrame::OnNavigate(wxCommandEvent
& event
)
524 switch (event
.GetId())
526 case ID_NORTH
: c
= m_life
->FindNorth(); break;
527 case ID_SOUTH
: c
= m_life
->FindSouth(); break;
528 case ID_WEST
: c
= m_life
->FindWest(); break;
529 case ID_EAST
: c
= m_life
->FindEast(); break;
530 case ID_CENTER
: c
= m_life
->FindCenter(); break;
534 case ID_ORIGIN
: c
.i
= c
.j
= 0; break;
537 m_canvas
->Recenter(c
.i
, c
.j
);
540 void LifeFrame::OnSlider(wxScrollEvent
& event
)
542 m_interval
= event
.GetPosition() * 100;
553 void LifeFrame::OnTimer(wxTimerEvent
& WXUNUSED(event
))
558 void LifeFrame::OnClose(wxCloseEvent
& WXUNUSED(event
))
560 // Stop if it was running; this is absolutely needed because
561 // the frame won't be actually destroyed until there are no
562 // more pending events, and this in turn won't ever happen
563 // if the timer is running faster than the window can redraw.
568 void LifeFrame::OnStart()
572 m_timer
->Start(m_interval
);
578 void LifeFrame::OnStop()
589 void LifeFrame::OnStep()
591 if (m_life
->NextTic())
596 m_canvas
->DrawChanged();
601 // --------------------------------------------------------------------------
602 // LifeNavigator miniframe
603 // --------------------------------------------------------------------------
605 LifeNavigator::LifeNavigator(wxWindow
*parent
)
606 : wxMiniFrame(parent
, wxID_ANY
,
610 wxCAPTION
| wxSIMPLE_BORDER
)
612 wxPanel
*panel
= new wxPanel(this, wxID_ANY
);
613 wxBoxSizer
*sizer1
= new wxBoxSizer(wxVERTICAL
);
614 wxBoxSizer
*sizer2
= new wxBoxSizer(wxHORIZONTAL
);
616 // create bitmaps and masks for the buttons
618 bmpn
= wxBITMAP(north
),
619 bmpw
= wxBITMAP(west
),
620 bmpc
= wxBITMAP(center
),
621 bmpe
= wxBITMAP(east
),
622 bmps
= wxBITMAP(south
);
624 #if !defined(__WXGTK__) && !defined(__WXMOTIF__) && !defined(__WXMAC__)
625 bmpn
.SetMask(new wxMask(bmpn
, *wxLIGHT_GREY
));
626 bmpw
.SetMask(new wxMask(bmpw
, *wxLIGHT_GREY
));
627 bmpc
.SetMask(new wxMask(bmpc
, *wxLIGHT_GREY
));
628 bmpe
.SetMask(new wxMask(bmpe
, *wxLIGHT_GREY
));
629 bmps
.SetMask(new wxMask(bmps
, *wxLIGHT_GREY
));
632 // create the buttons and attach tooltips to them
634 *bn
= new wxBitmapButton(panel
, ID_NORTH
, bmpn
),
635 *bw
= new wxBitmapButton(panel
, ID_WEST
, bmpw
),
636 *bc
= new wxBitmapButton(panel
, ID_CENTER
, bmpc
),
637 *be
= new wxBitmapButton(panel
, ID_EAST
, bmpe
),
638 *bs
= new wxBitmapButton(panel
, ID_SOUTH
, bmps
);
641 bn
->SetToolTip(_("Find northernmost cell"));
642 bw
->SetToolTip(_("Find westernmost cell"));
643 bc
->SetToolTip(_("Find center of mass"));
644 be
->SetToolTip(_("Find easternmost cell"));
645 bs
->SetToolTip(_("Find southernmost cell"));
648 // add buttons to sizers
649 sizer2
->Add( bw
, 0, wxCENTRE
| wxWEST
, 4 );
650 sizer2
->Add( bc
, 0, wxCENTRE
);
651 sizer2
->Add( be
, 0, wxCENTRE
| wxEAST
, 4 );
652 sizer1
->Add( bn
, 0, wxCENTRE
| wxNORTH
, 4 );
653 sizer1
->Add( sizer2
);
654 sizer1
->Add( bs
, 0, wxCENTRE
| wxSOUTH
, 4 );
656 // set the panel and miniframe size
657 panel
->SetSizer(sizer1
);
660 SetClientSize(panel
->GetSize());
661 wxSize sz
= GetSize();
662 SetSizeHints(sz
.x
, sz
.y
, sz
.x
, sz
.y
);
664 // move it to a sensible position
665 wxRect parentRect
= parent
->GetRect();
666 wxSize childSize
= GetSize();
667 int x
= parentRect
.GetX() +
668 parentRect
.GetWidth();
669 int y
= parentRect
.GetY() +
670 (parentRect
.GetHeight() - childSize
.GetHeight()) / 4;
677 void LifeNavigator::OnClose(wxCloseEvent
& event
)
687 // --------------------------------------------------------------------------
689 // --------------------------------------------------------------------------
691 // canvas constructor
692 LifeCanvas::LifeCanvas(wxWindow
*parent
, Life
*life
, bool interactive
)
693 : wxWindow(parent
, wxID_ANY
, wxDefaultPosition
, wxSize(100, 100),
694 wxFULL_REPAINT_ON_RESIZE
| wxHSCROLL
| wxVSCROLL
695 #if !defined(__SMARTPHONE__) && !defined(__POCKETPC__)
703 m_interactive
= interactive
;
705 m_status
= MOUSE_NOACTION
;
712 SetCursor(*wxCROSS_CURSOR
);
714 // reduce flicker if wxEVT_ERASE_BACKGROUND is not available
715 SetBackgroundColour(*wxWHITE
);
718 LifeCanvas::~LifeCanvas()
723 // recenter at the given position
724 void LifeCanvas::Recenter(wxInt32 i
, wxInt32 j
)
726 m_viewportX
= i
- m_viewportW
/ 2;
727 m_viewportY
= j
- m_viewportH
/ 2;
733 // set the cell size and refresh display
734 void LifeCanvas::SetCellSize(int cellsize
)
736 m_cellsize
= cellsize
;
738 // find current center
739 wxInt32 cx
= m_viewportX
+ m_viewportW
/ 2;
740 wxInt32 cy
= m_viewportY
+ m_viewportH
/ 2;
742 // get current canvas size and adjust viewport accordingly
744 GetClientSize(&w
, &h
);
745 m_viewportW
= (w
+ m_cellsize
- 1) / m_cellsize
;
746 m_viewportH
= (h
+ m_cellsize
- 1) / m_cellsize
;
749 m_viewportX
= cx
- m_viewportW
/ 2;
750 m_viewportY
= cy
- m_viewportH
/ 2;
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
;
765 void LifeCanvas::DrawCell(wxInt32 i
, wxInt32 j
, bool alive
)
769 dc
.SetPen(alive
? *wxBLACK_PEN
: *wxWHITE_PEN
);
770 dc
.SetBrush(alive
? *wxBLACK_BRUSH
: *wxWHITE_BRUSH
);
775 void LifeCanvas::DrawCell(wxInt32 i
, wxInt32 j
, wxDC
&dc
)
777 wxCoord x
= CellToX(i
);
778 wxCoord y
= CellToY(j
);
780 // if cellsize is 1 or 2, there will be no grid
787 dc
.DrawRectangle(x
, y
, 2, 2);
790 dc
.DrawRectangle(x
+ 1, y
+ 1, m_cellsize
- 1, m_cellsize
- 1);
794 // draw all changed cells
795 void LifeCanvas::DrawChanged()
803 m_life
->BeginFind(m_viewportX
,
805 m_viewportX
+ m_viewportW
,
806 m_viewportY
+ m_viewportH
,
811 dc
.SetPen(*wxBLACK_PEN
);
815 dc
.SetPen(*wxTRANSPARENT_PEN
);
816 dc
.SetBrush(*wxBLACK_BRUSH
);
818 dc
.SetLogicalFunction(wxINVERT
);
822 done
= m_life
->FindMore(&cells
, &ncells
);
824 for (size_t m
= 0; m
< ncells
; m
++)
825 DrawCell(cells
[m
].i
, cells
[m
].j
, dc
);
830 void LifeCanvas::OnPaint(wxPaintEvent
& WXUNUSED(event
))
833 wxRect rect
= GetUpdateRegion().GetBox();
835 wxInt32 i0
, j0
, i1
, j1
;
841 h
= rect
.GetHeight();
845 i1
= XToCell(x
+ w
- 1);
846 j1
= YToCell(y
+ h
- 1);
851 m_life
->BeginFind(i0
, j0
, i1
, j1
, false);
852 bool done
= m_life
->FindMore(&cells
, &ncells
);
854 // erase all damaged cells and draw the grid
855 dc
.SetBrush(*wxWHITE_BRUSH
);
860 dc
.SetPen(*wxWHITE_PEN
);
861 dc
.DrawRectangle(x
, y
, w
, h
);
867 w
= CellToX(i1
+ 1) - x
+ 1;
868 h
= CellToY(j1
+ 1) - y
+ 1;
870 dc
.SetPen(*wxLIGHT_GREY_PEN
);
871 for (wxInt32 yy
= y
; yy
<= (y
+ h
- m_cellsize
); yy
+= m_cellsize
)
872 dc
.DrawRectangle(x
, yy
, w
, m_cellsize
+ 1);
873 for (wxInt32 xx
= x
; xx
<= (x
+ w
- m_cellsize
); xx
+= m_cellsize
)
874 dc
.DrawLine(xx
, y
, xx
, y
+ h
);
877 // draw all alive cells
878 dc
.SetPen(*wxBLACK_PEN
);
879 dc
.SetBrush(*wxBLACK_BRUSH
);
883 for (size_t m
= 0; m
< ncells
; m
++)
884 DrawCell(cells
[m
].i
, cells
[m
].j
, dc
);
886 done
= m_life
->FindMore(&cells
, &ncells
);
890 for (size_t m
= 0; m
< ncells
; m
++)
891 DrawCell(cells
[m
].i
, cells
[m
].j
, dc
);
894 void LifeCanvas::OnMouse(wxMouseEvent
& event
)
899 // which cell are we pointing at?
900 wxInt32 i
= XToCell( event
.GetX() );
901 wxInt32 j
= YToCell( event
.GetY() );
904 // set statusbar text
906 msg
.Printf(_("Cell: (%d, %d)"), i
, j
);
907 ((LifeFrame
*) wxGetApp().GetTopWindow())->SetStatusText(msg
, 1);
908 #endif // wxUSE_STATUSBAR
910 // NOTE that wxMouseEvent::LeftDown() and wxMouseEvent::LeftIsDown()
911 // have different semantics. The first one is used to signal that the
912 // button was just pressed (i.e., in "button down" events); the second
913 // one just describes the current status of the button, independently
914 // of the mouse event type. LeftIsDown is typically used in "mouse
915 // move" events, to test if the button is _still_ pressed.
917 // is the button down?
918 if (!event
.LeftIsDown())
920 m_status
= MOUSE_NOACTION
;
924 // was it pressed just now?
925 if (event
.LeftDown())
927 // yes: start a new action and toggle this cell
928 m_status
= (m_life
->IsAlive(i
, j
)? MOUSE_ERASING
: MOUSE_DRAWING
);
932 m_life
->SetCell(i
, j
, m_status
== MOUSE_DRAWING
);
933 DrawCell(i
, j
, m_status
== MOUSE_DRAWING
);
935 else if ((m_mi
!= i
) || (m_mj
!= j
))
937 // no: continue ongoing action
938 bool alive
= (m_status
== MOUSE_DRAWING
);
940 // prepare DC and pen + brush to optimize drawing
942 dc
.SetPen(alive
? *wxBLACK_PEN
: *wxWHITE_PEN
);
943 dc
.SetBrush(alive
? *wxBLACK_BRUSH
: *wxWHITE_BRUSH
);
945 // draw a line of cells using Bresenham's algorithm
946 wxInt32 d
, ii
, jj
, di
, ai
, si
, dj
, aj
, sj
;
949 si
= (di
< 0)? -1 : 1;
952 sj
= (dj
< 0)? -1 : 1;
964 m_life
->SetCell(ii
, jj
, alive
);
965 DrawCell(ii
, jj
, dc
);
982 m_life
->SetCell(ii
, jj
, alive
);
983 DrawCell(ii
, jj
, dc
);
995 m_life
->SetCell(ii
, jj
, alive
);
996 DrawCell(ii
, jj
, dc
);
1001 ((LifeFrame
*) wxGetApp().GetTopWindow())->UpdateInfoText();
1004 void LifeCanvas::OnSize(wxSizeEvent
& event
)
1007 wxInt32 cx
= m_viewportX
+ m_viewportW
/ 2;
1008 wxInt32 cy
= m_viewportY
+ m_viewportH
/ 2;
1011 wxCoord w
= event
.GetSize().GetX();
1012 wxCoord h
= event
.GetSize().GetY();
1013 m_viewportW
= (w
+ m_cellsize
- 1) / m_cellsize
;
1014 m_viewportH
= (h
+ m_cellsize
- 1) / m_cellsize
;
1017 m_viewportX
= cx
- m_viewportW
/ 2;
1018 m_viewportY
= cy
- m_viewportH
/ 2;
1023 SetScrollbar(wxHORIZONTAL
, m_viewportW
, m_viewportW
, 3 * m_viewportW
);
1024 SetScrollbar(wxVERTICAL
, m_viewportH
, m_viewportH
, 3 * m_viewportH
);
1025 m_thumbX
= m_viewportW
;
1026 m_thumbY
= m_viewportH
;
1029 // allow default processing
1033 void LifeCanvas::OnScroll(wxScrollWinEvent
& event
)
1035 WXTYPE type
= (WXTYPE
)event
.GetEventType();
1036 int pos
= event
.GetPosition();
1037 int orient
= event
.GetOrientation();
1039 // calculate scroll increment
1041 if (type
== wxEVT_SCROLLWIN_TOP
)
1043 if (orient
== wxHORIZONTAL
)
1044 scrollinc
= -m_viewportW
;
1046 scrollinc
= -m_viewportH
;
1049 if (type
== wxEVT_SCROLLWIN_BOTTOM
)
1051 if (orient
== wxHORIZONTAL
)
1052 scrollinc
= m_viewportW
;
1054 scrollinc
= m_viewportH
;
1057 if (type
== wxEVT_SCROLLWIN_LINEUP
)
1062 if (type
== wxEVT_SCROLLWIN_LINEDOWN
)
1067 if (type
== wxEVT_SCROLLWIN_PAGEUP
)
1072 if (type
== wxEVT_SCROLLWIN_PAGEDOWN
)
1077 if (type
== wxEVT_SCROLLWIN_THUMBTRACK
)
1079 if (orient
== wxHORIZONTAL
)
1081 scrollinc
= pos
- m_thumbX
;
1086 scrollinc
= pos
- m_thumbY
;
1091 if (type
== wxEVT_SCROLLWIN_THUMBRELEASE
)
1093 m_thumbX
= m_viewportW
;
1094 m_thumbY
= m_viewportH
;
1097 #if defined(__WXGTK__) || defined(__WXMOTIF__)
1098 // wxGTK and wxMotif update the thumb automatically (wxMSW doesn't);
1099 // so reset it back as we always want it to be in the same position.
1100 if (type
!= wxEVT_SCROLLWIN_THUMBTRACK
)
1102 SetScrollbar(wxHORIZONTAL
, m_viewportW
, m_viewportW
, 3 * m_viewportW
);
1103 SetScrollbar(wxVERTICAL
, m_viewportH
, m_viewportH
, 3 * m_viewportH
);
1107 if (scrollinc
== 0) return;
1109 // scroll the window and adjust the viewport
1110 if (orient
== wxHORIZONTAL
)
1112 m_viewportX
+= scrollinc
;
1113 ScrollWindow( -m_cellsize
* scrollinc
, 0, (const wxRect
*) NULL
);
1117 m_viewportY
+= scrollinc
;
1118 ScrollWindow( 0, -m_cellsize
* scrollinc
, (const wxRect
*) NULL
);
1122 void LifeCanvas::OnEraseBackground(wxEraseEvent
& WXUNUSED(event
))
1124 // do nothing. I just don't want the background to be erased, you know.