X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/d90d994c74e4fe1b046fae276e1aae1dd2c4e684..2dace059ee7aefeea0f2b843f1808b117f112795:/demos/life/life.cpp diff --git a/demos/life/life.cpp b/demos/life/life.cpp index 44bca3027e..0043a399cc 100644 --- a/demos/life/life.cpp +++ b/demos/life/life.cpp @@ -29,25 +29,30 @@ #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__) +#if defined(__WXGTK__) || defined(__WXMOTIF__) || defined(__WXMAC__) // 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" @@ -67,22 +72,14 @@ enum // timer ID_TIMER = 1001, - // menu items and toolbar buttons - ID_RESET, + // file menu + ID_NEW, + ID_OPEN, ID_SAMPLES, ID_ABOUT, ID_EXIT, - ID_START, - ID_STEP, - ID_STOP, - ID_ZOOMIN, - ID_ZOOMOUT, - ID_TOPSPEED, - - // speed selection slider - ID_SLIDER, - // navigation + // view menu ID_SHOWNAV, ID_ORIGIN, ID_CENTER, @@ -90,6 +87,18 @@ enum ID_SOUTH, ID_EAST, ID_WEST, + ID_ZOOMIN, + ID_ZOOMOUT, + ID_INFO, + + // game menu + ID_START, + ID_STEP, + ID_STOP, + ID_TOPSPEED, + + // speed selection slider + ID_SLIDER, }; // -------------------------------------------------------------------------- @@ -98,14 +107,11 @@ 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_START, LifeFrame::OnMenu) - EVT_MENU (ID_STEP, LifeFrame::OnMenu) - EVT_MENU (ID_STOP, LifeFrame::OnMenu) - EVT_MENU (ID_TOPSPEED, LifeFrame::OnMenu) EVT_MENU (ID_SHOWNAV, LifeFrame::OnMenu) EVT_MENU (ID_ORIGIN, LifeFrame::OnNavigate) EVT_BUTTON (ID_CENTER, LifeFrame::OnNavigate) @@ -115,6 +121,11 @@ BEGIN_EVENT_TABLE(LifeFrame, wxFrame) 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_TOPSPEED, LifeFrame::OnMenu) EVT_COMMAND_SCROLL (ID_SLIDER, LifeFrame::OnSlider) EVT_TIMER (ID_TIMER, LifeFrame::OnTimer) EVT_CLOSE ( LifeFrame::OnClose) @@ -128,7 +139,10 @@ 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() @@ -142,7 +156,7 @@ 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) @@ -160,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; } @@ -179,32 +198,34 @@ LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(200, 200) 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")); - menuView->Append(ID_SHOWNAV, _("Navigation toolbox"), _("Show or hide toolbox"), TRUE); + 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->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")); - menuView->Append(ID_ZOOMOUT, _("Zoom &out\tCtrl-O")); + 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->Append(ID_TOPSPEED, _("T&op speed!"), _("Go as fast as possible")); wxMenuBar *menuBar = new wxMenuBar(); menuBar->Append(menuFile, _("&File")); @@ -213,23 +234,30 @@ LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(200, 200) 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[3], _("Zoom in"), _("Zoom in")); - ADD_TOOL(ID_ZOOMOUT, tbBitmaps[4], _("Zoom out"), _("Zoom out")); + 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_START, tbBitmaps[5], _("Start"), _("Start")); + ADD_TOOL(ID_STOP, tbBitmaps[6], _("Stop"), _("Stop")); + toolBar->Realize(); toolBar->EnableTool(ID_STOP, FALSE); // must be after Realize() ! @@ -285,6 +313,7 @@ LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(200, 200) sizer2->Add( m_text, 0, wxGROW | wxTOP, 4 ); sizer2->Add( slider, 0, wxCENTRE | wxALL, 4 ); + panel2->SetSizer( sizer2 ); panel2->SetAutoLayout( TRUE ); sizer2->Fit( panel2 ); @@ -338,20 +367,56 @@ void LifeFrame::UpdateUI() 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_START : OnStart(); break; - case ID_STEP : OnStep(); break; - case ID_STOP : OnStop(); break; + 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 : { bool checked = GetMenuBar()->GetMenu(1)->IsChecked(ID_SHOWNAV); m_navigator->Show(checked); break; } + case ID_INFO: + { + 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; @@ -364,41 +429,39 @@ void LifeFrame::OnMenu(wxCommandEvent& event) } 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.GetPath()); + 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 @@ -409,11 +472,11 @@ void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event)) 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); @@ -440,11 +503,11 @@ void LifeFrame::OnZoom(wxCommandEvent& event) void LifeFrame::OnNavigate(wxCommandEvent& event) { - Cell c; + LifeCell c; switch (event.GetId()) { - case ID_NORTH: c = m_life->FindNorth(); break; + 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; @@ -473,6 +536,16 @@ 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) @@ -529,7 +602,7 @@ LifeNavigator::LifeNavigator(wxWindow *parent) bmpe = wxBITMAP(east), bmps = wxBITMAP(south); -#ifdef __WXMSW__ +#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)); @@ -545,11 +618,13 @@ LifeNavigator::LifeNavigator(wxWindow *parent) *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 ); @@ -580,6 +655,7 @@ LifeNavigator::LifeNavigator(wxWindow *parent) void LifeNavigator::OnClose(wxCloseEvent& event) { + // avoid if we can if (event.CanVeto()) event.Veto(); else @@ -696,7 +772,7 @@ void LifeCanvas::DrawChanged() wxClientDC dc(this); size_t ncells; - Cell *cells; + LifeCell *cells; bool done = FALSE; m_life->BeginFind(m_viewportX, @@ -748,7 +824,7 @@ void LifeCanvas::OnPaint(wxPaintEvent& event) j1 = YToCell(y + h - 1); size_t ncells; - Cell *cells; + LifeCell *cells; bool done = FALSE; m_life->BeginFind(i0, j0, i1, j1, FALSE); @@ -811,17 +887,24 @@ void LifeCanvas::OnMouse(wxMouseEvent& event) msg.Printf(_("Cell: (%d, %d)"), i, j); ((LifeFrame *) wxGetApp().GetTopWindow())->SetStatusText(msg, 1); - // button pressed? + // 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. + + // is the button down? if (!event.LeftIsDown()) { m_status = MOUSE_NOACTION; return; } - // button just pressed? - if (m_status == MOUSE_NOACTION) + // was it pressed just now? + if (event.LeftDown()) { - // yes, update status and toggle this cell + // yes: start a new action and toggle this cell m_status = (m_life->IsAlive(i, j)? MOUSE_ERASING : MOUSE_DRAWING); m_mi = i; @@ -831,6 +914,7 @@ void LifeCanvas::OnMouse(wxMouseEvent& event) } 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 @@ -937,51 +1021,65 @@ void LifeCanvas::OnScroll(wxScrollWinEvent& event) // calculate scroll increment int scrollinc = 0; - switch (type) + if (type == wxEVT_SCROLLWIN_TOP) { - case wxEVT_SCROLLWIN_TOP: - { - if (orient == wxHORIZONTAL) - scrollinc = -m_viewportW; - else - scrollinc = -m_viewportH; - break; - } - case wxEVT_SCROLLWIN_BOTTOM: - { - if (orient == wxHORIZONTAL) - scrollinc = m_viewportW; - else - scrollinc = m_viewportH; - break; - } - case wxEVT_SCROLLWIN_LINEUP: scrollinc = -1; break; - case wxEVT_SCROLLWIN_LINEDOWN: scrollinc = +1; break; - case wxEVT_SCROLLWIN_PAGEUP: scrollinc = -10; break; - case wxEVT_SCROLLWIN_PAGEDOWN: scrollinc = +10; break; - case wxEVT_SCROLLWIN_THUMBTRACK: + if (orient == wxHORIZONTAL) + scrollinc = -m_viewportW; + else + scrollinc = -m_viewportH; + } + else + if (type == wxEVT_SCROLLWIN_BOTTOM) + { + if (orient == wxHORIZONTAL) + scrollinc = m_viewportW; + else + scrollinc = m_viewportH; + } + else + if (type == wxEVT_SCROLLWIN_LINEUP) + { + scrollinc = -1; + } + else + if (type == wxEVT_SCROLLWIN_LINEDOWN) + { + scrollinc = +1; + } + else + if (type == wxEVT_SCROLLWIN_PAGEUP) + { + scrollinc = -10; + } + else + if (type == wxEVT_SCROLLWIN_PAGEDOWN) + { + scrollinc = -10; + } + else + if (type == wxEVT_SCROLLWIN_THUMBTRACK) + { + if (orient == wxHORIZONTAL) { - if (orient == wxHORIZONTAL) - { - scrollinc = pos - m_thumbX; - m_thumbX = pos; - } - else - { - scrollinc = pos - m_thumbY; - m_thumbY = pos; - } - break; + scrollinc = pos - m_thumbX; + m_thumbX = pos; } - case wxEVT_SCROLLWIN_THUMBRELEASE: + else { - m_thumbX = m_viewportW; - m_thumbY = m_viewportH; + scrollinc = pos - m_thumbY; + m_thumbY = pos; } } + else + if (type == wxEVT_SCROLLWIN_THUMBRELEASE) + { + m_thumbX = m_viewportW; + m_thumbY = m_viewportH; + } -#if defined (__WXGTK__) || defined(__WXMOTIF__) // what about Motif? - // wxGTK updates the thumb automatically (wxMSW doesn't); reset it back +#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);