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