X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/e0a4029251c1d80d50f9cb0bd152d1fca7914bc4..7c0d801cb4b9231b71fb9c7fc69af58f43c1595f:/demos/life/life.cpp diff --git a/demos/life/life.cpp b/demos/life/life.cpp index b65451f8b5..f01e3e0be7 100644 --- a/demos/life/life.cpp +++ b/demos/life/life.cpp @@ -1,6 +1,6 @@ ///////////////////////////////////////////////////////////////////////////// // Name: life.cpp -// Purpose: The game of life, created by J. H. Conway +// Purpose: The game of Life, created by J. H. Conway // Author: Guillermo Rodriguez Garcia, // Modified by: // Created: Jan/2000 @@ -17,26 +17,49 @@ #pragma implementation "life.h" #endif +// For compilers that support precompilation, includes "wx/wx.h". +#include "wx/wxprec.h" + +#ifdef __BORLANDC__ + #pragma hdrstop +#endif + +#ifndef WX_PRECOMP + #include "wx/wx.h" +#endif + #include "wx/statline.h" +#include "wx/wfstream.h" +#include "wx/filedlg.h" #include "life.h" #include "game.h" #include "dialogs.h" +#include "reader.h" // -------------------------------------------------------------------------- // resources // -------------------------------------------------------------------------- #if defined(__WXGTK__) || defined(__WXMOTIF__) - // the application icon + // application icon #include "mondrian.xpm" // bitmap buttons for the toolbar #include "bitmaps/reset.xpm" + #include "bitmaps/open.xpm" #include "bitmaps/play.xpm" #include "bitmaps/stop.xpm" #include "bitmaps/zoomin.xpm" #include "bitmaps/zoomout.xpm" + #include "bitmaps/info.xpm" + + // navigator + #include "bitmaps/north.xpm" + #include "bitmaps/south.xpm" + #include "bitmaps/east.xpm" + #include "bitmaps/west.xpm" + #include "bitmaps/center.xpm" #endif // -------------------------------------------------------------------------- @@ -46,21 +69,36 @@ // IDs for the controls and the menu commands enum { - // menu items and toolbar buttons - ID_RESET = 1001, + // timer + ID_TIMER = 1001, + + // file menu + ID_NEW, + ID_OPEN, ID_SAMPLES, ID_ABOUT, ID_EXIT, + + // view menu + ID_SHOWNAV, + ID_ORIGIN, ID_CENTER, + ID_NORTH, + ID_SOUTH, + ID_EAST, + ID_WEST, + ID_ZOOMIN, + ID_ZOOMOUT, + ID_INFO, + + // game menu ID_START, ID_STEP, ID_STOP, - ID_ZOOMIN, - ID_ZOOMOUT, ID_TOPSPEED, // speed selection slider - ID_SLIDER + ID_SLIDER, }; // -------------------------------------------------------------------------- @@ -69,26 +107,42 @@ enum // Event tables BEGIN_EVENT_TABLE(LifeFrame, wxFrame) + EVT_MENU (ID_NEW, LifeFrame::OnMenu) + EVT_MENU (ID_OPEN, LifeFrame::OnOpen) EVT_MENU (ID_SAMPLES, LifeFrame::OnSamples) - EVT_MENU (ID_RESET, LifeFrame::OnMenu) EVT_MENU (ID_ABOUT, LifeFrame::OnMenu) EVT_MENU (ID_EXIT, LifeFrame::OnMenu) - EVT_MENU (ID_CENTER, LifeFrame::OnMenu) + EVT_MENU (ID_SHOWNAV, LifeFrame::OnMenu) + EVT_MENU (ID_ORIGIN, LifeFrame::OnNavigate) + EVT_BUTTON (ID_CENTER, LifeFrame::OnNavigate) + EVT_BUTTON (ID_NORTH, LifeFrame::OnNavigate) + EVT_BUTTON (ID_SOUTH, LifeFrame::OnNavigate) + EVT_BUTTON (ID_EAST, LifeFrame::OnNavigate) + EVT_BUTTON (ID_WEST, LifeFrame::OnNavigate) + EVT_MENU (ID_ZOOMIN, LifeFrame::OnZoom) + EVT_MENU (ID_ZOOMOUT, LifeFrame::OnZoom) + EVT_MENU (ID_INFO, LifeFrame::OnMenu) EVT_MENU (ID_START, LifeFrame::OnMenu) EVT_MENU (ID_STEP, LifeFrame::OnMenu) EVT_MENU (ID_STOP, LifeFrame::OnMenu) - EVT_MENU (ID_ZOOMIN, LifeFrame::OnMenu) - EVT_MENU (ID_ZOOMOUT, LifeFrame::OnMenu) EVT_MENU (ID_TOPSPEED, LifeFrame::OnMenu) EVT_COMMAND_SCROLL (ID_SLIDER, LifeFrame::OnSlider) + EVT_TIMER (ID_TIMER, LifeFrame::OnTimer) EVT_CLOSE ( LifeFrame::OnClose) END_EVENT_TABLE() +BEGIN_EVENT_TABLE(LifeNavigator, wxMiniFrame) + EVT_CLOSE ( LifeNavigator::OnClose) +END_EVENT_TABLE() + BEGIN_EVENT_TABLE(LifeCanvas, wxWindow) EVT_PAINT ( LifeCanvas::OnPaint) EVT_SCROLLWIN ( LifeCanvas::OnScroll) EVT_SIZE ( LifeCanvas::OnSize) - EVT_MOUSE_EVENTS ( LifeCanvas::OnMouse) + EVT_MOTION ( LifeCanvas::OnMouse) + EVT_LEFT_DOWN ( LifeCanvas::OnMouse) + EVT_LEFT_UP ( LifeCanvas::OnMouse) + EVT_LEFT_DCLICK ( LifeCanvas::OnMouse) EVT_ERASE_BACKGROUND( LifeCanvas::OnEraseBackground) END_EVENT_TABLE() @@ -102,10 +156,9 @@ IMPLEMENT_APP(LifeApp) // ========================================================================== // some shortcuts -#define ADD_TOOL(id, bmp, tooltip, help) \ +#define ADD_TOOL(id, bmp, tooltip, help) \ toolBar->AddTool(id, bmp, wxNullBitmap, FALSE, -1, -1, (wxObject *)0, tooltip, help) -#define GET_FRAME() ((LifeFrame *) wxGetApp().GetTopWindow()) // -------------------------------------------------------------------------- // LifeApp @@ -121,6 +174,11 @@ bool LifeApp::OnInit() frame->Show(TRUE); SetTopWindow(frame); + // just for Motif +#ifdef __WXMOTIF__ + frame->UpdateInfoText(); +#endif + // enter the main message loop and run the app return TRUE; } @@ -130,56 +188,76 @@ bool LifeApp::OnInit() // -------------------------------------------------------------------------- // frame constructor -LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(50, 50)) +LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(200, 200)) { // frame icon SetIcon(wxICON(mondrian)); // menu bar wxMenu *menuFile = new wxMenu("", wxMENU_TEAROFF); + wxMenu *menuView = new wxMenu("", wxMENU_TEAROFF); wxMenu *menuGame = new wxMenu("", wxMENU_TEAROFF); - menuFile->Append(ID_RESET, _("Reset"), _("Start a new game")); - menuFile->Append(ID_SAMPLES, _("Sample game..."), _("Select a sample configuration")); + menuFile->Append(ID_NEW, _("&New"), _("Start a new game")); + menuFile->Append(ID_OPEN, _("&Open..."), _("Open an existing Life pattern")); + menuFile->Append(ID_SAMPLES, _("&Sample game..."), _("Select a sample configuration")); menuFile->AppendSeparator(); menuFile->Append(ID_ABOUT, _("&About...\tCtrl-A"), _("Show about dialog")); menuFile->AppendSeparator(); menuFile->Append(ID_EXIT, _("E&xit\tAlt-X"), _("Quit this program")); - menuGame->Append(ID_CENTER, _("Re¢er\tCtrl-C"), _("Go to (0, 0)")); + menuView->Append(ID_SHOWNAV, _("Navigation &toolbox"), _("Show or hide toolbox"), TRUE); + menuView->Check (ID_SHOWNAV, TRUE); + menuView->AppendSeparator(); + menuView->Append(ID_ORIGIN, _("&Absolute origin"), _("Go to (0, 0)")); + menuView->Append(ID_CENTER, _("&Center of mass"), _("Find center of mass")); + menuView->Append(ID_NORTH, _("&North"), _("Find northernmost cell")); + menuView->Append(ID_SOUTH, _("&South"), _("Find southernmost cell")); + menuView->Append(ID_EAST, _("&East"), _("Find easternmost cell")); + menuView->Append(ID_WEST, _("&West"), _("Find westernmost cell")); + menuView->AppendSeparator(); + menuView->Append(ID_ZOOMIN, _("Zoom &in\tCtrl-I"), _("Zoom in")); + menuView->Append(ID_ZOOMOUT, _("Zoom &out\tCtrl-O"), _("Zoom out")); + menuView->Append(ID_INFO, _("&Description...\tCtrl-D"), _("View pattern description")); + menuGame->Append(ID_START, _("&Start\tCtrl-S"), _("Start")); menuGame->Append(ID_STEP, _("&Next\tCtrl-N"), _("Single step")); menuGame->Append(ID_STOP, _("S&top\tCtrl-T"), _("Stop")); menuGame->Enable(ID_STOP, FALSE); menuGame->AppendSeparator(); - menuGame->Append(ID_TOPSPEED, _("Top speed!"), _("Go as fast as possible")); - menuGame->AppendSeparator(); - menuGame->Append(ID_ZOOMIN, _("Zoom &in\tCtrl-I")); - menuGame->Append(ID_ZOOMOUT, _("Zoom &out\tCtrl-O")); + menuGame->Append(ID_TOPSPEED, _("T&op speed!"), _("Go as fast as possible")); wxMenuBar *menuBar = new wxMenuBar(); menuBar->Append(menuFile, _("&File")); + menuBar->Append(menuView, _("&View")); menuBar->Append(menuGame, _("&Game")); SetMenuBar(menuBar); // tool bar - wxBitmap tbBitmaps[5]; + wxBitmap tbBitmaps[7]; tbBitmaps[0] = wxBITMAP(reset); - tbBitmaps[1] = wxBITMAP(play); - tbBitmaps[2] = wxBITMAP(stop); - tbBitmaps[3] = wxBITMAP(zoomin); - tbBitmaps[4] = wxBITMAP(zoomout); + tbBitmaps[1] = wxBITMAP(open); + tbBitmaps[2] = wxBITMAP(zoomin); + tbBitmaps[3] = wxBITMAP(zoomout); + tbBitmaps[4] = wxBITMAP(info); + tbBitmaps[5] = wxBITMAP(play); + tbBitmaps[6] = wxBITMAP(stop); wxToolBar *toolBar = CreateToolBar(); toolBar->SetMargins(5, 5); toolBar->SetToolBitmapSize(wxSize(16, 16)); - ADD_TOOL(ID_RESET, tbBitmaps[0], _("Reset"), _("Start a new game")); - ADD_TOOL(ID_START, tbBitmaps[1], _("Start"), _("Start")); - ADD_TOOL(ID_STOP, tbBitmaps[2], _("Stop"), _("Stop")); + + ADD_TOOL(ID_NEW, tbBitmaps[0], _("New"), _("Start a new game")); + ADD_TOOL(ID_OPEN, tbBitmaps[1], _("Open"), _("Open an existing Life pattern")); + toolBar->AddSeparator(); + ADD_TOOL(ID_ZOOMIN, tbBitmaps[2], _("Zoom in"), _("Zoom in")); + ADD_TOOL(ID_ZOOMOUT, tbBitmaps[3], _("Zoom out"), _("Zoom out")); + ADD_TOOL(ID_INFO, tbBitmaps[4], _("Description"), _("Show description")); toolBar->AddSeparator(); - ADD_TOOL(ID_ZOOMIN, tbBitmaps[3], _("Zoom in"), _("Zoom in")); - ADD_TOOL(ID_ZOOMOUT, tbBitmaps[4], _("Zoom out"), _("Zoom out")); + ADD_TOOL(ID_START, tbBitmaps[5], _("Start"), _("Start")); + ADD_TOOL(ID_STOP, tbBitmaps[6], _("Stop"), _("Stop")); + toolBar->Realize(); toolBar->EnableTool(ID_STOP, FALSE); // must be after Realize() ! @@ -187,36 +265,70 @@ LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(50, 50)) CreateStatusBar(2); SetStatusText(_("Welcome to Life!")); - // game and canvas - wxPanel *panel = new wxPanel(this, -1); - m_life = new Life(); - m_canvas = new LifeCanvas(panel, m_life); - m_timer = new LifeTimer(); - m_running = FALSE; - m_topspeed = FALSE; - m_interval = 500; - m_tics = 0; - m_text = new wxStaticText(panel, -1, ""); - UpdateInfoText(); + // game and timer + m_life = new Life(); + m_timer = new wxTimer(this, ID_TIMER); + m_running = FALSE; + m_topspeed = FALSE; + m_interval = 500; + m_tics = 0; - // speed selection slider - wxSlider *slider = new wxSlider(panel, ID_SLIDER, + // We use two different panels to reduce flicker in wxGTK, because + // some widgets (like wxStaticText) don't have their own X11 window, + // and thus updating the text would result in a refresh of the canvas + // if they belong to the same parent. + + wxPanel *panel1 = new wxPanel(this, -1); + wxPanel *panel2 = new wxPanel(this, -1); + + // canvas + m_canvas = new LifeCanvas(panel1, m_life); + + // info panel + m_text = new wxStaticText(panel2, -1, + wxEmptyString, + wxDefaultPosition, + wxDefaultSize, + wxALIGN_CENTER | wxST_NO_AUTORESIZE); + + wxSlider *slider = new wxSlider(panel2, ID_SLIDER, 5, 1, 10, wxDefaultPosition, wxSize(200, -1), wxSL_HORIZONTAL | wxSL_AUTOTICKS); + UpdateInfoText(); + // component layout - wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE); - sizer->Add(m_canvas, 1, wxGROW | wxCENTRE | wxALL, 5); - sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE); - sizer->Add(m_text, 0, wxCENTRE | wxTOP, 5); - sizer->Add(slider, 0, wxCENTRE | wxALL, 5); - panel->SetSizer(sizer); - panel->SetAutoLayout(TRUE); - sizer->Fit(this); - sizer->SetSizeHints(this); + wxBoxSizer *sizer1 = new wxBoxSizer(wxVERTICAL); + wxBoxSizer *sizer2 = new wxBoxSizer(wxVERTICAL); + wxBoxSizer *sizer3 = new wxBoxSizer(wxVERTICAL); + + sizer1->Add( new wxStaticLine(panel1, -1), 0, wxGROW ); + sizer1->Add( m_canvas, 1, wxGROW | wxALL, 2 ); + sizer1->Add( new wxStaticLine(panel1, -1), 0, wxGROW ); + panel1->SetSizer( sizer1 ); + panel1->SetAutoLayout( TRUE ); + sizer1->Fit( panel1 ); + + sizer2->Add( m_text, 0, wxGROW | wxTOP, 4 ); + sizer2->Add( slider, 0, wxCENTRE | wxALL, 4 ); + + panel2->SetSizer( sizer2 ); + panel2->SetAutoLayout( TRUE ); + sizer2->Fit( panel2 ); + + sizer3->Add( panel1, 1, wxGROW ); + sizer3->Add( panel2, 0, wxGROW ); + SetSizer( sizer3 ); + SetAutoLayout( TRUE ); + sizer3->Fit( this ); + + // set minimum frame size + sizer3->SetSizeHints( this ); + + // navigator frame + m_navigator = new LifeNavigator(this); } LifeFrame::~LifeFrame() @@ -228,7 +340,7 @@ void LifeFrame::UpdateInfoText() { wxString msg; - msg.Printf(_(" Generation: %u (T: %u ms), Population: %u "), + msg.Printf(_(" Generation: %u (T: %u ms), Population: %u "), m_tics, m_topspeed? 0 : m_interval, m_life->GetNumCells()); @@ -240,36 +352,71 @@ void LifeFrame::UpdateInfoText() // way to do this. void LifeFrame::UpdateUI() { + // start / stop GetToolBar()->EnableTool(ID_START, !m_running); GetToolBar()->EnableTool(ID_STOP, m_running); - GetMenuBar()->GetMenu(1)->Enable(ID_START, !m_running); - GetMenuBar()->GetMenu(1)->Enable(ID_STEP, !m_running); - GetMenuBar()->GetMenu(1)->Enable(ID_STOP, m_running); + GetMenuBar()->GetMenu(2)->Enable(ID_START, !m_running); + GetMenuBar()->GetMenu(2)->Enable(ID_STEP, !m_running); + GetMenuBar()->GetMenu(2)->Enable(ID_STOP, m_running); + + // zooming + int cellsize = m_canvas->GetCellSize(); + GetToolBar()->EnableTool(ID_ZOOMIN, cellsize < 32); + GetToolBar()->EnableTool(ID_ZOOMOUT, cellsize > 1); + GetMenuBar()->GetMenu(1)->Enable(ID_ZOOMIN, cellsize < 32); + GetMenuBar()->GetMenu(1)->Enable(ID_ZOOMOUT, cellsize > 1); } -// event handlers +// Event handlers ----------------------------------------------------------- + +// OnMenu handles all events which don't have their own event handler void LifeFrame::OnMenu(wxCommandEvent& event) { switch (event.GetId()) { - case ID_CENTER : m_canvas->Recenter(0, 0); break; - case ID_START : OnStart(); break; - case ID_STEP : OnTimer(); break; - case ID_STOP : OnStop(); break; - case ID_ZOOMIN : + case ID_NEW: + { + // stop if it was running + OnStop(); + m_life->Clear(); + m_canvas->Recenter(0, 0); + m_tics = 0; + UpdateInfoText(); + break; + } + case ID_ABOUT: + { + LifeAboutDialog dialog(this); + dialog.ShowModal(); + break; + } + case ID_EXIT: + { + // TRUE is to force the frame to close + Close(TRUE); + break; + } + case ID_SHOWNAV : { - int cellsize = m_canvas->GetCellSize(); - if (cellsize < 32) - m_canvas->SetCellSize(cellsize * 2); + bool checked = GetMenuBar()->GetMenu(1)->IsChecked(ID_SHOWNAV); + m_navigator->Show(checked); break; } - case ID_ZOOMOUT : + case ID_INFO: { - int cellsize = m_canvas->GetCellSize(); - if (cellsize > 1) - m_canvas->SetCellSize(cellsize / 2); + wxString desc = m_life->GetDescription(); + + if ( desc.IsEmpty() ) + desc = _("Not available"); + + // should we make the description editable here? + wxMessageBox(desc, _("Description"), wxOK | wxICON_INFORMATION); + break; } + case ID_START : OnStart(); break; + case ID_STEP : OnStep(); break; + case ID_STOP : OnStop(); break; case ID_TOPSPEED: { m_running = TRUE; @@ -277,46 +424,44 @@ void LifeFrame::OnMenu(wxCommandEvent& event) UpdateUI(); while (m_running && m_topspeed) { - OnTimer(); + OnStep(); wxYield(); } break; } - case ID_RESET: + } +} + +void LifeFrame::OnOpen(wxCommandEvent& WXUNUSED(event)) +{ + wxFileDialog filedlg(this, + _("Choose a file to open"), + _(""), + _(""), + _("Life patterns (*.lif)|*.lif|All files (*.*)|*.*"), + wxOPEN | wxFILE_MUST_EXIST); + + if (filedlg.ShowModal() == wxID_OK) + { + wxFileInputStream stream(filedlg.GetFilename()); + LifeReader reader(stream); + + // the reader handles errors itself, no need to do anything here + if (reader.IsOk()) { - // stop if it was running + // stop if running and put the pattern OnStop(); m_life->Clear(); + m_life->SetPattern(reader.GetPattern()); + + // recenter canvas m_canvas->Recenter(0, 0); m_tics = 0; UpdateInfoText(); - break; - } - case ID_ABOUT: - { - LifeAboutDialog dialog(this); - dialog.ShowModal(); - break; - } - case ID_EXIT : - { - // TRUE is to force the frame to close - Close(TRUE); - break; } } } -void LifeFrame::OnClose(wxCloseEvent& WXUNUSED(event)) -{ - // Stop if it was running; this is absolutely needed because - // the frame won't be actually destroyed until there are no - // more pending events, and this in turn won't ever happen - // if the timer is running faster than the window can redraw. - OnStop(); - Destroy(); -} - void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event)) { // stop if it was running @@ -325,14 +470,13 @@ void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event)) // dialog box LifeSamplesDialog dialog(this); - // new game? if (dialog.ShowModal() == wxID_OK) { - const LifeShape shape = dialog.GetShape(); + const LifePattern pattern = dialog.GetPattern(); - // put the shape + // put the pattern m_life->Clear(); - m_life->SetShape(shape); + m_life->SetPattern(pattern); // recenter canvas m_canvas->Recenter(0, 0); @@ -341,6 +485,67 @@ void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event)) } } +void LifeFrame::OnZoom(wxCommandEvent& event) +{ + int cellsize = m_canvas->GetCellSize(); + + if ((event.GetId() == ID_ZOOMIN) && cellsize < 32) + { + m_canvas->SetCellSize(cellsize * 2); + UpdateUI(); + } + else if ((event.GetId() == ID_ZOOMOUT) && cellsize > 1) + { + m_canvas->SetCellSize(cellsize / 2); + UpdateUI(); + } +} + +void LifeFrame::OnNavigate(wxCommandEvent& event) +{ + Cell c; + + switch (event.GetId()) + { + case ID_NORTH: c = m_life->FindNorth(); break; + case ID_SOUTH: c = m_life->FindSouth(); break; + case ID_WEST: c = m_life->FindWest(); break; + case ID_EAST: c = m_life->FindEast(); break; + case ID_CENTER: c = m_life->FindCenter(); break; + case ID_ORIGIN: c.i = c.j = 0; break; + } + + m_canvas->Recenter(c.i, c.j); +} + +void LifeFrame::OnSlider(wxScrollEvent& event) +{ + m_interval = event.GetPosition() * 100; + + if (m_running) + { + OnStop(); + OnStart(); + } + + UpdateInfoText(); +} + +void LifeFrame::OnTimer(wxTimerEvent& WXUNUSED(event)) +{ + OnStep(); +} + +void LifeFrame::OnClose(wxCloseEvent& WXUNUSED(event)) +{ + // Stop if it was running; this is absolutely needed because + // the frame won't be actually destroyed until there are no + // more pending events, and this in turn won't ever happen + // if the timer is running faster than the window can redraw. + OnStop(); + Destroy(); +} + void LifeFrame::OnStart() { if (!m_running) @@ -362,7 +567,7 @@ void LifeFrame::OnStop() } } -void LifeFrame::OnTimer() +void LifeFrame::OnStep() { if (m_life->NextTic()) m_tics++; @@ -373,27 +578,90 @@ void LifeFrame::OnTimer() UpdateInfoText(); } -void LifeFrame::OnSlider(wxScrollEvent& event) -{ - m_interval = event.GetPosition() * 100; - - if (m_running) - { - OnStop(); - OnStart(); - } - - UpdateInfoText(); -} // -------------------------------------------------------------------------- -// LifeTimer +// LifeNavigator miniframe // -------------------------------------------------------------------------- -void LifeTimer::Notify() +LifeNavigator::LifeNavigator(wxWindow *parent) + : wxMiniFrame(parent, -1, + _("Navigation"), + wxDefaultPosition, + wxDefaultSize, + wxCAPTION | wxSIMPLE_BORDER) { - GET_FRAME()->OnTimer(); -}; + wxPanel *panel = new wxPanel(this, -1); + wxBoxSizer *sizer1 = new wxBoxSizer(wxVERTICAL); + wxBoxSizer *sizer2 = new wxBoxSizer(wxHORIZONTAL); + + // create bitmaps and masks for the buttons + wxBitmap + bmpn = wxBITMAP(north), + bmpw = wxBITMAP(west), + bmpc = wxBITMAP(center), + bmpe = wxBITMAP(east), + bmps = wxBITMAP(south); + +#if !defined(__WXGTK__) && !defined(__WXMOTIF__) + bmpn.SetMask(new wxMask(bmpn, *wxLIGHT_GREY)); + bmpw.SetMask(new wxMask(bmpw, *wxLIGHT_GREY)); + bmpc.SetMask(new wxMask(bmpc, *wxLIGHT_GREY)); + bmpe.SetMask(new wxMask(bmpe, *wxLIGHT_GREY)); + bmps.SetMask(new wxMask(bmps, *wxLIGHT_GREY)); +#endif + + // create the buttons and attach tooltips to them + wxBitmapButton + *bn = new wxBitmapButton(panel, ID_NORTH, bmpn), + *bw = new wxBitmapButton(panel, ID_WEST , bmpw), + *bc = new wxBitmapButton(panel, ID_CENTER, bmpc), + *be = new wxBitmapButton(panel, ID_EAST , bmpe), + *bs = new wxBitmapButton(panel, ID_SOUTH, bmps); + +#if wxUSE_TOOLTIPS + bn->SetToolTip(_("Find northernmost cell")); + bw->SetToolTip(_("Find westernmost cell")); + bc->SetToolTip(_("Find center of mass")); + be->SetToolTip(_("Find easternmost cell")); + bs->SetToolTip(_("Find southernmost cell")); +#endif + + // add buttons to sizers + sizer2->Add( bw, 0, wxCENTRE | wxWEST, 4 ); + sizer2->Add( bc, 0, wxCENTRE); + sizer2->Add( be, 0, wxCENTRE | wxEAST, 4 ); + sizer1->Add( bn, 0, wxCENTRE | wxNORTH, 4 ); + sizer1->Add( sizer2 ); + sizer1->Add( bs, 0, wxCENTRE | wxSOUTH, 4 ); + + // set the miniframe size + panel->SetSizer(sizer1); + panel->SetAutoLayout(TRUE); + sizer1->Fit(this); + sizer1->SetSizeHints(this); + + // move it to a sensible position + wxRect parentRect = parent->GetRect(); + wxSize childSize = GetSize(); + int x = parentRect.GetX() + + parentRect.GetWidth(); + int y = parentRect.GetY() + + (parentRect.GetHeight() - childSize.GetHeight()) / 4; + Move(x, y); + + // done + Show(TRUE); +} + +void LifeNavigator::OnClose(wxCloseEvent& event) +{ + // avoid if we can + if (event.CanVeto()) + event.Veto(); + else + Destroy(); +} + // -------------------------------------------------------------------------- // LifeCanvas @@ -445,7 +713,7 @@ void LifeCanvas::SetCellSize(int cellsize) wxInt32 cy = m_viewportY + m_viewportH / 2; // get current canvas size and adjust viewport accordingly - wxCoord w, h; + int w, h; GetClientSize(&w, &h); m_viewportW = (w + m_cellsize - 1) / m_cellsize; m_viewportH = (h + m_cellsize - 1) / m_cellsize; @@ -514,19 +782,17 @@ void LifeCanvas::DrawChanged() TRUE); dc.BeginDrawing(); - dc.SetLogicalFunction(wxINVERT); if (m_cellsize == 1) { - // drawn using DrawPoint dc.SetPen(*wxBLACK_PEN); } else { - // drawn using DrawRectangle dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush(*wxBLACK_BRUSH); } + dc.SetLogicalFunction(wxINVERT); while (!done) { @@ -619,30 +885,103 @@ void LifeCanvas::OnMouse(wxMouseEvent& event) // set statusbar text wxString msg; msg.Printf(_("Cell: (%d, %d)"), i, j); - GET_FRAME()->SetStatusText(msg, 1); + ((LifeFrame *) wxGetApp().GetTopWindow())->SetStatusText(msg, 1); + + // NOTE that wxMouseEvent::LeftDown() and wxMouseEvent::LeftIsDown() + // have different semantics. The first one is used to signal that the + // button was just pressed (i.e., in "button down" events); the second + // one just describes the current status of the button, independently + // of the mouse event type. LeftIsDown is typically used in "mouse + // move" events, to test if the button is _still_ pressed. - // button pressed? + // is the button down? if (!event.LeftIsDown()) { m_status = MOUSE_NOACTION; + return; } - else - { - bool alive = m_life->IsAlive(i, j); - - // if just pressed, update status - if (m_status == MOUSE_NOACTION) - m_status = (alive? MOUSE_ERASING : MOUSE_DRAWING); - // toggle cell and refresh if needed - if (((m_status == MOUSE_ERASING) && alive) || - ((m_status == MOUSE_DRAWING) && !alive)) + // was it pressed just now? + if (event.LeftDown()) + { + // yes: start a new action and toggle this cell + m_status = (m_life->IsAlive(i, j)? MOUSE_ERASING : MOUSE_DRAWING); + + m_mi = i; + m_mj = j; + m_life->SetCell(i, j, m_status == MOUSE_DRAWING); + DrawCell(i, j, m_status == MOUSE_DRAWING); + } + else if ((m_mi != i) || (m_mj != j)) + { + // no: continue ongoing action + bool alive = (m_status == MOUSE_DRAWING); + + // prepare DC and pen + brush to optimize drawing + wxClientDC dc(this); + dc.SetPen(alive? *wxBLACK_PEN : *wxWHITE_PEN); + dc.SetBrush(alive? *wxBLACK_BRUSH : *wxWHITE_BRUSH); + dc.BeginDrawing(); + + // draw a line of cells using Bresenham's algorithm + wxInt32 d, ii, jj, di, ai, si, dj, aj, sj; + di = i - m_mi; + ai = abs(di) << 1; + si = (di < 0)? -1 : 1; + dj = j - m_mj; + aj = abs(dj) << 1; + sj = (dj < 0)? -1 : 1; + + ii = m_mi; + jj = m_mj; + + if (ai > aj) { - m_life->SetCell(i, j, !alive); - DrawCell(i, j, !alive); - GET_FRAME()->UpdateInfoText(); + // iterate over i + d = aj - (ai >> 1); + + while (ii != i) + { + m_life->SetCell(ii, jj, alive); + DrawCell(ii, jj, dc); + if (d >= 0) + { + jj += sj; + d -= ai; + } + ii += si; + d += aj; + } + } + else + { + // iterate over j + d = ai - (aj >> 1); + + while (jj != j) + { + m_life->SetCell(ii, jj, alive); + DrawCell(ii, jj, dc); + if (d >= 0) + { + ii += si; + d -= aj; + } + jj += sj; + d += ai; + } } + + // last cell + m_life->SetCell(ii, jj, alive); + DrawCell(ii, jj, dc); + m_mi = ii; + m_mj = jj; + + dc.EndDrawing(); } + + ((LifeFrame *) wxGetApp().GetTopWindow())->UpdateInfoText(); } void LifeCanvas::OnSize(wxSizeEvent& event) @@ -679,10 +1018,9 @@ void LifeCanvas::OnScroll(wxScrollWinEvent& event) WXTYPE type = event.GetEventType(); int pos = event.GetPosition(); int orient = event.GetOrientation(); - bool scrolling = event.IsScrolling(); - int scrollinc = 0; // calculate scroll increment + int scrollinc = 0; switch (type) { case wxEVT_SCROLLWIN_TOP: @@ -707,33 +1045,29 @@ void LifeCanvas::OnScroll(wxScrollWinEvent& event) case wxEVT_SCROLLWIN_PAGEDOWN: scrollinc = +10; break; case wxEVT_SCROLLWIN_THUMBTRACK: { - if (scrolling) + if (orient == wxHORIZONTAL) { - // user is dragging the thumb in the scrollbar - if (orient == wxHORIZONTAL) - { - scrollinc = pos - m_thumbX; - m_thumbX = pos; - } - else - { - scrollinc = pos - m_thumbY; - m_thumbY = pos; - } + scrollinc = pos - m_thumbX; + m_thumbX = pos; } else { - // user released the thumb after dragging - m_thumbX = m_viewportW; - m_thumbY = m_viewportH; + scrollinc = pos - m_thumbY; + m_thumbY = pos; } break; } + case wxEVT_SCROLLWIN_THUMBRELEASE: + { + m_thumbX = m_viewportW; + m_thumbY = m_viewportH; + } } -#ifdef __WXGTK__ // what about Motif? - // wxGTK updates the thumb automatically (wxMSW doesn't); reset it back - if ((type != wxEVT_SCROLLWIN_THUMBTRACK) || !scrolling) +#if defined(__WXGTK__) || defined(__WXMOTIF__) + // wxGTK and wxMotif update the thumb automatically (wxMSW doesn't); + // so reset it back as we always want it to be in the same position. + if (type != wxEVT_SCROLLWIN_THUMBTRACK) { SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW); SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH); @@ -759,3 +1093,5 @@ void LifeCanvas::OnEraseBackground(wxEraseEvent& WXUNUSED(event)) { // do nothing. I just don't want the background to be erased, you know. } + +