Various VC++ 1.5 and other corrections
[wxWidgets.git] / demos / life / life.cpp
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$
8 // Copyright: (c) 2000, Guillermo Rodriguez Garcia
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ==========================================================================
13 // headers, declarations, constants
14 // ==========================================================================
15
16 #ifdef __GNUG__
17 #pragma implementation "life.h"
18 #endif
19
20 // For compilers that support precompilation, includes "wx/wx.h".
21 #include "wx/wxprec.h"
22
23 #ifdef __BORLANDC__
24 #pragma hdrstop
25 #endif
26
27 #ifndef WX_PRECOMP
28 #include "wx/wx.h"
29 #endif
30
31 #include "wx/statline.h"
32
33 #include "life.h"
34 #include "game.h"
35 #include "dialogs.h"
36
37 // --------------------------------------------------------------------------
38 // resources
39 // --------------------------------------------------------------------------
40
41 #if defined(__WXGTK__) || defined(__WXMOTIF__)
42 // the application icon
43 #include "mondrian.xpm"
44
45 // bitmap buttons for the toolbar
46 #include "bitmaps/reset.xpm"
47 #include "bitmaps/play.xpm"
48 #include "bitmaps/stop.xpm"
49 #include "bitmaps/zoomin.xpm"
50 #include "bitmaps/zoomout.xpm"
51 #endif
52
53 // --------------------------------------------------------------------------
54 // constants
55 // --------------------------------------------------------------------------
56
57 // IDs for the controls and the menu commands
58 enum
59 {
60 // menu items and toolbar buttons
61 ID_RESET = 1001,
62 ID_SAMPLES,
63 ID_ABOUT,
64 ID_EXIT,
65 ID_CENTER,
66 ID_START,
67 ID_STEP,
68 ID_STOP,
69 ID_ZOOMIN,
70 ID_ZOOMOUT,
71 ID_TOPSPEED,
72
73 // speed selection slider
74 ID_SLIDER
75 };
76
77 // --------------------------------------------------------------------------
78 // event tables and other macros for wxWindows
79 // --------------------------------------------------------------------------
80
81 // Event tables
82 BEGIN_EVENT_TABLE(LifeFrame, wxFrame)
83 EVT_MENU (ID_SAMPLES, LifeFrame::OnSamples)
84 EVT_MENU (ID_RESET, LifeFrame::OnMenu)
85 EVT_MENU (ID_ABOUT, LifeFrame::OnMenu)
86 EVT_MENU (ID_EXIT, LifeFrame::OnMenu)
87 EVT_MENU (ID_CENTER, LifeFrame::OnMenu)
88 EVT_MENU (ID_START, LifeFrame::OnMenu)
89 EVT_MENU (ID_STEP, LifeFrame::OnMenu)
90 EVT_MENU (ID_STOP, LifeFrame::OnMenu)
91 EVT_MENU (ID_ZOOMIN, LifeFrame::OnMenu)
92 EVT_MENU (ID_ZOOMOUT, LifeFrame::OnMenu)
93 EVT_MENU (ID_TOPSPEED, LifeFrame::OnMenu)
94 EVT_COMMAND_SCROLL (ID_SLIDER, LifeFrame::OnSlider)
95 EVT_CLOSE ( LifeFrame::OnClose)
96 END_EVENT_TABLE()
97
98 BEGIN_EVENT_TABLE(LifeCanvas, wxWindow)
99 EVT_PAINT ( LifeCanvas::OnPaint)
100 EVT_SCROLLWIN ( LifeCanvas::OnScroll)
101 EVT_SIZE ( LifeCanvas::OnSize)
102 EVT_MOUSE_EVENTS ( LifeCanvas::OnMouse)
103 EVT_ERASE_BACKGROUND( LifeCanvas::OnEraseBackground)
104 END_EVENT_TABLE()
105
106
107 // Create a new application object
108 IMPLEMENT_APP(LifeApp)
109
110
111 // ==========================================================================
112 // implementation
113 // ==========================================================================
114
115 // some shortcuts
116 #define ADD_TOOL(id, bmp, tooltip, help) \
117 toolBar->AddTool(id, bmp, wxNullBitmap, FALSE, -1, -1, (wxObject *)0, tooltip, help)
118
119 #define GET_FRAME() ((LifeFrame *) wxGetApp().GetTopWindow())
120
121 // --------------------------------------------------------------------------
122 // LifeApp
123 // --------------------------------------------------------------------------
124
125 // 'Main program' equivalent: the program execution "starts" here
126 bool LifeApp::OnInit()
127 {
128 // create the main application window
129 LifeFrame *frame = new LifeFrame();
130
131 // show it and tell the application that it's our main window
132 frame->Show(TRUE);
133 SetTopWindow(frame);
134
135 // enter the main message loop and run the app
136 return TRUE;
137 }
138
139 // --------------------------------------------------------------------------
140 // LifeFrame
141 // --------------------------------------------------------------------------
142
143 // frame constructor
144 LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(50, 50))
145 {
146 // frame icon
147 SetIcon(wxICON(mondrian));
148
149 // menu bar
150 wxMenu *menuFile = new wxMenu("", wxMENU_TEAROFF);
151 wxMenu *menuGame = new wxMenu("", wxMENU_TEAROFF);
152
153 menuFile->Append(ID_RESET, _("Reset"), _("Start a new game"));
154 menuFile->Append(ID_SAMPLES, _("Sample game..."), _("Select a sample configuration"));
155 menuFile->AppendSeparator();
156 menuFile->Append(ID_ABOUT, _("&About...\tCtrl-A"), _("Show about dialog"));
157 menuFile->AppendSeparator();
158 menuFile->Append(ID_EXIT, _("E&xit\tAlt-X"), _("Quit this program"));
159
160 menuGame->Append(ID_CENTER, _("Re&center\tCtrl-C"), _("Go to (0, 0)"));
161 menuGame->Append(ID_START, _("&Start\tCtrl-S"), _("Start"));
162 menuGame->Append(ID_STEP, _("&Next\tCtrl-N"), _("Single step"));
163 menuGame->Append(ID_STOP, _("S&top\tCtrl-T"), _("Stop"));
164 menuGame->Enable(ID_STOP, FALSE);
165 menuGame->AppendSeparator();
166 menuGame->Append(ID_TOPSPEED, _("Top speed!"), _("Go as fast as possible"));
167 menuGame->AppendSeparator();
168 menuGame->Append(ID_ZOOMIN, _("Zoom &in\tCtrl-I"));
169 menuGame->Append(ID_ZOOMOUT, _("Zoom &out\tCtrl-O"));
170
171 wxMenuBar *menuBar = new wxMenuBar();
172 menuBar->Append(menuFile, _("&File"));
173 menuBar->Append(menuGame, _("&Game"));
174 SetMenuBar(menuBar);
175
176 // tool bar
177 wxBitmap tbBitmaps[5];
178
179 tbBitmaps[0] = wxBITMAP(reset);
180 tbBitmaps[1] = wxBITMAP(play);
181 tbBitmaps[2] = wxBITMAP(stop);
182 tbBitmaps[3] = wxBITMAP(zoomin);
183 tbBitmaps[4] = wxBITMAP(zoomout);
184
185 wxToolBar *toolBar = CreateToolBar();
186 toolBar->SetMargins(5, 5);
187 toolBar->SetToolBitmapSize(wxSize(16, 16));
188 ADD_TOOL(ID_RESET, tbBitmaps[0], _("Reset"), _("Start a new game"));
189 ADD_TOOL(ID_START, tbBitmaps[1], _("Start"), _("Start"));
190 ADD_TOOL(ID_STOP, tbBitmaps[2], _("Stop"), _("Stop"));
191 toolBar->AddSeparator();
192 ADD_TOOL(ID_ZOOMIN, tbBitmaps[3], _("Zoom in"), _("Zoom in"));
193 ADD_TOOL(ID_ZOOMOUT, tbBitmaps[4], _("Zoom out"), _("Zoom out"));
194 toolBar->Realize();
195 toolBar->EnableTool(ID_STOP, FALSE); // must be after Realize() !
196
197 // status bar
198 CreateStatusBar(2);
199 SetStatusText(_("Welcome to Life!"));
200
201 // game and canvas
202 wxPanel *panel = new wxPanel(this, -1);
203 m_life = new Life();
204 m_canvas = new LifeCanvas(panel, m_life);
205 m_timer = new LifeTimer();
206 m_running = FALSE;
207 m_topspeed = FALSE;
208 m_interval = 500;
209 m_tics = 0;
210 m_text = new wxStaticText(panel, -1, "");
211 UpdateInfoText();
212
213 // speed selection slider
214 wxSlider *slider = new wxSlider(panel, ID_SLIDER,
215 5, 1, 10,
216 wxDefaultPosition,
217 wxSize(200, -1),
218 wxSL_HORIZONTAL | wxSL_AUTOTICKS);
219
220 // component layout
221 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
222 sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
223 sizer->Add(m_canvas, 1, wxGROW | wxCENTRE | wxALL, 2);
224 sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
225 sizer->Add(m_text, 0, wxCENTRE | wxTOP, 4);
226 sizer->Add(slider, 0, wxCENTRE | wxALL, 4);
227 panel->SetSizer(sizer);
228 panel->SetAutoLayout(TRUE);
229 sizer->Fit(this);
230 sizer->SetSizeHints(this);
231 }
232
233 LifeFrame::~LifeFrame()
234 {
235 delete m_timer;
236 }
237
238 void LifeFrame::UpdateInfoText()
239 {
240 wxString msg;
241
242 msg.Printf(_(" Generation: %u (T: %u ms), Population: %u "),
243 m_tics,
244 m_topspeed? 0 : m_interval,
245 m_life->GetNumCells());
246 m_text->SetLabel(msg);
247 }
248
249 // Enable or disable tools and menu entries according to the current
250 // state. See also wxEVT_UPDATE_UI events for a slightly different
251 // way to do this.
252 void LifeFrame::UpdateUI()
253 {
254 // start / stop
255 GetToolBar()->EnableTool(ID_START, !m_running);
256 GetToolBar()->EnableTool(ID_STOP, m_running);
257 GetMenuBar()->GetMenu(1)->Enable(ID_START, !m_running);
258 GetMenuBar()->GetMenu(1)->Enable(ID_STEP, !m_running);
259 GetMenuBar()->GetMenu(1)->Enable(ID_STOP, m_running);
260
261 // zooming
262 int cellsize = m_canvas->GetCellSize();
263 GetToolBar()->EnableTool(ID_ZOOMIN, cellsize < 32);
264 GetToolBar()->EnableTool(ID_ZOOMOUT, cellsize > 1);
265 GetMenuBar()->GetMenu(1)->Enable(ID_ZOOMIN, cellsize < 32);
266 GetMenuBar()->GetMenu(1)->Enable(ID_ZOOMOUT, cellsize > 1);
267 }
268
269 // event handlers
270 void LifeFrame::OnMenu(wxCommandEvent& event)
271 {
272 switch (event.GetId())
273 {
274 case ID_CENTER : m_canvas->Recenter(0, 0); break;
275 case ID_START : OnStart(); break;
276 case ID_STEP : OnTimer(); break;
277 case ID_STOP : OnStop(); break;
278 case ID_ZOOMIN :
279 {
280 int cellsize = m_canvas->GetCellSize();
281 if (cellsize < 32)
282 {
283 m_canvas->SetCellSize(cellsize * 2);
284 UpdateUI();
285 }
286 break;
287 }
288 case ID_ZOOMOUT :
289 {
290 int cellsize = m_canvas->GetCellSize();
291 if (cellsize > 1)
292 {
293 m_canvas->SetCellSize(cellsize / 2);
294 UpdateUI();
295 }
296 break;
297 }
298 case ID_TOPSPEED:
299 {
300 m_running = TRUE;
301 m_topspeed = TRUE;
302 UpdateUI();
303 while (m_running && m_topspeed)
304 {
305 OnTimer();
306 wxYield();
307 }
308 break;
309 }
310 case ID_RESET:
311 {
312 // stop if it was running
313 OnStop();
314 m_life->Clear();
315 m_canvas->Recenter(0, 0);
316 m_tics = 0;
317 UpdateInfoText();
318 break;
319 }
320 case ID_ABOUT:
321 {
322 LifeAboutDialog dialog(this);
323 dialog.ShowModal();
324 break;
325 }
326 case ID_EXIT :
327 {
328 // TRUE is to force the frame to close
329 Close(TRUE);
330 break;
331 }
332 }
333 }
334
335 void LifeFrame::OnClose(wxCloseEvent& WXUNUSED(event))
336 {
337 // Stop if it was running; this is absolutely needed because
338 // the frame won't be actually destroyed until there are no
339 // more pending events, and this in turn won't ever happen
340 // if the timer is running faster than the window can redraw.
341 OnStop();
342 Destroy();
343 }
344
345 void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event))
346 {
347 // stop if it was running
348 OnStop();
349
350 // dialog box
351 LifeSamplesDialog dialog(this);
352
353 // new game?
354 if (dialog.ShowModal() == wxID_OK)
355 {
356 const LifeShape shape = dialog.GetShape();
357
358 // put the shape
359 m_life->Clear();
360 m_life->SetShape(shape);
361
362 // recenter canvas
363 m_canvas->Recenter(0, 0);
364 m_tics = 0;
365 UpdateInfoText();
366 }
367 }
368
369 void LifeFrame::OnStart()
370 {
371 if (!m_running)
372 {
373 m_timer->Start(m_interval);
374 m_running = TRUE;
375 UpdateUI();
376 }
377 }
378
379 void LifeFrame::OnStop()
380 {
381 if (m_running)
382 {
383 m_timer->Stop();
384 m_running = FALSE;
385 m_topspeed = FALSE;
386 UpdateUI();
387 }
388 }
389
390 void LifeFrame::OnTimer()
391 {
392 if (m_life->NextTic())
393 m_tics++;
394 else
395 OnStop();
396
397 m_canvas->DrawChanged();
398 UpdateInfoText();
399 }
400
401 void LifeFrame::OnSlider(wxScrollEvent& event)
402 {
403 m_interval = event.GetPosition() * 100;
404
405 if (m_running)
406 {
407 OnStop();
408 OnStart();
409 }
410
411 UpdateInfoText();
412 }
413
414 // --------------------------------------------------------------------------
415 // LifeTimer
416 // --------------------------------------------------------------------------
417
418 void LifeTimer::Notify()
419 {
420 GET_FRAME()->OnTimer();
421 };
422
423 // --------------------------------------------------------------------------
424 // LifeCanvas
425 // --------------------------------------------------------------------------
426
427 // canvas constructor
428 LifeCanvas::LifeCanvas(wxWindow *parent, Life *life, bool interactive)
429 : wxWindow(parent, -1, wxPoint(0, 0), wxSize(100, 100),
430 wxSUNKEN_BORDER)
431 {
432 m_life = life;
433 m_interactive = interactive;
434 m_cellsize = 8;
435 m_status = MOUSE_NOACTION;
436 m_viewportX = 0;
437 m_viewportY = 0;
438 m_viewportH = 0;
439 m_viewportW = 0;
440
441 if (m_interactive)
442 SetCursor(*wxCROSS_CURSOR);
443
444 // reduce flicker if wxEVT_ERASE_BACKGROUND is not available
445 SetBackgroundColour(*wxWHITE);
446 }
447
448 LifeCanvas::~LifeCanvas()
449 {
450 delete m_life;
451 }
452
453 // recenter at the given position
454 void LifeCanvas::Recenter(wxInt32 i, wxInt32 j)
455 {
456 m_viewportX = i - m_viewportW / 2;
457 m_viewportY = j - m_viewportH / 2;
458
459 // redraw everything
460 Refresh(FALSE);
461 }
462
463 // set the cell size and refresh display
464 void LifeCanvas::SetCellSize(int cellsize)
465 {
466 m_cellsize = cellsize;
467
468 // find current center
469 wxInt32 cx = m_viewportX + m_viewportW / 2;
470 wxInt32 cy = m_viewportY + m_viewportH / 2;
471
472 // get current canvas size and adjust viewport accordingly
473 int w, h;
474 GetClientSize(&w, &h);
475 m_viewportW = (w + m_cellsize - 1) / m_cellsize;
476 m_viewportH = (h + m_cellsize - 1) / m_cellsize;
477
478 // recenter
479 m_viewportX = cx - m_viewportW / 2;
480 m_viewportY = cy - m_viewportH / 2;
481
482 // adjust scrollbars
483 if (m_interactive)
484 {
485 SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
486 SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
487 m_thumbX = m_viewportW;
488 m_thumbY = m_viewportH;
489 }
490
491 Refresh(FALSE);
492 }
493
494 // draw a cell
495 void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, bool alive)
496 {
497 wxClientDC dc(this);
498
499 dc.SetPen(alive? *wxBLACK_PEN : *wxWHITE_PEN);
500 dc.SetBrush(alive? *wxBLACK_BRUSH : *wxWHITE_BRUSH);
501
502 dc.BeginDrawing();
503 DrawCell(i, j, dc);
504 dc.EndDrawing();
505 }
506
507 void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, wxDC &dc)
508 {
509 wxCoord x = CellToX(i);
510 wxCoord y = CellToY(j);
511
512 // if cellsize is 1 or 2, there will be no grid
513 switch (m_cellsize)
514 {
515 case 1:
516 dc.DrawPoint(x, y);
517 break;
518 case 2:
519 dc.DrawRectangle(x, y, 2, 2);
520 break;
521 default:
522 dc.DrawRectangle(x + 1, y + 1, m_cellsize - 1, m_cellsize - 1);
523 }
524 }
525
526 // draw all changed cells
527 void LifeCanvas::DrawChanged()
528 {
529 wxClientDC dc(this);
530
531 size_t ncells;
532 Cell *cells;
533 bool done = FALSE;
534
535 m_life->BeginFind(m_viewportX,
536 m_viewportY,
537 m_viewportX + m_viewportW,
538 m_viewportY + m_viewportH,
539 TRUE);
540
541 dc.BeginDrawing();
542 dc.SetLogicalFunction(wxINVERT);
543
544 if (m_cellsize == 1)
545 {
546 // drawn using DrawPoint
547 dc.SetPen(*wxBLACK_PEN);
548 }
549 else
550 {
551 // drawn using DrawRectangle
552 dc.SetPen(*wxTRANSPARENT_PEN);
553 dc.SetBrush(*wxBLACK_BRUSH);
554 }
555
556 while (!done)
557 {
558 done = m_life->FindMore(&cells, &ncells);
559
560 for (size_t m = 0; m < ncells; m++)
561 DrawCell(cells[m].i, cells[m].j, dc);
562 }
563 dc.EndDrawing();
564 }
565
566 // event handlers
567 void LifeCanvas::OnPaint(wxPaintEvent& event)
568 {
569 wxPaintDC dc(this);
570 wxRect rect = GetUpdateRegion().GetBox();
571 wxCoord x, y, w, h;
572 wxInt32 i0, j0, i1, j1;
573
574 // find damaged area
575 x = rect.GetX();
576 y = rect.GetY();
577 w = rect.GetWidth();
578 h = rect.GetHeight();
579
580 i0 = XToCell(x);
581 j0 = YToCell(y);
582 i1 = XToCell(x + w - 1);
583 j1 = YToCell(y + h - 1);
584
585 size_t ncells;
586 Cell *cells;
587 bool done = FALSE;
588
589 m_life->BeginFind(i0, j0, i1, j1, FALSE);
590 done = m_life->FindMore(&cells, &ncells);
591
592 // erase all damaged cells and draw the grid
593 dc.BeginDrawing();
594 dc.SetBrush(*wxWHITE_BRUSH);
595
596 if (m_cellsize <= 2)
597 {
598 // no grid
599 dc.SetPen(*wxWHITE_PEN);
600 dc.DrawRectangle(x, y, w, h);
601 }
602 else
603 {
604 x = CellToX(i0);
605 y = CellToY(j0);
606 w = CellToX(i1 + 1) - x + 1;
607 h = CellToY(j1 + 1) - y + 1;
608
609 dc.SetPen(*wxLIGHT_GREY_PEN);
610 for (wxInt32 yy = y; yy <= (y + h - m_cellsize); yy += m_cellsize)
611 dc.DrawRectangle(x, yy, w, m_cellsize + 1);
612 for (wxInt32 xx = x; xx <= (x + w - m_cellsize); xx += m_cellsize)
613 dc.DrawLine(xx, y, xx, y + h);
614 }
615
616 // draw all alive cells
617 dc.SetPen(*wxBLACK_PEN);
618 dc.SetBrush(*wxBLACK_BRUSH);
619
620 while (!done)
621 {
622 for (size_t m = 0; m < ncells; m++)
623 DrawCell(cells[m].i, cells[m].j, dc);
624
625 done = m_life->FindMore(&cells, &ncells);
626 }
627
628 // last set
629 for (size_t m = 0; m < ncells; m++)
630 DrawCell(cells[m].i, cells[m].j, dc);
631
632 dc.EndDrawing();
633 }
634
635 void LifeCanvas::OnMouse(wxMouseEvent& event)
636 {
637 if (!m_interactive)
638 return;
639
640 // which cell are we pointing at?
641 wxInt32 i = XToCell( event.GetX() );
642 wxInt32 j = YToCell( event.GetY() );
643
644 // set statusbar text
645 wxString msg;
646 msg.Printf(_("Cell: (%d, %d)"), i, j);
647 GET_FRAME()->SetStatusText(msg, 1);
648
649 // button pressed?
650 if (!event.LeftIsDown())
651 {
652 m_status = MOUSE_NOACTION;
653 return;
654 }
655
656 // button just pressed?
657 if (m_status == MOUSE_NOACTION)
658 {
659 // yes, update status and toggle this cell
660 m_status = (m_life->IsAlive(i, j)? MOUSE_ERASING : MOUSE_DRAWING);
661
662 m_mi = i;
663 m_mj = j;
664 m_life->SetCell(i, j, m_status == MOUSE_DRAWING);
665 DrawCell(i, j, m_status == MOUSE_DRAWING);
666 }
667 else if ((m_mi != i) || (m_mj != j))
668 {
669 // draw a line of cells using Bresenham's algorithm
670 wxInt32 d, ii, jj, di, ai, si, dj, aj, sj;
671 di = i - m_mi;
672 ai = abs(di) << 1;
673 si = (di < 0)? -1 : 1;
674 dj = j - m_mj;
675 aj = abs(dj) << 1;
676 sj = (dj < 0)? -1 : 1;
677
678 ii = m_mi;
679 jj = m_mj;
680
681 if (ai > aj)
682 {
683 // iterate over i
684 d = aj - (ai >> 1);
685
686 while (ii != i)
687 {
688 m_life->SetCell(ii, jj, m_status == MOUSE_DRAWING);
689 DrawCell(ii, jj, m_status == MOUSE_DRAWING);
690 if (d >= 0)
691 {
692 jj += sj;
693 d -= ai;
694 }
695 ii += si;
696 d += aj;
697 }
698 }
699 else
700 {
701 // iterate over j
702 d = ai - (aj >> 1);
703
704 while (jj != j)
705 {
706 m_life->SetCell(ii, jj, m_status == MOUSE_DRAWING);
707 DrawCell(ii, jj, m_status == MOUSE_DRAWING);
708 if (d >= 0)
709 {
710 ii += si;
711 d -= aj;
712 }
713 jj += sj;
714 d += ai;
715 }
716 }
717
718 // last cell
719 m_life->SetCell(ii, jj, m_status == MOUSE_DRAWING);
720 DrawCell(ii, jj, m_status == MOUSE_DRAWING);
721 m_mi = ii;
722 m_mj = jj;
723 }
724
725 GET_FRAME()->UpdateInfoText();
726 }
727
728 void LifeCanvas::OnSize(wxSizeEvent& event)
729 {
730 // find center
731 wxInt32 cx = m_viewportX + m_viewportW / 2;
732 wxInt32 cy = m_viewportY + m_viewportH / 2;
733
734 // get new size
735 wxCoord w = event.GetSize().GetX();
736 wxCoord h = event.GetSize().GetY();
737 m_viewportW = (w + m_cellsize - 1) / m_cellsize;
738 m_viewportH = (h + m_cellsize - 1) / m_cellsize;
739
740 // recenter
741 m_viewportX = cx - m_viewportW / 2;
742 m_viewportY = cy - m_viewportH / 2;
743
744 // scrollbars
745 if (m_interactive)
746 {
747 SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
748 SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
749 m_thumbX = m_viewportW;
750 m_thumbY = m_viewportH;
751 }
752
753 // allow default processing
754 event.Skip();
755 }
756
757 void LifeCanvas::OnScroll(wxScrollWinEvent& event)
758 {
759 WXTYPE type = event.GetEventType();
760 int pos = event.GetPosition();
761 int orient = event.GetOrientation();
762
763 // calculate scroll increment
764 int scrollinc = 0;
765 switch (type)
766 {
767 case wxEVT_SCROLLWIN_TOP:
768 {
769 if (orient == wxHORIZONTAL)
770 scrollinc = -m_viewportW;
771 else
772 scrollinc = -m_viewportH;
773 break;
774 }
775 case wxEVT_SCROLLWIN_BOTTOM:
776 {
777 if (orient == wxHORIZONTAL)
778 scrollinc = m_viewportW;
779 else
780 scrollinc = m_viewportH;
781 break;
782 }
783 case wxEVT_SCROLLWIN_LINEUP: scrollinc = -1; break;
784 case wxEVT_SCROLLWIN_LINEDOWN: scrollinc = +1; break;
785 case wxEVT_SCROLLWIN_PAGEUP: scrollinc = -10; break;
786 case wxEVT_SCROLLWIN_PAGEDOWN: scrollinc = +10; break;
787 case wxEVT_SCROLLWIN_THUMBTRACK:
788 {
789 if (orient == wxHORIZONTAL)
790 {
791 scrollinc = pos - m_thumbX;
792 m_thumbX = pos;
793 }
794 else
795 {
796 scrollinc = pos - m_thumbY;
797 m_thumbY = pos;
798 }
799 break;
800 }
801 case wxEVT_SCROLLWIN_THUMBRELEASE:
802 {
803 m_thumbX = m_viewportW;
804 m_thumbY = m_viewportH;
805 }
806 }
807
808 #ifdef __WXGTK__ // what about Motif?
809 // wxGTK updates the thumb automatically (wxMSW doesn't); reset it back
810 if (type != wxEVT_SCROLLWIN_THUMBTRACK)
811 {
812 SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
813 SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
814 }
815 #endif
816
817 if (scrollinc == 0) return;
818
819 // scroll the window and adjust the viewport
820 if (orient == wxHORIZONTAL)
821 {
822 m_viewportX += scrollinc;
823 ScrollWindow( -m_cellsize * scrollinc, 0, (const wxRect *) NULL);
824 }
825 else
826 {
827 m_viewportY += scrollinc;
828 ScrollWindow( 0, -m_cellsize * scrollinc, (const wxRect *) NULL);
829 }
830 }
831
832 void LifeCanvas::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
833 {
834 // do nothing. I just don't want the background to be erased, you know.
835 }