1 // This file is part of Golly.
2 // See docs/License.html for the copyright notice.
3
4 #include "wx/wxprec.h" // for compilers that support precompilation
5 #ifndef WX_PRECOMP
6 #include "wx/wx.h" // for all others include the necessary headers
7 #endif
8
9 #if wxUSE_TOOLTIPS
10 #include "wx/tooltip.h" // for wxToolTip
11 #endif
12 #include "wx/file.h" // for wxFile
13
14 #include "bigint.h"
15 #include "lifealgo.h"
16 #include "qlifealgo.h"
17 #include "hlifealgo.h"
18 #include "viewport.h"
19
20 #include "wxgolly.h" // for mainptr, statusptr
21 #include "wxutils.h" // for Warning, Fatal, Beep
22 #include "wxprefs.h" // for showgridlines, canchangerule, etc
23 #include "wxhelp.h" // for ShowHelp
24 #include "wxmain.h" // for mainptr->...
25 #include "wxstatus.h" // for statusptr->...
26 #include "wxrender.h" // for InitPaste, DrawView
27 #include "wxscript.h" // for inscript, PassKeyToScript, PassClickToScript, etc
28 #include "wxselect.h" // for Selection
29 #include "wxedit.h" // for UpdateEditBar, ToggleEditBar, etc
30 #include "wxundo.h" // for currlayer->undoredo->...
31 #include "wxalgos.h" // for algo_type, *_ALGO, CreateNewUniverse, etc
32 #include "wxlayer.h" // for currlayer, ResizeLayers, etc
33 #include "wxoverlay.h" // for curroverlay
34 #include "wxtimeline.h" // for StartStopRecording, DeleteTimeline, etc
35 #include "wxview.h"
36
37 // -----------------------------------------------------------------------------
38
39 static bool stopdrawing = false; // terminate a draw done while generating?
40
41 static Layer* pastelayer = NULL; // temporary layer with pattern to be pasted
42 static wxRect pastebox; // bounding box for paste pattern
43 static wxString oldrule; // rule before readclipboard is called
44 static wxString newrule; // rule after readclipboard is called
45
46 // remember which translucent button was clicked, and when
47 static control_id clickedcontrol = NO_CONTROL;
48 static long clicktime;
49
50 // panning buttons are treated differently
51 #define PANNING_CONTROL (clickedcontrol >= NW_CONTROL && \
52 clickedcontrol <= SE_CONTROL && \
53 clickedcontrol != MIDDLE_CONTROL)
54
55 // this determines the rate at which OnDragTimer will be called after the mouse
56 // is dragged outside the viewport but then kept still (note that OnMouseMotion
57 // calls OnDragTimer when the mouse is moved, inside or outside the viewport)
58 const int TEN_HERTZ = 100;
59
60 // OpenGL parameters
61 int glMajor = 0; // major version
62 int glMinor = 0; // minor version
63 int glMaxTextureSize = 1024; // maximum texture size
64
65 // -----------------------------------------------------------------------------
66
67 // event table and handlers:
68
BEGIN_EVENT_TABLE(PatternView,wxGLCanvas)69 BEGIN_EVENT_TABLE(PatternView, wxGLCanvas)
70 EVT_PAINT ( PatternView::OnPaint)
71 EVT_SIZE ( PatternView::OnSize)
72 EVT_KEY_DOWN ( PatternView::OnKeyDown)
73 EVT_KEY_UP ( PatternView::OnKeyUp)
74 EVT_CHAR ( PatternView::OnChar)
75 EVT_LEFT_DOWN ( PatternView::OnMouseDown)
76 EVT_LEFT_DCLICK ( PatternView::OnMouseDown)
77 EVT_RIGHT_DOWN ( PatternView::OnMouseDown)
78 EVT_RIGHT_DCLICK ( PatternView::OnMouseDown)
79 EVT_MIDDLE_DOWN ( PatternView::OnMouseDown)
80 EVT_MIDDLE_DCLICK ( PatternView::OnMouseDown)
81 EVT_LEFT_UP ( PatternView::OnMouseUp)
82 EVT_RIGHT_UP ( PatternView::OnMouseUp)
83 EVT_MIDDLE_UP ( PatternView::OnMouseUp)
84 EVT_MOUSE_CAPTURE_LOST ( PatternView::OnMouseCaptureLost)
85 EVT_MOTION ( PatternView::OnMouseMotion)
86 EVT_ENTER_WINDOW ( PatternView::OnMouseEnter)
87 EVT_LEAVE_WINDOW ( PatternView::OnMouseExit)
88 EVT_MOUSEWHEEL ( PatternView::OnMouseWheel)
89 EVT_TIMER (wxID_ANY, PatternView::OnDragTimer)
90 EVT_SCROLLWIN ( PatternView::OnScroll)
91 EVT_ERASE_BACKGROUND ( PatternView::OnEraseBackground)
92 END_EVENT_TABLE()
93
94 // -----------------------------------------------------------------------------
95
96 static void RefreshView()
97 {
98 // refresh main viewport window, including all tile windows if they exist
99 // (tile windows are children of bigview)
100 if (!mainptr->IsIconized()) bigview->Refresh(false);
101 }
102
103 // -----------------------------------------------------------------------------
104
OutsideLimits(bigint & t,bigint & l,bigint & b,bigint & r)105 bool PatternView::OutsideLimits(bigint& t, bigint& l, bigint& b, bigint& r)
106 {
107 return ( t < bigint::min_coord || l < bigint::min_coord ||
108 b > bigint::max_coord || r > bigint::max_coord );
109 }
110
111 // -----------------------------------------------------------------------------
112
SelectionExists()113 bool PatternView::SelectionExists()
114 {
115 return currlayer->currsel.Exists();
116 }
117
118 // -----------------------------------------------------------------------------
119
CopyRect(int itop,int ileft,int ibottom,int iright,lifealgo * srcalgo,lifealgo * destalgo,bool erasesrc,const wxString & progmsg)120 bool PatternView::CopyRect(int itop, int ileft, int ibottom, int iright,
121 lifealgo* srcalgo, lifealgo* destalgo,
122 bool erasesrc, const wxString& progmsg)
123 {
124 int wd = iright - ileft + 1;
125 int ht = ibottom - itop + 1;
126 int cx, cy;
127 double maxcount = (double)wd * (double)ht;
128 int cntr = 0;
129 int v = 0;
130 bool abort = false;
131
132 // copy (and erase if requested) live cells from given rect
133 // in source universe to same rect in destination universe
134 BeginProgress(progmsg);
135 for ( cy=itop; cy<=ibottom; cy++ ) {
136 for ( cx=ileft; cx<=iright; cx++ ) {
137 int skip = srcalgo->nextcell(cx, cy, v);
138 if (skip + cx > iright)
139 skip = -1; // pretend we found no more live cells
140 if (skip >= 0) {
141 // found next live cell
142 cx += skip;
143 destalgo->setcell(cx, cy, v);
144 if (erasesrc) srcalgo->setcell(cx, cy, 0);
145 } else {
146 cx = iright + 1; // done this row
147 }
148 cntr++;
149 if ((cntr % 4096) == 0) {
150 double prog = ((cy - itop) * (double)(iright - ileft + 1) +
151 (cx - ileft)) / maxcount;
152 abort = AbortProgress(prog, wxEmptyString);
153 if (abort) break;
154 }
155 }
156 if (abort) break;
157 }
158 if (erasesrc) srcalgo->endofpattern();
159 destalgo->endofpattern();
160 EndProgress();
161
162 return !abort;
163 }
164
165 // -----------------------------------------------------------------------------
166
CopyAllRect(int itop,int ileft,int ibottom,int iright,lifealgo * srcalgo,lifealgo * destalgo,const wxString & progmsg)167 void PatternView::CopyAllRect(int itop, int ileft, int ibottom, int iright,
168 lifealgo* srcalgo, lifealgo* destalgo,
169 const wxString& progmsg)
170 {
171 int wd = iright - ileft + 1;
172 int ht = ibottom - itop + 1;
173 int cx, cy;
174 double maxcount = (double)wd * (double)ht;
175 int cntr = 0;
176 bool abort = false;
177
178 // copy all cells from given rect in srcalgo to same rect in destalgo
179 BeginProgress(progmsg);
180 for ( cy=itop; cy<=ibottom; cy++ ) {
181 for ( cx=ileft; cx<=iright; cx++ ) {
182 destalgo->setcell(cx, cy, srcalgo->getcell(cx, cy));
183 cntr++;
184 if ((cntr % 4096) == 0) {
185 abort = AbortProgress((double)cntr / maxcount, wxEmptyString);
186 if (abort) break;
187 }
188 }
189 if (abort) break;
190 }
191 destalgo->endofpattern();
192 EndProgress();
193 }
194
195 // -----------------------------------------------------------------------------
196
ClearSelection()197 void PatternView::ClearSelection()
198 {
199 currlayer->currsel.Clear();
200 }
201
202 // -----------------------------------------------------------------------------
203
ClearOutsideSelection()204 void PatternView::ClearOutsideSelection()
205 {
206 currlayer->currsel.ClearOutside();
207 }
208
209 // -----------------------------------------------------------------------------
210
CutSelection()211 void PatternView::CutSelection()
212 {
213 if (!SelectionExists()) return;
214
215 if (mainptr->generating) {
216 mainptr->command_pending = true;
217 mainptr->cmdevent.SetId(ID_CUT);
218 mainptr->Stop();
219 return;
220 }
221
222 currlayer->currsel.CopyToClipboard(true);
223 }
224
225 // -----------------------------------------------------------------------------
226
CopySelection()227 void PatternView::CopySelection()
228 {
229 if (!SelectionExists()) return;
230
231 if (mainptr->generating) {
232 mainptr->command_pending = true;
233 mainptr->cmdevent.SetId(ID_COPY);
234 mainptr->Stop();
235 return;
236 }
237
238 currlayer->currsel.CopyToClipboard(false);
239 }
240
241 // -----------------------------------------------------------------------------
242
CellInGrid(const bigint & x,const bigint & y)243 bool PatternView::CellInGrid(const bigint& x, const bigint& y)
244 {
245 // return true if cell at x,y is within bounded grid
246 if (currlayer->algo->gridwd > 0 &&
247 (x < currlayer->algo->gridleft ||
248 x > currlayer->algo->gridright)) return false;
249
250 if (currlayer->algo->gridht > 0 &&
251 (y < currlayer->algo->gridtop ||
252 y > currlayer->algo->gridbottom)) return false;
253
254 return true;
255 }
256
257 // -----------------------------------------------------------------------------
258
PointInGrid(int x,int y)259 bool PatternView::PointInGrid(int x, int y)
260 {
261 // is given viewport location also in grid?
262 if (currlayer->algo->gridwd == 0 && currlayer->algo->gridht == 0) {
263 // unbounded grid
264 return true;
265 }
266 pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
267 return CellInGrid(cellpos.first, cellpos.second);
268 }
269
270 // -----------------------------------------------------------------------------
271
RectOutsideGrid(const wxRect & rect)272 bool PatternView::RectOutsideGrid(const wxRect& rect)
273 {
274 if (currlayer->algo->gridwd == 0 && currlayer->algo->gridht == 0) {
275 // unbounded grid
276 return false;
277 }
278
279 pair<bigint, bigint> lt = currlayer->view->at(rect.x, rect.y);
280 pair<bigint, bigint> rb = currlayer->view->at(rect.x+rect.width-1, rect.y+rect.height-1);
281
282 return (currlayer->algo->gridwd > 0 &&
283 (lt.first > currlayer->algo->gridright ||
284 rb.first < currlayer->algo->gridleft)) ||
285 (currlayer->algo->gridht > 0 &&
286 (lt.second > currlayer->algo->gridbottom ||
287 rb.second < currlayer->algo->gridtop));
288 }
289
290 // -----------------------------------------------------------------------------
291
SetPasteRect(wxRect & rect,bigint & wd,bigint & ht)292 void PatternView::SetPasteRect(wxRect& rect, bigint& wd, bigint& ht)
293 {
294 int x, y, pastewd, pasteht;
295 int mag = currlayer->view->getmag();
296
297 // find cell coord of current paste cursor position
298 pair<bigint, bigint> pcell = currlayer->view->at(pastex, pastey);
299
300 // determine bottom right cell
301 bigint right = pcell.first; right += wd; right -= 1;
302 bigint bottom = pcell.second; bottom += ht; bottom -= 1;
303
304 // best to use same method as in Selection::Visible
305 pair<int,int> lt = currlayer->view->screenPosOf(pcell.first, pcell.second, currlayer->algo);
306 pair<int,int> rb = currlayer->view->screenPosOf(right, bottom, currlayer->algo);
307
308 if (mag > 0) {
309 // move rb to pixel at bottom right corner of cell
310 rb.first += (1 << mag) - 1;
311 rb.second += (1 << mag) - 1;
312 if (mag > 1) {
313 // avoid covering gaps at scale 1:4 and above
314 rb.first--;
315 rb.second--;
316 }
317 }
318
319 x = lt.first;
320 y = lt.second;
321 pastewd = rb.first - lt.first + 1;
322 pasteht = rb.second - lt.second + 1;
323
324 // this should never happen but play safe
325 if (pastewd <= 0) pastewd = 1;
326 if (pasteht <= 0) pasteht = 1;
327
328 rect = wxRect(x, y, pastewd, pasteht);
329 int xoffset, yoffset;
330 int cellsize = 1 << mag; // only used if mag > 0
331 int gap = 1; // ditto
332 if (mag == 1) gap = 0; // but no gap between cells at scale 1:2
333 switch (plocation) {
334 case TopLeft:
335 break;
336 case TopRight:
337 xoffset = mag > 0 ? -(pastewd - cellsize + gap) : -pastewd + 1;
338 rect.Offset(xoffset, 0);
339 break;
340 case BottomRight:
341 xoffset = mag > 0 ? -(pastewd - cellsize + gap) : -pastewd + 1;
342 yoffset = mag > 0 ? -(pasteht - cellsize + gap) : -pasteht + 1;
343 rect.Offset(xoffset, yoffset);
344 break;
345 case BottomLeft:
346 yoffset = mag > 0 ? -(pasteht - cellsize + gap) : -pasteht + 1;
347 rect.Offset(0, yoffset);
348 break;
349 case Middle:
350 xoffset = mag > 0 ? -(pastewd / cellsize / 2) * cellsize : -pastewd / 2;
351 yoffset = mag > 0 ? -(pasteht / cellsize / 2) * cellsize : -pasteht / 2;
352 rect.Offset(xoffset, yoffset);
353 break;
354 }
355 }
356
357 // -----------------------------------------------------------------------------
358
PasteTemporaryToCurrent(bool toselection,bigint top,bigint left,bigint bottom,bigint right)359 void PatternView::PasteTemporaryToCurrent(bool toselection,
360 bigint top, bigint left, bigint bottom, bigint right)
361 {
362 // make sure given edges are within getcell/setcell limits
363 if ( OutsideLimits(top, left, bottom, right) ) {
364 statusptr->ErrorMessage(_("Clipboard pattern is too big."));
365 return;
366 }
367 int itop = top.toint();
368 int ileft = left.toint();
369 int ibottom = bottom.toint();
370 int iright = right.toint();
371 bigint wd = iright - ileft + 1;
372 bigint ht = ibottom - itop + 1;
373
374 if ( toselection ) {
375 if ( !currlayer->currsel.CanPaste(wd, ht, top, left) ) {
376 statusptr->ErrorMessage(_("Clipboard pattern is bigger than selection."));
377 return;
378 }
379 // top and left have been set to the selection's top left corner
380
381 } else {
382 // ask user where to paste the clipboard pattern
383 statusptr->DisplayMessage(_("Click where you want to paste..."));
384
385 // temporarily change cursor to cross
386 wxCursor* savecurs = currlayer->curs;
387 currlayer->curs = curs_cross;
388 CheckCursor(true);
389
390 // pastelayer contains the pattern to be pasted; note that pastebox
391 // is not necessarily the minimal bounding box because clipboard pattern
392 // might have blank borders (in fact it could be empty)
393 pastebox = wxRect(ileft, itop, wd.toint(), ht.toint());
394 InitPaste(pastelayer, pastebox);
395
396 waitingforclick = true;
397 mainptr->UpdateMenuAccelerators(); // remove all accelerators so keyboard shortcuts can be used
398 mainptr->EnableAllMenus(false); // disable all menu items
399 mainptr->UpdateToolBar(); // disable all tool bar buttons
400 UpdateLayerBar(); // disable all layer bar buttons
401 UpdateEditBar(); // disable all edit bar buttons
402 CaptureMouse(); // get mouse down event even if outside view
403 pasterect = wxRect(-1,-1,0,0);
404
405 while (waitingforclick) {
406 wxPoint pt = ScreenToClient( wxGetMousePosition() );
407 pastex = pt.x;
408 pastey = pt.y;
409 if (PointInView(pt.x, pt.y)) {
410 // determine new paste rectangle
411 wxRect newrect;
412 if (wd.toint() != pastebox.width || ht.toint() != pastebox.height) {
413 // RotatePastePattern was called
414 itop = pastebox.y;
415 ileft = pastebox.x;
416 ibottom = itop + pastebox.height - 1;
417 iright = ileft + pastebox.width - 1;
418 wd = pastebox.width;
419 ht = pastebox.height;
420 }
421 SetPasteRect(newrect, wd, ht);
422 if (newrect != pasterect) {
423 // draw new pasterect
424 pasterect = newrect;
425 Refresh(false);
426 // don't update immediately
427 }
428 } else {
429 // mouse outside viewport so erase old pasterect if necessary
430 if ( pasterect.width > 0 ) {
431 pasterect = wxRect(-1,-1,0,0);
432 Refresh(false);
433 // don't update immediately
434 }
435 }
436 wxMilliSleep(10); // don't hog CPU
437 wxGetApp().Yield(true);
438 // make sure viewport retains focus so we can use keyboard shortcuts
439 SetFocus();
440 // waitingforclick becomes false if OnMouseDown is called
441 }
442
443 #ifdef __WXMAC__
444 // avoid problem on Mac due to OnMouseUp not being called
445 mouseisdown = false;
446 #endif
447
448 if ( HasCapture() ) ReleaseMouse();
449 mainptr->EnableAllMenus(true);
450 mainptr->UpdateMenuAccelerators(); // restore accelerators
451
452 // restore cursor
453 currlayer->curs = savecurs;
454 CheckCursor(mainptr->infront);
455
456 if ( pasterect.width > 0 ) {
457 // erase old pasterect
458 Refresh(false);
459 }
460
461 // only allow paste if some part of pasterect is within the (possibly bounded) grid
462 if ( !PointInView(pastex, pastey) || RectOutsideGrid(pasterect) ) {
463 statusptr->DisplayMessage(_("Paste aborted."));
464 return;
465 }
466
467 // set paste rectangle's top left cell coord
468 pair<bigint, bigint> clickpos = currlayer->view->at(pastex, pastey);
469 top = clickpos.second;
470 left = clickpos.first;
471 bigint halfht = ht;
472 bigint halfwd = wd;
473 halfht.div2();
474 halfwd.div2();
475 if (currlayer->view->getmag() > 1) {
476 if (ht.even()) halfht -= 1;
477 if (wd.even()) halfwd -= 1;
478 }
479 switch (plocation) {
480 case TopLeft: /* no change*/ break;
481 case TopRight: left -= wd; left += 1; break;
482 case BottomRight: left -= wd; left += 1; top -= ht; top += 1; break;
483 case BottomLeft: top -= ht; top += 1; break;
484 case Middle: left -= halfwd; top -= halfht; break;
485 }
486 }
487
488 // check that paste rectangle is within edit limits
489 bottom = top; bottom += ht; bottom -= 1;
490 right = left; right += wd; right -= 1;
491 if ( OutsideLimits(top, left, bottom, right) ) {
492 statusptr->ErrorMessage(_("Pasting is not allowed outside +/- 10^9 boundary."));
493 return;
494 }
495
496 // selection might change if grid becomes smaller,
497 // so save current selection for RememberRuleChange/RememberAlgoChange
498 SaveCurrentSelection();
499
500 // pasting clipboard pattern can cause a rule change
501 int oldmaxstate = currlayer->algo->NumCellStates() - 1;
502 if (canchangerule > 0 && oldrule != newrule) {
503 const char* err = currlayer->algo->setrule( newrule.mb_str(wxConvLocal) );
504 // setrule can fail if readclipboard loaded clipboard pattern into
505 // a different type of algo
506 if (err) {
507 // allow rule change to cause algo change
508 mainptr->ChangeAlgorithm(pastelayer->algtype, newrule);
509 } else {
510 // show new rule in title bar
511 mainptr->SetWindowTitle(wxEmptyString);
512
513 // if pattern exists and is at starting gen then ensure savestart is true
514 // so that SaveStartingPattern will save pattern to suitable file
515 // (and thus undo/reset will work correctly)
516 if (currlayer->algo->getGeneration() == currlayer->startgen && !currlayer->algo->isEmpty()) {
517 currlayer->savestart = true;
518 }
519
520 // if grid is bounded then remove any live cells outside grid edges
521 if (currlayer->algo->gridwd > 0 || currlayer->algo->gridht > 0) {
522 mainptr->ClearOutsideGrid();
523 }
524
525 // rule change might have changed the number of cell states;
526 // if there are fewer states then pattern might change
527 int newmaxstate = currlayer->algo->NumCellStates() - 1;
528 if (newmaxstate < oldmaxstate && !currlayer->algo->isEmpty()) {
529 mainptr->ReduceCellStates(newmaxstate);
530 }
531
532 // use colors for new rule
533 UpdateLayerColors();
534
535 if (allowundo && !currlayer->stayclean) {
536 currlayer->undoredo->RememberRuleChange(oldrule);
537 }
538 }
539 }
540
541 // set pastex,pastey to top left cell of paste rectangle
542 pastex = left.toint();
543 pastey = top.toint();
544
545 // save cell changes if undo/redo is enabled and script isn't constructing a pattern
546 bool savecells = allowundo && !currlayer->stayclean;
547 if (savecells && inscript) SavePendingChanges();
548
549 // don't paste cells outside bounded grid
550 int gtop = currlayer->algo->gridtop.toint();
551 int gleft = currlayer->algo->gridleft.toint();
552 int gbottom = currlayer->algo->gridbottom.toint();
553 int gright = currlayer->algo->gridright.toint();
554 if (currlayer->algo->gridwd == 0) {
555 // grid has infinite width
556 gleft = INT_MIN;
557 gright = INT_MAX;
558 }
559 if (currlayer->algo->gridht == 0) {
560 // grid has infinite height
561 gtop = INT_MIN;
562 gbottom = INT_MAX;
563 }
564
565 // copy pattern from temporary universe to current universe
566 int tx, ty, cx, cy;
567 double maxcount = wd.todouble() * ht.todouble();
568 int cntr = 0;
569 bool abort = false;
570 bool pattchanged = false;
571 bool reduced = false;
572 lifealgo* pastealgo = pastelayer->algo;
573 lifealgo* curralgo = currlayer->algo;
574 int maxstate = curralgo->NumCellStates() - 1;
575
576 BeginProgress(_("Pasting pattern"));
577
578 // we can speed up pasting sparse patterns by using nextcell in these cases:
579 // - if using Or mode
580 // - if current universe is empty
581 // - if paste rect is outside current pattern edges
582 bool usenextcell;
583 if ( pmode == Or || curralgo->isEmpty() ) {
584 usenextcell = true;
585 } else {
586 bigint ctop, cleft, cbottom, cright;
587 curralgo->findedges(&ctop, &cleft, &cbottom, &cright);
588 usenextcell = top > cbottom || bottom < ctop || left > cright || right < cleft;
589 }
590
591 if ( usenextcell && pmode == And ) {
592 // current universe is empty or paste rect is outside current pattern edges
593 // so don't change any cells
594 } else if ( usenextcell ) {
595 int newstate = 0;
596 cy = pastey;
597 for ( ty=itop; ty<=ibottom; ty++ ) {
598 cx = pastex;
599 for ( tx=ileft; tx<=iright; tx++ ) {
600 int skip = pastealgo->nextcell(tx, ty, newstate);
601 if (skip + tx > iright)
602 skip = -1; // pretend we found no more live cells
603 if (skip >= 0) {
604 // found next live cell so paste it into current universe
605 tx += skip;
606 cx += skip;
607 if (cx >= gleft && cx <= gright && cy >= gtop && cy <= gbottom) {
608 int currstate = curralgo->getcell(cx, cy);
609 if (currstate != newstate) {
610 if (newstate > maxstate) {
611 newstate = maxstate;
612 reduced = true;
613 }
614 curralgo->setcell(cx, cy, newstate);
615 pattchanged = true;
616 if (savecells) currlayer->undoredo->SaveCellChange(cx, cy, currstate, newstate);
617 }
618 }
619 cx++;
620 } else {
621 tx = iright + 1; // done this row
622 }
623 cntr++;
624 if ((cntr % 4096) == 0) {
625 double prog = ((ty - itop) * (double)(iright - ileft + 1) +
626 (tx - ileft)) / maxcount;
627 abort = AbortProgress(prog, wxEmptyString);
628 if (abort) break;
629 }
630 }
631 if (abort) break;
632 cy++;
633 }
634 } else {
635 // have to use slower getcell/setcell calls
636 int tempstate, currstate;
637 int numstates = curralgo->NumCellStates();
638 cy = pastey;
639 for ( ty=itop; ty<=ibottom; ty++ ) {
640 cx = pastex;
641 for ( tx=ileft; tx<=iright; tx++ ) {
642 tempstate = pastealgo->getcell(tx, ty);
643 currstate = curralgo->getcell(cx, cy);
644 if (cx >= gleft && cx <= gright && cy >= gtop && cy <= gbottom) {
645 switch (pmode) {
646 case And:
647 if (tempstate != currstate && currstate > 0) {
648 curralgo->setcell(cx, cy, 0);
649 pattchanged = true;
650 if (savecells) currlayer->undoredo->SaveCellChange(cx, cy, currstate, 0);
651 }
652 break;
653 case Copy:
654 if (tempstate != currstate) {
655 if (tempstate > maxstate) {
656 tempstate = maxstate;
657 reduced = true;
658 }
659 curralgo->setcell(cx, cy, tempstate);
660 pattchanged = true;
661 if (savecells) currlayer->undoredo->SaveCellChange(cx, cy, currstate, tempstate);
662 }
663 break;
664 case Or:
665 // Or mode is done using above nextcell loop;
666 // we only include this case to avoid compiler warning
667 break;
668 case Xor:
669 if (tempstate == currstate) {
670 if (currstate != 0) {
671 curralgo->setcell(cx, cy, 0);
672 pattchanged = true;
673 if (savecells) currlayer->undoredo->SaveCellChange(cx, cy, currstate, 0);
674 }
675 } else {
676 // tempstate != currstate
677 int newstate = tempstate ^ currstate;
678 // if xor overflows then don't change current state
679 if (newstate >= numstates) newstate = currstate;
680 if (currstate != newstate) {
681 curralgo->setcell(cx, cy, newstate);
682 pattchanged = true;
683 if (savecells) currlayer->undoredo->SaveCellChange(cx, cy, currstate, newstate);
684 }
685 }
686 break;
687 }
688 }
689 cx++;
690 cntr++;
691 if ( (cntr % 4096) == 0 ) {
692 abort = AbortProgress((double)cntr / maxcount, wxEmptyString);
693 if (abort) break;
694 }
695 }
696 if (abort) break;
697 cy++;
698 }
699 }
700
701 if (pattchanged) curralgo->endofpattern();
702 EndProgress();
703
704 // tidy up and display result
705 statusptr->ClearMessage();
706 if (pattchanged) {
707 if (savecells) currlayer->undoredo->RememberCellChanges(_("Paste"), currlayer->dirty);
708 MarkLayerDirty(); // calls SetWindowTitle
709 mainptr->UpdatePatternAndStatus();
710 }
711
712 if (reduced) statusptr->ErrorMessage(_("Some cell states were reduced."));
713 }
714
715 // -----------------------------------------------------------------------------
716
GetClipboardPattern(Layer * templayer,bigint * t,bigint * l,bigint * b,bigint * r)717 bool PatternView::GetClipboardPattern(Layer* templayer, bigint* t, bigint* l, bigint* b, bigint* r)
718 {
719 wxTextDataObject data;
720 if ( !mainptr->GetTextFromClipboard(&data) ) return false;
721
722 // copy clipboard data to temporary file so we can handle all formats supported by readclipboard
723 wxFile tmpfile(mainptr->clipfile, wxFile::write);
724 if ( !tmpfile.IsOpened() ) {
725 Warning(_("Could not create temporary file for clipboard data!"));
726 return false;
727 }
728 if ( !tmpfile.Write(data.GetText()) ) {
729 Warning(_("Could not write clipboard data to temporary file! Maybe disk is full?"));
730 tmpfile.Close();
731 return false;
732 }
733 tmpfile.Close();
734
735 // remember current rule
736 oldrule = wxString(currlayer->algo->getrule(), wxConvLocal);
737
738 const char* err = readclipboard(mainptr->clipfile.mb_str(wxConvLocal), *templayer->algo, t, l, b, r);
739 if (err) {
740 // cycle thru all other algos until readclipboard succeeds
741 for (int i = 0; i < NumAlgos(); i++) {
742 if (i != currlayer->algtype) {
743 delete templayer->algo;
744 templayer->algo = CreateNewUniverse(i);
745 err = readclipboard(mainptr->clipfile.mb_str(wxConvLocal), *templayer->algo, t, l, b, r);
746 if (!err) {
747 templayer->algtype = i;
748 break;
749 }
750 }
751 }
752 }
753
754 if (!err && canchangerule > 0) {
755 // set newrule for later use in PasteTemporaryToCurrent
756 if (canchangerule == 1 && !currlayer->algo->isEmpty()) {
757 // don't change rule if universe isn't empty
758 newrule = oldrule;
759 } else {
760 // remember rule set by readclipboard
761 newrule = wxString(templayer->algo->getrule(), wxConvLocal);
762 }
763 }
764
765 wxRemoveFile(mainptr->clipfile);
766
767 if (err) {
768 // error probably due to bad rule string in clipboard data
769 Warning(_("Could not load clipboard pattern\n(probably due to unknown rule)."));
770 return false;
771 }
772
773 return true;
774 }
775
776 // -----------------------------------------------------------------------------
777
778 static bool doing_paste = false; // inside PasteTemporaryToCurrent?
779
PasteClipboard(bool toselection)780 void PatternView::PasteClipboard(bool toselection)
781 {
782 // prevent re-entrancy in PasteTemporaryToCurrent due to rapid pasting of large clipboard pattern
783 if (doing_paste) return;
784
785 if (waitingforclick || !mainptr->ClipboardHasText()) return;
786 if (toselection && !SelectionExists()) return;
787
788 if (mainptr->generating) {
789 mainptr->command_pending = true;
790 mainptr->cmdevent.SetId(toselection ? ID_PASTE_SEL : ID_PASTE);
791 mainptr->Stop();
792 return;
793 }
794
795 // if clipboard text starts with "@RULE rulename" then install rulename.rule
796 // and switch to that rule
797 if (mainptr->ClipboardContainsRule()) return;
798
799 // if clipboard text starts with "3D version" then start up 3D.lua
800 // and load the RLE3 pattern
801 if (mainptr->ClipboardContainsRLE3()) return;
802
803 // create a temporary layer for storing the clipboard pattern
804 pastelayer = CreateTemporaryLayer();
805 if (pastelayer) {
806 // read clipboard pattern into pastelayer
807 bigint top, left, bottom, right;
808 if ( GetClipboardPattern(pastelayer, &top, &left, &bottom, &right) ) {
809 // temporarily set currlayer to pastelayer so we can update the paste pattern's colors and icons
810 Layer* savelayer = currlayer;
811 currlayer = pastelayer;
812 UpdateLayerColors();
813 currlayer = savelayer;
814
815 doing_paste = true;
816 PasteTemporaryToCurrent(toselection, top, left, bottom, right);
817 doing_paste = false;
818 }
819 delete pastelayer;
820 pastelayer = NULL;
821 }
822 }
823
824 // -----------------------------------------------------------------------------
825
AbortPaste()826 void PatternView::AbortPaste()
827 {
828 pastex = -1;
829 pastey = -1;
830 waitingforclick = false; // terminate while (waitingforclick) loop
831 }
832
833 // -----------------------------------------------------------------------------
834
CyclePasteLocation()835 void PatternView::CyclePasteLocation()
836 {
837 if (plocation == TopLeft) {
838 plocation = TopRight;
839 if (!waitingforclick) statusptr->DisplayMessage(_("Paste location is Top Right."));
840 } else if (plocation == TopRight) {
841 plocation = BottomRight;
842 if (!waitingforclick) statusptr->DisplayMessage(_("Paste location is Bottom Right."));
843 } else if (plocation == BottomRight) {
844 plocation = BottomLeft;
845 if (!waitingforclick) statusptr->DisplayMessage(_("Paste location is Bottom Left."));
846 } else if (plocation == BottomLeft) {
847 plocation = Middle;
848 if (!waitingforclick) statusptr->DisplayMessage(_("Paste location is Middle."));
849 } else {
850 plocation = TopLeft;
851 if (!waitingforclick) statusptr->DisplayMessage(_("Paste location is Top Left."));
852 }
853 if (waitingforclick) {
854 // force redraw of paste rectangle if mouse is inside viewport
855 pasterect = wxRect(-1,-1,0,0);
856 }
857 }
858
859 // -----------------------------------------------------------------------------
860
CyclePasteMode()861 void PatternView::CyclePasteMode()
862 {
863 if (pmode == And) {
864 pmode = Copy;
865 if (!waitingforclick) statusptr->DisplayMessage(_("Paste mode is Copy."));
866 } else if (pmode == Copy) {
867 pmode = Or;
868 if (!waitingforclick) statusptr->DisplayMessage(_("Paste mode is Or."));
869 } else if (pmode == Or) {
870 pmode = Xor;
871 if (!waitingforclick) statusptr->DisplayMessage(_("Paste mode is Xor."));
872 } else {
873 pmode = And;
874 if (!waitingforclick) statusptr->DisplayMessage(_("Paste mode is And."));
875 }
876 if (waitingforclick) {
877 // force redraw of paste rectangle if mouse is inside viewport
878 pasterect = wxRect(-1,-1,0,0);
879 }
880 }
881
882 // -----------------------------------------------------------------------------
883
DisplaySelectionSize()884 void PatternView::DisplaySelectionSize()
885 {
886 if (waitingforclick || inscript || currlayer->undoredo->doingscriptchanges)
887 return;
888
889 currlayer->currsel.DisplaySize();
890 }
891
892 // -----------------------------------------------------------------------------
893
SaveCurrentSelection()894 void PatternView::SaveCurrentSelection()
895 {
896 if (allowundo && !currlayer->stayclean) {
897 currlayer->savesel = currlayer->currsel;
898 }
899 }
900
901 // -----------------------------------------------------------------------------
902
RememberNewSelection(const wxString & action)903 void PatternView::RememberNewSelection(const wxString& action)
904 {
905 if (TimelineExists()) {
906 // we allow selections while a timeline exists but we can't
907 // remember them in the undo/redo history
908 return;
909 }
910 if (allowundo && !currlayer->stayclean) {
911 if (inscript) SavePendingChanges();
912 currlayer->undoredo->RememberSelection(action);
913 }
914 }
915
916 // -----------------------------------------------------------------------------
917
SelectAll()918 void PatternView::SelectAll()
919 {
920 SaveCurrentSelection();
921 if (SelectionExists()) {
922 currlayer->currsel.Deselect();
923 mainptr->UpdatePatternAndStatus();
924 }
925
926 if (currlayer->algo->isEmpty()) {
927 statusptr->ErrorMessage(empty_pattern);
928 RememberNewSelection(_("Deselection"));
929 return;
930 }
931
932 bigint top, left, bottom, right;
933 currlayer->algo->findedges(&top, &left, &bottom, &right);
934 currlayer->currsel.SetEdges(top, left, bottom, right);
935
936 RememberNewSelection(_("Select All"));
937 DisplaySelectionSize();
938 mainptr->UpdatePatternAndStatus();
939 }
940
941 // -----------------------------------------------------------------------------
942
RemoveSelection()943 void PatternView::RemoveSelection()
944 {
945 if (SelectionExists()) {
946 SaveCurrentSelection();
947 currlayer->currsel.Deselect();
948 RememberNewSelection(_("Deselection"));
949 mainptr->UpdatePatternAndStatus();
950 }
951 }
952
953 // -----------------------------------------------------------------------------
954
ShrinkSelection(bool fit)955 void PatternView::ShrinkSelection(bool fit)
956 {
957 currlayer->currsel.Shrink(fit);
958 }
959
960 // -----------------------------------------------------------------------------
961
RandomFill()962 void PatternView::RandomFill()
963 {
964 currlayer->currsel.RandomFill();
965 }
966
967 // -----------------------------------------------------------------------------
968
FlipPastePattern(bool topbottom)969 bool PatternView::FlipPastePattern(bool topbottom)
970 {
971 bool result;
972 Selection pastesel(pastebox.GetTop(), pastebox.GetLeft(),
973 pastebox.GetBottom(), pastebox.GetRight());
974
975 // flip the pattern in pastelayer
976 lifealgo* savealgo = currlayer->algo;
977 int savetype = currlayer->algtype;
978 currlayer->algo = pastelayer->algo;
979 currlayer->algtype = pastelayer->algtype;
980 // pass in true for inundoredo parameter so flip won't be remembered
981 // and layer won't be marked as dirty; also set inscript temporarily
982 // so that viewport won't be updated
983 inscript = true;
984 result = pastesel.Flip(topbottom, true);
985 // currlayer->algo might point to a *different* universe
986 pastelayer->algo = currlayer->algo;
987 currlayer->algo = savealgo;
988 currlayer->algtype = savetype;
989 inscript = false;
990
991 if (result) {
992 InitPaste(pastelayer, pastebox);
993 RefreshView();
994 }
995
996 return result;
997 }
998
999 // -----------------------------------------------------------------------------
1000
RotatePastePattern(bool clockwise)1001 bool PatternView::RotatePastePattern(bool clockwise)
1002 {
1003 Selection pastesel(pastebox.GetTop(), pastebox.GetLeft(),
1004 pastebox.GetBottom(), pastebox.GetRight());
1005
1006 // check if pastelayer->algo uses a finite grid
1007 if (!pastelayer->algo->unbounded) {
1008 // readclipboard has loaded the pattern into top left corner of grid,
1009 // so if pastebox isn't square we need to expand the grid to avoid the
1010 // rotated pattern being clipped (WARNING: this assumes the algo won't
1011 // change the pattern's cell coordinates when setrule expands the grid)
1012 int x, y, wd, ht;
1013 pastesel.GetRect(&x, &y, &wd, &ht);
1014 if (wd != ht) {
1015
1016 // better solution would be to check if pastebox is small enough for
1017 // pattern to be safely rotated after shifting to center of grid and only
1018 // expand grid if it can't!!!??? (must also update pastesel edges)
1019
1020 int newwd, newht;
1021 if (wd > ht) {
1022 // expand grid vertically
1023 newht = pastelayer->algo->gridht + wd;
1024 newwd = pastelayer->algo->gridwd;
1025 } else {
1026 // wd < ht so expand grid horizontally
1027 newwd = pastelayer->algo->gridwd + ht;
1028 newht = pastelayer->algo->gridht;
1029 }
1030 char rule[MAXRULESIZE];
1031 sprintf(rule, "%s", pastelayer->algo->getrule());
1032 char topology = 'T';
1033 char *suffix = strchr(rule, ':');
1034 if (suffix) {
1035 topology = suffix[1];
1036 suffix[0] = 0;
1037 }
1038 sprintf(rule, "%s:%c%d,%d", rule, topology, newwd, newht);
1039 if (pastelayer->algo->setrule(rule)) {
1040 // unlikely, but could happen if the new grid size is too big
1041 Warning(_("Sorry, but the clipboard pattern could not be rotated."));
1042 return false;
1043 }
1044 }
1045 }
1046
1047 // rotate the pattern in pastelayer
1048 lifealgo* savealgo = currlayer->algo;
1049 int savetype = currlayer->algtype;
1050 currlayer->algo = pastelayer->algo;
1051 currlayer->algtype = pastelayer->algtype;
1052
1053 // pass in true for inundoredo parameter so rotate won't be remembered
1054 // and layer won't be marked as dirty; also set inscript temporarily
1055 // so that viewport won't be updated
1056 inscript = true;
1057 bool result = pastesel.Rotate(clockwise, true);
1058
1059 // currlayer->algo might point to a *different* universe
1060 pastelayer->algo = currlayer->algo;
1061 currlayer->algo = savealgo;
1062 currlayer->algtype = savetype;
1063 inscript = false;
1064
1065 if (result) {
1066 // get rotated selection and update pastebox
1067 int x, y, wd, ht;
1068 pastesel.GetRect(&x, &y, &wd, &ht);
1069 pastebox = wxRect(x, y, wd, ht);
1070 InitPaste(pastelayer, pastebox);
1071 if (wd == ht) RefreshView();
1072 // if wd != ht then PasteTemporaryToCurrent will call Refresh
1073 }
1074
1075 return result;
1076 }
1077
1078 // -----------------------------------------------------------------------------
1079
FlipSelection(bool topbottom,bool inundoredo)1080 bool PatternView::FlipSelection(bool topbottom, bool inundoredo)
1081 {
1082 if (waitingforclick) {
1083 // more useful to flip the pattern about to be pasted
1084 return FlipPastePattern(topbottom);
1085 } else {
1086 return currlayer->currsel.Flip(topbottom, inundoredo);
1087 }
1088 }
1089
1090 // -----------------------------------------------------------------------------
1091
RotateSelection(bool clockwise,bool inundoredo)1092 bool PatternView::RotateSelection(bool clockwise, bool inundoredo)
1093 {
1094 if (waitingforclick) {
1095 // more useful to rotate the pattern about to be pasted
1096 return RotatePastePattern(clockwise);
1097 } else {
1098 return currlayer->currsel.Rotate(clockwise, inundoredo);
1099 }
1100 }
1101
1102 // -----------------------------------------------------------------------------
1103
SetCursorMode(wxCursor * cursor)1104 void PatternView::SetCursorMode(wxCursor* cursor)
1105 {
1106 currlayer->curs = cursor;
1107 }
1108
1109 // -----------------------------------------------------------------------------
1110
CycleCursorMode()1111 void PatternView::CycleCursorMode()
1112 {
1113 if (drawingcells || selectingcells || movingview || waitingforclick)
1114 return;
1115
1116 if (currlayer->curs == curs_pencil)
1117 currlayer->curs = curs_pick;
1118 else if (currlayer->curs == curs_pick)
1119 currlayer->curs = curs_cross;
1120 else if (currlayer->curs == curs_cross)
1121 currlayer->curs = curs_hand;
1122 else if (currlayer->curs == curs_hand)
1123 currlayer->curs = curs_zoomin;
1124 else if (currlayer->curs == curs_zoomin)
1125 currlayer->curs = curs_zoomout;
1126 else
1127 currlayer->curs = curs_pencil;
1128 }
1129
1130 // -----------------------------------------------------------------------------
1131
ZoomOut()1132 void PatternView::ZoomOut()
1133 {
1134 TestAutoFit();
1135 currlayer->view->unzoom();
1136 mainptr->UpdateEverything();
1137 }
1138
1139 // -----------------------------------------------------------------------------
1140
ZoomIn()1141 void PatternView::ZoomIn()
1142 {
1143 TestAutoFit();
1144 if (currlayer->view->getmag() < MAX_MAG) {
1145 currlayer->view->zoom();
1146 mainptr->UpdateEverything();
1147 } else {
1148 Beep();
1149 }
1150 }
1151
1152 // -----------------------------------------------------------------------------
1153
SetPixelsPerCell(int pxlspercell)1154 void PatternView::SetPixelsPerCell(int pxlspercell)
1155 {
1156 int mag = 0;
1157 while (pxlspercell > 1) {
1158 mag++;
1159 pxlspercell >>= 1;
1160 }
1161 if (mag == currlayer->view->getmag()) return;
1162 TestAutoFit();
1163 currlayer->view->setmag(mag);
1164 mainptr->UpdateEverything();
1165 }
1166
1167 // -----------------------------------------------------------------------------
1168
FitPattern()1169 void PatternView::FitPattern()
1170 {
1171 currlayer->algo->fit(*currlayer->view, 1);
1172 // best not to call TestAutoFit
1173 mainptr->UpdateEverything();
1174 }
1175
1176 // -----------------------------------------------------------------------------
1177
FitSelection()1178 void PatternView::FitSelection()
1179 {
1180 if (!SelectionExists()) return;
1181
1182 currlayer->currsel.Fit();
1183
1184 TestAutoFit();
1185 mainptr->UpdateEverything();
1186 }
1187
1188 // -----------------------------------------------------------------------------
1189
ViewOrigin()1190 void PatternView::ViewOrigin()
1191 {
1192 // put 0,0 cell in middle of view
1193 if ( currlayer->originx == bigint::zero && currlayer->originy == bigint::zero ) {
1194 currlayer->view->center();
1195 } else {
1196 // put cell saved by ChangeOrigin in middle
1197 currlayer->view->setpositionmag(currlayer->originx, currlayer->originy,
1198 currlayer->view->getmag());
1199 }
1200 TestAutoFit();
1201 mainptr->UpdateEverything();
1202 }
1203
1204 // -----------------------------------------------------------------------------
1205
ChangeOrigin()1206 void PatternView::ChangeOrigin()
1207 {
1208 if (waitingforclick) return;
1209 // change cell under cursor to 0,0
1210 wxPoint pt = ScreenToClient( wxGetMousePosition() );
1211 if ( pt.x < 0 || pt.x > currlayer->view->getxmax() ||
1212 pt.y < 0 || pt.y > currlayer->view->getymax() ) {
1213 statusptr->ErrorMessage(_("Origin not changed."));
1214 } else {
1215 pair<bigint, bigint> cellpos = currlayer->view->at(pt.x, pt.y);
1216 currlayer->originx = cellpos.first;
1217 currlayer->originy = cellpos.second;
1218 statusptr->DisplayMessage(_("Origin changed."));
1219 if ( GridVisible() )
1220 mainptr->UpdatePatternAndStatus();
1221 else
1222 statusptr->UpdateXYLocation();
1223 }
1224 }
1225
1226 // -----------------------------------------------------------------------------
1227
RestoreOrigin()1228 void PatternView::RestoreOrigin()
1229 {
1230 if (waitingforclick) return;
1231 if (currlayer->originx != bigint::zero || currlayer->originy != bigint::zero) {
1232 currlayer->originx = 0;
1233 currlayer->originy = 0;
1234 statusptr->DisplayMessage(origin_restored);
1235 if ( GridVisible() )
1236 mainptr->UpdatePatternAndStatus();
1237 else
1238 statusptr->UpdateXYLocation();
1239 }
1240 }
1241
1242 // -----------------------------------------------------------------------------
1243
GridVisible()1244 bool PatternView::GridVisible()
1245 {
1246 return ( showgridlines && currlayer->view->getmag() >= mingridmag );
1247 }
1248
1249 // -----------------------------------------------------------------------------
1250
ToggleGridLines()1251 void PatternView::ToggleGridLines()
1252 {
1253 showgridlines = !showgridlines;
1254 mainptr->UpdateEverything();
1255 }
1256
1257 // -----------------------------------------------------------------------------
1258
ToggleCellIcons()1259 void PatternView::ToggleCellIcons()
1260 {
1261 showicons = !showicons;
1262 mainptr->UpdateEverything();
1263 }
1264
1265 // -----------------------------------------------------------------------------
1266
ToggleCellColors()1267 void PatternView::ToggleCellColors()
1268 {
1269 swapcolors = !swapcolors;
1270 InvertCellColors();
1271
1272 if (pastelayer) {
1273 // invert colors used to draw paste pattern
1274 for (int n = 0; n <= pastelayer->numicons; n++) {
1275 pastelayer->cellr[n] = 255 - pastelayer->cellr[n];
1276 pastelayer->cellg[n] = 255 - pastelayer->cellg[n];
1277 pastelayer->cellb[n] = 255 - pastelayer->cellb[n];
1278 }
1279 InvertIconColors(pastelayer->atlas7x7, 8, pastelayer->numicons);
1280 InvertIconColors(pastelayer->atlas15x15, 16, pastelayer->numicons);
1281 InvertIconColors(pastelayer->atlas31x31, 32, pastelayer->numicons);
1282 }
1283
1284 mainptr->UpdateEverything();
1285 }
1286
1287 // -----------------------------------------------------------------------------
1288
ToggleSmarterScaling()1289 void PatternView::ToggleSmarterScaling()
1290 {
1291 smartscale = !smartscale;
1292 mainptr->UpdateEverything();
1293 }
1294
1295 // -----------------------------------------------------------------------------
1296
GetCellPos(bigint & xpos,bigint & ypos)1297 bool PatternView::GetCellPos(bigint& xpos, bigint& ypos)
1298 {
1299 wxPoint pt = ScreenToClient( wxGetMousePosition() );
1300 if (PointInView(pt.x, pt.y)) {
1301 // get mouse location in cell coords
1302 pair<bigint, bigint> cellpos = currlayer->view->at(pt.x, pt.y);
1303 xpos = cellpos.first;
1304 ypos = cellpos.second;
1305
1306 // check if xpos,ypos is outside bounded grid
1307 if (!CellInGrid(xpos, ypos)) return false;
1308
1309 return true;
1310 } else {
1311 // mouse not in viewport
1312 return false;
1313 }
1314 }
1315
1316 // -----------------------------------------------------------------------------
1317
PointInView(int x,int y)1318 bool PatternView::PointInView(int x, int y)
1319 {
1320 return ( x >= 0 && x <= currlayer->view->getxmax() &&
1321 y >= 0 && y <= currlayer->view->getymax() );
1322 }
1323
1324 // -----------------------------------------------------------------------------
1325
1326 #ifdef __WXMAC__
1327 #define RefreshControls() RefreshRect(controlsrect,false)
1328 #else
1329 // safer to redraw entire viewport on Windows and Linux
1330 // otherwise we see partial drawing in some cases
1331 #define RefreshControls() Refresh(false)
1332 #endif
1333
CheckCursor(bool active)1334 void PatternView::CheckCursor(bool active)
1335 {
1336 if (!active) return; // main window is not active so don't change cursor
1337
1338 if (mainptr->IsIconized()) return;
1339
1340 // make sure cursor is up to date
1341 wxPoint pt = ScreenToClient( wxGetMousePosition() );
1342 if (PointInView(pt.x, pt.y)) {
1343 int ox, oy;
1344 if (numlayers > 1 && tilelayers && tileindex != currindex) {
1345 // show arrow cursor if over tile border (ie. bigview) or non-current tile
1346 SetCursor(*wxSTANDARD_CURSOR);
1347 if (showcontrols) {
1348 showcontrols = false;
1349 RefreshControls();
1350 }
1351
1352 } else if (showoverlay && curroverlay->PointInOverlay(pt.x, pt.y, &ox, &oy)
1353 && !curroverlay->TransparentPixel(ox, oy)) {
1354 // cursor is over non-transparent pixel in overlay
1355 curroverlay->SetOverlayCursor();
1356 if (showcontrols) {
1357 showcontrols = false;
1358 RefreshControls();
1359 }
1360
1361 } else if ((controlsrect.Contains(pt) || clickedcontrol > NO_CONTROL) &&
1362 !(drawingcells || selectingcells || movingview || waitingforclick) ) {
1363 // cursor is over translucent controls, or user clicked in a control
1364 // and hasn't released mouse button yet
1365 SetCursor(*wxSTANDARD_CURSOR);
1366 if (!showcontrols) {
1367 showcontrols = true;
1368 RefreshControls();
1369 }
1370
1371 } else {
1372 // show current cursor mode
1373 SetCursor(*currlayer->curs);
1374 if (showcontrols) {
1375 showcontrols = false;
1376 RefreshControls();
1377 }
1378 }
1379
1380 } else {
1381 // cursor is not in viewport
1382 SetCursor(*wxSTANDARD_CURSOR);
1383 if (showcontrols) {
1384 showcontrols = false;
1385 RefreshControls();
1386 }
1387 }
1388 }
1389
1390 // -----------------------------------------------------------------------------
1391
GetMag()1392 int PatternView::GetMag()
1393 {
1394 return currlayer->view->getmag();
1395 }
1396
1397 // -----------------------------------------------------------------------------
1398
SetMag(int mag)1399 void PatternView::SetMag(int mag)
1400 {
1401 TestAutoFit();
1402 if (mag > MAX_MAG) mag = MAX_MAG;
1403 currlayer->view->setmag(mag);
1404 mainptr->UpdateEverything();
1405 }
1406
1407 // -----------------------------------------------------------------------------
1408
SetPosMag(const bigint & x,const bigint & y,int mag)1409 void PatternView::SetPosMag(const bigint& x, const bigint& y, int mag)
1410 {
1411 currlayer->view->setpositionmag(x, y, mag);
1412 }
1413
1414 // -----------------------------------------------------------------------------
1415
GetPos(bigint & x,bigint & y)1416 void PatternView::GetPos(bigint& x, bigint& y)
1417 {
1418 x = currlayer->view->x;
1419 y = currlayer->view->y;
1420 }
1421
1422 // -----------------------------------------------------------------------------
1423
FitInView(int force)1424 void PatternView::FitInView(int force)
1425 {
1426 currlayer->algo->fit(*currlayer->view, force);
1427 }
1428
1429 // -----------------------------------------------------------------------------
1430
CellVisible(const bigint & x,const bigint & y)1431 bool PatternView::CellVisible(const bigint& x, const bigint& y)
1432 {
1433 return currlayer->view->contains(x, y) != 0;
1434 }
1435
1436 // -----------------------------------------------------------------------------
1437
1438 // scrolling functions:
1439
PanUp(int amount)1440 void PatternView::PanUp(int amount)
1441 {
1442 TestAutoFit();
1443 currlayer->view->move(0, -amount);
1444 mainptr->UpdateEverything();
1445 }
1446
1447 // -----------------------------------------------------------------------------
1448
PanDown(int amount)1449 void PatternView::PanDown(int amount)
1450 {
1451 TestAutoFit();
1452 currlayer->view->move(0, amount);
1453 mainptr->UpdateEverything();
1454 }
1455
1456 // -----------------------------------------------------------------------------
1457
PanLeft(int amount)1458 void PatternView::PanLeft(int amount)
1459 {
1460 TestAutoFit();
1461 currlayer->view->move(-amount, 0);
1462 mainptr->UpdateEverything();
1463 }
1464
1465 // -----------------------------------------------------------------------------
1466
PanRight(int amount)1467 void PatternView::PanRight(int amount)
1468 {
1469 TestAutoFit();
1470 currlayer->view->move(amount, 0);
1471 mainptr->UpdateEverything();
1472 }
1473
1474 // -----------------------------------------------------------------------------
1475
PanNE()1476 void PatternView::PanNE()
1477 {
1478 TestAutoFit();
1479 int xamount = SmallScroll(currlayer->view->getwidth());
1480 int yamount = SmallScroll(currlayer->view->getheight());
1481 int amount = (xamount < yamount) ? xamount : yamount;
1482 currlayer->view->move(amount, -amount);
1483 mainptr->UpdateEverything();
1484 }
1485
1486 // -----------------------------------------------------------------------------
1487
PanNW()1488 void PatternView::PanNW()
1489 {
1490 TestAutoFit();
1491 int xamount = SmallScroll(currlayer->view->getwidth());
1492 int yamount = SmallScroll(currlayer->view->getheight());
1493 int amount = (xamount < yamount) ? xamount : yamount;
1494 currlayer->view->move(-amount, -amount);
1495 mainptr->UpdateEverything();
1496 }
1497
1498 // -----------------------------------------------------------------------------
1499
PanSE()1500 void PatternView::PanSE()
1501 {
1502 TestAutoFit();
1503 int xamount = SmallScroll(currlayer->view->getwidth());
1504 int yamount = SmallScroll(currlayer->view->getheight());
1505 int amount = (xamount < yamount) ? xamount : yamount;
1506 currlayer->view->move(amount, amount);
1507 mainptr->UpdateEverything();
1508 }
1509
1510 // -----------------------------------------------------------------------------
1511
PanSW()1512 void PatternView::PanSW()
1513 {
1514 TestAutoFit();
1515 int xamount = SmallScroll(currlayer->view->getwidth());
1516 int yamount = SmallScroll(currlayer->view->getheight());
1517 int amount = (xamount < yamount) ? xamount : yamount;
1518 currlayer->view->move(-amount, amount);
1519 mainptr->UpdateEverything();
1520 }
1521
1522 // -----------------------------------------------------------------------------
1523
SmallScroll(int xysize)1524 int PatternView::SmallScroll(int xysize)
1525 {
1526 int amount;
1527 int mag = currlayer->view->getmag();
1528 if (mag > 0) {
1529 // scroll an integral number of cells (1 cell = 2^mag pixels)
1530 if (mag < 3) {
1531 amount = ((xysize >> mag) / 20) << mag;
1532 if (amount == 0) amount = 1 << mag;
1533 return amount;
1534 } else {
1535 // grid lines are visible so scroll by only 1 cell
1536 return 1 << mag;
1537 }
1538 } else {
1539 // scroll by approx 5% of current wd/ht
1540 amount = xysize / 20;
1541 if (amount == 0) amount = 1;
1542 return amount;
1543 }
1544 }
1545
1546 // -----------------------------------------------------------------------------
1547
BigScroll(int xysize)1548 int PatternView::BigScroll(int xysize)
1549 {
1550 int amount;
1551 int mag = currlayer->view->getmag();
1552 if (mag > 0) {
1553 // scroll an integral number of cells (1 cell = 2^mag pixels)
1554 amount = ((xysize >> mag) * 9 / 10) << mag;
1555 if (amount == 0) amount = 1 << mag;
1556 return amount;
1557 } else {
1558 // scroll by approx 90% of current wd/ht
1559 amount = xysize * 9 / 10;
1560 if (amount == 0) amount = 1;
1561 return amount;
1562 }
1563 }
1564
1565 // -----------------------------------------------------------------------------
1566
UpdateScrollBars()1567 void PatternView::UpdateScrollBars()
1568 {
1569 if (mainptr->fullscreen) return;
1570
1571 int viewwd, viewht;
1572 int mag = currlayer->view->getmag();
1573 if (mag > 0) {
1574 // scroll by cells, so determine number of cells visible in viewport
1575 viewwd = currlayer->view->getwidth() >> mag;
1576 viewht = currlayer->view->getheight() >> mag;
1577 } else {
1578 // scroll by pixels, so get pixel dimensions of viewport
1579 viewwd = currlayer->view->getwidth();
1580 viewht = currlayer->view->getheight();
1581 }
1582 if (viewwd < 1) viewwd = 1;
1583 if (viewht < 1) viewht = 1;
1584
1585 if (currlayer->algo->gridwd > 0) {
1586 // restrict scrolling to left/right edges of grid if its width is finite
1587 int range = currlayer->algo->gridwd;
1588 // avoid scroll bar disappearing
1589 if (range < 3) range = 3;
1590 hthumb = currlayer->view->x.toint() + range / 2;
1591 mainptr->hbar->SetScrollbar(hthumb, 1, range, 1, true);
1592 } else {
1593 // keep thumb box in middle of scroll bar if grid width is infinite
1594 hthumb = (thumbrange - 1) * viewwd / 2;
1595 mainptr->hbar->SetScrollbar(hthumb, viewwd, thumbrange * viewwd, viewwd, true);
1596 }
1597
1598 if (currlayer->algo->gridht > 0) {
1599 // restrict scrolling to top/bottom edges of grid if its height is finite
1600 int range = currlayer->algo->gridht;
1601 // avoid scroll bar disappearing
1602 if (range < 3) range = 3;
1603 vthumb = currlayer->view->y.toint() + range / 2;
1604 mainptr->vbar->SetScrollbar(vthumb, 1, range, 1, true);
1605 } else {
1606 // keep thumb box in middle of scroll bar if grid height is infinite
1607 vthumb = (thumbrange - 1) * viewht / 2;
1608 mainptr->vbar->SetScrollbar(vthumb, viewht, thumbrange * viewht, viewht, true);
1609 }
1610 }
1611
1612 // -----------------------------------------------------------------------------
1613
ProcessKey(int key,int modifiers)1614 void PatternView::ProcessKey(int key, int modifiers)
1615 {
1616 mainptr->showbanner = false;
1617
1618 // WARNING: ProcessKey can be called while running a script, or reading
1619 // a large pattern file, or waiting for a paste click etc, so we must avoid
1620 // doing any actions that could cause havoc at such times.
1621 bool busy = nopattupdate || waitingforclick || dragtimer->IsRunning();
1622 bool timeline = TimelineExists();
1623
1624 action_info action = FindAction(key, modifiers);
1625 switch (action.id) {
1626 case DO_NOTHING:
1627 // any unassigned key turns off full screen mode
1628 if (mainptr->fullscreen) mainptr->ToggleFullScreen();
1629 break;
1630
1631 case DO_OPENFILE:
1632 if (IsHTMLFile(action.file)) {
1633 // show HTML file in help window
1634 if (!busy) ShowHelp(action.file);
1635 } else {
1636 // load pattern or run script
1637 if (!inscript && !busy) mainptr->OpenFile(action.file, true);
1638 }
1639 break;
1640
1641 // File menu actions
1642 case DO_NEWPATT: if (!inscript && !busy) mainptr->NewPattern(); break;
1643 case DO_OPENPATT: if (!inscript && !busy) mainptr->OpenPattern(); break;
1644 case DO_OPENCLIP: if (!inscript && !busy) mainptr->OpenClipboard(); break;
1645 case DO_SAVE: if (!inscript && !busy) mainptr->SavePattern(); break;
1646 case DO_SAVEXRLE: if (!inscript) savexrle = !savexrle; break;
1647 case DO_RUNSCRIPT: if (!inscript && !timeline && !busy) mainptr->OpenScript(); break;
1648 case DO_RUNCLIP: if (!inscript && !timeline && !busy) mainptr->RunClipboard(); break;
1649 case DO_PREFS: if (!busy) mainptr->ShowPrefsDialog(); break;
1650 case DO_FILEDIR: if (!busy) mainptr->ChangeFileDir(); break;
1651 case DO_SHOWFILES: mainptr->ToggleShowFiles(); break;
1652 case DO_QUIT: mainptr->QuitApp(); break;
1653
1654 // Edit menu actions
1655 case DO_UNDO: if (!inscript && !timeline && !busy) currlayer->undoredo->UndoChange(); break;
1656 case DO_REDO: if (!inscript && !timeline && !busy) currlayer->undoredo->RedoChange(); break;
1657 case DO_DISABLE: if (!inscript) mainptr->ToggleAllowUndo(); break;
1658 case DO_CUT: if (!inscript && !timeline) CutSelection(); break;
1659 case DO_COPY: if (!inscript) CopySelection(); break;
1660 case DO_CLEAR: if (!inscript && !timeline) ClearSelection(); break;
1661 case DO_CLEAROUT: if (!inscript && !timeline) ClearOutsideSelection(); break;
1662 case DO_PASTE:
1663 if (!inscript && !timeline && !busy) {
1664 // PasteClipboard(false) has a Yield loop so we do the following to avoid
1665 // calling ProcessKey re-entrantly as it causes problems on Mac OS X
1666 // and possibly the other platforms
1667 wxCommandEvent evt(wxEVT_COMMAND_MENU_SELECTED, ID_PASTE);
1668 wxPostEvent(mainptr->GetEventHandler(), evt);
1669 return;
1670 }
1671 break;
1672 // case DO_PASTE: if (!inscript && !timeline && !busy) PasteClipboard(false); break;
1673 case DO_PASTESEL: if (!inscript && !timeline && !busy) PasteClipboard(true); break;
1674 case DO_SELALL: if (!inscript) SelectAll(); break;
1675 case DO_REMOVESEL: if (!inscript) RemoveSelection(); break;
1676 case DO_SHRINK: if (!inscript) ShrinkSelection(false); break;
1677 case DO_SHRINKFIT: if (!inscript) ShrinkSelection(true); break;
1678 case DO_RANDFILL: if (!inscript && !timeline) RandomFill(); break;
1679 case DO_FLIPTB: if (!inscript && !timeline) FlipSelection(true); break;
1680 case DO_FLIPLR: if (!inscript && !timeline) FlipSelection(false); break;
1681 case DO_ROTATECW: if (!inscript && !timeline) RotateSelection(true); break;
1682 case DO_ROTATEACW: if (!inscript && !timeline) RotateSelection(false); break;
1683 case DO_ADVANCE: if (!inscript && !timeline) currlayer->currsel.Advance(); break;
1684 case DO_ADVANCEOUT: if (!inscript && !timeline) currlayer->currsel.AdvanceOutside(); break;
1685 case DO_CURSDRAW: SetCursorMode(curs_pencil); break;
1686 case DO_CURSPICK: SetCursorMode(curs_pick); break;
1687 case DO_CURSSEL: SetCursorMode(curs_cross); break;
1688 case DO_CURSMOVE: SetCursorMode(curs_hand); break;
1689 case DO_CURSIN: SetCursorMode(curs_zoomin); break;
1690 case DO_CURSOUT: SetCursorMode(curs_zoomout); break;
1691 case DO_CURSCYCLE: CycleCursorMode(); break;
1692 case DO_PASTEMODE: CyclePasteMode(); break;
1693 case DO_PASTELOC: CyclePasteLocation(); break;
1694 case DO_NEXTHIGHER: CycleDrawingState(true); break;
1695 case DO_NEXTLOWER: CycleDrawingState(false); break;
1696
1697 // Control menu actions
1698 case DO_STARTSTOP: if (!inscript) mainptr->StartOrStop(); break;
1699 case DO_NEXTGEN: if (!inscript && !timeline) mainptr->NextGeneration(false); break;
1700 case DO_NEXTSTEP: if (!inscript && !timeline) mainptr->NextGeneration(true); break;
1701 case DO_RESET: if (!inscript && !timeline && !busy) mainptr->ResetPattern(); break;
1702 case DO_SETGEN: if (!inscript && !timeline && !busy) mainptr->SetGeneration(); break;
1703 case DO_SETBASE: if (!inscript && !timeline && !busy) mainptr->SetBaseStep(); break;
1704 case DO_FASTER: mainptr->GoFaster(); break;
1705 case DO_SLOWER: mainptr->GoSlower(); break;
1706 case DO_AUTOFIT: mainptr->ToggleAutoFit(); break;
1707 case DO_HYPER: if (!timeline) mainptr->ToggleHyperspeed(); break;
1708 case DO_HASHINFO: mainptr->ToggleHashInfo(); break;
1709 case DO_SHOWPOP: mainptr->ToggleShowPopulation(); break;
1710 case DO_RECORD: StartStopRecording(); break;
1711 case DO_DELTIME: DeleteTimeline(); break;
1712 case DO_PLAYBACK: if (!inscript && timeline) PlayTimeline(-1); break;
1713 case DO_SETRULE: if (!inscript && !timeline && !busy) mainptr->ShowRuleDialog(); break;
1714 case DO_TIMING: if (!inscript && !timeline) mainptr->DisplayTimingInfo(); break;
1715 case DO_HASHING:
1716 if (!inscript && !timeline && !busy) {
1717 if (currlayer->algtype != HLIFE_ALGO)
1718 mainptr->ChangeAlgorithm(HLIFE_ALGO);
1719 else
1720 mainptr->ChangeAlgorithm(QLIFE_ALGO);
1721 }
1722 break;
1723
1724 // View menu actions
1725 case DO_LEFT: PanLeft( SmallScroll(currlayer->view->getwidth()) ); break;
1726 case DO_RIGHT: PanRight( SmallScroll(currlayer->view->getwidth()) ); break;
1727 case DO_UP: PanUp( SmallScroll(currlayer->view->getheight()) ); break;
1728 case DO_DOWN: PanDown( SmallScroll(currlayer->view->getheight()) ); break;
1729 case DO_NE: PanNE(); break;
1730 case DO_NW: PanNW(); break;
1731 case DO_SE: PanSE(); break;
1732 case DO_SW: PanSW(); break;
1733 case DO_FULLSCREEN: mainptr->ToggleFullScreen(); break;
1734 case DO_FIT: FitPattern(); break;
1735 case DO_FITSEL: FitSelection(); break;
1736 case DO_MIDDLE: ViewOrigin(); break;
1737 case DO_CHANGE00: ChangeOrigin(); break;
1738 case DO_RESTORE00: RestoreOrigin(); break;
1739 case DO_ZOOMIN: ZoomIn(); break;
1740 case DO_ZOOMOUT: ZoomOut(); break;
1741 case DO_SCALE1: SetPixelsPerCell(1); break;
1742 case DO_SCALE2: SetPixelsPerCell(2); break;
1743 case DO_SCALE4: SetPixelsPerCell(4); break;
1744 case DO_SCALE8: SetPixelsPerCell(8); break;
1745 case DO_SCALE16: SetPixelsPerCell(16); break;
1746 case DO_SCALE32: SetPixelsPerCell(32); break;
1747 case DO_SHOWTOOL: mainptr->ToggleToolBar(); break;
1748 case DO_SHOWLAYER: ToggleLayerBar(); break;
1749 case DO_SHOWEDIT: ToggleEditBar(); break;
1750 case DO_SHOWSTATES: ToggleAllStates(); break;
1751 case DO_SHOWSCROLL: mainptr->ToggleScrollBars(); break;
1752 case DO_SHOWSTATUS: mainptr->ToggleStatusBar(); break;
1753 case DO_SHOWEXACT: mainptr->ToggleExactNumbers(); break;
1754 case DO_SHOWICONS: ToggleCellIcons(); break;
1755 case DO_INVERT: ToggleCellColors(); break;
1756 case DO_SMARTSCALE: ToggleSmarterScaling(); break;
1757 case DO_SHOWGRID: ToggleGridLines(); break;
1758 case DO_SHOWTIME: ToggleTimelineBar(); break;
1759 case DO_INFO: if (!busy) mainptr->ShowPatternInfo(); break;
1760
1761 // Layer menu actions
1762 case DO_SAVEOVERLAY: mainptr->SaveOverlay(); break;
1763 case DO_SHOWOVERLAY: mainptr->ToggleOverlay(); break;
1764 case DO_DELOVERLAY: if (!inscript) mainptr->DeleteOverlay(); break;
1765 case DO_ADD: if (!inscript) AddLayer(); break;
1766 case DO_CLONE: if (!inscript) CloneLayer(); break;
1767 case DO_DUPLICATE: if (!inscript) DuplicateLayer(); break;
1768 case DO_DELETE: if (!inscript) DeleteLayer(); break;
1769 case DO_DELOTHERS: if (!inscript) DeleteOtherLayers(); break;
1770 case DO_MOVELAYER: if (!inscript && !busy) MoveLayerDialog(); break;
1771 case DO_NAMELAYER: if (!inscript && !busy) NameLayerDialog(); break;
1772 case DO_SETCOLORS: if (!inscript && !busy) SetLayerColors(); break;
1773 case DO_SYNCVIEWS: if (!inscript) ToggleSyncViews(); break;
1774 case DO_SYNCCURS: if (!inscript) ToggleSyncCursors(); break;
1775 case DO_STACK: if (!inscript) ToggleStackLayers(); break;
1776 case DO_TILE: if (!inscript) ToggleTileLayers(); break;
1777
1778 // Help menu actions
1779 case DO_HELP:
1780 if (!busy) {
1781 // if help window is open then bring it to the front,
1782 // otherwise open it and display most recent help file
1783 ShowHelp(wxEmptyString);
1784 }
1785 break;
1786 case DO_ABOUT: if (!inscript && !busy) ShowAboutBox(); break;
1787
1788 default: Warning(_("Bug detected in ProcessKey!"));
1789 }
1790
1791 // note that we avoid updating viewport if inscript and action.id is DO_OPENFILE
1792 // otherwise problem can occur when rapidly repeating a keyboard shortcut that
1793 // runs a script
1794 if (inscript && action.id != DO_NOTHING && action.id != DO_OPENFILE) {
1795 // update viewport, status bar, scroll bars
1796 inscript = false;
1797 mainptr->UpdatePatternAndStatus();
1798 bigview->UpdateScrollBars();
1799 inscript = true;
1800 }
1801
1802 mainptr->UpdateUserInterface();
1803 }
1804
1805 // -----------------------------------------------------------------------------
1806
RememberOneCellChange(int cx,int cy,int oldstate,int newstate)1807 void PatternView::RememberOneCellChange(int cx, int cy, int oldstate, int newstate)
1808 {
1809 if (allowundo) {
1810 // remember this cell change for later undo/redo
1811 currlayer->undoredo->SaveCellChange(cx, cy, oldstate, newstate);
1812 }
1813 }
1814
1815 // -----------------------------------------------------------------------------
1816
StartDrawingCells(int x,int y)1817 void PatternView::StartDrawingCells(int x, int y)
1818 {
1819 pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
1820 // check that cellpos is within getcell/setcell limits
1821 if ( OutsideLimits(cellpos.second, cellpos.first, cellpos.second, cellpos.first) ) {
1822 statusptr->ErrorMessage(_("Drawing is not allowed outside +/- 10^9 boundary."));
1823 return;
1824 }
1825
1826 drawingcells = true;
1827
1828 // save dirty state now for later use by RememberCellChanges
1829 if (allowundo) currlayer->savedirty = currlayer->dirty;
1830
1831 cellx = cellpos.first.toint();
1832 celly = cellpos.second.toint();
1833 int currstate = currlayer->algo->getcell(cellx, celly);
1834
1835 // reset drawing state in case it's no longer valid (due to algo/rule change)
1836 if (currlayer->drawingstate >= currlayer->algo->NumCellStates()) {
1837 currlayer->drawingstate = 1;
1838 }
1839
1840 if (currstate == currlayer->drawingstate) {
1841 drawstate = 0;
1842 } else {
1843 drawstate = currlayer->drawingstate;
1844 }
1845 if (currstate != drawstate) {
1846 currlayer->algo->setcell(cellx, celly, drawstate);
1847 currlayer->algo->endofpattern();
1848
1849 // remember this cell change for later undo/redo
1850 RememberOneCellChange(cellx, celly, currstate, drawstate);
1851
1852 MarkLayerDirty();
1853 if (showstatus) statusptr->Refresh(false);
1854 RefreshView();
1855 }
1856
1857 CaptureMouse(); // get mouse up event even if outside view
1858 dragtimer->Start(TEN_HERTZ);
1859
1860 if (stopdrawing) {
1861 // mouse up event has already been seen so terminate drawing immediately
1862 stopdrawing = false;
1863 StopDraggingMouse();
1864 }
1865 }
1866
1867 // -----------------------------------------------------------------------------
1868
DrawCells(int x,int y)1869 void PatternView::DrawCells(int x, int y)
1870 {
1871 // make sure x,y is within viewport
1872 if (x < 0) x = 0;
1873 if (y < 0) y = 0;
1874 if (x > currlayer->view->getxmax()) x = currlayer->view->getxmax();
1875 if (y > currlayer->view->getymax()) y = currlayer->view->getymax();
1876
1877 // make sure x,y is within bounded grid
1878 pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
1879 if (currlayer->algo->gridwd > 0) {
1880 if (cellpos.first < currlayer->algo->gridleft) cellpos.first = currlayer->algo->gridleft;
1881 if (cellpos.first > currlayer->algo->gridright) cellpos.first = currlayer->algo->gridright;
1882 }
1883 if (currlayer->algo->gridht > 0) {
1884 if (cellpos.second < currlayer->algo->gridtop) cellpos.second = currlayer->algo->gridtop;
1885 if (cellpos.second > currlayer->algo->gridbottom) cellpos.second = currlayer->algo->gridbottom;
1886 }
1887
1888 if ( currlayer->view->getmag() < 0 ||
1889 OutsideLimits(cellpos.second, cellpos.first, cellpos.second, cellpos.first) ) {
1890 return;
1891 }
1892
1893 int currstate;
1894 int numchanged = 0;
1895 int newx = cellpos.first.toint();
1896 int newy = cellpos.second.toint();
1897 if ( newx != cellx || newy != celly ) {
1898
1899 // draw a line of cells using Bresenham's algorithm
1900 int d, ii, jj, di, ai, si, dj, aj, sj;
1901 di = newx - cellx;
1902 ai = abs(di) << 1;
1903 si = (di < 0)? -1 : 1;
1904 dj = newy - celly;
1905 aj = abs(dj) << 1;
1906 sj = (dj < 0)? -1 : 1;
1907
1908 ii = cellx;
1909 jj = celly;
1910
1911 lifealgo* curralgo = currlayer->algo;
1912 if (ai > aj) {
1913 d = aj - (ai >> 1);
1914 while (ii != newx) {
1915 currstate = curralgo->getcell(ii, jj);
1916 if (currstate != drawstate) {
1917 curralgo->setcell(ii, jj, drawstate);
1918 RememberOneCellChange(ii, jj, currstate, drawstate);
1919 numchanged++;
1920 }
1921 if (d >= 0) {
1922 jj += sj;
1923 d -= ai;
1924 }
1925 ii += si;
1926 d += aj;
1927 }
1928 } else {
1929 d = ai - (aj >> 1);
1930 while (jj != newy) {
1931 currstate = curralgo->getcell(ii, jj);
1932 if (currstate != drawstate) {
1933 curralgo->setcell(ii, jj, drawstate);
1934 RememberOneCellChange(ii, jj, currstate, drawstate);
1935 numchanged++;
1936 }
1937 if (d >= 0) {
1938 ii += si;
1939 d -= aj;
1940 }
1941 jj += sj;
1942 d += ai;
1943 }
1944 }
1945
1946 cellx = newx;
1947 celly = newy;
1948
1949 currstate = curralgo->getcell(cellx, celly);
1950 if (currstate != drawstate) {
1951 curralgo->setcell(cellx, celly, drawstate);
1952 RememberOneCellChange(cellx, celly, currstate, drawstate);
1953 numchanged++;
1954 }
1955 }
1956
1957 if (numchanged > 0) {
1958 currlayer->algo->endofpattern();
1959 MarkLayerDirty();
1960 if (showstatus) statusptr->Refresh(false);
1961 RefreshView();
1962 }
1963 }
1964
1965 // -----------------------------------------------------------------------------
1966
PickCell(int x,int y)1967 void PatternView::PickCell(int x, int y)
1968 {
1969 pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
1970 if ( currlayer->view->getmag() < 0 ||
1971 OutsideLimits(cellpos.second, cellpos.first, cellpos.second, cellpos.first) ) {
1972 return;
1973 }
1974
1975 int cellx = cellpos.first.toint();
1976 int celly = cellpos.second.toint();
1977 currlayer->drawingstate = currlayer->algo->getcell(cellx, celly);
1978 UpdateEditBar();
1979 }
1980
1981 // -----------------------------------------------------------------------------
1982
StartSelectingCells(int x,int y,bool shiftdown)1983 void PatternView::StartSelectingCells(int x, int y, bool shiftdown)
1984 {
1985 // make sure anchor cell is within bounded grid (x,y can be outside grid)
1986 pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
1987 if (currlayer->algo->gridwd > 0) {
1988 if (cellpos.first < currlayer->algo->gridleft) cellpos.first = currlayer->algo->gridleft;
1989 if (cellpos.first > currlayer->algo->gridright) cellpos.first = currlayer->algo->gridright;
1990 }
1991 if (currlayer->algo->gridht > 0) {
1992 if (cellpos.second < currlayer->algo->gridtop) cellpos.second = currlayer->algo->gridtop;
1993 if (cellpos.second > currlayer->algo->gridbottom) cellpos.second = currlayer->algo->gridbottom;
1994 }
1995 anchorx = cellpos.first;
1996 anchory = cellpos.second;
1997
1998 // save original selection so it can be restored if user hits escape;
1999 // also used by RememberNewSelection
2000 currlayer->savesel = currlayer->currsel;
2001
2002 // reset previous selection
2003 prevsel.Deselect();
2004
2005 // for avoiding 1x1 selection if mouse doesn't move much
2006 initselx = x;
2007 initsely = y;
2008
2009 // allow changing size in any direction
2010 forceh = false;
2011 forcev = false;
2012
2013 if (SelectionExists()) {
2014 if (shiftdown) {
2015 // modify current selection
2016 currlayer->currsel.Modify(cellpos.first, cellpos.second,
2017 anchorx, anchory, &forceh, &forcev);
2018 DisplaySelectionSize();
2019 } else {
2020 // remove current selection
2021 currlayer->currsel.Deselect();
2022 }
2023 // allow mouse interaction if script is running
2024 bool saveinscript = inscript;
2025 inscript = false;
2026 mainptr->UpdatePatternAndStatus();
2027 inscript = saveinscript;
2028 }
2029
2030 selectingcells = true;
2031 CaptureMouse(); // get mouse up event even if outside view
2032 dragtimer->Start(TEN_HERTZ);
2033 }
2034
2035 // -----------------------------------------------------------------------------
2036
SelectCells(int x,int y)2037 void PatternView::SelectCells(int x, int y)
2038 {
2039 // only select cells within view
2040 if (x < 0) x = 0;
2041 if (y < 0) y = 0;
2042 if (x > currlayer->view->getxmax()) x = currlayer->view->getxmax();
2043 if (y > currlayer->view->getymax()) y = currlayer->view->getymax();
2044
2045 if ( abs(initselx - x) < 2 && abs(initsely - y) < 2 && !SelectionExists() ) {
2046 // avoid 1x1 selection if mouse hasn't moved much
2047 return;
2048 }
2049
2050 // make sure x,y is within bounded grid
2051 pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
2052 if (currlayer->algo->gridwd > 0) {
2053 if (cellpos.first < currlayer->algo->gridleft) cellpos.first = currlayer->algo->gridleft;
2054 if (cellpos.first > currlayer->algo->gridright) cellpos.first = currlayer->algo->gridright;
2055 }
2056 if (currlayer->algo->gridht > 0) {
2057 if (cellpos.second < currlayer->algo->gridtop) cellpos.second = currlayer->algo->gridtop;
2058 if (cellpos.second > currlayer->algo->gridbottom) cellpos.second = currlayer->algo->gridbottom;
2059 }
2060
2061 if (!forcev) currlayer->currsel.SetLeftRight(cellpos.first, anchorx);
2062 if (!forceh) currlayer->currsel.SetTopBottom(cellpos.second, anchory);
2063
2064 if (currlayer->currsel != prevsel) {
2065 // selection has changed
2066 DisplaySelectionSize();
2067 prevsel = currlayer->currsel;
2068
2069 // allow mouse interaction if script is running
2070 bool saveinscript = inscript;
2071 inscript = false;
2072 mainptr->UpdatePatternAndStatus();
2073 inscript = saveinscript;
2074 }
2075 }
2076
2077 // -----------------------------------------------------------------------------
2078
StartMovingView(int x,int y)2079 void PatternView::StartMovingView(int x, int y)
2080 {
2081 pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
2082 bigcellx = cellpos.first;
2083 bigcelly = cellpos.second;
2084 movingview = true;
2085 if (waitingforclick) {
2086 // avoid calling CaptureMouse again (middle button was pressed)
2087 } else {
2088 CaptureMouse(); // get mouse up event even if outside view
2089 }
2090 dragtimer->Start(TEN_HERTZ);
2091 }
2092
2093 // -----------------------------------------------------------------------------
2094
MoveView(int x,int y)2095 void PatternView::MoveView(int x, int y)
2096 {
2097 pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
2098 bigint newx = cellpos.first;
2099 bigint newy = cellpos.second;
2100 bigint xdelta = bigcellx;
2101 bigint ydelta = bigcelly;
2102 xdelta -= newx;
2103 ydelta -= newy;
2104
2105 int xamount, yamount;
2106 int mag = currlayer->view->getmag();
2107 if (mag >= 0) {
2108 // move an integral number of cells
2109 xamount = xdelta.toint() << mag;
2110 yamount = ydelta.toint() << mag;
2111 } else {
2112 // convert cell deltas to screen pixels
2113 xdelta >>= -mag;
2114 ydelta >>= -mag;
2115 xamount = xdelta.toint();
2116 yamount = ydelta.toint();
2117 }
2118
2119 if ( xamount != 0 || yamount != 0 ) {
2120 currlayer->view->move(xamount, yamount);
2121
2122 // allow mouse interaction if script is running
2123 bool saveinscript = inscript;
2124 inscript = false;
2125 mainptr->UpdatePatternAndStatus();
2126 inscript = saveinscript;
2127
2128 cellpos = currlayer->view->at(x, y);
2129 bigcellx = cellpos.first;
2130 bigcelly = cellpos.second;
2131 }
2132
2133 // need to update scroll bars if grid is bounded
2134 if (currlayer->algo->gridwd > 0 || currlayer->algo->gridht > 0) {
2135 UpdateScrollBars();
2136 }
2137 }
2138
2139 // -----------------------------------------------------------------------------
2140
StopDraggingMouse()2141 void PatternView::StopDraggingMouse()
2142 {
2143 if ( HasCapture() ) {
2144 if (movingview && waitingforclick) {
2145 // don't release mouse capture here (paste loop won't detect click outside view)
2146 } else {
2147 ReleaseMouse();
2148 }
2149 }
2150
2151 if ( dragtimer->IsRunning() ) dragtimer->Stop();
2152
2153 if (selectingcells) {
2154 if (allowundo) RememberNewSelection(_("Selection"));
2155 selectingcells = false; // tested by CanUndo
2156 mainptr->UpdateMenuItems(); // enable various Edit menu items
2157 if (allowundo) UpdateEditBar(); // update Undo/Redo buttons
2158 }
2159
2160 if (drawingcells && allowundo) {
2161 // MarkLayerDirty has set dirty flag, so we need to
2162 // pass in the flag state saved before drawing started
2163 currlayer->undoredo->RememberCellChanges(_("Drawing"), currlayer->savedirty);
2164 drawingcells = false; // tested by CanUndo
2165 mainptr->UpdateMenuItems(); // enable Undo item
2166 UpdateEditBar(); // update Undo/Redo buttons
2167 }
2168
2169 if (clickedcontrol > NO_CONTROL) {
2170 if (currcontrol == clickedcontrol && !PANNING_CONTROL) {
2171 // only do non-panning function when button is released
2172 ProcessClickedControl();
2173 }
2174 clickedcontrol = NO_CONTROL;
2175 currcontrol = NO_CONTROL;
2176 RefreshRect(controlsrect, false);
2177 Update();
2178 }
2179
2180 if (movingview && restorecursor != NULL) {
2181 // restore cursor temporarily changed to curs_hand due to middle button click
2182 SetCursorMode(restorecursor);
2183 restorecursor = NULL;
2184 mainptr->UpdateMenuItems(); // enable Edit > Cursor Mode
2185 UpdateEditBar(); // update cursor mode buttons
2186 }
2187
2188 drawingcells = false;
2189 selectingcells = false;
2190 movingview = false;
2191
2192 CheckCursor(true);
2193 }
2194
2195 // -----------------------------------------------------------------------------
2196
RestoreSelection()2197 void PatternView::RestoreSelection()
2198 {
2199 currlayer->currsel = currlayer->savesel;
2200 StopDraggingMouse();
2201
2202 // allow mouse interaction if script is running
2203 bool saveinscript = inscript;
2204 inscript = false;
2205 mainptr->UpdatePatternAndStatus();
2206 inscript = saveinscript;
2207
2208 statusptr->DisplayMessage(_("New selection aborted."));
2209 }
2210
2211 // -----------------------------------------------------------------------------
2212
TestAutoFit()2213 void PatternView::TestAutoFit()
2214 {
2215 if (currlayer->autofit && mainptr->generating) {
2216 // assume user no longer wants us to do autofitting
2217 currlayer->autofit = false;
2218 }
2219 }
2220
2221 // -----------------------------------------------------------------------------
2222
ZoomInPos(int x,int y)2223 void PatternView::ZoomInPos(int x, int y)
2224 {
2225 // zoom in so that clicked cell stays under cursor
2226 TestAutoFit();
2227 if (currlayer->view->getmag() < MAX_MAG) {
2228 currlayer->view->zoom(x, y);
2229 // allow mouse interaction if script is running
2230 bool saveinscript = inscript;
2231 inscript = false;
2232 mainptr->UpdatePatternAndStatus();
2233 bigview->UpdateScrollBars();
2234 inscript = saveinscript;
2235 } else {
2236 Beep(); // can't zoom in any further
2237 }
2238 }
2239
2240 // -----------------------------------------------------------------------------
2241
ZoomOutPos(int x,int y)2242 void PatternView::ZoomOutPos(int x, int y)
2243 {
2244 // zoom out so that clicked cell stays under cursor
2245 TestAutoFit();
2246 currlayer->view->unzoom(x, y);
2247 // allow mouse interaction if script is running
2248 bool saveinscript = inscript;
2249 inscript = false;
2250 mainptr->UpdatePatternAndStatus();
2251 bigview->UpdateScrollBars();
2252 inscript = saveinscript;
2253 }
2254
2255 // -----------------------------------------------------------------------------
2256
SetViewSize(int wd,int ht)2257 void PatternView::SetViewSize(int wd, int ht)
2258 {
2259 // wd or ht might be < 1 on Windows
2260 if (wd < 1) wd = 1;
2261 if (ht < 1) ht = 1;
2262
2263 if (tileindex < 0) {
2264 // use main viewport window's size to reset viewport in each layer
2265 ResizeLayers(wd, ht);
2266 }
2267
2268 // only autofit when generating
2269 if (currlayer->autofit && mainptr && mainptr->generating)
2270 currlayer->algo->fit(*currlayer->view, 0);
2271
2272 // update position of translucent controls
2273 switch (controlspos) {
2274 case 1:
2275 // top left corner
2276 controlsrect = wxRect(0, 0, controlswd, controlsht);
2277 break;
2278 case 2:
2279 // top right corner
2280 controlsrect = wxRect(wd - controlswd, 0, controlswd, controlsht);
2281 break;
2282 case 3:
2283 // bottom right corner
2284 controlsrect = wxRect(wd - controlswd, ht - controlsht, controlswd, controlsht);
2285 break;
2286 case 4:
2287 // bottom left corner
2288 controlsrect = wxRect(0, ht - controlsht, controlswd, controlsht);
2289 break;
2290 default:
2291 // controlspos should be 0 (controls are disabled)
2292 controlsrect = wxRect(0, 0, 0, 0);
2293 }
2294 }
2295
2296 // -----------------------------------------------------------------------------
2297
OnPaint(wxPaintEvent & WXUNUSED (event))2298 void PatternView::OnPaint(wxPaintEvent& WXUNUSED(event))
2299 {
2300 // OnPaint handlers must always create a wxPaintDC
2301 wxPaintDC dc(this);
2302
2303 int wd, ht;
2304 GetClientSize(&wd, &ht);
2305 // wd or ht might be < 1 on Windows
2306 if (wd < 1) wd = 1;
2307 if (ht < 1) ht = 1;
2308
2309 if ( numclones > 0 && numlayers > 1 && (stacklayers || tilelayers) )
2310 SyncClones();
2311
2312 if ( numlayers > 1 && tilelayers ) {
2313 if ( tileindex >= 0 && ( wd != GetLayer(tileindex)->view->getwidth() ||
2314 ht != GetLayer(tileindex)->view->getheight() ) ) {
2315 // might happen on Win/GTK???
2316 GetLayer(tileindex)->view->resize(wd, ht);
2317 }
2318 } else if ( wd != currlayer->view->getwidth() || ht != currlayer->view->getheight() ) {
2319 // need to change viewport size;
2320 // can happen on Windows when resizing/maximizing main window
2321 SetViewSize(wd, ht);
2322 }
2323
2324 SetCurrent(*glcontext);
2325
2326 if (initgl) {
2327 // do these gl calls once (and only after the window has been created)
2328 initgl = false;
2329
2330 glDisable(GL_DEPTH_TEST); // we only do 2D drawing
2331 glDisable(GL_DITHER);
2332 // glDisable(GL_MULTISAMPLE); // unknown on Windows
2333 glDisable(GL_STENCIL_TEST);
2334 glDisable(GL_FOG);
2335
2336 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
2337 glEnableClientState(GL_VERTEX_ARRAY);
2338
2339 glEnable(GL_BLEND);
2340 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2341
2342 // initialize GL matrix to match our preferred coordinate system
2343 glMatrixMode(GL_PROJECTION);
2344 glLoadIdentity();
2345 glOrtho(0, wd, ht, 0, -1, 1); // origin is top left and y increases down
2346 glViewport(0, 0, wd, ht);
2347 glMatrixMode(GL_MODELVIEW);
2348
2349 // get OpenGL version
2350 const char* version = NULL;
2351 version = (const char*)glGetString(GL_VERSION);
2352 if (version) {
2353 sscanf(version, "%d.%d", &glMajor, &glMinor);
2354 }
2355
2356 // get maximum texture size
2357 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glMaxTextureSize);
2358 if (glMaxTextureSize < 1024) glMaxTextureSize = 1024;
2359 }
2360
2361 DrawView(tileindex);
2362
2363 SwapBuffers();
2364 }
2365
2366 // -----------------------------------------------------------------------------
2367
OnSize(wxSizeEvent & event)2368 void PatternView::OnSize(wxSizeEvent& event)
2369 {
2370 if (!IsShownOnScreen()) return; // must not call SetCurrent (on Linux at least)
2371
2372 int wd, ht;
2373 GetClientSize(&wd, &ht);
2374
2375 // resize this viewport
2376 SetViewSize(wd, ht);
2377
2378 SetCurrent(*glcontext);
2379
2380 // update GL matrix
2381 glMatrixMode(GL_PROJECTION);
2382 glLoadIdentity();
2383 glOrtho(0, wd, ht, 0, -1, 1); // origin is top left and y increases down
2384 glViewport(0, 0, wd, ht);
2385 glMatrixMode(GL_MODELVIEW);
2386
2387 event.Skip();
2388 }
2389
2390 // -----------------------------------------------------------------------------
2391
2392 #if defined(__WXMAC__) && wxCHECK_VERSION(2,9,0)
2393 // wxMOD_CONTROL has been changed to mean Command key down (sheesh!)
2394 #define wxMOD_CONTROL wxMOD_RAW_CONTROL
2395 #define ControlDown RawControlDown
2396 #endif
2397
OnKeyDown(wxKeyEvent & event)2398 void PatternView::OnKeyDown(wxKeyEvent& event)
2399 {
2400 #ifdef __WXMAC__
2401 // close any open tool tip window (fixes wxMac bug?)
2402 wxToolTip::RemoveToolTips();
2403 #endif
2404
2405 realkey = event.GetKeyCode();
2406 int mods = event.GetModifiers();
2407
2408 #ifdef __WXMSW__
2409 // avoid selection size message being cleared
2410 if (selectingcells && realkey == WXK_SHIFT) return;
2411 #endif
2412
2413 statusptr->ClearMessage();
2414
2415 // wxGTK 2.8.12 does not set modifier flag if shift key pressed by itself
2416 // so best to play safe and test wxMOD_SHIFT or wxMOD_NONE
2417 if (realkey == WXK_SHIFT && (mods == wxMOD_SHIFT || mods == wxMOD_NONE)) {
2418 // pressing unmodified shift key temporarily toggles the draw/pick cursors or
2419 // the zoom in/out cursors; note that Windows sends multiple key-down events
2420 // while shift key is pressed so we must be careful to toggle only once
2421 if (currlayer->curs == curs_pencil && oldcursor == NULL) {
2422 oldcursor = curs_pencil;
2423 SetCursorMode(curs_pick);
2424 mainptr->UpdateUserInterface();
2425 } else if (currlayer->curs == curs_pick && oldcursor == NULL) {
2426 oldcursor = curs_pick;
2427 SetCursorMode(curs_pencil);
2428 mainptr->UpdateUserInterface();
2429 } else if (currlayer->curs == curs_zoomin && oldcursor == NULL) {
2430 oldcursor = curs_zoomin;
2431 SetCursorMode(curs_zoomout);
2432 mainptr->UpdateUserInterface();
2433 } else if (currlayer->curs == curs_zoomout && oldcursor == NULL) {
2434 oldcursor = curs_zoomout;
2435 SetCursorMode(curs_zoomin);
2436 mainptr->UpdateUserInterface();
2437 }
2438 } else if (oldcursor) {
2439 // for any other key combo we restore the cursor immediately rather than
2440 // wait for the shift key to be released; this avoids problems with OnKeyUp
2441 // not being called if the shift key is used in a keyboard shortcut that
2442 // adds a new layer or opens another window
2443 SetCursorMode(oldcursor);
2444 oldcursor = NULL;
2445 mainptr->UpdateUserInterface();
2446 }
2447
2448 if (debuglevel == 1) {
2449 // set debugkey now but don't show it until OnChar
2450 debugkey = wxString::Format(_("OnKeyDown: key=%d (%c) mods=%d"),
2451 realkey, realkey < 128 ? wxChar(realkey) : wxChar('?'), mods);
2452 }
2453
2454 // WARNING: logic must match that in KeyComboCtrl::OnKeyDown in wxprefs.cpp
2455 if (mods == wxMOD_NONE || realkey == WXK_ESCAPE || realkey > 127) {
2456 // tell OnChar handler to ignore realkey
2457 realkey = 0;
2458 }
2459
2460 #ifdef __WXOSX__
2461 // pass ctrl/cmd-key combos directly to OnChar
2462 if (realkey > 0 && ((mods & wxMOD_CONTROL) || (mods & wxMOD_CMD))) {
2463 OnChar(event);
2464 return;
2465 }
2466 #endif
2467
2468 #ifdef __WXMAC__
2469 // allow option-E/I/N/U/` (OnChar is not called for those key combos
2470 // although the prefs dialog KeyComboCtrl::OnChar *is* called)
2471 if (mods == wxMOD_ALT && (realkey == 'E' || realkey == 'I' || realkey == 'N' ||
2472 realkey == 'U' || realkey == '`')) {
2473 OnChar(event);
2474 return;
2475 }
2476 #endif
2477
2478 #ifdef __WXMSW__
2479 // on Windows, OnChar is NOT called for some ctrl-key combos like
2480 // ctrl-0..9 or ctrl-alt-key, so we call OnChar ourselves
2481 if (realkey > 0 && (mods & wxMOD_CONTROL)) {
2482 OnChar(event);
2483 return;
2484 }
2485 #endif
2486
2487 #ifdef __WXGTK__
2488 if (realkey == ' ' && mods == wxMOD_SHIFT) {
2489 // fix wxGTK bug (curiously, the bug isn't seen in the prefs dialog);
2490 // OnChar won't see the shift modifier, so set realkey to a special
2491 // value to tell OnChar that shift-space was pressed
2492 realkey = -666;
2493 }
2494 #endif
2495
2496 event.Skip();
2497 }
2498
2499 // -----------------------------------------------------------------------------
2500
OnKeyUp(wxKeyEvent & event)2501 void PatternView::OnKeyUp(wxKeyEvent& event)
2502 {
2503 int key = event.GetKeyCode();
2504
2505 if (key == WXK_SHIFT) {
2506 // releasing shift key sets cursor back to original state
2507 if (oldcursor) {
2508 SetCursorMode(oldcursor);
2509 oldcursor = NULL;
2510 mainptr->UpdateUserInterface();
2511 }
2512 }
2513
2514 if (inscript && pass_key_events) {
2515 // let script decide what to do with key-up events
2516 PassKeyUpToScript(key);
2517 }
2518
2519 // no need to call event.Skip() here
2520 }
2521
2522 // -----------------------------------------------------------------------------
2523
OnChar(wxKeyEvent & event)2524 void PatternView::OnChar(wxKeyEvent& event)
2525 {
2526 // get translated keyboard event
2527 int key = event.GetKeyCode();
2528 int mods = event.GetModifiers();
2529
2530 if (debuglevel == 1) {
2531 debugkey += wxString::Format(_("\nOnChar: key=%d (%c) mods=%d"),
2532 key, key < 128 ? wxChar(key) : wxChar('?'), mods);
2533 Warning(debugkey);
2534 }
2535
2536 // WARNING: logic must match that in KeyComboCtrl::OnChar in wxprefs.cpp
2537 if (realkey > 0 && mods != wxMOD_NONE) {
2538 #ifdef __WXGTK__
2539 // sigh... wxGTK returns inconsistent results for shift-comma combos
2540 // so we assume that '<' is produced by pressing shift-comma
2541 // (which might only be true for US keyboards)
2542 if (key == '<' && (mods & wxMOD_SHIFT)) realkey = ',';
2543 #endif
2544 #ifdef __WXMSW__
2545 // sigh... wxMSW returns inconsistent results for some shift-key combos
2546 // so again we assume we're using a US keyboard
2547 if (key == '~' && (mods & wxMOD_SHIFT)) realkey = '`';
2548 if (key == '+' && (mods & wxMOD_SHIFT)) realkey = '=';
2549 #endif
2550 if (mods == wxMOD_SHIFT && key != realkey) {
2551 // use translated key code but remove shift key;
2552 // eg. we want shift-'/' to be seen as '?'
2553 mods = wxMOD_NONE;
2554 } else {
2555 // use key code seen by OnKeyDown
2556 key = realkey;
2557 if (key >= 'A' && key <= 'Z') key += 32; // convert A..Z to a..z
2558 }
2559 }
2560
2561 #ifdef __WXGTK__
2562 if (realkey == -666) {
2563 // OnKeyDown saw that shift-space was pressed but for some reason
2564 // OnChar doesn't see the modifier (ie. mods is wxMOD_NONE)
2565 key = ' ';
2566 mods = wxMOD_SHIFT;
2567 }
2568 #endif
2569
2570 // do this check first because we allow user to make a selection while
2571 // generating a pattern or running a script
2572 if ( selectingcells && key == WXK_ESCAPE ) {
2573 RestoreSelection();
2574 return;
2575 }
2576
2577 if ( inscript && (pass_key_events || key == WXK_ESCAPE) ) {
2578 // let script decide what to do with the key
2579 PassKeyToScript(key, mods);
2580 return;
2581 }
2582
2583 // test waitingforclick before mainptr->generating so user can cancel
2584 // a paste operation while generating
2585 if ( waitingforclick && key == WXK_ESCAPE ) {
2586 AbortPaste();
2587 return;
2588 }
2589
2590 if ( TimelineExists() && key == WXK_ESCAPE ) {
2591 if (currlayer->algo->isrecording()) {
2592 StartStopRecording(); // stop recording
2593 } else {
2594 PlayTimeline(0); // stop autoplay
2595 }
2596 return;
2597 }
2598
2599 if ( mainptr->generating && key == WXK_ESCAPE ) {
2600 mainptr->Stop();
2601 return;
2602 }
2603
2604 ProcessKey(key, mods);
2605 }
2606
2607 // -----------------------------------------------------------------------------
2608
ProcessClickedControl()2609 void PatternView::ProcessClickedControl()
2610 {
2611 switch (clickedcontrol) {
2612 case STEP1_CONTROL:
2613 if (TimelineExists()) {
2614 // reset autoplay speed to 0 (no delay, no frame skipping)
2615 ResetTimelineSpeed();
2616 } else if (currlayer->currexpo != 0) {
2617 mainptr->SetStepExponent(0);
2618 statusptr->Refresh(false);
2619 }
2620 break;
2621
2622 case SLOWER_CONTROL:
2623 mainptr->GoSlower();
2624 break;
2625
2626 case FASTER_CONTROL:
2627 mainptr->GoFaster();
2628 break;
2629
2630 case FIT_CONTROL:
2631 FitPattern();
2632 break;
2633
2634 case ZOOMIN_CONTROL:
2635 ZoomIn();
2636 break;
2637
2638 case ZOOMOUT_CONTROL:
2639 ZoomOut();
2640 break;
2641
2642 case NW_CONTROL:
2643 PanNW();
2644 break;
2645
2646 case UP_CONTROL:
2647 PanUp( SmallScroll(currlayer->view->getheight()) );
2648 break;
2649
2650 case NE_CONTROL:
2651 PanNE();
2652 break;
2653
2654 case LEFT_CONTROL:
2655 PanLeft( SmallScroll(currlayer->view->getwidth()) );
2656 break;
2657
2658 case MIDDLE_CONTROL:
2659 ViewOrigin();
2660 break;
2661
2662 case RIGHT_CONTROL:
2663 PanRight( SmallScroll(currlayer->view->getwidth()) );
2664 break;
2665
2666 case SW_CONTROL:
2667 PanSW();
2668 break;
2669
2670 case DOWN_CONTROL:
2671 PanDown( SmallScroll(currlayer->view->getheight()) );
2672 break;
2673
2674 case SE_CONTROL:
2675 PanSE();
2676 break;
2677
2678 default:
2679 // should never happen
2680 Warning(_("Bug detected in ProcessClickedControl!"));
2681 }
2682
2683 // need to update viewport and status bar if script is running
2684 if (inscript) {
2685 inscript = false;
2686 mainptr->UpdatePatternAndStatus();
2687 inscript = true;
2688 }
2689 }
2690
2691 // -----------------------------------------------------------------------------
2692
ProcessClick(int x,int y,int button,int modifiers)2693 void PatternView::ProcessClick(int x, int y, int button, int modifiers)
2694 {
2695 // user has clicked x,y pixel in viewport
2696 if (button == wxMOUSE_BTN_LEFT) {
2697 if (currlayer->curs == curs_pencil) {
2698 if (!PointInGrid(x, y)) {
2699 // best not to clobber any status bar message displayed by script
2700 Warning(_("Drawing is not allowed outside grid."));
2701 return;
2702 }
2703 if (inscript) {
2704 // best not to clobber any status bar message displayed by script
2705 Warning(_("Drawing is not allowed while a script is running."));
2706 return;
2707 }
2708 if (TimelineExists()) {
2709 statusptr->ErrorMessage(_("Drawing is not allowed if there is a timeline."));
2710 return;
2711 }
2712 if (currlayer->view->getmag() < 0) {
2713 statusptr->ErrorMessage(_("Drawing is not allowed at scales greater than 1 cell per pixel."));
2714 return;
2715 }
2716 if (mainptr->generating) {
2717 // we allow drawing while generating
2718 mainptr->draw_pending = true;
2719 mainptr->mouseevent.m_x = x;
2720 mainptr->mouseevent.m_y = y;
2721 mainptr->Stop();
2722 return;
2723 }
2724 StartDrawingCells(x, y);
2725
2726 } else if (currlayer->curs == curs_pick) {
2727 if (!PointInGrid(x, y)) {
2728 // best not to clobber any status bar message displayed by script
2729 Warning(_("Picking is not allowed outside grid."));
2730 return;
2731 }
2732 if (inscript) {
2733 // best not to clobber any status bar message displayed by script
2734 Warning(_("Picking is not allowed while a script is running."));
2735 return;
2736 }
2737 if (currlayer->view->getmag() < 0) {
2738 statusptr->ErrorMessage(_("Picking is not allowed at scales greater than 1 cell per pixel."));
2739 return;
2740 }
2741 PickCell(x, y);
2742
2743 } else if (currlayer->curs == curs_cross) {
2744 // note that we allow starting a selection outside the grid to make it
2745 // easier to select all of a bounded grid, but not when a script is running
2746 // because there's currently no way for the script to detect such a click
2747 if (inscript && !PointInGrid(x, y)) {
2748 Warning(_("Selecting is not allowed outside grid."));
2749 return;
2750 }
2751 TestAutoFit();
2752 StartSelectingCells(x, y, (modifiers & wxMOD_SHIFT) != 0);
2753
2754 } else if (currlayer->curs == curs_hand) {
2755 TestAutoFit();
2756 StartMovingView(x, y);
2757
2758 } else if (currlayer->curs == curs_zoomin) {
2759 ZoomInPos(x, y);
2760
2761 } else if (currlayer->curs == curs_zoomout) {
2762 ZoomOutPos(x, y);
2763 }
2764
2765 } else if (button == wxMOUSE_BTN_RIGHT) {
2766 // reverse the usual zoom direction
2767 if (currlayer->curs == curs_zoomin) {
2768 ZoomOutPos(x, y);
2769 } else if (currlayer->curs == curs_zoomout) {
2770 ZoomInPos(x, y);
2771 }
2772
2773 } else if (button == wxMOUSE_BTN_MIDDLE) {
2774 // start panning, regardless of current cursor mode
2775 if (currlayer->curs != curs_hand) {
2776 restorecursor = currlayer->curs;
2777 SetCursorMode(curs_hand);
2778 }
2779 TestAutoFit();
2780 StartMovingView(x, y);
2781 }
2782
2783 mainptr->UpdateUserInterface();
2784 }
2785
2786 // -----------------------------------------------------------------------------
2787
GetMouseModifiers(wxMouseEvent & event)2788 static int GetMouseModifiers(wxMouseEvent& event)
2789 {
2790 int modbits = wxMOD_NONE;
2791 if (event.AltDown()) modbits |= wxMOD_ALT;
2792 if (event.CmdDown()) modbits |= wxMOD_CMD;
2793 if (event.ControlDown()) modbits |= wxMOD_CONTROL;
2794 if (event.MetaDown()) modbits |= wxMOD_META;
2795 if (event.ShiftDown()) modbits |= wxMOD_SHIFT;
2796 return modbits;
2797 }
2798
2799 // -----------------------------------------------------------------------------
2800
OnMouseDown(wxMouseEvent & event)2801 void PatternView::OnMouseDown(wxMouseEvent& event)
2802 {
2803 int x = event.GetX();
2804 int y = event.GetY();
2805 int button = event.GetButton();
2806 int modifiers = GetMouseModifiers(event);
2807
2808 // ignore if a mouse button is already down
2809 if (mouseisdown) return;
2810
2811 // flag that a mouse button is down
2812 mouseisdown = true;
2813 whichbuttondown = button;
2814
2815 if (waitingforclick && button == wxMOUSE_BTN_LEFT) {
2816 // save paste location
2817 pastex = x;
2818 pastey = y;
2819 waitingforclick = false; // terminate while (waitingforclick) loop
2820 return;
2821 }
2822
2823 statusptr->ClearMessage();
2824 mainptr->showbanner = false;
2825
2826 if (numlayers > 1 && tilelayers && tileindex < 0) {
2827 // ignore click in tile border
2828 return;
2829 }
2830
2831 if (tileindex >= 0 && tileindex != currindex) {
2832 // switch current layer to clicked tile
2833 SwitchToClickedTile(tileindex);
2834 return;
2835 }
2836
2837 // tileindex == currindex
2838
2839 int ox, oy;
2840 if (showoverlay && curroverlay->PointInOverlay(x, y, &ox, &oy)
2841 && !curroverlay->TransparentPixel(ox, oy)) {
2842 if (inscript && pass_mouse_events) {
2843 // let script decide what to do with click in non-transparent pixel in overlay
2844 PassOverlayClickToScript(ox, oy, button, modifiers);
2845 }
2846 // otherwise just ignore click in overlay
2847 return;
2848 }
2849
2850 if (showcontrols) {
2851 currcontrol = WhichControl(x - controlsrect.x, y - controlsrect.y);
2852 if (currcontrol > NO_CONTROL) {
2853 clickedcontrol = currcontrol; // remember which control was clicked
2854 clicktime = stopwatch->Time(); // remember when clicked (in millisecs)
2855 CaptureMouse(); // get mouse up event even if outside view
2856 dragtimer->Start(SIXTY_HERTZ); // call OnDragTimer ~60 times per sec
2857 RefreshRect(controlsrect, false); // redraw clicked button
2858 #ifdef __WXGTK__
2859 // nicer to see change immediately on Linux
2860 Update();
2861 #endif
2862 if (PANNING_CONTROL) {
2863 // scroll immediately
2864 ProcessClickedControl();
2865 }
2866 }
2867 return;
2868 }
2869
2870 if (inscript && pass_mouse_events && PointInGrid(x, y)) {
2871 // let script decide what to do with click in grid
2872 pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
2873 PassClickToScript(cellpos.first, cellpos.second, button, modifiers);
2874 return;
2875 }
2876
2877 ProcessClick(x, y, button, modifiers);
2878 }
2879
2880 // -----------------------------------------------------------------------------
2881
OnMouseUp(wxMouseEvent & event)2882 void PatternView::OnMouseUp(wxMouseEvent& event)
2883 {
2884 // if the button released was not the first held down then ignore
2885 int button = event.GetButton();
2886 if (button != whichbuttondown) return;
2887
2888 // same button released so process
2889 mouseisdown = false;
2890
2891 if (drawingcells || selectingcells || movingview || clickedcontrol > NO_CONTROL) {
2892 StopDraggingMouse();
2893 } else if (mainptr->draw_pending) {
2894 // this can happen if user does a quick click while pattern is generating,
2895 // so set a special flag to force drawing to terminate
2896 stopdrawing = true;
2897 }
2898
2899 if (inscript && pass_mouse_events) {
2900 // let script decide what to do with mouse up event
2901 PassMouseUpToScript(event.GetButton());
2902 return;
2903 }
2904 }
2905
2906 // -----------------------------------------------------------------------------
2907
ResetMouseDown()2908 void PatternView::ResetMouseDown()
2909 {
2910 // mouseisdown needs to be reset if a right-click causes a modal dialog
2911 // to appear which then fails to send a mouse-up event (bug only in wxMac???)
2912 mouseisdown = false;
2913 }
2914
2915 // -----------------------------------------------------------------------------
2916
2917 // mouse capture can be lost on Windows before mouse-up event
OnMouseCaptureLost(wxMouseCaptureLostEvent & WXUNUSED (event))2918 void PatternView::OnMouseCaptureLost(wxMouseCaptureLostEvent& WXUNUSED(event))
2919 {
2920 mouseisdown = false;
2921 if (drawingcells || selectingcells || movingview || clickedcontrol > NO_CONTROL) {
2922 StopDraggingMouse();
2923 }
2924 }
2925
2926 // -----------------------------------------------------------------------------
2927
OnMouseMotion(wxMouseEvent & event)2928 void PatternView::OnMouseMotion(wxMouseEvent& event)
2929 {
2930 statusptr->CheckMouseLocation(mainptr->infront);
2931
2932 // check if translucent controls need to be shown/hidden
2933 // or if the cursor has moved out of or into the overlay
2934 if (mainptr->infront) {
2935 wxPoint pt(event.GetX(), event.GetY());
2936 bool active_tile = !(numlayers > 1 && tilelayers && tileindex != currindex);
2937 bool busy = drawingcells || selectingcells || movingview || waitingforclick;
2938 bool show = active_tile && !busy && (controlsrect.Contains(pt) || clickedcontrol > NO_CONTROL);
2939 if (showcontrols != show) {
2940 // let CheckCursor set showcontrols and call RefreshRect
2941 CheckCursor(true);
2942 } else if (showoverlay && active_tile && !busy) {
2943 // cursor might need to change
2944 CheckCursor(true);
2945 }
2946 }
2947
2948 if (drawingcells || selectingcells || movingview || clickedcontrol > NO_CONTROL) {
2949 if (event.Dragging()) {
2950 wxTimerEvent unused;
2951 OnDragTimer(unused);
2952 } else {
2953 // no mouse buttons are being pressed so ensure ReleaseMouse gets called
2954 // (in case OnMouseUp doesn't get called when it should)
2955 StopDraggingMouse();
2956 }
2957 }
2958 }
2959
2960 // -----------------------------------------------------------------------------
2961
OnMouseEnter(wxMouseEvent & WXUNUSED (event))2962 void PatternView::OnMouseEnter(wxMouseEvent& WXUNUSED(event))
2963 {
2964 // wx bug??? we don't get this event if CaptureMouse has been called
2965 CheckCursor(mainptr->infront);
2966 // no need to call CheckMouseLocation here (OnMouseMotion will be called)
2967 }
2968
2969 // -----------------------------------------------------------------------------
2970
OnMouseExit(wxMouseEvent & WXUNUSED (event))2971 void PatternView::OnMouseExit(wxMouseEvent& WXUNUSED(event))
2972 {
2973 // Win only bug??? we don't get this event if CaptureMouse has been called
2974 CheckCursor(mainptr->infront);
2975 statusptr->CheckMouseLocation(mainptr->infront);
2976 }
2977
2978 // -----------------------------------------------------------------------------
2979
OnMouseWheel(wxMouseEvent & event)2980 void PatternView::OnMouseWheel(wxMouseEvent& event)
2981 {
2982 // wheelpos should be persistent, because in theory we should keep track of
2983 // the remainder if the amount scrolled was not an even number of deltas
2984 static int wheelpos = 0;
2985 int delta, rot, x, y;
2986
2987 if (mousewheelmode == 0) {
2988 // ignore wheel, according to user preference
2989 event.Skip();
2990 return;
2991 }
2992
2993 // delta is the amount that represents one "step" of rotation.
2994 // Normally 120 on Win/Linux but 10 on Mac.
2995 // If wheelsens < MAX_SENSITIVITY then mouse wheel will be less sensitive.
2996 delta = event.GetWheelDelta() * (MAX_SENSITIVITY + 1 - wheelsens);
2997 rot = event.GetWheelRotation();
2998 x = event.GetX();
2999 y = event.GetY();
3000
3001 if (mousewheelmode == 2)
3002 wheelpos -= rot;
3003 else
3004 wheelpos += rot;
3005
3006 // DEBUG:
3007 // statusptr->DisplayMessage(wxString::Format(_("delta=%d rot=%d wheelpos=%d"), delta, rot, wheelpos));
3008
3009 while (wheelpos >= delta) {
3010 wheelpos -= delta;
3011 if (inscript && pass_mouse_events) {
3012 // let script decide what to do with wheel event
3013 PassZoomOutToScript(x, y);
3014 } else {
3015 TestAutoFit();
3016 currlayer->view->unzoom(x, y);
3017 }
3018 }
3019
3020 while (wheelpos <= -delta) {
3021 wheelpos += delta;
3022 if (inscript && pass_mouse_events) {
3023 // let script decide what to do with wheel event
3024 PassZoomInToScript(x, y);
3025 } else {
3026 TestAutoFit();
3027 if (currlayer->view->getmag() < MAX_MAG) {
3028 currlayer->view->zoom(x, y);
3029 } else {
3030 Beep();
3031 wheelpos = 0;
3032 break; // best not to beep lots of times
3033 }
3034 }
3035 }
3036
3037 if (inscript && pass_mouse_events) return;
3038
3039 // allow mouse interaction if script is running
3040 bool saveinscript = inscript;
3041 inscript = false;
3042 mainptr->UpdatePatternAndStatus();
3043 bigview->UpdateScrollBars();
3044 inscript = saveinscript;
3045 // do following after restoring inscript so we don't change stop button if inscript
3046 mainptr->UpdateUserInterface();
3047 }
3048
3049 // -----------------------------------------------------------------------------
3050
3051 // this flag is used to avoid re-entrancy in OnDragTimer
3052 static bool in_timer = false;
3053
OnDragTimer(wxTimerEvent & WXUNUSED (event))3054 void PatternView::OnDragTimer(wxTimerEvent& WXUNUSED(event))
3055 {
3056 // called periodically while drawing/selecting/moving,
3057 // or if user has clicked a translucent control and button is still down
3058 if (in_timer) return;
3059 in_timer = true;
3060
3061 wxPoint pt = ScreenToClient( wxGetMousePosition() );
3062 int x = pt.x;
3063 int y = pt.y;
3064
3065 if (clickedcontrol > NO_CONTROL) {
3066 control_id oldcontrol = currcontrol;
3067 currcontrol = WhichControl(x - controlsrect.x, y - controlsrect.y);
3068 if (currcontrol == clickedcontrol) {
3069 if (PANNING_CONTROL && stopwatch->Time() - clicktime > 300) {
3070 // panning can be repeated while button is pressed, but only after
3071 // a short pause (0.3 secs) from the time the button was clicked
3072 // (this matches the way scroll buttons work on Mac/Windows)
3073 ProcessClickedControl();
3074 }
3075 } else {
3076 currcontrol = NO_CONTROL;
3077 }
3078 if (currcontrol != oldcontrol) RefreshRect(controlsrect, false);
3079 in_timer = false;
3080 return;
3081 }
3082
3083 // don't test "!PointInView(x, y)" here -- we want to allow scrolling
3084 // in full screen mode when mouse is at outer edge of view
3085 if ( x <= 0 || x >= currlayer->view->getxmax() ||
3086 y <= 0 || y >= currlayer->view->getymax() ) {
3087
3088 // user can disable scrolling
3089 if ( drawingcells && !scrollpencil ) {
3090 DrawCells(x, y);
3091 in_timer = false;
3092 return;
3093 }
3094 if ( selectingcells && !scrollcross ) {
3095 SelectCells(x, y);
3096 in_timer = false;
3097 return;
3098 }
3099 if ( movingview && !scrollhand ) {
3100 // make sure x,y is within viewport
3101 if (x < 0) x = 0;
3102 if (y < 0) y = 0;
3103 if (x > currlayer->view->getxmax()) x = currlayer->view->getxmax();
3104 if (y > currlayer->view->getymax()) y = currlayer->view->getymax();
3105 MoveView(x, y);
3106 in_timer = false;
3107 return;
3108 }
3109
3110 // scroll view
3111 int xamount = 0;
3112 int yamount = 0;
3113 if (x <= 0) xamount = -SmallScroll( currlayer->view->getwidth() );
3114 if (y <= 0) yamount = -SmallScroll( currlayer->view->getheight() );
3115 if (x >= currlayer->view->getxmax())
3116 xamount = SmallScroll( currlayer->view->getwidth() );
3117 if (y >= currlayer->view->getymax())
3118 yamount = SmallScroll( currlayer->view->getheight() );
3119
3120 if ( drawingcells ) {
3121 currlayer->view->move(xamount, yamount);
3122 mainptr->UpdatePatternAndStatus();
3123
3124 } else if ( selectingcells ) {
3125 currlayer->view->move(xamount, yamount);
3126 // no need to call UpdatePatternAndStatus() here because
3127 // it will be called soon in SelectCells, except in this case:
3128 if (forceh || forcev || currlayer->algo->gridwd > 0 || currlayer->algo->gridht > 0) {
3129 // selection might not change so must update pattern
3130 RefreshView();
3131 // need to update now if script is running
3132 if (inscript) {
3133 inscript = false;
3134 mainptr->UpdatePatternAndStatus();
3135 inscript = true;
3136 }
3137 }
3138
3139 } else if ( movingview ) {
3140 // scroll in opposite direction, and if both amounts are non-zero then
3141 // set both to same (larger) absolute value so user can scroll at 45 degrees
3142 if ( xamount != 0 && yamount != 0 ) {
3143 if ( abs(xamount) > abs(yamount) ) {
3144 yamount = yamount < 0 ? -abs(xamount) : abs(xamount);
3145 } else {
3146 xamount = xamount < 0 ? -abs(yamount) : abs(yamount);
3147 }
3148 }
3149 currlayer->view->move(-xamount, -yamount);
3150
3151 // allow mouse interaction if script is running
3152 bool saveinscript = inscript;
3153 inscript = false;
3154 mainptr->UpdatePatternAndStatus();
3155 inscript = saveinscript;
3156
3157 // adjust x,y and bigcellx,bigcelly for MoveView call below
3158 x += xamount;
3159 y += yamount;
3160 pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
3161 bigcellx = cellpos.first;
3162 bigcelly = cellpos.second;
3163 }
3164
3165 // need to update scroll bars if grid is bounded
3166 if (currlayer->algo->gridwd > 0 || currlayer->algo->gridht > 0) {
3167 UpdateScrollBars();
3168 }
3169 }
3170
3171 if ( drawingcells ) {
3172 DrawCells(x, y);
3173
3174 } else if ( selectingcells ) {
3175 SelectCells(x, y);
3176
3177 } else if ( movingview ) {
3178 MoveView(x, y);
3179 }
3180
3181 in_timer = false;
3182 }
3183
3184 // -----------------------------------------------------------------------------
3185
OnScroll(wxScrollWinEvent & event)3186 void PatternView::OnScroll(wxScrollWinEvent& event)
3187 {
3188 WXTYPE type = event.GetEventType();
3189 int orient = event.GetOrientation();
3190
3191 if (type == wxEVT_SCROLLWIN_LINEUP) {
3192 if (orient == wxHORIZONTAL) {
3193 PanLeft( SmallScroll(currlayer->view->getwidth()) );
3194 } else {
3195 PanUp( SmallScroll(currlayer->view->getheight()) );
3196 }
3197
3198 } else if (type == wxEVT_SCROLLWIN_LINEDOWN) {
3199 if (orient == wxHORIZONTAL) {
3200 PanRight( SmallScroll(currlayer->view->getwidth()) );
3201 } else {
3202 PanDown( SmallScroll(currlayer->view->getheight()) );
3203 }
3204
3205 } else if (type == wxEVT_SCROLLWIN_PAGEUP) {
3206 if (orient == wxHORIZONTAL) {
3207 PanLeft( BigScroll(currlayer->view->getwidth()) );
3208 } else {
3209 PanUp( BigScroll(currlayer->view->getheight()) );
3210 }
3211
3212 } else if (type == wxEVT_SCROLLWIN_PAGEDOWN) {
3213 if (orient == wxHORIZONTAL) {
3214 PanRight( BigScroll(currlayer->view->getwidth()) );
3215 } else {
3216 PanDown( BigScroll(currlayer->view->getheight()) );
3217 }
3218
3219 } else if (type == wxEVT_SCROLLWIN_THUMBTRACK) {
3220 int newpos = event.GetPosition();
3221 int amount = newpos - (orient == wxHORIZONTAL ? hthumb : vthumb);
3222 if (amount != 0) {
3223 TestAutoFit();
3224 if (currlayer->view->getmag() > 0) {
3225 // amount is in cells so convert to pixels
3226 amount = amount << currlayer->view->getmag();
3227 }
3228 if (orient == wxHORIZONTAL) {
3229 hthumb = newpos;
3230 currlayer->view->move(amount, 0);
3231 // don't call UpdateEverything here because it calls UpdateScrollBars
3232 RefreshView();
3233 } else {
3234 vthumb = newpos;
3235 currlayer->view->move(0, amount);
3236 // don't call UpdateEverything here because it calls UpdateScrollBars
3237 RefreshView();
3238 }
3239 }
3240
3241 } else if (type == wxEVT_SCROLLWIN_THUMBRELEASE) {
3242 // now we can call UpdateScrollBars
3243 mainptr->UpdateEverything();
3244 }
3245
3246 // need an update if script is running
3247 if (inscript && type != wxEVT_SCROLLWIN_THUMBTRACK) {
3248 inscript = false;
3249 mainptr->UpdatePatternAndStatus();
3250 bigview->UpdateScrollBars();
3251 inscript = true;
3252 }
3253 }
3254
3255 // -----------------------------------------------------------------------------
3256
OnEraseBackground(wxEraseEvent & WXUNUSED (event))3257 void PatternView::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
3258 {
3259 // do nothing because we'll be painting the entire viewport
3260
3261 // why does this get called even though we always call Refresh(false)???
3262 // and why does bg still get erased (on Mac and GTK, but not Windows)???
3263 // note that eraseBack parameter in wxWindowMac::Refresh in window.cpp is never used!
3264 }
3265
3266 // -----------------------------------------------------------------------------
3267
3268 /* using this doesn't seem to change anything
3269 static int attributes[5] = {
3270 WX_GL_DOUBLEBUFFER,
3271 WX_GL_RGBA,
3272 WX_GL_DEPTH_SIZE, 0, // Golly only does 2D drawing
3273 0
3274 };
3275 */
3276
3277 // create the viewport canvas
3278
PatternView(wxWindow * parent,wxCoord x,wxCoord y,int wd,int ht,long style)3279 PatternView::PatternView(wxWindow* parent, wxCoord x, wxCoord y, int wd, int ht, long style)
3280 : wxGLCanvas(parent, wxID_ANY, NULL /* attributes */, wxPoint(x,y), wxSize(wd,ht), style)
3281 {
3282 // create a new rendering context instance for this canvas
3283 glcontext = new wxGLContext(this);
3284 if (glcontext == NULL) Fatal(_("Failed to create OpenGL context!"));
3285
3286 dragtimer = new wxTimer(this, wxID_ANY);
3287 if (dragtimer == NULL) Fatal(_("Failed to create drag timer!"));
3288
3289 // avoid erasing background on Linux -- doesn't work
3290 // SetBackgroundStyle(wxBG_STYLE_CUSTOM);
3291
3292 // avoid resizing problems on Mac/Linux -- doesn't work
3293 // SetBackgroundStyle(wxBG_STYLE_PAINT);
3294
3295 initgl = true; // need to initialize GL state
3296 drawingcells = false; // not drawing cells
3297 selectingcells = false; // not selecting cells
3298 movingview = false; // not moving view
3299 waitingforclick = false; // not waiting for user to click
3300 mouseisdown = false; // mouse button is not down
3301 nopattupdate = false; // enable pattern updates
3302 showcontrols = false; // not showing translucent controls
3303 oldcursor = NULL; // for toggling cursor via shift key
3304 restorecursor = NULL; // for restoring cursor changed by middle button click
3305 }
3306
3307 // -----------------------------------------------------------------------------
3308
~PatternView()3309 PatternView::~PatternView()
3310 {
3311 delete glcontext;
3312 delete dragtimer;
3313 }
3314