1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: The game of Life, created by J. H. Conway
4 // Author: Guillermo Rodriguez Garcia, <guille@iies.es>
8 // Copyright: (c) 2000, Guillermo Rodriguez Garcia
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ==========================================================================
13 // headers, declarations, constants
14 // ==========================================================================
17 #pragma implementation "life.h"
20 // For compilers that support precompilation, includes "wx/wx.h".
21 #include "wx/wxprec.h"
31 #include "wx/statline.h"
32 #include "wx/wfstream.h"
33 #include "wx/filedlg.h"
40 // --------------------------------------------------------------------------
42 // --------------------------------------------------------------------------
44 #if defined(__WXGTK__) || defined(__WXMOTIF__) || defined(__WXMAC__) || defined(__WXMGL__) || defined(__WXX11__)
46 #include "mondrian.xpm"
48 // bitmap buttons for the toolbar
49 #include "bitmaps/reset.xpm"
50 #include "bitmaps/open.xpm"
51 #include "bitmaps/play.xpm"
52 #include "bitmaps/stop.xpm"
53 #include "bitmaps/zoomin.xpm"
54 #include "bitmaps/zoomout.xpm"
55 #include "bitmaps/info.xpm"
58 #include "bitmaps/north.xpm"
59 #include "bitmaps/south.xpm"
60 #include "bitmaps/east.xpm"
61 #include "bitmaps/west.xpm"
62 #include "bitmaps/center.xpm"
65 // --------------------------------------------------------------------------
67 // --------------------------------------------------------------------------
69 // IDs for the controls and the menu commands
100 // speed selection slider
104 // --------------------------------------------------------------------------
105 // event tables and other macros for wxWindows
106 // --------------------------------------------------------------------------
109 BEGIN_EVENT_TABLE(LifeFrame
, wxFrame
)
110 EVT_MENU (ID_NEW
, LifeFrame::OnMenu
)
111 EVT_MENU (ID_OPEN
, LifeFrame::OnOpen
)
112 EVT_MENU (ID_SAMPLES
, LifeFrame::OnSamples
)
113 EVT_MENU (ID_ABOUT
, LifeFrame::OnMenu
)
114 EVT_MENU (ID_EXIT
, LifeFrame::OnMenu
)
115 EVT_MENU (ID_SHOWNAV
, LifeFrame::OnMenu
)
116 EVT_MENU (ID_ORIGIN
, LifeFrame::OnNavigate
)
117 EVT_BUTTON (ID_CENTER
, LifeFrame::OnNavigate
)
118 EVT_BUTTON (ID_NORTH
, LifeFrame::OnNavigate
)
119 EVT_BUTTON (ID_SOUTH
, LifeFrame::OnNavigate
)
120 EVT_BUTTON (ID_EAST
, LifeFrame::OnNavigate
)
121 EVT_BUTTON (ID_WEST
, LifeFrame::OnNavigate
)
122 EVT_MENU (ID_ZOOMIN
, LifeFrame::OnZoom
)
123 EVT_MENU (ID_ZOOMOUT
, LifeFrame::OnZoom
)
124 EVT_MENU (ID_INFO
, LifeFrame::OnMenu
)
125 EVT_MENU (ID_START
, LifeFrame::OnMenu
)
126 EVT_MENU (ID_STEP
, LifeFrame::OnMenu
)
127 EVT_MENU (ID_STOP
, LifeFrame::OnMenu
)
128 EVT_MENU (ID_TOPSPEED
, LifeFrame::OnMenu
)
129 EVT_COMMAND_SCROLL (ID_SLIDER
, LifeFrame::OnSlider
)
130 EVT_TIMER (ID_TIMER
, LifeFrame::OnTimer
)
131 EVT_CLOSE ( LifeFrame::OnClose
)
134 BEGIN_EVENT_TABLE(LifeNavigator
, wxMiniFrame
)
135 EVT_CLOSE ( LifeNavigator::OnClose
)
138 BEGIN_EVENT_TABLE(LifeCanvas
, wxWindow
)
139 EVT_PAINT ( LifeCanvas::OnPaint
)
140 EVT_SCROLLWIN ( LifeCanvas::OnScroll
)
141 EVT_SIZE ( LifeCanvas::OnSize
)
142 EVT_MOTION ( LifeCanvas::OnMouse
)
143 EVT_LEFT_DOWN ( LifeCanvas::OnMouse
)
144 EVT_LEFT_UP ( LifeCanvas::OnMouse
)
145 EVT_LEFT_DCLICK ( LifeCanvas::OnMouse
)
146 EVT_ERASE_BACKGROUND( LifeCanvas::OnEraseBackground
)
150 // Create a new application object
151 IMPLEMENT_APP(LifeApp
)
154 // ==========================================================================
156 // ==========================================================================
159 #define ADD_TOOL(id, bmp, tooltip, help) \
160 toolBar->AddTool(id, bmp, wxNullBitmap, FALSE, -1, -1, (wxObject *)0, tooltip, help)
163 // --------------------------------------------------------------------------
165 // --------------------------------------------------------------------------
167 // 'Main program' equivalent: the program execution "starts" here
168 bool LifeApp::OnInit()
170 // create the main application window
171 LifeFrame
*frame
= new LifeFrame();
173 // show it and tell the application that it's our main window
179 frame
->UpdateInfoText();
182 // enter the main message loop and run the app
186 // --------------------------------------------------------------------------
188 // --------------------------------------------------------------------------
191 LifeFrame::LifeFrame() : wxFrame((wxFrame
*)0, -1, _("Life!"), wxPoint(200, 200))
194 SetIcon(wxICON(mondrian
));
197 wxMenu
*menuFile
= new wxMenu("", wxMENU_TEAROFF
);
198 wxMenu
*menuView
= new wxMenu("", wxMENU_TEAROFF
);
199 wxMenu
*menuGame
= new wxMenu("", wxMENU_TEAROFF
);
201 menuFile
->Append(ID_NEW
, _("&New"), _("Start a new game"));
202 menuFile
->Append(ID_OPEN
, _("&Open..."), _("Open an existing Life pattern"));
203 menuFile
->Append(ID_SAMPLES
, _("&Sample game..."), _("Select a sample configuration"));
204 menuFile
->AppendSeparator();
205 menuFile
->Append(ID_ABOUT
, _("&About...\tCtrl-A"), _("Show about dialog"));
206 menuFile
->AppendSeparator();
207 menuFile
->Append(ID_EXIT
, _("E&xit\tAlt-X"), _("Quit this program"));
209 menuView
->Append(ID_SHOWNAV
, _("Navigation &toolbox"), _("Show or hide toolbox"), TRUE
);
210 menuView
->Check (ID_SHOWNAV
, TRUE
);
211 menuView
->AppendSeparator();
212 menuView
->Append(ID_ORIGIN
, _("&Absolute origin"), _("Go to (0, 0)"));
213 menuView
->Append(ID_CENTER
, _("&Center of mass"), _("Find center of mass"));
214 menuView
->Append(ID_NORTH
, _("&North"), _("Find northernmost cell"));
215 menuView
->Append(ID_SOUTH
, _("&South"), _("Find southernmost cell"));
216 menuView
->Append(ID_EAST
, _("&East"), _("Find easternmost cell"));
217 menuView
->Append(ID_WEST
, _("&West"), _("Find westernmost cell"));
218 menuView
->AppendSeparator();
219 menuView
->Append(ID_ZOOMIN
, _("Zoom &in\tCtrl-I"), _("Zoom in"));
220 menuView
->Append(ID_ZOOMOUT
, _("Zoom &out\tCtrl-O"), _("Zoom out"));
221 menuView
->Append(ID_INFO
, _("&Description...\tCtrl-D"), _("View pattern description"));
223 menuGame
->Append(ID_START
, _("&Start\tCtrl-S"), _("Start"));
224 menuGame
->Append(ID_STEP
, _("&Next\tCtrl-N"), _("Single step"));
225 menuGame
->Append(ID_STOP
, _("S&top\tCtrl-T"), _("Stop"));
226 menuGame
->Enable(ID_STOP
, FALSE
);
227 menuGame
->AppendSeparator();
228 menuGame
->Append(ID_TOPSPEED
, _("T&op speed!"), _("Go as fast as possible"));
230 wxMenuBar
*menuBar
= new wxMenuBar();
231 menuBar
->Append(menuFile
, _("&File"));
232 menuBar
->Append(menuView
, _("&View"));
233 menuBar
->Append(menuGame
, _("&Game"));
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(ID_NEW
, tbBitmaps
[0], _("New"), _("Start a new game"));
252 ADD_TOOL(ID_OPEN
, tbBitmaps
[1], _("Open"), _("Open an existing Life pattern"));
253 toolBar
->AddSeparator();
254 ADD_TOOL(ID_ZOOMIN
, tbBitmaps
[2], _("Zoom in"), _("Zoom in"));
255 ADD_TOOL(ID_ZOOMOUT
, tbBitmaps
[3], _("Zoom out"), _("Zoom out"));
256 ADD_TOOL(ID_INFO
, tbBitmaps
[4], _("Description"), _("Show description"));
257 toolBar
->AddSeparator();
258 ADD_TOOL(ID_START
, tbBitmaps
[5], _("Start"), _("Start"));
259 ADD_TOOL(ID_STOP
, tbBitmaps
[6], _("Stop"), _("Stop"));
262 toolBar
->EnableTool(ID_STOP
, FALSE
); // must be after Realize() !
266 SetStatusText(_("Welcome to Life!"));
270 m_timer
= new wxTimer(this, ID_TIMER
);
276 // We use two different panels to reduce flicker in wxGTK, because
277 // some widgets (like wxStaticText) don't have their own X11 window,
278 // and thus updating the text would result in a refresh of the canvas
279 // if they belong to the same parent.
281 wxPanel
*panel1
= new wxPanel(this, -1);
282 wxPanel
*panel2
= new wxPanel(this, -1);
285 m_canvas
= new LifeCanvas(panel1
, m_life
);
288 m_text
= new wxStaticText(panel2
, -1,
292 wxALIGN_CENTER
| wxST_NO_AUTORESIZE
);
294 wxSlider
*slider
= new wxSlider(panel2
, ID_SLIDER
,
298 wxSL_HORIZONTAL
| wxSL_AUTOTICKS
);
303 wxBoxSizer
*sizer1
= new wxBoxSizer(wxVERTICAL
);
304 wxBoxSizer
*sizer2
= new wxBoxSizer(wxVERTICAL
);
305 wxBoxSizer
*sizer3
= new wxBoxSizer(wxVERTICAL
);
307 sizer1
->Add( new wxStaticLine(panel1
, -1), 0, wxGROW
);
308 sizer1
->Add( m_canvas
, 1, wxGROW
| wxALL
, 2 );
309 sizer1
->Add( new wxStaticLine(panel1
, -1), 0, wxGROW
);
310 panel1
->SetSizer( sizer1
);
311 panel1
->SetAutoLayout( TRUE
);
312 sizer1
->Fit( panel1
);
314 sizer2
->Add( m_text
, 0, wxGROW
| wxTOP
, 4 );
315 sizer2
->Add( slider
, 0, wxCENTRE
| wxALL
, 4 );
317 panel2
->SetSizer( sizer2
);
318 panel2
->SetAutoLayout( TRUE
);
319 sizer2
->Fit( panel2
);
321 sizer3
->Add( panel1
, 1, wxGROW
);
322 sizer3
->Add( panel2
, 0, wxGROW
);
324 SetAutoLayout( TRUE
);
327 // set minimum frame size
328 sizer3
->SetSizeHints( this );
331 m_navigator
= new LifeNavigator(this);
334 LifeFrame::~LifeFrame()
339 void LifeFrame::UpdateInfoText()
343 msg
.Printf(_(" Generation: %u (T: %u ms), Population: %u "),
345 m_topspeed
? 0 : m_interval
,
346 m_life
->GetNumCells());
347 m_text
->SetLabel(msg
);
350 // Enable or disable tools and menu entries according to the current
351 // state. See also wxEVT_UPDATE_UI events for a slightly different
353 void LifeFrame::UpdateUI()
356 GetToolBar()->EnableTool(ID_START
, !m_running
);
357 GetToolBar()->EnableTool(ID_STOP
, m_running
);
358 GetMenuBar()->GetMenu(2)->Enable(ID_START
, !m_running
);
359 GetMenuBar()->GetMenu(2)->Enable(ID_STEP
, !m_running
);
360 GetMenuBar()->GetMenu(2)->Enable(ID_STOP
, m_running
);
363 int cellsize
= m_canvas
->GetCellSize();
364 GetToolBar()->EnableTool(ID_ZOOMIN
, cellsize
< 32);
365 GetToolBar()->EnableTool(ID_ZOOMOUT
, cellsize
> 1);
366 GetMenuBar()->GetMenu(1)->Enable(ID_ZOOMIN
, cellsize
< 32);
367 GetMenuBar()->GetMenu(1)->Enable(ID_ZOOMOUT
, cellsize
> 1);
370 // Event handlers -----------------------------------------------------------
372 // OnMenu handles all events which don't have their own event handler
373 void LifeFrame::OnMenu(wxCommandEvent
& event
)
375 switch (event
.GetId())
379 // stop if it was running
382 m_canvas
->Recenter(0, 0);
389 LifeAboutDialog
dialog(this);
395 // TRUE is to force the frame to close
401 bool checked
= GetMenuBar()->GetMenu(1)->IsChecked(ID_SHOWNAV
);
402 m_navigator
->Show(checked
);
407 wxString desc
= m_life
->GetDescription();
409 if ( desc
.IsEmpty() )
410 desc
= _("Not available");
412 // should we make the description editable here?
413 wxMessageBox(desc
, _("Description"), wxOK
| wxICON_INFORMATION
);
417 case ID_START
: OnStart(); break;
418 case ID_STEP
: OnStep(); break;
419 case ID_STOP
: OnStop(); break;
425 while (m_running
&& m_topspeed
)
435 void LifeFrame::OnOpen(wxCommandEvent
& WXUNUSED(event
))
437 wxFileDialog
filedlg(this,
438 _("Choose a file to open"),
441 _("Life patterns (*.lif)|*.lif|All files (*.*)|*.*"),
442 wxOPEN
| wxFILE_MUST_EXIST
);
444 if (filedlg
.ShowModal() == wxID_OK
)
446 wxFileInputStream
stream(filedlg
.GetPath());
447 LifeReader
reader(stream
);
449 // the reader handles errors itself, no need to do anything here
452 // stop if running and put the pattern
455 m_life
->SetPattern(reader
.GetPattern());
458 m_canvas
->Recenter(0, 0);
465 void LifeFrame::OnSamples(wxCommandEvent
& WXUNUSED(event
))
467 // stop if it was running
471 LifeSamplesDialog
dialog(this);
473 if (dialog
.ShowModal() == wxID_OK
)
475 const LifePattern pattern
= dialog
.GetPattern();
479 m_life
->SetPattern(pattern
);
482 m_canvas
->Recenter(0, 0);
488 void LifeFrame::OnZoom(wxCommandEvent
& event
)
490 int cellsize
= m_canvas
->GetCellSize();
492 if ((event
.GetId() == ID_ZOOMIN
) && cellsize
< 32)
494 m_canvas
->SetCellSize(cellsize
* 2);
497 else if ((event
.GetId() == ID_ZOOMOUT
) && cellsize
> 1)
499 m_canvas
->SetCellSize(cellsize
/ 2);
504 void LifeFrame::OnNavigate(wxCommandEvent
& event
)
508 switch (event
.GetId())
510 case ID_NORTH
: c
= m_life
->FindNorth(); break;
511 case ID_SOUTH
: c
= m_life
->FindSouth(); break;
512 case ID_WEST
: c
= m_life
->FindWest(); break;
513 case ID_EAST
: c
= m_life
->FindEast(); break;
514 case ID_CENTER
: c
= m_life
->FindCenter(); break;
515 case ID_ORIGIN
: c
.i
= c
.j
= 0; break;
518 m_canvas
->Recenter(c
.i
, c
.j
);
521 void LifeFrame::OnSlider(wxScrollEvent
& event
)
523 m_interval
= event
.GetPosition() * 100;
534 void LifeFrame::OnTimer(wxTimerEvent
& WXUNUSED(event
))
539 void LifeFrame::OnClose(wxCloseEvent
& WXUNUSED(event
))
541 // Stop if it was running; this is absolutely needed because
542 // the frame won't be actually destroyed until there are no
543 // more pending events, and this in turn won't ever happen
544 // if the timer is running faster than the window can redraw.
549 void LifeFrame::OnStart()
553 m_timer
->Start(m_interval
);
559 void LifeFrame::OnStop()
570 void LifeFrame::OnStep()
572 if (m_life
->NextTic())
577 m_canvas
->DrawChanged();
582 // --------------------------------------------------------------------------
583 // LifeNavigator miniframe
584 // --------------------------------------------------------------------------
586 LifeNavigator::LifeNavigator(wxWindow
*parent
)
587 : wxMiniFrame(parent
, -1,
591 wxCAPTION
| wxSIMPLE_BORDER
)
593 wxPanel
*panel
= new wxPanel(this, -1);
594 wxBoxSizer
*sizer1
= new wxBoxSizer(wxVERTICAL
);
595 wxBoxSizer
*sizer2
= new wxBoxSizer(wxHORIZONTAL
);
597 // create bitmaps and masks for the buttons
599 bmpn
= wxBITMAP(north
),
600 bmpw
= wxBITMAP(west
),
601 bmpc
= wxBITMAP(center
),
602 bmpe
= wxBITMAP(east
),
603 bmps
= wxBITMAP(south
);
605 #if !defined(__WXGTK__) && !defined(__WXMOTIF__) && !defined(__WXMAC__)
606 bmpn
.SetMask(new wxMask(bmpn
, *wxLIGHT_GREY
));
607 bmpw
.SetMask(new wxMask(bmpw
, *wxLIGHT_GREY
));
608 bmpc
.SetMask(new wxMask(bmpc
, *wxLIGHT_GREY
));
609 bmpe
.SetMask(new wxMask(bmpe
, *wxLIGHT_GREY
));
610 bmps
.SetMask(new wxMask(bmps
, *wxLIGHT_GREY
));
613 // create the buttons and attach tooltips to them
615 *bn
= new wxBitmapButton(panel
, ID_NORTH
, bmpn
),
616 *bw
= new wxBitmapButton(panel
, ID_WEST
, bmpw
),
617 *bc
= new wxBitmapButton(panel
, ID_CENTER
, bmpc
),
618 *be
= new wxBitmapButton(panel
, ID_EAST
, bmpe
),
619 *bs
= new wxBitmapButton(panel
, ID_SOUTH
, bmps
);
622 bn
->SetToolTip(_("Find northernmost cell"));
623 bw
->SetToolTip(_("Find westernmost cell"));
624 bc
->SetToolTip(_("Find center of mass"));
625 be
->SetToolTip(_("Find easternmost cell"));
626 bs
->SetToolTip(_("Find southernmost cell"));
629 // add buttons to sizers
630 sizer2
->Add( bw
, 0, wxCENTRE
| wxWEST
, 4 );
631 sizer2
->Add( bc
, 0, wxCENTRE
);
632 sizer2
->Add( be
, 0, wxCENTRE
| wxEAST
, 4 );
633 sizer1
->Add( bn
, 0, wxCENTRE
| wxNORTH
, 4 );
634 sizer1
->Add( sizer2
);
635 sizer1
->Add( bs
, 0, wxCENTRE
| wxSOUTH
, 4 );
637 // set the miniframe size
638 panel
->SetSizer(sizer1
);
639 panel
->SetAutoLayout(TRUE
);
641 sizer1
->SetSizeHints(this);
643 // move it to a sensible position
644 wxRect parentRect
= parent
->GetRect();
645 wxSize childSize
= GetSize();
646 int x
= parentRect
.GetX() +
647 parentRect
.GetWidth();
648 int y
= parentRect
.GetY() +
649 (parentRect
.GetHeight() - childSize
.GetHeight()) / 4;
656 void LifeNavigator::OnClose(wxCloseEvent
& event
)
666 // --------------------------------------------------------------------------
668 // --------------------------------------------------------------------------
670 // canvas constructor
671 LifeCanvas::LifeCanvas(wxWindow
*parent
, Life
*life
, bool interactive
)
672 : wxWindow(parent
, -1, wxPoint(0, 0), wxSize(100, 100),
676 m_interactive
= interactive
;
678 m_status
= MOUSE_NOACTION
;
685 SetCursor(*wxCROSS_CURSOR
);
687 // reduce flicker if wxEVT_ERASE_BACKGROUND is not available
688 SetBackgroundColour(*wxWHITE
);
691 LifeCanvas::~LifeCanvas()
696 // recenter at the given position
697 void LifeCanvas::Recenter(wxInt32 i
, wxInt32 j
)
699 m_viewportX
= i
- m_viewportW
/ 2;
700 m_viewportY
= j
- m_viewportH
/ 2;
706 // set the cell size and refresh display
707 void LifeCanvas::SetCellSize(int cellsize
)
709 m_cellsize
= cellsize
;
711 // find current center
712 wxInt32 cx
= m_viewportX
+ m_viewportW
/ 2;
713 wxInt32 cy
= m_viewportY
+ m_viewportH
/ 2;
715 // get current canvas size and adjust viewport accordingly
717 GetClientSize(&w
, &h
);
718 m_viewportW
= (w
+ m_cellsize
- 1) / m_cellsize
;
719 m_viewportH
= (h
+ m_cellsize
- 1) / m_cellsize
;
722 m_viewportX
= cx
- m_viewportW
/ 2;
723 m_viewportY
= cy
- m_viewportH
/ 2;
728 SetScrollbar(wxHORIZONTAL
, m_viewportW
, m_viewportW
, 3 * m_viewportW
);
729 SetScrollbar(wxVERTICAL
, m_viewportH
, m_viewportH
, 3 * m_viewportH
);
730 m_thumbX
= m_viewportW
;
731 m_thumbY
= m_viewportH
;
738 void LifeCanvas::DrawCell(wxInt32 i
, wxInt32 j
, bool alive
)
742 dc
.SetPen(alive
? *wxBLACK_PEN
: *wxWHITE_PEN
);
743 dc
.SetBrush(alive
? *wxBLACK_BRUSH
: *wxWHITE_BRUSH
);
750 void LifeCanvas::DrawCell(wxInt32 i
, wxInt32 j
, wxDC
&dc
)
752 wxCoord x
= CellToX(i
);
753 wxCoord y
= CellToY(j
);
755 // if cellsize is 1 or 2, there will be no grid
762 dc
.DrawRectangle(x
, y
, 2, 2);
765 dc
.DrawRectangle(x
+ 1, y
+ 1, m_cellsize
- 1, m_cellsize
- 1);
769 // draw all changed cells
770 void LifeCanvas::DrawChanged()
778 m_life
->BeginFind(m_viewportX
,
780 m_viewportX
+ m_viewportW
,
781 m_viewportY
+ m_viewportH
,
788 dc
.SetPen(*wxBLACK_PEN
);
792 dc
.SetPen(*wxTRANSPARENT_PEN
);
793 dc
.SetBrush(*wxBLACK_BRUSH
);
795 dc
.SetLogicalFunction(wxINVERT
);
799 done
= m_life
->FindMore(&cells
, &ncells
);
801 for (size_t m
= 0; m
< ncells
; m
++)
802 DrawCell(cells
[m
].i
, cells
[m
].j
, dc
);
808 void LifeCanvas::OnPaint(wxPaintEvent
& event
)
811 wxRect rect
= GetUpdateRegion().GetBox();
813 wxInt32 i0
, j0
, i1
, j1
;
819 h
= rect
.GetHeight();
823 i1
= XToCell(x
+ w
- 1);
824 j1
= YToCell(y
+ h
- 1);
830 m_life
->BeginFind(i0
, j0
, i1
, j1
, FALSE
);
831 done
= m_life
->FindMore(&cells
, &ncells
);
833 // erase all damaged cells and draw the grid
835 dc
.SetBrush(*wxWHITE_BRUSH
);
840 dc
.SetPen(*wxWHITE_PEN
);
841 dc
.DrawRectangle(x
, y
, w
, h
);
847 w
= CellToX(i1
+ 1) - x
+ 1;
848 h
= CellToY(j1
+ 1) - y
+ 1;
850 dc
.SetPen(*wxLIGHT_GREY_PEN
);
851 for (wxInt32 yy
= y
; yy
<= (y
+ h
- m_cellsize
); yy
+= m_cellsize
)
852 dc
.DrawRectangle(x
, yy
, w
, m_cellsize
+ 1);
853 for (wxInt32 xx
= x
; xx
<= (x
+ w
- m_cellsize
); xx
+= m_cellsize
)
854 dc
.DrawLine(xx
, y
, xx
, y
+ h
);
857 // draw all alive cells
858 dc
.SetPen(*wxBLACK_PEN
);
859 dc
.SetBrush(*wxBLACK_BRUSH
);
863 for (size_t m
= 0; m
< ncells
; m
++)
864 DrawCell(cells
[m
].i
, cells
[m
].j
, dc
);
866 done
= m_life
->FindMore(&cells
, &ncells
);
870 for (size_t m
= 0; m
< ncells
; m
++)
871 DrawCell(cells
[m
].i
, cells
[m
].j
, dc
);
876 void LifeCanvas::OnMouse(wxMouseEvent
& event
)
881 // which cell are we pointing at?
882 wxInt32 i
= XToCell( event
.GetX() );
883 wxInt32 j
= YToCell( event
.GetY() );
885 // set statusbar text
887 msg
.Printf(_("Cell: (%d, %d)"), i
, j
);
888 ((LifeFrame
*) wxGetApp().GetTopWindow())->SetStatusText(msg
, 1);
890 // NOTE that wxMouseEvent::LeftDown() and wxMouseEvent::LeftIsDown()
891 // have different semantics. The first one is used to signal that the
892 // button was just pressed (i.e., in "button down" events); the second
893 // one just describes the current status of the button, independently
894 // of the mouse event type. LeftIsDown is typically used in "mouse
895 // move" events, to test if the button is _still_ pressed.
897 // is the button down?
898 if (!event
.LeftIsDown())
900 m_status
= MOUSE_NOACTION
;
904 // was it pressed just now?
905 if (event
.LeftDown())
907 // yes: start a new action and toggle this cell
908 m_status
= (m_life
->IsAlive(i
, j
)? MOUSE_ERASING
: MOUSE_DRAWING
);
912 m_life
->SetCell(i
, j
, m_status
== MOUSE_DRAWING
);
913 DrawCell(i
, j
, m_status
== MOUSE_DRAWING
);
915 else if ((m_mi
!= i
) || (m_mj
!= j
))
917 // no: continue ongoing action
918 bool alive
= (m_status
== MOUSE_DRAWING
);
920 // prepare DC and pen + brush to optimize drawing
922 dc
.SetPen(alive
? *wxBLACK_PEN
: *wxWHITE_PEN
);
923 dc
.SetBrush(alive
? *wxBLACK_BRUSH
: *wxWHITE_BRUSH
);
926 // draw a line of cells using Bresenham's algorithm
927 wxInt32 d
, ii
, jj
, di
, ai
, si
, dj
, aj
, sj
;
930 si
= (di
< 0)? -1 : 1;
933 sj
= (dj
< 0)? -1 : 1;
945 m_life
->SetCell(ii
, jj
, alive
);
946 DrawCell(ii
, jj
, dc
);
963 m_life
->SetCell(ii
, jj
, alive
);
964 DrawCell(ii
, jj
, dc
);
976 m_life
->SetCell(ii
, jj
, alive
);
977 DrawCell(ii
, jj
, dc
);
984 ((LifeFrame
*) wxGetApp().GetTopWindow())->UpdateInfoText();
987 void LifeCanvas::OnSize(wxSizeEvent
& event
)
990 wxInt32 cx
= m_viewportX
+ m_viewportW
/ 2;
991 wxInt32 cy
= m_viewportY
+ m_viewportH
/ 2;
994 wxCoord w
= event
.GetSize().GetX();
995 wxCoord h
= event
.GetSize().GetY();
996 m_viewportW
= (w
+ m_cellsize
- 1) / m_cellsize
;
997 m_viewportH
= (h
+ m_cellsize
- 1) / m_cellsize
;
1000 m_viewportX
= cx
- m_viewportW
/ 2;
1001 m_viewportY
= cy
- m_viewportH
/ 2;
1006 SetScrollbar(wxHORIZONTAL
, m_viewportW
, m_viewportW
, 3 * m_viewportW
);
1007 SetScrollbar(wxVERTICAL
, m_viewportH
, m_viewportH
, 3 * m_viewportH
);
1008 m_thumbX
= m_viewportW
;
1009 m_thumbY
= m_viewportH
;
1012 // allow default processing
1016 void LifeCanvas::OnScroll(wxScrollWinEvent
& event
)
1018 WXTYPE type
= event
.GetEventType();
1019 int pos
= event
.GetPosition();
1020 int orient
= event
.GetOrientation();
1022 // calculate scroll increment
1024 if (type
== wxEVT_SCROLLWIN_TOP
)
1026 if (orient
== wxHORIZONTAL
)
1027 scrollinc
= -m_viewportW
;
1029 scrollinc
= -m_viewportH
;
1032 if (type
== wxEVT_SCROLLWIN_BOTTOM
)
1034 if (orient
== wxHORIZONTAL
)
1035 scrollinc
= m_viewportW
;
1037 scrollinc
= m_viewportH
;
1040 if (type
== wxEVT_SCROLLWIN_LINEUP
)
1045 if (type
== wxEVT_SCROLLWIN_LINEDOWN
)
1050 if (type
== wxEVT_SCROLLWIN_PAGEUP
)
1055 if (type
== wxEVT_SCROLLWIN_PAGEDOWN
)
1060 if (type
== wxEVT_SCROLLWIN_THUMBTRACK
)
1062 if (orient
== wxHORIZONTAL
)
1064 scrollinc
= pos
- m_thumbX
;
1069 scrollinc
= pos
- m_thumbY
;
1074 if (type
== wxEVT_SCROLLWIN_THUMBRELEASE
)
1076 m_thumbX
= m_viewportW
;
1077 m_thumbY
= m_viewportH
;
1080 #if defined(__WXGTK__) || defined(__WXMOTIF__)
1081 // wxGTK and wxMotif update the thumb automatically (wxMSW doesn't);
1082 // so reset it back as we always want it to be in the same position.
1083 if (type
!= wxEVT_SCROLLWIN_THUMBTRACK
)
1085 SetScrollbar(wxHORIZONTAL
, m_viewportW
, m_viewportW
, 3 * m_viewportW
);
1086 SetScrollbar(wxVERTICAL
, m_viewportH
, m_viewportH
, 3 * m_viewportH
);
1090 if (scrollinc
== 0) return;
1092 // scroll the window and adjust the viewport
1093 if (orient
== wxHORIZONTAL
)
1095 m_viewportX
+= scrollinc
;
1096 ScrollWindow( -m_cellsize
* scrollinc
, 0, (const wxRect
*) NULL
);
1100 m_viewportY
+= scrollinc
;
1101 ScrollWindow( 0, -m_cellsize
* scrollinc
, (const wxRect
*) NULL
);
1105 void LifeCanvas::OnEraseBackground(wxEraseEvent
& WXUNUSED(event
))
1107 // do nothing. I just don't want the background to be erased, you know.