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: life.cpp 41160 2006-09-11 14:07:41Z VZ $
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, bmp, wxNullBitmap, false, wxDefaultCoord, wxDefaultCoord, (wxObject *)NULL, 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 and tell the application that it's our main window
167    frame->Show(true);
168    SetTopWindow(frame);
169
170    // just for Motif
171#ifdef __WXMOTIF__
172    frame->UpdateInfoText();
173#endif
174
175    // enter the main message loop and run the app
176    return true;
177}
178
179// --------------------------------------------------------------------------
180// LifeFrame
181// --------------------------------------------------------------------------
182
183// frame constructor
184LifeFrame::LifeFrame() :
185  wxFrame( (wxFrame *) NULL, wxID_ANY, _("Life!"), wxDefaultPosition ),
186  m_navigator(NULL)
187{
188    // frame icon
189    SetIcon(wxICON(mondrian));
190
191    // menu bar
192    wxMenu *menuFile = new wxMenu(wxMENU_TEAROFF);
193    wxMenu *menuView = new wxMenu(wxMENU_TEAROFF);
194    wxMenu *menuGame = new wxMenu(wxMENU_TEAROFF);
195    wxMenu *menuHelp = new wxMenu(wxMENU_TEAROFF);
196
197    menuFile->Append(wxID_NEW, wxEmptyString, _("Start a new game"));
198#if wxUSE_FILEDLG
199    menuFile->Append(wxID_OPEN, wxEmptyString, _("Open an existing Life pattern"));
200#endif
201    menuFile->Append(ID_SAMPLES, _("&Sample game..."), _("Select a sample configuration"));
202#if ! (defined(__SMARTPHONE__) || defined(__POCKETPC__))
203    menuFile->AppendSeparator();
204    menuFile->Append(wxID_EXIT);
205
206    menuView->Append(ID_SHOWNAV, _("Navigation &toolbox"), _("Show or hide toolbox"), wxITEM_CHECK);
207    menuView->Check(ID_SHOWNAV, true);
208    menuView->AppendSeparator();
209#endif
210
211    menuView->Append(ID_ORIGIN, _("&Absolute origin"), _("Go to (0, 0)"));
212    menuView->Append(ID_CENTER, _("&Center of mass"), _("Find center of mass"));
213    menuView->Append(ID_NORTH, _("&North"), _("Find northernmost cell"));
214    menuView->Append(ID_SOUTH, _("&South"), _("Find southernmost cell"));
215    menuView->Append(ID_EAST, _("&East"), _("Find easternmost cell"));
216    menuView->Append(ID_WEST, _("&West"), _("Find westernmost cell"));
217    menuView->AppendSeparator();
218    menuView->Append(wxID_ZOOM_IN, wxEmptyString, _("Zoom in"));
219    menuView->Append(wxID_ZOOM_OUT, wxEmptyString, _("Zoom out"));
220    menuView->Append(ID_INFO, _("&Description\tCtrl-D"), _("View pattern description"));
221
222    menuGame->Append(ID_START, _("&Start\tCtrl-S"), _("Start"));
223    menuGame->Append(ID_STEP, _("&Next\tCtrl-N"), _("Single step"));
224    menuGame->Append(wxID_STOP, wxEmptyString, _("Stop"));
225    menuGame->Enable(wxID_STOP, false);
226    menuGame->AppendSeparator();
227    menuGame->Append(ID_TOPSPEED, _("T&op speed!"), _("Go as fast as possible"));
228
229    menuHelp->Append(wxID_ABOUT, _("&About\tCtrl-A"), _("Show about dialog"));
230
231    wxMenuBar *menuBar = new wxMenuBar();
232    menuBar->Append(menuFile, _("&File"));
233    menuBar->Append(menuView, _("&View"));
234    menuBar->Append(menuGame, _("&Game"));
235    menuBar->Append(menuHelp, _("&Help"));
236    SetMenuBar(menuBar);
237
238    // tool bar
239    wxBitmap tbBitmaps[7];
240
241    tbBitmaps[0] = wxBITMAP(reset);
242    tbBitmaps[1] = wxBITMAP(open);
243    tbBitmaps[2] = wxBITMAP(zoomin);
244    tbBitmaps[3] = wxBITMAP(zoomout);
245    tbBitmaps[4] = wxBITMAP(info);
246    tbBitmaps[5] = wxBITMAP(play);
247    tbBitmaps[6] = wxBITMAP(stop);
248
249    wxToolBar *toolBar = CreateToolBar();
250    toolBar->SetMargins(5, 5);
251    toolBar->SetToolBitmapSize(wxSize(16, 16));
252
253    ADD_TOOL(wxID_NEW, tbBitmaps[0], wxGetStockLabel(wxID_NEW, wxSTOCK_NOFLAGS), _("Start a new game"));
254#ifndef __POCKETPC__
255#if wxUSE_FILEDLG
256    ADD_TOOL(wxID_OPEN, tbBitmaps[1], wxGetStockLabel(wxID_OPEN, wxSTOCK_NOFLAGS), _("Open an existing Life pattern"));
257#endif // wxUSE_FILEDLG
258
259    toolBar->AddSeparator();
260    ADD_TOOL(wxID_ZOOM_IN, tbBitmaps[2], wxGetStockLabel(wxID_ZOOM_IN, wxSTOCK_NOFLAGS), _("Zoom in"));
261    ADD_TOOL(wxID_ZOOM_OUT, tbBitmaps[3], wxGetStockLabel(wxID_ZOOM_OUT, wxSTOCK_NOFLAGS), _("Zoom out"));
262    ADD_TOOL(ID_INFO, tbBitmaps[4], _("Description"), _("Show description"));
263    toolBar->AddSeparator();
264#endif // __POCKETPC__
265    ADD_TOOL(ID_START, tbBitmaps[5], _("Start"), _("Start"));
266    ADD_TOOL(wxID_STOP, tbBitmaps[6], _("Stop"), _("Stop"));
267
268    toolBar->Realize();
269    toolBar->EnableTool(wxID_STOP, false);    // must be after Realize() !
270
271#if wxUSE_STATUSBAR
272    // status bar
273    CreateStatusBar(2);
274    SetStatusText(_("Welcome to Life!"));
275#endif // wxUSE_STATUSBAR
276
277    // game and timer
278    m_life     = new Life();
279    m_timer    = new wxTimer(this, ID_TIMER);
280    m_running  = false;
281    m_topspeed = false;
282    m_interval = 500;
283    m_tics     = 0;
284
285    // We use two different panels to reduce flicker in wxGTK, because
286    // some widgets (like wxStaticText) don't have their own X11 window,
287    // and thus updating the text would result in a refresh of the canvas
288    // if they belong to the same parent.
289
290    wxPanel *panel1 = new wxPanel(this, wxID_ANY);
291    wxPanel *panel2 = new wxPanel(this, wxID_ANY);
292
293    // canvas
294    m_canvas = new LifeCanvas(panel1, m_life);
295
296    // info panel
297    m_text = new wxStaticText(panel2, wxID_ANY,
298        wxEmptyString,
299        wxDefaultPosition,
300        wxDefaultSize,
301        wxALIGN_CENTER | wxST_NO_AUTORESIZE);
302
303    wxSlider *slider = new wxSlider(panel2, ID_SLIDER,
304        5, 1, 10,
305        wxDefaultPosition,
306        wxSize(200, wxDefaultCoord),
307        wxSL_HORIZONTAL | wxSL_AUTOTICKS);
308
309    UpdateInfoText();
310
311    // component layout
312    wxBoxSizer *sizer1 = new wxBoxSizer(wxVERTICAL);
313    wxBoxSizer *sizer2 = new wxBoxSizer(wxVERTICAL);
314    wxBoxSizer *sizer3 = new wxBoxSizer(wxVERTICAL);
315
316#if wxUSE_STATLINE
317    sizer1->Add( new wxStaticLine(panel1, wxID_ANY), 0, wxGROW );
318#endif // wxUSE_STATLINE
319    sizer1->Add( m_canvas, 1, wxGROW | wxALL, 2 );
320#if wxUSE_STATLINE
321    sizer1->Add( new wxStaticLine(panel1, wxID_ANY), 0, wxGROW );
322#endif // wxUSE_STATLINE
323    panel1->SetSizer( sizer1 );
324    sizer1->Fit( panel1 );
325
326    sizer2->Add( m_text, 0, wxGROW | wxTOP, 4 );
327    sizer2->Add( slider, 0, wxCENTRE | wxALL, 4 );
328
329    panel2->SetSizer( sizer2 );
330    sizer2->Fit( panel2 );
331
332    sizer3->Add( panel1, 1, wxGROW );
333    sizer3->Add( panel2, 0, wxGROW );
334    SetSizer( sizer3 );
335
336#ifndef __WXWINCE__
337    sizer3->Fit( this );
338
339    // set minimum frame size
340    sizer3->SetSizeHints( this );
341
342    // navigator frame - not appropriate for small devices
343    m_navigator = new LifeNavigator(this);
344#endif
345
346}
347
348LifeFrame::~LifeFrame()
349{
350    delete m_timer;
351}
352
353void LifeFrame::UpdateInfoText()
354{
355    wxString msg;
356
357    msg.Printf(_(" Generation: %u (T: %u ms),  Population: %u "),
358               m_tics,
359               m_topspeed? 0 : m_interval,
360               m_life->GetNumCells());
361    m_text->SetLabel(msg);
362}
363
364// Enable or disable tools and menu entries according to the current
365// state. See also wxEVT_UPDATE_UI events for a slightly different
366// way to do this.
367void LifeFrame::UpdateUI()
368{
369    // start / stop
370    GetToolBar()->EnableTool(ID_START, !m_running);
371    GetToolBar()->EnableTool(wxID_STOP,  m_running);
372    GetMenuBar()->Enable(ID_START, !m_running);
373    GetMenuBar()->Enable(ID_STEP,  !m_running);
374    GetMenuBar()->Enable(wxID_STOP,  m_running);
375    GetMenuBar()->Enable(ID_TOPSPEED, !m_topspeed);
376
377    // zooming
378    int cellsize = m_canvas->GetCellSize();
379    GetToolBar()->EnableTool(wxID_ZOOM_IN,  cellsize < 32);
380    GetToolBar()->EnableTool(wxID_ZOOM_OUT, cellsize > 1);
381    GetMenuBar()->Enable(wxID_ZOOM_IN,  cellsize < 32);
382    GetMenuBar()->Enable(wxID_ZOOM_OUT, cellsize > 1);
383}
384
385// Event handlers -----------------------------------------------------------
386
387// OnMenu handles all events which don't have their own event handler
388void LifeFrame::OnMenu(wxCommandEvent& event)
389{
390    switch (event.GetId())
391    {
392        case wxID_NEW:
393        {
394            // stop if it was running
395            OnStop();
396            m_life->Clear();
397            m_canvas->Recenter(0, 0);
398            m_tics = 0;
399            UpdateInfoText();
400            break;
401        }
402        case wxID_ABOUT:
403        {
404            LifeAboutDialog dialog(this);
405            dialog.ShowModal();
406            break;
407        }
408        case wxID_EXIT:
409        {
410            // true is to force the frame to close
411            Close(true);
412            break;
413        }
414        case ID_SHOWNAV:
415        {
416            bool checked = GetMenuBar()->GetMenu(1)->IsChecked(ID_SHOWNAV);
417            if (m_navigator)
418                m_navigator->Show(checked);
419            break;
420        }
421        case ID_INFO:
422        {
423            wxString desc = m_life->GetDescription();
424
425            if ( desc.empty() )
426                desc = _("Not available");
427
428            // should we make the description editable here?
429            wxMessageBox(desc, _("Description"), wxOK | wxICON_INFORMATION);
430
431            break;
432        }
433        case ID_START   : OnStart(); break;
434        case ID_STEP    : OnStep(); break;
435        case wxID_STOP  : OnStop(); break;
436        case ID_TOPSPEED:
437        {
438            m_running = true;
439            m_topspeed = true;
440            UpdateUI();
441            while (m_running && m_topspeed)
442            {
443                OnStep();
444                wxYield();
445            }
446            break;
447        }
448    }
449}
450
451#if wxUSE_FILEDLG
452void LifeFrame::OnOpen(wxCommandEvent& WXUNUSED(event))
453{
454    wxFileDialog filedlg(this,
455                         _("Choose a file to open"),
456                         wxEmptyString,
457                         wxEmptyString,
458                         _("Life patterns (*.lif)|*.lif|All files (*.*)|*.*"),
459                         wxFD_OPEN | wxFD_FILE_MUST_EXIST);
460
461    if (filedlg.ShowModal() == wxID_OK)
462    {
463        wxFileInputStream stream(filedlg.GetPath());
464        LifeReader reader(stream);
465
466        // the reader handles errors itself, no need to do anything here
467        if (reader.IsOk())
468        {
469            // stop if running and put the pattern
470            OnStop();
471            m_life->Clear();
472            m_life->SetPattern(reader.GetPattern());
473
474            // recenter canvas
475            m_canvas->Recenter(0, 0);
476            m_tics = 0;
477            UpdateInfoText();
478        }
479    }
480}
481#endif
482
483void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event))
484{
485    // stop if it was running
486    OnStop();
487
488    // dialog box
489    LifeSamplesDialog dialog(this);
490
491    if (dialog.ShowModal() == wxID_OK)
492    {
493        const LifePattern pattern = dialog.GetPattern();
494
495        // put the pattern
496        m_life->Clear();
497        m_life->SetPattern(pattern);
498
499        // recenter canvas
500        m_canvas->Recenter(0, 0);
501        m_tics = 0;
502        UpdateInfoText();
503    }
504}
505
506void LifeFrame::OnZoom(wxCommandEvent& event)
507{
508    int cellsize = m_canvas->GetCellSize();
509
510    if ((event.GetId() == wxID_ZOOM_IN) && cellsize < 32)
511    {
512        m_canvas->SetCellSize(cellsize * 2);
513        UpdateUI();
514    }
515    else if ((event.GetId() == wxID_ZOOM_OUT) && cellsize > 1)
516    {
517        m_canvas->SetCellSize(cellsize / 2);
518        UpdateUI();
519    }
520}
521
522void LifeFrame::OnNavigate(wxCommandEvent& event)
523{
524    LifeCell c;
525
526    switch (event.GetId())
527    {
528        case ID_NORTH:  c = m_life->FindNorth(); break;
529        case ID_SOUTH:  c = m_life->FindSouth(); break;
530        case ID_WEST:   c = m_life->FindWest(); break;
531        case ID_EAST:   c = m_life->FindEast(); break;
532        case ID_CENTER: c = m_life->FindCenter(); break;
533        default :
534            wxFAIL;
535            // Fall through!
536        case ID_ORIGIN: c.i = c.j = 0; break;
537    }
538
539    m_canvas->Recenter(c.i, c.j);
540}
541
542void LifeFrame::OnSlider(wxScrollEvent& event)
543{
544    m_interval = event.GetPosition() * 100;
545
546    if (m_running)
547    {
548        OnStop();
549        OnStart();
550    }
551
552    UpdateInfoText();
553}
554
555void LifeFrame::OnTimer(wxTimerEvent& WXUNUSED(event))
556{
557    OnStep();
558}
559
560void LifeFrame::OnClose(wxCloseEvent& WXUNUSED(event))
561{
562    // Stop if it was running; this is absolutely needed because
563    // the frame won't be actually destroyed until there are no
564    // more pending events, and this in turn won't ever happen
565    // if the timer is running faster than the window can redraw.
566    OnStop();
567    Destroy();
568}
569
570void LifeFrame::OnStart()
571{
572    if (!m_running)
573    {
574        m_timer->Start(m_interval);
575        m_running = true;
576        UpdateUI();
577    }
578}
579
580void LifeFrame::OnStop()
581{
582    if (m_running)
583    {
584        m_timer->Stop();
585        m_running = false;
586        m_topspeed = false;
587        UpdateUI();
588    }
589}
590
591void LifeFrame::OnStep()
592{
593    if (m_life->NextTic())
594        m_tics++;
595    else
596        OnStop();
597
598    m_canvas->DrawChanged();
599    UpdateInfoText();
600}
601
602
603// --------------------------------------------------------------------------
604// LifeNavigator miniframe
605// --------------------------------------------------------------------------
606
607LifeNavigator::LifeNavigator(wxWindow *parent)
608             : wxMiniFrame(parent, wxID_ANY,
609                           _("Navigation"),
610                           wxDefaultPosition,
611                           wxDefaultSize,
612                           wxCAPTION | wxSIMPLE_BORDER)
613{
614    wxPanel    *panel  = new wxPanel(this, wxID_ANY);
615    wxBoxSizer *sizer1 = new wxBoxSizer(wxVERTICAL);
616    wxBoxSizer *sizer2 = new wxBoxSizer(wxHORIZONTAL);
617
618    // create bitmaps and masks for the buttons
619    wxBitmap
620        bmpn = wxBITMAP(north),
621        bmpw = wxBITMAP(west),
622        bmpc = wxBITMAP(center),
623        bmpe = wxBITMAP(east),
624        bmps = wxBITMAP(south);
625
626#if !defined(__WXGTK__) && !defined(__WXMOTIF__) && !defined(__WXMAC__)
627    bmpn.SetMask(new wxMask(bmpn, *wxLIGHT_GREY));
628    bmpw.SetMask(new wxMask(bmpw, *wxLIGHT_GREY));
629    bmpc.SetMask(new wxMask(bmpc, *wxLIGHT_GREY));
630    bmpe.SetMask(new wxMask(bmpe, *wxLIGHT_GREY));
631    bmps.SetMask(new wxMask(bmps, *wxLIGHT_GREY));
632#endif
633
634    // create the buttons and attach tooltips to them
635    wxBitmapButton
636        *bn = new wxBitmapButton(panel, ID_NORTH,  bmpn),
637        *bw = new wxBitmapButton(panel, ID_WEST ,  bmpw),
638        *bc = new wxBitmapButton(panel, ID_CENTER, bmpc),
639        *be = new wxBitmapButton(panel, ID_EAST ,  bmpe),
640        *bs = new wxBitmapButton(panel, ID_SOUTH,  bmps);
641
642#if wxUSE_TOOLTIPS
643    bn->SetToolTip(_("Find northernmost cell"));
644    bw->SetToolTip(_("Find westernmost cell"));
645    bc->SetToolTip(_("Find center of mass"));
646    be->SetToolTip(_("Find easternmost cell"));
647    bs->SetToolTip(_("Find southernmost cell"));
648#endif
649
650    // add buttons to sizers
651    sizer2->Add( bw, 0, wxCENTRE | wxWEST,  4 );
652    sizer2->Add( bc, 0, wxCENTRE);
653    sizer2->Add( be, 0, wxCENTRE | wxEAST,  4 );
654    sizer1->Add( bn, 0, wxCENTRE | wxNORTH, 4 );
655    sizer1->Add( sizer2 );
656    sizer1->Add( bs, 0, wxCENTRE | wxSOUTH, 4 );
657
658    // set the panel and miniframe size
659    panel->SetSizer(sizer1);
660
661    sizer1->Fit(panel);
662    SetClientSize(panel->GetSize());
663    wxSize sz = GetSize();
664    SetSizeHints(sz.x, sz.y, sz.x, sz.y);
665
666    // move it to a sensible position
667    wxRect parentRect = parent->GetRect();
668    wxSize childSize  = GetSize();
669    int x = parentRect.GetX() +
670            parentRect.GetWidth();
671    int y = parentRect.GetY() +
672            (parentRect.GetHeight() - childSize.GetHeight()) / 4;
673    Move(x, y);
674
675    // done
676    Show(true);
677}
678
679void LifeNavigator::OnClose(wxCloseEvent& event)
680{
681    // avoid if we can
682    if (event.CanVeto())
683        event.Veto();
684    else
685        Destroy();
686}
687
688
689// --------------------------------------------------------------------------
690// LifeCanvas
691// --------------------------------------------------------------------------
692
693// canvas constructor
694LifeCanvas::LifeCanvas(wxWindow *parent, Life *life, bool interactive)
695          : wxWindow(parent, wxID_ANY, wxDefaultPosition, wxSize(100, 100),
696            wxFULL_REPAINT_ON_RESIZE
697#if !defined(__SMARTPHONE__) && !defined(__POCKETPC__)
698            |wxSUNKEN_BORDER
699#else
700            |wxSIMPLE_BORDER
701#endif
702            )
703{
704    m_life        = life;
705    m_interactive = interactive;
706    m_cellsize    = 8;
707    m_status      = MOUSE_NOACTION;
708    m_viewportX   = 0;
709    m_viewportY   = 0;
710    m_viewportH   = 0;
711    m_viewportW   = 0;
712
713    if (m_interactive)
714        SetCursor(*wxCROSS_CURSOR);
715
716    // reduce flicker if wxEVT_ERASE_BACKGROUND is not available
717    SetBackgroundColour(*wxWHITE);
718}
719
720LifeCanvas::~LifeCanvas()
721{
722    delete m_life;
723}
724
725// recenter at the given position
726void LifeCanvas::Recenter(wxInt32 i, wxInt32 j)
727{
728    m_viewportX = i - m_viewportW / 2;
729    m_viewportY = j - m_viewportH / 2;
730
731    // redraw everything
732    Refresh(false);
733}
734
735// set the cell size and refresh display
736void LifeCanvas::SetCellSize(int cellsize)
737{
738    m_cellsize = cellsize;
739
740    // find current center
741    wxInt32 cx = m_viewportX + m_viewportW / 2;
742    wxInt32 cy = m_viewportY + m_viewportH / 2;
743
744    // get current canvas size and adjust viewport accordingly
745    int w, h;
746    GetClientSize(&w, &h);
747    m_viewportW = (w + m_cellsize - 1) / m_cellsize;
748    m_viewportH = (h + m_cellsize - 1) / m_cellsize;
749
750    // recenter
751    m_viewportX = cx - m_viewportW / 2;
752    m_viewportY = cy - m_viewportH / 2;
753
754    // adjust scrollbars
755    if (m_interactive)
756    {
757        SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
758        SetScrollbar(wxVERTICAL,   m_viewportH, m_viewportH, 3 * m_viewportH);
759        m_thumbX = m_viewportW;
760        m_thumbY = m_viewportH;
761    }
762
763    Refresh(false);
764}
765
766// draw a cell
767void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, bool alive)
768{
769    wxClientDC dc(this);
770
771    dc.SetPen(alive? *wxBLACK_PEN : *wxWHITE_PEN);
772    dc.SetBrush(alive? *wxBLACK_BRUSH : *wxWHITE_BRUSH);
773
774    DrawCell(i, j, dc);
775}
776
777void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, wxDC &dc)
778{
779    wxCoord x = CellToX(i);
780    wxCoord y = CellToY(j);
781
782    // if cellsize is 1 or 2, there will be no grid
783    switch (m_cellsize)
784    {
785        case 1:
786            dc.DrawPoint(x, y);
787            break;
788        case 2:
789            dc.DrawRectangle(x, y, 2, 2);
790            break;
791        default:
792            dc.DrawRectangle(x + 1, y + 1, m_cellsize - 1, m_cellsize - 1);
793    }
794}
795
796// draw all changed cells
797void LifeCanvas::DrawChanged()
798{
799    wxClientDC dc(this);
800
801    size_t ncells;
802    LifeCell *cells;
803    bool done = false;
804
805    m_life->BeginFind(m_viewportX,
806                      m_viewportY,
807                      m_viewportX + m_viewportW,
808                      m_viewportY + m_viewportH,
809                      true);
810
811    if (m_cellsize == 1)
812    {
813        dc.SetPen(*wxBLACK_PEN);
814    }
815    else
816    {
817        dc.SetPen(*wxTRANSPARENT_PEN);
818        dc.SetBrush(*wxBLACK_BRUSH);
819    }
820    dc.SetLogicalFunction(wxINVERT);
821
822    while (!done)
823    {
824        done = m_life->FindMore(&cells, &ncells);
825
826        for (size_t m = 0; m < ncells; m++)
827            DrawCell(cells[m].i, cells[m].j, dc);
828    }
829}
830
831// event handlers
832void LifeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event))
833{
834    wxPaintDC dc(this);
835    wxRect  rect = GetUpdateRegion().GetBox();
836    wxCoord x, y, w, h;
837    wxInt32 i0, j0, i1, j1;
838
839    // find damaged area
840    x = rect.GetX();
841    y = rect.GetY();
842    w = rect.GetWidth();
843    h = rect.GetHeight();
844
845    i0 = XToCell(x);
846    j0 = YToCell(y);
847    i1 = XToCell(x + w - 1);
848    j1 = YToCell(y + h - 1);
849
850    size_t ncells;
851    LifeCell *cells;
852
853    m_life->BeginFind(i0, j0, i1, j1, false);
854    bool done = m_life->FindMore(&cells, &ncells);
855
856    // erase all damaged cells and draw the grid
857    dc.SetBrush(*wxWHITE_BRUSH);
858
859    if (m_cellsize <= 2)
860    {
861       // no grid
862       dc.SetPen(*wxWHITE_PEN);
863       dc.DrawRectangle(x, y, w, h);
864    }
865    else
866    {
867        x = CellToX(i0);
868        y = CellToY(j0);
869        w = CellToX(i1 + 1) - x + 1;
870        h = CellToY(j1 + 1) - y + 1;
871
872        dc.SetPen(*wxLIGHT_GREY_PEN);
873        for (wxInt32 yy = y; yy <= (y + h - m_cellsize); yy += m_cellsize)
874            dc.DrawRectangle(x, yy, w, m_cellsize + 1);
875        for (wxInt32 xx = x; xx <= (x + w - m_cellsize); xx += m_cellsize)
876            dc.DrawLine(xx, y, xx, y + h);
877    }
878
879    // draw all alive cells
880    dc.SetPen(*wxBLACK_PEN);
881    dc.SetBrush(*wxBLACK_BRUSH);
882
883    while (!done)
884    {
885        for (size_t m = 0; m < ncells; m++)
886            DrawCell(cells[m].i, cells[m].j, dc);
887
888        done = m_life->FindMore(&cells, &ncells);
889    }
890
891    // last set
892    for (size_t m = 0; m < ncells; m++)
893        DrawCell(cells[m].i, cells[m].j, dc);
894}
895
896void LifeCanvas::OnMouse(wxMouseEvent& event)
897{
898    if (!m_interactive)
899        return;
900
901    // which cell are we pointing at?
902    wxInt32 i = XToCell( event.GetX() );
903    wxInt32 j = YToCell( event.GetY() );
904
905#if wxUSE_STATUSBAR
906    // set statusbar text
907    wxString msg;
908    msg.Printf(_("Cell: (%d, %d)"), i, j);
909    ((LifeFrame *) wxGetApp().GetTopWindow())->SetStatusText(msg, 1);
910#endif // wxUSE_STATUSBAR
911
912    // NOTE that wxMouseEvent::LeftDown() and wxMouseEvent::LeftIsDown()
913    // have different semantics. The first one is used to signal that the
914    // button was just pressed (i.e., in "button down" events); the second
915    // one just describes the current status of the button, independently
916    // of the mouse event type. LeftIsDown is typically used in "mouse
917    // move" events, to test if the button is _still_ pressed.
918
919    // is the button down?
920    if (!event.LeftIsDown())
921    {
922        m_status = MOUSE_NOACTION;
923        return;
924    }
925
926    // was it pressed just now?
927    if (event.LeftDown())
928    {
929        // yes: start a new action and toggle this cell
930        m_status = (m_life->IsAlive(i, j)? MOUSE_ERASING : MOUSE_DRAWING);
931
932        m_mi = i;
933        m_mj = j;
934        m_life->SetCell(i, j, m_status == MOUSE_DRAWING);
935        DrawCell(i, j, m_status == MOUSE_DRAWING);
936    }
937    else if ((m_mi != i) || (m_mj != j))
938    {
939        // no: continue ongoing action
940        bool alive = (m_status == MOUSE_DRAWING);
941
942        // prepare DC and pen + brush to optimize drawing
943        wxClientDC dc(this);
944        dc.SetPen(alive? *wxBLACK_PEN : *wxWHITE_PEN);
945        dc.SetBrush(alive? *wxBLACK_BRUSH : *wxWHITE_BRUSH);
946
947        // draw a line of cells using Bresenham's algorithm
948        wxInt32 d, ii, jj, di, ai, si, dj, aj, sj;
949        di = i - m_mi;
950        ai = abs(di) << 1;
951        si = (di < 0)? -1 : 1;
952        dj = j - m_mj;
953        aj = abs(dj) << 1;
954        sj = (dj < 0)? -1 : 1;
955
956        ii = m_mi;
957        jj = m_mj;
958
959        if (ai > aj)
960        {
961            // iterate over i
962            d = aj - (ai >> 1);
963
964            while (ii != i)
965            {
966                m_life->SetCell(ii, jj, alive);
967                DrawCell(ii, jj, dc);
968                if (d >= 0)
969                {
970                    jj += sj;
971                    d  -= ai;
972                }
973                ii += si;
974                d  += aj;
975            }
976        }
977        else
978        {
979            // iterate over j
980            d = ai - (aj >> 1);
981
982            while (jj != j)
983            {
984                m_life->SetCell(ii, jj, alive);
985                DrawCell(ii, jj, dc);
986                if (d >= 0)
987                {
988                    ii += si;
989                    d  -= aj;
990                }
991                jj += sj;
992                d  += ai;
993            }
994        }
995
996        // last cell
997        m_life->SetCell(ii, jj, alive);
998        DrawCell(ii, jj, dc);
999        m_mi = ii;
1000        m_mj = jj;
1001    }
1002
1003    ((LifeFrame *) wxGetApp().GetTopWindow())->UpdateInfoText();
1004}
1005
1006void LifeCanvas::OnSize(wxSizeEvent& event)
1007{
1008    // find center
1009    wxInt32 cx = m_viewportX + m_viewportW / 2;
1010    wxInt32 cy = m_viewportY + m_viewportH / 2;
1011
1012    // get new size
1013    wxCoord w = event.GetSize().GetX();
1014    wxCoord h = event.GetSize().GetY();
1015    m_viewportW = (w + m_cellsize - 1) / m_cellsize;
1016    m_viewportH = (h + m_cellsize - 1) / m_cellsize;
1017
1018    // recenter
1019    m_viewportX = cx - m_viewportW / 2;
1020    m_viewportY = cy - m_viewportH / 2;
1021
1022    // scrollbars
1023    if (m_interactive)
1024    {
1025        SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
1026        SetScrollbar(wxVERTICAL,   m_viewportH, m_viewportH, 3 * m_viewportH);
1027        m_thumbX = m_viewportW;
1028        m_thumbY = m_viewportH;
1029    }
1030
1031    // allow default processing
1032    event.Skip();
1033}
1034
1035void LifeCanvas::OnScroll(wxScrollWinEvent& event)
1036{
1037    WXTYPE type = (WXTYPE)event.GetEventType();
1038    int pos     = event.GetPosition();
1039    int orient  = event.GetOrientation();
1040
1041    // calculate scroll increment
1042    int scrollinc = 0;
1043    if (type == wxEVT_SCROLLWIN_TOP)
1044    {
1045        if (orient == wxHORIZONTAL)
1046            scrollinc = -m_viewportW;
1047        else
1048            scrollinc = -m_viewportH;
1049    }
1050    else
1051    if (type == wxEVT_SCROLLWIN_BOTTOM)
1052    {
1053        if (orient == wxHORIZONTAL)
1054            scrollinc = m_viewportW;
1055        else
1056            scrollinc = m_viewportH;
1057    }
1058    else
1059    if (type == wxEVT_SCROLLWIN_LINEUP)
1060    {
1061        scrollinc = -1;
1062    }
1063    else
1064    if (type == wxEVT_SCROLLWIN_LINEDOWN)
1065    {
1066        scrollinc = +1;
1067    }
1068    else
1069    if (type == wxEVT_SCROLLWIN_PAGEUP)
1070    {
1071        scrollinc = -10;
1072    }
1073    else
1074    if (type == wxEVT_SCROLLWIN_PAGEDOWN)
1075    {
1076        scrollinc = +10;
1077    }
1078    else
1079    if (type == wxEVT_SCROLLWIN_THUMBTRACK)
1080    {
1081        if (orient == wxHORIZONTAL)
1082        {
1083            scrollinc = pos - m_thumbX;
1084            m_thumbX = pos;
1085        }
1086        else
1087        {
1088            scrollinc = pos - m_thumbY;
1089            m_thumbY = pos;
1090        }
1091    }
1092    else
1093    if (type == wxEVT_SCROLLWIN_THUMBRELEASE)
1094    {
1095        m_thumbX = m_viewportW;
1096        m_thumbY = m_viewportH;
1097    }
1098
1099#if defined(__WXGTK__) || defined(__WXMOTIF__)
1100    // wxGTK and wxMotif update the thumb automatically (wxMSW doesn't);
1101    // so reset it back as we always want it to be in the same position.
1102    if (type != wxEVT_SCROLLWIN_THUMBTRACK)
1103    {
1104        SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
1105        SetScrollbar(wxVERTICAL,   m_viewportH, m_viewportH, 3 * m_viewportH);
1106    }
1107#endif
1108
1109    if (scrollinc == 0) return;
1110
1111    // scroll the window and adjust the viewport
1112    if (orient == wxHORIZONTAL)
1113    {
1114        m_viewportX += scrollinc;
1115        ScrollWindow( -m_cellsize * scrollinc, 0, (const wxRect *) NULL);
1116    }
1117    else
1118    {
1119        m_viewportY += scrollinc;
1120        ScrollWindow( 0, -m_cellsize * scrollinc, (const wxRect *) NULL);
1121    }
1122}
1123
1124void LifeCanvas::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
1125{
1126    // do nothing. I just don't want the background to be erased, you know.
1127}
1128