life.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125
  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. // Copyright: (c) 2000, Guillermo Rodriguez Garcia
  8. // Licence: wxWindows licence
  9. /////////////////////////////////////////////////////////////////////////////
  10. // ==========================================================================
  11. // headers, declarations, constants
  12. // ==========================================================================
  13. // For compilers that support precompilation, includes "wx/wx.h".
  14. #include "wx/wxprec.h"
  15. #ifdef __BORLANDC__
  16. #pragma hdrstop
  17. #endif
  18. #ifndef WX_PRECOMP
  19. #include "wx/wx.h"
  20. #endif
  21. #include "wx/statline.h"
  22. #include "wx/wfstream.h"
  23. #include "wx/filedlg.h"
  24. #include "wx/stockitem.h"
  25. #include "life.h"
  26. #include "game.h"
  27. #include "dialogs.h"
  28. #include "reader.h"
  29. // --------------------------------------------------------------------------
  30. // resources
  31. // --------------------------------------------------------------------------
  32. #ifndef wxHAS_IMAGES_IN_RESOURCES
  33. // application icon
  34. #include "mondrian.xpm"
  35. // bitmap buttons for the toolbar
  36. #include "bitmaps/reset.xpm"
  37. #include "bitmaps/open.xpm"
  38. #include "bitmaps/play.xpm"
  39. #include "bitmaps/stop.xpm"
  40. #include "bitmaps/zoomin.xpm"
  41. #include "bitmaps/zoomout.xpm"
  42. #include "bitmaps/info.xpm"
  43. // navigator
  44. #include "bitmaps/north.xpm"
  45. #include "bitmaps/south.xpm"
  46. #include "bitmaps/east.xpm"
  47. #include "bitmaps/west.xpm"
  48. #include "bitmaps/center.xpm"
  49. #endif
  50. // --------------------------------------------------------------------------
  51. // constants
  52. // --------------------------------------------------------------------------
  53. // IDs for the controls and the menu commands. Exluding those already defined
  54. // by wxWidgets, such as wxID_NEW.
  55. enum
  56. {
  57. // timer
  58. ID_TIMER = wxID_HIGHEST,
  59. // file menu
  60. ID_SAMPLES,
  61. // view menu
  62. ID_SHOWNAV,
  63. ID_ORIGIN,
  64. ID_CENTER,
  65. ID_NORTH,
  66. ID_SOUTH,
  67. ID_EAST,
  68. ID_WEST,
  69. ID_INFO,
  70. // game menu
  71. ID_START,
  72. ID_STEP,
  73. ID_TOPSPEED,
  74. // speed selection slider
  75. ID_SLIDER
  76. };
  77. // --------------------------------------------------------------------------
  78. // event tables and other macros for wxWidgets
  79. // --------------------------------------------------------------------------
  80. // Event tables
  81. BEGIN_EVENT_TABLE(LifeFrame, wxFrame)
  82. EVT_MENU (wxID_NEW, LifeFrame::OnMenu)
  83. #if wxUSE_FILEDLG
  84. EVT_MENU (wxID_OPEN, LifeFrame::OnOpen)
  85. #endif
  86. EVT_MENU (ID_SAMPLES, LifeFrame::OnSamples)
  87. EVT_MENU (wxID_ABOUT, LifeFrame::OnMenu)
  88. EVT_MENU (wxID_EXIT, LifeFrame::OnMenu)
  89. EVT_MENU (ID_SHOWNAV, LifeFrame::OnMenu)
  90. EVT_MENU (ID_ORIGIN, LifeFrame::OnNavigate)
  91. EVT_BUTTON (ID_CENTER, LifeFrame::OnNavigate)
  92. EVT_BUTTON (ID_NORTH, LifeFrame::OnNavigate)
  93. EVT_BUTTON (ID_SOUTH, LifeFrame::OnNavigate)
  94. EVT_BUTTON (ID_EAST, LifeFrame::OnNavigate)
  95. EVT_BUTTON (ID_WEST, LifeFrame::OnNavigate)
  96. EVT_MENU (wxID_ZOOM_IN, LifeFrame::OnZoom)
  97. EVT_MENU (wxID_ZOOM_OUT,LifeFrame::OnZoom)
  98. EVT_MENU (ID_INFO, LifeFrame::OnMenu)
  99. EVT_MENU (ID_START, LifeFrame::OnMenu)
  100. EVT_MENU (ID_STEP, LifeFrame::OnMenu)
  101. EVT_MENU (wxID_STOP, LifeFrame::OnMenu)
  102. EVT_MENU (ID_TOPSPEED, LifeFrame::OnMenu)
  103. EVT_COMMAND_SCROLL (ID_SLIDER, LifeFrame::OnSlider)
  104. EVT_TIMER (ID_TIMER, LifeFrame::OnTimer)
  105. EVT_CLOSE ( LifeFrame::OnClose)
  106. END_EVENT_TABLE()
  107. BEGIN_EVENT_TABLE(LifeNavigator, wxMiniFrame)
  108. EVT_CLOSE ( LifeNavigator::OnClose)
  109. END_EVENT_TABLE()
  110. BEGIN_EVENT_TABLE(LifeCanvas, wxWindow)
  111. EVT_PAINT ( LifeCanvas::OnPaint)
  112. EVT_SCROLLWIN ( LifeCanvas::OnScroll)
  113. EVT_SIZE ( LifeCanvas::OnSize)
  114. EVT_MOTION ( LifeCanvas::OnMouse)
  115. EVT_LEFT_DOWN ( LifeCanvas::OnMouse)
  116. EVT_LEFT_UP ( LifeCanvas::OnMouse)
  117. EVT_LEFT_DCLICK ( LifeCanvas::OnMouse)
  118. EVT_ERASE_BACKGROUND( LifeCanvas::OnEraseBackground)
  119. END_EVENT_TABLE()
  120. // Create a new application object
  121. IMPLEMENT_APP(LifeApp)
  122. // ==========================================================================
  123. // implementation
  124. // ==========================================================================
  125. // some shortcuts
  126. #define ADD_TOOL(id, bmp, tooltip, help) \
  127. toolBar->AddTool(id, wxEmptyString, bmp, wxNullBitmap, wxITEM_NORMAL, tooltip, help)
  128. // --------------------------------------------------------------------------
  129. // LifeApp
  130. // --------------------------------------------------------------------------
  131. // 'Main program' equivalent: the program execution "starts" here
  132. bool LifeApp::OnInit()
  133. {
  134. // create the main application window
  135. LifeFrame *frame = new LifeFrame();
  136. // show it
  137. frame->Show(true);
  138. // just for Motif
  139. #ifdef __WXMOTIF__
  140. frame->UpdateInfoText();
  141. #endif
  142. // enter the main message loop and run the app
  143. return true;
  144. }
  145. // --------------------------------------------------------------------------
  146. // LifeFrame
  147. // --------------------------------------------------------------------------
  148. // frame constructor
  149. LifeFrame::LifeFrame() :
  150. wxFrame( (wxFrame *) NULL, wxID_ANY, _("Life!"), wxDefaultPosition ),
  151. m_navigator(NULL)
  152. {
  153. // frame icon
  154. SetIcon(wxICON(mondrian));
  155. // menu bar
  156. wxMenu *menuFile = new wxMenu(wxMENU_TEAROFF);
  157. wxMenu *menuView = new wxMenu(wxMENU_TEAROFF);
  158. wxMenu *menuGame = new wxMenu(wxMENU_TEAROFF);
  159. wxMenu *menuHelp = new wxMenu(wxMENU_TEAROFF);
  160. menuFile->Append(wxID_NEW, wxEmptyString, _("Start a new game"));
  161. #if wxUSE_FILEDLG
  162. menuFile->Append(wxID_OPEN, wxEmptyString, _("Open an existing Life pattern"));
  163. #endif
  164. menuFile->Append(ID_SAMPLES, _("&Sample game..."), _("Select a sample configuration"));
  165. #if ! (defined(__SMARTPHONE__) || defined(__POCKETPC__))
  166. menuFile->AppendSeparator();
  167. menuFile->Append(wxID_EXIT);
  168. menuView->Append(ID_SHOWNAV, _("Navigation &toolbox"), _("Show or hide toolbox"), wxITEM_CHECK);
  169. menuView->Check(ID_SHOWNAV, true);
  170. menuView->AppendSeparator();
  171. #endif
  172. menuView->Append(ID_ORIGIN, _("&Absolute origin"), _("Go to (0, 0)"));
  173. menuView->Append(ID_CENTER, _("&Center of mass"), _("Find center of mass"));
  174. menuView->Append(ID_NORTH, _("&North"), _("Find northernmost cell"));
  175. menuView->Append(ID_SOUTH, _("&South"), _("Find southernmost cell"));
  176. menuView->Append(ID_EAST, _("&East"), _("Find easternmost cell"));
  177. menuView->Append(ID_WEST, _("&West"), _("Find westernmost cell"));
  178. menuView->AppendSeparator();
  179. menuView->Append(wxID_ZOOM_IN, wxEmptyString, _("Zoom in"));
  180. menuView->Append(wxID_ZOOM_OUT, wxEmptyString, _("Zoom out"));
  181. menuView->Append(ID_INFO, _("&Description\tCtrl-D"), _("View pattern description"));
  182. menuGame->Append(ID_START, _("&Start\tCtrl-S"), _("Start"));
  183. menuGame->Append(ID_STEP, _("&Next\tCtrl-N"), _("Single step"));
  184. menuGame->Append(wxID_STOP, wxEmptyString, _("Stop"));
  185. menuGame->Enable(wxID_STOP, false);
  186. menuGame->AppendSeparator();
  187. menuGame->Append(ID_TOPSPEED, _("T&op speed!"), _("Go as fast as possible"));
  188. menuHelp->Append(wxID_ABOUT, _("&About\tCtrl-A"), _("Show about dialog"));
  189. wxMenuBar *menuBar = new wxMenuBar();
  190. menuBar->Append(menuFile, _("&File"));
  191. menuBar->Append(menuView, _("&View"));
  192. menuBar->Append(menuGame, _("&Game"));
  193. menuBar->Append(menuHelp, _("&Help"));
  194. SetMenuBar(menuBar);
  195. // tool bar
  196. wxBitmap tbBitmaps[7];
  197. tbBitmaps[0] = wxBITMAP(reset);
  198. tbBitmaps[1] = wxBITMAP(open);
  199. tbBitmaps[2] = wxBITMAP(zoomin);
  200. tbBitmaps[3] = wxBITMAP(zoomout);
  201. tbBitmaps[4] = wxBITMAP(info);
  202. tbBitmaps[5] = wxBITMAP(play);
  203. tbBitmaps[6] = wxBITMAP(stop);
  204. wxToolBar *toolBar = CreateToolBar();
  205. toolBar->SetMargins(5, 5);
  206. toolBar->SetToolBitmapSize(wxSize(16, 16));
  207. ADD_TOOL(wxID_NEW, tbBitmaps[0], wxGetStockLabel(wxID_NEW, wxSTOCK_NOFLAGS), _("Start a new game"));
  208. #ifndef __POCKETPC__
  209. #if wxUSE_FILEDLG
  210. ADD_TOOL(wxID_OPEN, tbBitmaps[1], wxGetStockLabel(wxID_OPEN, wxSTOCK_NOFLAGS), _("Open an existing Life pattern"));
  211. #endif // wxUSE_FILEDLG
  212. toolBar->AddSeparator();
  213. ADD_TOOL(wxID_ZOOM_IN, tbBitmaps[2], wxGetStockLabel(wxID_ZOOM_IN, wxSTOCK_NOFLAGS), _("Zoom in"));
  214. ADD_TOOL(wxID_ZOOM_OUT, tbBitmaps[3], wxGetStockLabel(wxID_ZOOM_OUT, wxSTOCK_NOFLAGS), _("Zoom out"));
  215. ADD_TOOL(ID_INFO, tbBitmaps[4], _("Description"), _("Show description"));
  216. toolBar->AddSeparator();
  217. #endif // __POCKETPC__
  218. ADD_TOOL(ID_START, tbBitmaps[5], _("Start"), _("Start"));
  219. ADD_TOOL(wxID_STOP, tbBitmaps[6], _("Stop"), _("Stop"));
  220. toolBar->Realize();
  221. toolBar->EnableTool(wxID_STOP, false); // must be after Realize() !
  222. #if wxUSE_STATUSBAR
  223. // status bar
  224. CreateStatusBar(2);
  225. SetStatusText(_("Welcome to Life!"));
  226. #endif // wxUSE_STATUSBAR
  227. // game and timer
  228. m_life = new Life();
  229. m_timer = new wxTimer(this, ID_TIMER);
  230. m_running = false;
  231. m_topspeed = false;
  232. m_interval = 500;
  233. m_tics = 0;
  234. // We use two different panels to reduce flicker in wxGTK, because
  235. // some widgets (like wxStaticText) don't have their own X11 window,
  236. // and thus updating the text would result in a refresh of the canvas
  237. // if they belong to the same parent.
  238. wxPanel *panel1 = new wxPanel(this, wxID_ANY);
  239. wxPanel *panel2 = new wxPanel(this, wxID_ANY);
  240. // canvas
  241. m_canvas = new LifeCanvas(panel1, m_life);
  242. // info panel
  243. m_text = new wxStaticText(panel2, wxID_ANY,
  244. wxEmptyString,
  245. wxDefaultPosition,
  246. wxDefaultSize,
  247. wxALIGN_CENTER | wxST_NO_AUTORESIZE);
  248. wxSlider *slider = new wxSlider(panel2, ID_SLIDER,
  249. 5, 1, 10,
  250. wxDefaultPosition,
  251. wxSize(200, wxDefaultCoord),
  252. wxSL_HORIZONTAL | wxSL_AUTOTICKS);
  253. UpdateInfoText();
  254. // component layout
  255. wxBoxSizer *sizer1 = new wxBoxSizer(wxVERTICAL);
  256. wxBoxSizer *sizer2 = new wxBoxSizer(wxVERTICAL);
  257. wxBoxSizer *sizer3 = new wxBoxSizer(wxVERTICAL);
  258. #if wxUSE_STATLINE
  259. sizer1->Add( new wxStaticLine(panel1, wxID_ANY), 0, wxGROW );
  260. #endif // wxUSE_STATLINE
  261. sizer1->Add( m_canvas, 1, wxGROW | wxALL, 2 );
  262. #if wxUSE_STATLINE
  263. sizer1->Add( new wxStaticLine(panel1, wxID_ANY), 0, wxGROW );
  264. #endif // wxUSE_STATLINE
  265. panel1->SetSizer( sizer1 );
  266. sizer1->Fit( panel1 );
  267. sizer2->Add( m_text, 0, wxGROW | wxTOP, 4 );
  268. sizer2->Add( slider, 0, wxCENTRE | wxALL, 4 );
  269. panel2->SetSizer( sizer2 );
  270. sizer2->Fit( panel2 );
  271. sizer3->Add( panel1, 1, wxGROW );
  272. sizer3->Add( panel2, 0, wxGROW );
  273. SetSizer( sizer3 );
  274. #ifndef __WXWINCE__
  275. sizer3->Fit( this );
  276. // set minimum frame size
  277. sizer3->SetSizeHints( this );
  278. // navigator frame - not appropriate for small devices
  279. m_navigator = new LifeNavigator(this);
  280. #endif
  281. }
  282. LifeFrame::~LifeFrame()
  283. {
  284. delete m_timer;
  285. }
  286. void LifeFrame::UpdateInfoText()
  287. {
  288. wxString msg;
  289. msg.Printf(_(" Generation: %lu (T: %lu ms), Population: %lu "),
  290. m_tics,
  291. m_topspeed? 0 : m_interval,
  292. static_cast<unsigned long>(m_life->GetNumCells()));
  293. m_text->SetLabel(msg);
  294. }
  295. // Enable or disable tools and menu entries according to the current
  296. // state. See also wxEVT_UPDATE_UI events for a slightly different
  297. // way to do this.
  298. void LifeFrame::UpdateUI()
  299. {
  300. // start / stop
  301. GetToolBar()->EnableTool(ID_START, !m_running);
  302. GetToolBar()->EnableTool(wxID_STOP, m_running);
  303. GetMenuBar()->Enable(ID_START, !m_running);
  304. GetMenuBar()->Enable(ID_STEP, !m_running);
  305. GetMenuBar()->Enable(wxID_STOP, m_running);
  306. GetMenuBar()->Enable(ID_TOPSPEED, !m_topspeed);
  307. // zooming
  308. int cellsize = m_canvas->GetCellSize();
  309. GetToolBar()->EnableTool(wxID_ZOOM_IN, cellsize < 32);
  310. GetToolBar()->EnableTool(wxID_ZOOM_OUT, cellsize > 1);
  311. GetMenuBar()->Enable(wxID_ZOOM_IN, cellsize < 32);
  312. GetMenuBar()->Enable(wxID_ZOOM_OUT, cellsize > 1);
  313. }
  314. // Event handlers -----------------------------------------------------------
  315. // OnMenu handles all events which don't have their own event handler
  316. void LifeFrame::OnMenu(wxCommandEvent& event)
  317. {
  318. switch (event.GetId())
  319. {
  320. case wxID_NEW:
  321. {
  322. // stop if it was running
  323. OnStop();
  324. m_life->Clear();
  325. m_canvas->Recenter(0, 0);
  326. m_tics = 0;
  327. UpdateInfoText();
  328. break;
  329. }
  330. case wxID_ABOUT:
  331. {
  332. LifeAboutDialog dialog(this);
  333. dialog.ShowModal();
  334. break;
  335. }
  336. case wxID_EXIT:
  337. {
  338. // true is to force the frame to close
  339. Close(true);
  340. break;
  341. }
  342. case ID_SHOWNAV:
  343. {
  344. bool checked = GetMenuBar()->GetMenu(1)->IsChecked(ID_SHOWNAV);
  345. if (m_navigator)
  346. m_navigator->Show(checked);
  347. break;
  348. }
  349. case ID_INFO:
  350. {
  351. wxString desc = m_life->GetDescription();
  352. if ( desc.empty() )
  353. desc = _("Not available");
  354. // should we make the description editable here?
  355. wxMessageBox(desc, _("Description"), wxOK | wxICON_INFORMATION);
  356. break;
  357. }
  358. case ID_START : OnStart(); break;
  359. case ID_STEP : OnStep(); break;
  360. case wxID_STOP : OnStop(); break;
  361. case ID_TOPSPEED:
  362. {
  363. m_running = true;
  364. m_topspeed = true;
  365. UpdateUI();
  366. while (m_running && m_topspeed)
  367. {
  368. OnStep();
  369. wxYield();
  370. }
  371. break;
  372. }
  373. }
  374. }
  375. #if wxUSE_FILEDLG
  376. void LifeFrame::OnOpen(wxCommandEvent& WXUNUSED(event))
  377. {
  378. wxFileDialog filedlg(this,
  379. _("Choose a file to open"),
  380. wxEmptyString,
  381. wxEmptyString,
  382. _("Life patterns (*.lif)|*.lif|All files (*.*)|*.*"),
  383. wxFD_OPEN | wxFD_FILE_MUST_EXIST);
  384. if (filedlg.ShowModal() == wxID_OK)
  385. {
  386. wxFileInputStream stream(filedlg.GetPath());
  387. LifeReader reader(stream);
  388. // the reader handles errors itself, no need to do anything here
  389. if (reader.IsOk())
  390. {
  391. // stop if running and put the pattern
  392. OnStop();
  393. m_life->Clear();
  394. m_life->SetPattern(reader.GetPattern());
  395. // recenter canvas
  396. m_canvas->Recenter(0, 0);
  397. m_tics = 0;
  398. UpdateInfoText();
  399. }
  400. }
  401. }
  402. #endif
  403. void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event))
  404. {
  405. // stop if it was running
  406. OnStop();
  407. // dialog box
  408. LifeSamplesDialog dialog(this);
  409. if (dialog.ShowModal() == wxID_OK)
  410. {
  411. const LifePattern pattern = dialog.GetPattern();
  412. // put the pattern
  413. m_life->Clear();
  414. m_life->SetPattern(pattern);
  415. // recenter canvas
  416. m_canvas->Recenter(0, 0);
  417. m_tics = 0;
  418. UpdateInfoText();
  419. }
  420. }
  421. void LifeFrame::OnZoom(wxCommandEvent& event)
  422. {
  423. int cellsize = m_canvas->GetCellSize();
  424. if ((event.GetId() == wxID_ZOOM_IN) && cellsize < 32)
  425. {
  426. m_canvas->SetCellSize(cellsize * 2);
  427. UpdateUI();
  428. }
  429. else if ((event.GetId() == wxID_ZOOM_OUT) && cellsize > 1)
  430. {
  431. m_canvas->SetCellSize(cellsize / 2);
  432. UpdateUI();
  433. }
  434. }
  435. void LifeFrame::OnNavigate(wxCommandEvent& event)
  436. {
  437. LifeCell c;
  438. switch (event.GetId())
  439. {
  440. case ID_NORTH: c = m_life->FindNorth(); break;
  441. case ID_SOUTH: c = m_life->FindSouth(); break;
  442. case ID_WEST: c = m_life->FindWest(); break;
  443. case ID_EAST: c = m_life->FindEast(); break;
  444. case ID_CENTER: c = m_life->FindCenter(); break;
  445. default :
  446. wxFAIL;
  447. // Fall through!
  448. case ID_ORIGIN: c.i = c.j = 0; break;
  449. }
  450. m_canvas->Recenter(c.i, c.j);
  451. }
  452. void LifeFrame::OnSlider(wxScrollEvent& event)
  453. {
  454. m_interval = event.GetPosition() * 100;
  455. if (m_running)
  456. {
  457. OnStop();
  458. OnStart();
  459. }
  460. UpdateInfoText();
  461. }
  462. void LifeFrame::OnTimer(wxTimerEvent& WXUNUSED(event))
  463. {
  464. OnStep();
  465. }
  466. void LifeFrame::OnClose(wxCloseEvent& WXUNUSED(event))
  467. {
  468. // Stop if it was running; this is absolutely needed because
  469. // the frame won't be actually destroyed until there are no
  470. // more pending events, and this in turn won't ever happen
  471. // if the timer is running faster than the window can redraw.
  472. OnStop();
  473. Destroy();
  474. }
  475. void LifeFrame::OnStart()
  476. {
  477. if (!m_running)
  478. {
  479. m_timer->Start(m_interval);
  480. m_running = true;
  481. UpdateUI();
  482. }
  483. }
  484. void LifeFrame::OnStop()
  485. {
  486. if (m_running)
  487. {
  488. m_timer->Stop();
  489. m_running = false;
  490. m_topspeed = false;
  491. UpdateUI();
  492. }
  493. }
  494. void LifeFrame::OnStep()
  495. {
  496. if (m_life->NextTic())
  497. m_tics++;
  498. else
  499. OnStop();
  500. m_canvas->DrawChanged();
  501. UpdateInfoText();
  502. }
  503. // --------------------------------------------------------------------------
  504. // LifeNavigator miniframe
  505. // --------------------------------------------------------------------------
  506. LifeNavigator::LifeNavigator(wxWindow *parent)
  507. : wxMiniFrame(parent, wxID_ANY,
  508. _("Navigation"),
  509. wxDefaultPosition,
  510. wxDefaultSize,
  511. wxCAPTION | wxSIMPLE_BORDER)
  512. {
  513. wxPanel *panel = new wxPanel(this, wxID_ANY);
  514. wxBoxSizer *sizer1 = new wxBoxSizer(wxVERTICAL);
  515. wxBoxSizer *sizer2 = new wxBoxSizer(wxHORIZONTAL);
  516. // create bitmaps and masks for the buttons
  517. wxBitmap
  518. bmpn = wxBITMAP(north),
  519. bmpw = wxBITMAP(west),
  520. bmpc = wxBITMAP(center),
  521. bmpe = wxBITMAP(east),
  522. bmps = wxBITMAP(south);
  523. #if !defined(__WXGTK__) && !defined(__WXMOTIF__) && !defined(__WXMAC__)
  524. bmpn.SetMask(new wxMask(bmpn, *wxLIGHT_GREY));
  525. bmpw.SetMask(new wxMask(bmpw, *wxLIGHT_GREY));
  526. bmpc.SetMask(new wxMask(bmpc, *wxLIGHT_GREY));
  527. bmpe.SetMask(new wxMask(bmpe, *wxLIGHT_GREY));
  528. bmps.SetMask(new wxMask(bmps, *wxLIGHT_GREY));
  529. #endif
  530. // create the buttons and attach tooltips to them
  531. wxBitmapButton
  532. *bn = new wxBitmapButton(panel, ID_NORTH, bmpn),
  533. *bw = new wxBitmapButton(panel, ID_WEST , bmpw),
  534. *bc = new wxBitmapButton(panel, ID_CENTER, bmpc),
  535. *be = new wxBitmapButton(panel, ID_EAST , bmpe),
  536. *bs = new wxBitmapButton(panel, ID_SOUTH, bmps);
  537. #if wxUSE_TOOLTIPS
  538. bn->SetToolTip(_("Find northernmost cell"));
  539. bw->SetToolTip(_("Find westernmost cell"));
  540. bc->SetToolTip(_("Find center of mass"));
  541. be->SetToolTip(_("Find easternmost cell"));
  542. bs->SetToolTip(_("Find southernmost cell"));
  543. #endif
  544. // add buttons to sizers
  545. sizer2->Add( bw, 0, wxCENTRE | wxWEST, 4 );
  546. sizer2->Add( bc, 0, wxCENTRE);
  547. sizer2->Add( be, 0, wxCENTRE | wxEAST, 4 );
  548. sizer1->Add( bn, 0, wxCENTRE | wxNORTH, 4 );
  549. sizer1->Add( sizer2 );
  550. sizer1->Add( bs, 0, wxCENTRE | wxSOUTH, 4 );
  551. // set the panel and miniframe size
  552. panel->SetSizer(sizer1);
  553. sizer1->Fit(panel);
  554. SetClientSize(panel->GetSize());
  555. wxSize sz = GetSize();
  556. SetSizeHints(sz.x, sz.y, sz.x, sz.y);
  557. // move it to a sensible position
  558. wxRect parentRect = parent->GetRect();
  559. wxSize childSize = GetSize();
  560. int x = parentRect.GetX() +
  561. parentRect.GetWidth();
  562. int y = parentRect.GetY() +
  563. (parentRect.GetHeight() - childSize.GetHeight()) / 4;
  564. Move(x, y);
  565. // done
  566. Show(true);
  567. }
  568. void LifeNavigator::OnClose(wxCloseEvent& event)
  569. {
  570. // avoid if we can
  571. if (event.CanVeto())
  572. event.Veto();
  573. else
  574. Destroy();
  575. }
  576. // --------------------------------------------------------------------------
  577. // LifeCanvas
  578. // --------------------------------------------------------------------------
  579. // canvas constructor
  580. LifeCanvas::LifeCanvas(wxWindow *parent, Life *life, bool interactive)
  581. : wxWindow(parent, wxID_ANY, wxDefaultPosition, wxSize(100, 100),
  582. wxFULL_REPAINT_ON_RESIZE | wxHSCROLL | wxVSCROLL
  583. #if !defined(__SMARTPHONE__) && !defined(__POCKETPC__)
  584. |wxSUNKEN_BORDER
  585. #else
  586. |wxSIMPLE_BORDER
  587. #endif
  588. )
  589. {
  590. m_life = life;
  591. m_interactive = interactive;
  592. m_cellsize = 8;
  593. m_status = MOUSE_NOACTION;
  594. m_viewportX = 0;
  595. m_viewportY = 0;
  596. m_viewportH = 0;
  597. m_viewportW = 0;
  598. if (m_interactive)
  599. SetCursor(*wxCROSS_CURSOR);
  600. // reduce flicker if wxEVT_ERASE_BACKGROUND is not available
  601. SetBackgroundColour(*wxWHITE);
  602. }
  603. LifeCanvas::~LifeCanvas()
  604. {
  605. delete m_life;
  606. }
  607. // recenter at the given position
  608. void LifeCanvas::Recenter(wxInt32 i, wxInt32 j)
  609. {
  610. m_viewportX = i - m_viewportW / 2;
  611. m_viewportY = j - m_viewportH / 2;
  612. // redraw everything
  613. Refresh(false);
  614. }
  615. // set the cell size and refresh display
  616. void LifeCanvas::SetCellSize(int cellsize)
  617. {
  618. m_cellsize = cellsize;
  619. // find current center
  620. wxInt32 cx = m_viewportX + m_viewportW / 2;
  621. wxInt32 cy = m_viewportY + m_viewportH / 2;
  622. // get current canvas size and adjust viewport accordingly
  623. int w, h;
  624. GetClientSize(&w, &h);
  625. m_viewportW = (w + m_cellsize - 1) / m_cellsize;
  626. m_viewportH = (h + m_cellsize - 1) / m_cellsize;
  627. // recenter
  628. m_viewportX = cx - m_viewportW / 2;
  629. m_viewportY = cy - m_viewportH / 2;
  630. // adjust scrollbars
  631. if (m_interactive)
  632. {
  633. SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
  634. SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
  635. m_thumbX = m_viewportW;
  636. m_thumbY = m_viewportH;
  637. }
  638. Refresh(false);
  639. }
  640. // draw a cell
  641. void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, bool alive)
  642. {
  643. wxClientDC dc(this);
  644. dc.SetPen(alive? *wxBLACK_PEN : *wxWHITE_PEN);
  645. dc.SetBrush(alive? *wxBLACK_BRUSH : *wxWHITE_BRUSH);
  646. DrawCell(i, j, dc);
  647. }
  648. void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, wxDC &dc)
  649. {
  650. wxCoord x = CellToX(i);
  651. wxCoord y = CellToY(j);
  652. // if cellsize is 1 or 2, there will be no grid
  653. switch (m_cellsize)
  654. {
  655. case 1:
  656. dc.DrawPoint(x, y);
  657. break;
  658. case 2:
  659. dc.DrawRectangle(x, y, 2, 2);
  660. break;
  661. default:
  662. dc.DrawRectangle(x + 1, y + 1, m_cellsize - 1, m_cellsize - 1);
  663. }
  664. }
  665. // draw all changed cells
  666. void LifeCanvas::DrawChanged()
  667. {
  668. wxClientDC dc(this);
  669. size_t ncells;
  670. LifeCell *cells;
  671. bool done = false;
  672. m_life->BeginFind(m_viewportX,
  673. m_viewportY,
  674. m_viewportX + m_viewportW,
  675. m_viewportY + m_viewportH,
  676. true);
  677. if (m_cellsize == 1)
  678. {
  679. dc.SetPen(*wxBLACK_PEN);
  680. }
  681. else
  682. {
  683. dc.SetPen(*wxTRANSPARENT_PEN);
  684. dc.SetBrush(*wxBLACK_BRUSH);
  685. }
  686. dc.SetLogicalFunction(wxINVERT);
  687. while (!done)
  688. {
  689. done = m_life->FindMore(&cells, &ncells);
  690. for (size_t m = 0; m < ncells; m++)
  691. DrawCell(cells[m].i, cells[m].j, dc);
  692. }
  693. }
  694. // event handlers
  695. void LifeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event))
  696. {
  697. wxPaintDC dc(this);
  698. wxRect rect = GetUpdateRegion().GetBox();
  699. wxCoord x, y, w, h;
  700. wxInt32 i0, j0, i1, j1;
  701. // find damaged area
  702. x = rect.GetX();
  703. y = rect.GetY();
  704. w = rect.GetWidth();
  705. h = rect.GetHeight();
  706. i0 = XToCell(x);
  707. j0 = YToCell(y);
  708. i1 = XToCell(x + w - 1);
  709. j1 = YToCell(y + h - 1);
  710. size_t ncells;
  711. LifeCell *cells;
  712. m_life->BeginFind(i0, j0, i1, j1, false);
  713. bool done = m_life->FindMore(&cells, &ncells);
  714. // erase all damaged cells and draw the grid
  715. dc.SetBrush(*wxWHITE_BRUSH);
  716. if (m_cellsize <= 2)
  717. {
  718. // no grid
  719. dc.SetPen(*wxWHITE_PEN);
  720. dc.DrawRectangle(x, y, w, h);
  721. }
  722. else
  723. {
  724. x = CellToX(i0);
  725. y = CellToY(j0);
  726. w = CellToX(i1 + 1) - x + 1;
  727. h = CellToY(j1 + 1) - y + 1;
  728. dc.SetPen(*wxLIGHT_GREY_PEN);
  729. for (wxInt32 yy = y; yy <= (y + h - m_cellsize); yy += m_cellsize)
  730. dc.DrawRectangle(x, yy, w, m_cellsize + 1);
  731. for (wxInt32 xx = x; xx <= (x + w - m_cellsize); xx += m_cellsize)
  732. dc.DrawLine(xx, y, xx, y + h);
  733. }
  734. // draw all alive cells
  735. dc.SetPen(*wxBLACK_PEN);
  736. dc.SetBrush(*wxBLACK_BRUSH);
  737. while (!done)
  738. {
  739. for (size_t m = 0; m < ncells; m++)
  740. DrawCell(cells[m].i, cells[m].j, dc);
  741. done = m_life->FindMore(&cells, &ncells);
  742. }
  743. // last set
  744. for (size_t m = 0; m < ncells; m++)
  745. DrawCell(cells[m].i, cells[m].j, dc);
  746. }
  747. void LifeCanvas::OnMouse(wxMouseEvent& event)
  748. {
  749. if (!m_interactive)
  750. return;
  751. // which cell are we pointing at?
  752. wxInt32 i = XToCell( event.GetX() );
  753. wxInt32 j = YToCell( event.GetY() );
  754. #if wxUSE_STATUSBAR
  755. // set statusbar text
  756. wxString msg;
  757. msg.Printf(_("Cell: (%d, %d)"), i, j);
  758. ((LifeFrame *) wxGetApp().GetTopWindow())->SetStatusText(msg, 1);
  759. #endif // wxUSE_STATUSBAR
  760. // NOTE that wxMouseEvent::LeftDown() and wxMouseEvent::LeftIsDown()
  761. // have different semantics. The first one is used to signal that the
  762. // button was just pressed (i.e., in "button down" events); the second
  763. // one just describes the current status of the button, independently
  764. // of the mouse event type. LeftIsDown is typically used in "mouse
  765. // move" events, to test if the button is _still_ pressed.
  766. // is the button down?
  767. if (!event.LeftIsDown())
  768. {
  769. m_status = MOUSE_NOACTION;
  770. return;
  771. }
  772. // was it pressed just now?
  773. if (event.LeftDown())
  774. {
  775. // yes: start a new action and toggle this cell
  776. m_status = (m_life->IsAlive(i, j)? MOUSE_ERASING : MOUSE_DRAWING);
  777. m_mi = i;
  778. m_mj = j;
  779. m_life->SetCell(i, j, m_status == MOUSE_DRAWING);
  780. DrawCell(i, j, m_status == MOUSE_DRAWING);
  781. }
  782. else if ((m_mi != i) || (m_mj != j))
  783. {
  784. // no: continue ongoing action
  785. bool alive = (m_status == MOUSE_DRAWING);
  786. // prepare DC and pen + brush to optimize drawing
  787. wxClientDC dc(this);
  788. dc.SetPen(alive? *wxBLACK_PEN : *wxWHITE_PEN);
  789. dc.SetBrush(alive? *wxBLACK_BRUSH : *wxWHITE_BRUSH);
  790. // draw a line of cells using Bresenham's algorithm
  791. wxInt32 d, ii, jj, di, ai, si, dj, aj, sj;
  792. di = i - m_mi;
  793. ai = abs(di) << 1;
  794. si = (di < 0)? -1 : 1;
  795. dj = j - m_mj;
  796. aj = abs(dj) << 1;
  797. sj = (dj < 0)? -1 : 1;
  798. ii = m_mi;
  799. jj = m_mj;
  800. if (ai > aj)
  801. {
  802. // iterate over i
  803. d = aj - (ai >> 1);
  804. while (ii != i)
  805. {
  806. m_life->SetCell(ii, jj, alive);
  807. DrawCell(ii, jj, dc);
  808. if (d >= 0)
  809. {
  810. jj += sj;
  811. d -= ai;
  812. }
  813. ii += si;
  814. d += aj;
  815. }
  816. }
  817. else
  818. {
  819. // iterate over j
  820. d = ai - (aj >> 1);
  821. while (jj != j)
  822. {
  823. m_life->SetCell(ii, jj, alive);
  824. DrawCell(ii, jj, dc);
  825. if (d >= 0)
  826. {
  827. ii += si;
  828. d -= aj;
  829. }
  830. jj += sj;
  831. d += ai;
  832. }
  833. }
  834. // last cell
  835. m_life->SetCell(ii, jj, alive);
  836. DrawCell(ii, jj, dc);
  837. m_mi = ii;
  838. m_mj = jj;
  839. }
  840. ((LifeFrame *) wxGetApp().GetTopWindow())->UpdateInfoText();
  841. }
  842. void LifeCanvas::OnSize(wxSizeEvent& event)
  843. {
  844. // find center
  845. wxInt32 cx = m_viewportX + m_viewportW / 2;
  846. wxInt32 cy = m_viewportY + m_viewportH / 2;
  847. // get new size
  848. wxCoord w = event.GetSize().GetX();
  849. wxCoord h = event.GetSize().GetY();
  850. m_viewportW = (w + m_cellsize - 1) / m_cellsize;
  851. m_viewportH = (h + m_cellsize - 1) / m_cellsize;
  852. // recenter
  853. m_viewportX = cx - m_viewportW / 2;
  854. m_viewportY = cy - m_viewportH / 2;
  855. // scrollbars
  856. if (m_interactive)
  857. {
  858. SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
  859. SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
  860. m_thumbX = m_viewportW;
  861. m_thumbY = m_viewportH;
  862. }
  863. // allow default processing
  864. event.Skip();
  865. }
  866. void LifeCanvas::OnScroll(wxScrollWinEvent& event)
  867. {
  868. WXTYPE type = (WXTYPE)event.GetEventType();
  869. int pos = event.GetPosition();
  870. int orient = event.GetOrientation();
  871. // calculate scroll increment
  872. int scrollinc = 0;
  873. if (type == wxEVT_SCROLLWIN_TOP)
  874. {
  875. if (orient == wxHORIZONTAL)
  876. scrollinc = -m_viewportW;
  877. else
  878. scrollinc = -m_viewportH;
  879. }
  880. else
  881. if (type == wxEVT_SCROLLWIN_BOTTOM)
  882. {
  883. if (orient == wxHORIZONTAL)
  884. scrollinc = m_viewportW;
  885. else
  886. scrollinc = m_viewportH;
  887. }
  888. else
  889. if (type == wxEVT_SCROLLWIN_LINEUP)
  890. {
  891. scrollinc = -1;
  892. }
  893. else
  894. if (type == wxEVT_SCROLLWIN_LINEDOWN)
  895. {
  896. scrollinc = +1;
  897. }
  898. else
  899. if (type == wxEVT_SCROLLWIN_PAGEUP)
  900. {
  901. scrollinc = -10;
  902. }
  903. else
  904. if (type == wxEVT_SCROLLWIN_PAGEDOWN)
  905. {
  906. scrollinc = +10;
  907. }
  908. else
  909. if (type == wxEVT_SCROLLWIN_THUMBTRACK)
  910. {
  911. if (orient == wxHORIZONTAL)
  912. {
  913. scrollinc = pos - m_thumbX;
  914. m_thumbX = pos;
  915. }
  916. else
  917. {
  918. scrollinc = pos - m_thumbY;
  919. m_thumbY = pos;
  920. }
  921. }
  922. else
  923. if (type == wxEVT_SCROLLWIN_THUMBRELEASE)
  924. {
  925. m_thumbX = m_viewportW;
  926. m_thumbY = m_viewportH;
  927. }
  928. #if defined(__WXGTK__) || defined(__WXMOTIF__)
  929. // wxGTK and wxMotif update the thumb automatically (wxMSW doesn't);
  930. // so reset it back as we always want it to be in the same position.
  931. if (type != wxEVT_SCROLLWIN_THUMBTRACK)
  932. {
  933. SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
  934. SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
  935. }
  936. #endif
  937. if (scrollinc == 0) return;
  938. // scroll the window and adjust the viewport
  939. if (orient == wxHORIZONTAL)
  940. {
  941. m_viewportX += scrollinc;
  942. ScrollWindow( -m_cellsize * scrollinc, 0, (const wxRect *) NULL);
  943. }
  944. else
  945. {
  946. m_viewportY += scrollinc;
  947. ScrollWindow( 0, -m_cellsize * scrollinc, (const wxRect *) NULL);
  948. }
  949. }
  950. void LifeCanvas::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
  951. {
  952. // do nothing. I just don't want the background to be erased, you know.
  953. }