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