1 // This file is part of Golly.
2 // See docs/License.html for the copyright notice.
3 
4 #include "bigint.h"
5 #include "lifealgo.h"
6 #include "qlifealgo.h"
7 #include "hlifealgo.h"
8 #include "viewport.h"
9 
10 #include "utils.h"          // for Warning, Fatal, YesNo, Beep, etc
11 #include "prefs.h"          // for showgridlines, etc
12 #include "status.h"         // for DisplayMessage, etc
13 #include "render.h"         // for InitPaste
14 #include "undo.h"           // for currlayer->undoredo->...
15 #include "select.h"         // for Selection
16 #include "algos.h"          // for algo_type, *_ALGO, CreateNewUniverse, etc
17 #include "layer.h"          // for currlayer, ResizeLayers, etc
18 #include "control.h"        // for generating, ChangeRule
19 #include "file.h"           // for GetTextFromClipboard
20 #include "view.h"
21 #include <cstdlib>          // for abs
22 
23 #ifdef ANDROID_GUI
24     #include "jnicalls.h"   // for UpdatePattern, BeginProgress, etc
25 #endif
26 
27 #ifdef WEB_GUI
28     #include "webcalls.h"   // for UpdatePattern, BeginProgress, etc
29 #endif
30 
31 #ifdef IOS_GUI
32     #import "PatternViewController.h"   // for UpdatePattern, BeginProgress, etc
33 #endif
34 
35 // -----------------------------------------------------------------------------
36 
37 // exported data:
38 
39 const char* empty_selection     = "There are no live cells in the selection.";
40 const char* empty_outside       = "There are no live cells outside the selection.";
41 const char* no_selection        = "There is no selection.";
42 const char* selection_too_big   = "Selection is outside +/- 10^9 boundary.";
43 const char* pattern_too_big     = "Pattern is outside +/- 10^9 boundary.";
44 const char* origin_restored     = "Origin restored.";
45 
46 bool widescreen = true;         // is screen wide enough to show all info? (assume a tablet device; eg. iPad)
47 bool fullscreen = false;        // in full screen mode?
48 bool nopattupdate = false;      // disable pattern updates?
49 bool waitingforpaste = false;   // waiting for user to decide what to do with paste image?
50 gRect pasterect;                // bounding box of paste image
51 int pastex, pastey;             // where user wants to paste clipboard pattern
52 
53 bool drawingcells = false;      // currently drawing cells?
54 bool draw_pending = false;      // delay drawing?
55 int pendingx, pendingy;         // start of delayed drawing
56 
57 // -----------------------------------------------------------------------------
58 
59 // local data:
60 
61 static int cellx, celly;                // current cell's 32-bit position
62 static bigint bigcellx, bigcelly;       // current cell's position
63 static int initselx, initsely;          // location of initial selection click
64 static bool forceh;                     // resize selection horizontally?
65 static bool forcev;                     // resize selection vertically?
66 static bigint anchorx, anchory;         // anchor cell of current selection
67 static Selection prevsel;               // previous selection
68 static int drawstate;                   // new cell state (0..255)
69 
70 static Layer* pastelayer = NULL;        // temporary layer with pattern to be pasted
71 static gRect pastebox;                  // bounding box (in cells) for paste pattern
72 static std::string oldrule;             // rule before readclipboard is called
73 static std::string newrule;             // rule after readclipboard is called
74 
75 static bool pickingcells = false;       // picking cell states by dragging finger?
76 static bool selectingcells = false;     // selecting cells by dragging finger?
77 static bool movingview = false;         // moving view by dragging finger?
78 static bool movingpaste = false;        // moving paste image by dragging finger?
79 
80 // -----------------------------------------------------------------------------
81 
UpdatePatternAndStatus()82 void UpdatePatternAndStatus()
83 {
84     if (inscript || currlayer->undoredo->doingscriptchanges) return;
85 
86     UpdatePattern();
87     UpdateStatus();
88 }
89 
90 // -----------------------------------------------------------------------------
91 
UpdateEverything()92 void UpdateEverything()
93 {
94     UpdatePattern();
95     UpdateStatus();
96     UpdateEditBar();
97 }
98 
99 // -----------------------------------------------------------------------------
100 
OutsideLimits(bigint & t,bigint & l,bigint & b,bigint & r)101 bool OutsideLimits(bigint& t, bigint& l, bigint& b, bigint& r)
102 {
103     return ( t < bigint::min_coord || l < bigint::min_coord ||
104              b > bigint::max_coord || r > bigint::max_coord );
105 }
106 
107 // -----------------------------------------------------------------------------
108 
TestAutoFit()109 void TestAutoFit()
110 {
111     if (currlayer->autofit && generating) {
112         // assume user no longer wants us to do autofitting
113         currlayer->autofit = false;
114     }
115 }
116 
117 // -----------------------------------------------------------------------------
118 
FitInView(int force)119 void FitInView(int force)
120 {
121     if (waitingforpaste && currlayer->algo->isEmpty()) {
122         // fit paste image in viewport if there is no pattern
123         // (note that pastelayer->algo->fit() won't work because paste image
124         // might be bigger than paste pattern)
125 
126         int vwd = currlayer->view->getxmax();
127         int vht = currlayer->view->getymax();
128         int pwd, pht;
129         int mag = MAX_MAG;
130         while (true) {
131             pwd = mag >= 0 ? (pastebox.width << mag) - 1 : (pastebox.width >> -mag);
132             pht = mag >= 0 ? (pastebox.height << mag) - 1 : (pastebox.height >> -mag);
133             if (vwd >= pwd && vht >= pht) {
134                 // all of paste image can fit within viewport at this mag
135                 break;
136             }
137             mag--;
138         }
139 
140         // set mag and move viewport to origin
141         currlayer->view->setpositionmag(bigint::zero, bigint::zero, mag);
142 
143         // move paste image to middle of viewport
144         pastex = (vwd - pwd) / 2;
145         pastey = (vht - pht) / 2;
146     } else {
147         // fit current pattern in viewport
148         // (if no pattern this will set mag to MAX_MAG and move to origin)
149         currlayer->algo->fit(*currlayer->view, force);
150     }
151 }
152 
153 // -----------------------------------------------------------------------------
154 
PointInView(int x,int y)155 bool PointInView(int x, int y)
156 {
157     return ( x >= 0 && x <= currlayer->view->getxmax() &&
158              y >= 0 && y <= currlayer->view->getymax() );
159 }
160 
161 // -----------------------------------------------------------------------------
162 
PointInPasteImage(int x,int y)163 bool PointInPasteImage(int x, int y)
164 {
165     return (x >= pasterect.x && x <= pasterect.x + pasterect.width-1 &&
166             y >= pasterect.y && y <= pasterect.y + pasterect.height-1);
167 }
168 
169 // -----------------------------------------------------------------------------
170 
PointInSelection(int x,int y)171 bool PointInSelection(int x, int y)
172 {
173     pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
174     int cx = cellpos.first.toint();
175     int cy = cellpos.second.toint();
176     return currlayer->currsel.ContainsCell(cx, cy);
177 }
178 
179 // -----------------------------------------------------------------------------
180 
CellInGrid(const bigint & x,const bigint & y)181 bool CellInGrid(const bigint& x, const bigint& y)
182 {
183     // return true if cell at x,y is within bounded grid
184     if (currlayer->algo->gridwd > 0 &&
185         (x < currlayer->algo->gridleft ||
186          x > currlayer->algo->gridright)) return false;
187 
188     if (currlayer->algo->gridht > 0 &&
189         (y < currlayer->algo->gridtop ||
190          y > currlayer->algo->gridbottom)) return false;
191 
192     return true;
193 }
194 
195 // -----------------------------------------------------------------------------
196 
PointInGrid(int x,int y)197 bool PointInGrid(int x, int y)
198 {
199     // is given viewport location also in grid?
200     if (currlayer->algo->gridwd == 0 && currlayer->algo->gridht == 0) {
201         // unbounded grid
202         return true;
203     }
204     pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
205     return CellInGrid(cellpos.first, cellpos.second);
206 }
207 
208 // -----------------------------------------------------------------------------
209 
RememberOneCellChange(int cx,int cy,int oldstate,int newstate)210 void RememberOneCellChange(int cx, int cy, int oldstate, int newstate)
211 {
212     if (allowundo) {
213         // remember this cell change for later undo/redo
214         currlayer->undoredo->SaveCellChange(cx, cy, oldstate, newstate);
215     }
216 }
217 
218 // -----------------------------------------------------------------------------
219 
DrawCells(int x,int y)220 void DrawCells(int x, int y)
221 {
222     // make sure x,y is within viewport
223     if (x < 0) x = 0;
224     if (y < 0) y = 0;
225     if (x > currlayer->view->getxmax()) x = currlayer->view->getxmax();
226     if (y > currlayer->view->getymax()) y = currlayer->view->getymax();
227 
228     // make sure x,y is within bounded grid
229     pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
230     if (currlayer->algo->gridwd > 0) {
231         if (cellpos.first < currlayer->algo->gridleft) cellpos.first = currlayer->algo->gridleft;
232         if (cellpos.first > currlayer->algo->gridright) cellpos.first = currlayer->algo->gridright;
233     }
234     if (currlayer->algo->gridht > 0) {
235         if (cellpos.second < currlayer->algo->gridtop) cellpos.second = currlayer->algo->gridtop;
236         if (cellpos.second > currlayer->algo->gridbottom) cellpos.second = currlayer->algo->gridbottom;
237     }
238 
239     if ( currlayer->view->getmag() < 0 ||
240         OutsideLimits(cellpos.second, cellpos.first, cellpos.second, cellpos.first) ) {
241         return;
242     }
243 
244     int currstate;
245     int numchanged = 0;
246     int newx = cellpos.first.toint();
247     int newy = cellpos.second.toint();
248     if ( newx != cellx || newy != celly ) {
249 
250         // draw a line of cells using Bresenham's algorithm
251         int d, ii, jj, di, ai, si, dj, aj, sj;
252         di = newx - cellx;
253         ai = abs(di) << 1;
254         si = (di < 0)? -1 : 1;
255         dj = newy - celly;
256         aj = abs(dj) << 1;
257         sj = (dj < 0)? -1 : 1;
258 
259         ii = cellx;
260         jj = celly;
261 
262         lifealgo* curralgo = currlayer->algo;
263         if (ai > aj) {
264             d = aj - (ai >> 1);
265             while (ii != newx) {
266                 currstate = curralgo->getcell(ii, jj);
267                 if (currstate != drawstate) {
268                     curralgo->setcell(ii, jj, drawstate);
269                     RememberOneCellChange(ii, jj, currstate, drawstate);
270                     numchanged++;
271                 }
272                 if (d >= 0) {
273                     jj += sj;
274                     d  -= ai;
275                 }
276                 ii += si;
277                 d  += aj;
278             }
279         } else {
280             d = ai - (aj >> 1);
281             while (jj != newy) {
282                 currstate = curralgo->getcell(ii, jj);
283                 if (currstate != drawstate) {
284                     curralgo->setcell(ii, jj, drawstate);
285                     RememberOneCellChange(ii, jj, currstate, drawstate);
286                     numchanged++;
287                 }
288                 if (d >= 0) {
289                     ii += si;
290                     d  -= aj;
291                 }
292                 jj += sj;
293                 d  += ai;
294             }
295         }
296 
297         cellx = newx;
298         celly = newy;
299 
300         currstate = curralgo->getcell(cellx, celly);
301         if (currstate != drawstate) {
302             curralgo->setcell(cellx, celly, drawstate);
303             RememberOneCellChange(cellx, celly, currstate, drawstate);
304             numchanged++;
305         }
306     }
307 
308     if (numchanged > 0) {
309         currlayer->algo->endofpattern();
310         MarkLayerDirty();
311         UpdatePattern();
312         UpdateStatus();
313     }
314 }
315 
316 // -----------------------------------------------------------------------------
317 
StartDrawingCells(int x,int y)318 void StartDrawingCells(int x, int y)
319 {
320     if (generating) {
321         // temporarily stop generating when drawing cells (necessary for undo/redo)
322         PauseGenerating();
323         if (event_checker > 0) {
324             // delay drawing until after step() finishes in NextGeneration()
325             draw_pending = true;
326             pendingx = x;
327             pendingy = y;
328             return;
329         }
330         // NOTE: ResumeGenerating() is called in TouchEnded()
331     }
332 
333     if (!PointInGrid(x, y)) {
334         ErrorMessage("Drawing is not allowed outside grid.");
335         return;
336     }
337 
338     if (currlayer->view->getmag() < 0) {
339         ErrorMessage("Drawing is not allowed at scales greater than 1 cell per pixel.");
340         return;
341     }
342 
343     // check that x,y is within getcell/setcell limits
344     pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
345     if ( OutsideLimits(cellpos.second, cellpos.first, cellpos.second, cellpos.first) ) {
346         ErrorMessage("Drawing is not allowed outside +/- 10^9 boundary.");
347         return;
348     }
349 
350     drawingcells = true;
351 
352     // save dirty state now for later use by RememberCellChanges
353     if (allowundo) currlayer->savedirty = currlayer->dirty;
354 
355     cellx = cellpos.first.toint();
356     celly = cellpos.second.toint();
357     int currstate = currlayer->algo->getcell(cellx, celly);
358 
359     // reset drawing state in case it's no longer valid (due to algo/rule change)
360     if (currlayer->drawingstate >= currlayer->algo->NumCellStates()) {
361         currlayer->drawingstate = 1;
362     }
363 
364     if (currstate == currlayer->drawingstate) {
365         drawstate = 0;
366     } else {
367         drawstate = currlayer->drawingstate;
368     }
369     if (currstate != drawstate) {
370         currlayer->algo->setcell(cellx, celly, drawstate);
371         currlayer->algo->endofpattern();
372 
373         // remember this cell change for later undo/redo
374         RememberOneCellChange(cellx, celly, currstate, drawstate);
375         MarkLayerDirty();
376 
377         UpdatePattern();
378         UpdateStatus();     // update population count
379     }
380 }
381 
382 // -----------------------------------------------------------------------------
383 
PickCell(int x,int y)384 void PickCell(int x, int y)
385 {
386     if (!PointInGrid(x, y)) return;
387 
388     pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
389     if ( currlayer->view->getmag() < 0 ||
390         OutsideLimits(cellpos.second, cellpos.first, cellpos.second, cellpos.first) ) {
391         return;
392     }
393 
394     int newx = cellpos.first.toint();
395     int newy = cellpos.second.toint();
396     if ( newx != cellx || newy != celly ) {
397         cellx = newx;
398         celly = newy;
399         currlayer->drawingstate = currlayer->algo->getcell(cellx, celly);
400         UpdateEditBar();
401     }
402 }
403 
404 // -----------------------------------------------------------------------------
405 
StartPickingCells(int x,int y)406 void StartPickingCells(int x, int y)
407 {
408     if (!PointInGrid(x, y)) {
409         ErrorMessage("Picking is not allowed outside grid.");
410         return;
411     }
412 
413     if (currlayer->view->getmag() < 0) {
414         ErrorMessage("Picking is not allowed at scales greater than 1 cell per pixel.");
415         return;
416     }
417 
418     cellx = INT_MAX;
419     celly = INT_MAX;
420 
421     PickCell(x, y);
422     pickingcells = true;
423 }
424 
425 // -----------------------------------------------------------------------------
426 
SelectCells(int x,int y)427 void SelectCells(int x, int y)
428 {
429     // only select cells within view
430     if (x < 0) x = 0;
431     if (y < 0) y = 0;
432     if (x > currlayer->view->getxmax()) x = currlayer->view->getxmax();
433     if (y > currlayer->view->getymax()) y = currlayer->view->getymax();
434 
435     if ( abs(initselx - x) < 2 && abs(initsely - y) < 2 && !SelectionExists() ) {
436         // avoid 1x1 selection if finger hasn't moved much
437         return;
438     }
439 
440     // make sure x,y is within bounded grid
441     pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
442     if (currlayer->algo->gridwd > 0) {
443         if (cellpos.first < currlayer->algo->gridleft) cellpos.first = currlayer->algo->gridleft;
444         if (cellpos.first > currlayer->algo->gridright) cellpos.first = currlayer->algo->gridright;
445     }
446     if (currlayer->algo->gridht > 0) {
447         if (cellpos.second < currlayer->algo->gridtop) cellpos.second = currlayer->algo->gridtop;
448         if (cellpos.second > currlayer->algo->gridbottom) cellpos.second = currlayer->algo->gridbottom;
449     }
450 
451     if (forceh && forcev) {
452         // move selection
453         bigint xdelta = cellpos.first;
454         bigint ydelta = cellpos.second;
455         xdelta -= bigcellx;
456         ydelta -= bigcelly;
457         if ( xdelta != bigint::zero || ydelta != bigint::zero ) {
458             currlayer->currsel.Move(xdelta, ydelta);
459             bigcellx = cellpos.first;
460             bigcelly = cellpos.second;
461         }
462     } else {
463         // change selection size
464         if (!forcev) currlayer->currsel.SetLeftRight(cellpos.first, anchorx);
465         if (!forceh) currlayer->currsel.SetTopBottom(cellpos.second, anchory);
466     }
467 
468     if (currlayer->currsel != prevsel) {
469         // selection has changed
470         DisplaySelectionSize();
471         prevsel = currlayer->currsel;
472         UpdatePatternAndStatus();
473     }
474 }
475 
476 // -----------------------------------------------------------------------------
477 
StartSelectingCells(int x,int y)478 void StartSelectingCells(int x, int y)
479 {
480     TestAutoFit();
481 
482     // make sure anchor cell is within bounded grid (x,y can be outside grid)
483     pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
484     if (currlayer->algo->gridwd > 0) {
485         if (cellpos.first < currlayer->algo->gridleft) cellpos.first = currlayer->algo->gridleft;
486         if (cellpos.first > currlayer->algo->gridright) cellpos.first = currlayer->algo->gridright;
487     }
488     if (currlayer->algo->gridht > 0) {
489         if (cellpos.second < currlayer->algo->gridtop) cellpos.second = currlayer->algo->gridtop;
490         if (cellpos.second > currlayer->algo->gridbottom) cellpos.second = currlayer->algo->gridbottom;
491     }
492     anchorx = cellpos.first;
493     anchory = cellpos.second;
494     bigcellx = anchorx;
495     bigcelly = anchory;
496 
497     // save original selection for RememberNewSelection
498     currlayer->savesel = currlayer->currsel;
499 
500     // reset previous selection
501     prevsel.Deselect();
502 
503     // for avoiding 1x1 selection if finger doesn't move much
504     initselx = x;
505     initsely = y;
506 
507     // allow changing size in any direction
508     forceh = false;
509     forcev = false;
510 
511     if (SelectionExists()) {
512         if (PointInSelection(x, y)) {
513             // modify current selection
514             currlayer->currsel.Modify(x, y, anchorx, anchory, &forceh, &forcev);
515             DisplaySelectionSize();
516         } else {
517             // remove current selection
518             currlayer->currsel.Deselect();
519         }
520         UpdatePatternAndStatus();
521     }
522 
523     selectingcells = true;
524 }
525 
526 // -----------------------------------------------------------------------------
527 
MoveView(int x,int y)528 void MoveView(int x, int y)
529 {
530     pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
531     bigint xdelta = bigcellx;
532     bigint ydelta = bigcelly;
533     xdelta -= cellpos.first;
534     ydelta -= cellpos.second;
535 
536     int xamount, yamount;
537     int mag = currlayer->view->getmag();
538     if (mag >= 0) {
539         // move an integral number of cells
540         xamount = xdelta.toint() << mag;
541         yamount = ydelta.toint() << mag;
542     } else {
543         // convert cell deltas to screen pixels
544         xdelta >>= -mag;
545         ydelta >>= -mag;
546         xamount = xdelta.toint();
547         yamount = ydelta.toint();
548     }
549 
550     if ( xamount != 0 || yamount != 0 ) {
551         currlayer->view->move(xamount, yamount);
552         cellpos = currlayer->view->at(x, y);
553         bigcellx = cellpos.first;
554         bigcelly = cellpos.second;
555         UpdatePattern();
556         UpdateStatus();
557     }
558 }
559 
560 // -----------------------------------------------------------------------------
561 
StartMovingView(int x,int y)562 void StartMovingView(int x, int y)
563 {
564     TestAutoFit();
565     pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
566     bigcellx = cellpos.first;
567     bigcelly = cellpos.second;
568     movingview = true;
569 }
570 
571 // -----------------------------------------------------------------------------
572 
MovePaste(int x,int y)573 void MovePaste(int x, int y)
574 {
575     pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
576     bigint xdelta = bigcellx;
577     bigint ydelta = bigcelly;
578     xdelta -= cellpos.first;
579     ydelta -= cellpos.second;
580 
581     int xamount, yamount;
582     int mag = currlayer->view->getmag();
583     if (mag >= 0) {
584         // move an integral number of cells
585         xamount = xdelta.toint() << mag;
586         yamount = ydelta.toint() << mag;
587     } else {
588         // convert cell deltas to screen pixels
589         xdelta >>= -mag;
590         ydelta >>= -mag;
591         xamount = xdelta.toint();
592         yamount = ydelta.toint();
593     }
594 
595     if ( xamount != 0 || yamount != 0 ) {
596         // shift location of pasterect
597         pastex -= xamount;
598         pastey -= yamount;
599         cellpos = currlayer->view->at(x, y);
600         bigcellx = cellpos.first;
601         bigcelly = cellpos.second;
602         UpdatePattern();
603     }
604 }
605 
606 // -----------------------------------------------------------------------------
607 
StartMovingPaste(int x,int y)608 void StartMovingPaste(int x, int y)
609 {
610     pair<bigint, bigint> cellpos = currlayer->view->at(x, y);
611     bigcellx = cellpos.first;
612     bigcelly = cellpos.second;
613     movingpaste = true;
614 }
615 
616 // -----------------------------------------------------------------------------
617 
TouchBegan(int x,int y)618 void TouchBegan(int x, int y)
619 {
620     if (waitingforpaste && PointInPasteImage(x, y)) {
621         StartMovingPaste(x, y);
622 
623     } else if (currlayer->touchmode == drawmode) {
624         StartDrawingCells(x, y);
625 
626     } else if (currlayer->touchmode == pickmode) {
627         StartPickingCells(x, y);
628 
629     } else if (currlayer->touchmode == selectmode) {
630         StartSelectingCells(x, y);
631 
632     } else if (currlayer->touchmode == movemode) {
633         StartMovingView(x, y);
634 
635     } else if (currlayer->touchmode == zoominmode) {
636         ZoomInPos(x, y);
637 
638     } else if (currlayer->touchmode == zoomoutmode) {
639         ZoomOutPos(x, y);
640 
641     }
642 }
643 
644 // -----------------------------------------------------------------------------
645 
TouchMoved(int x,int y)646 void TouchMoved(int x, int y)
647 {
648     // make sure x,y is within viewport
649     if (x < 0) x = 0;
650     if (y < 0) y = 0;
651     if (x > currlayer->view->getxmax()) x = currlayer->view->getxmax();
652     if (y > currlayer->view->getymax()) y = currlayer->view->getymax();
653 
654     if ( drawingcells ) {
655         DrawCells(x, y);
656 
657     } else if ( pickingcells ) {
658         PickCell(x, y);
659 
660     } else if ( selectingcells ) {
661         SelectCells(x, y);
662 
663     } else if ( movingview ) {
664         MoveView(x, y);
665 
666     } else if ( movingpaste ) {
667         MovePaste(x, y);
668     }
669 }
670 
671 // -----------------------------------------------------------------------------
672 
TouchEnded()673 void TouchEnded()
674 {
675     if (drawingcells && allowundo) {
676         // MarkLayerDirty has set dirty flag, so we need to
677         // pass in the flag state saved before drawing started
678         currlayer->undoredo->RememberCellChanges("Drawing", currlayer->savedirty);
679         UpdateEditBar();    // update various buttons
680     }
681 
682     if (selectingcells) {
683         if (allowundo) RememberNewSelection("Selection");
684         UpdateEditBar();    // update various buttons
685     }
686 
687     drawingcells = false;
688     pickingcells = false;
689     selectingcells = false;
690     movingview = false;
691     movingpaste = false;
692 
693     ResumeGenerating();
694 }
695 
696 // -----------------------------------------------------------------------------
697 
CopyRect(int itop,int ileft,int ibottom,int iright,lifealgo * srcalgo,lifealgo * destalgo,bool erasesrc,const char * progmsg)698 bool CopyRect(int itop, int ileft, int ibottom, int iright,
699               lifealgo* srcalgo, lifealgo* destalgo,
700               bool erasesrc, const char* progmsg)
701 {
702     int wd = iright - ileft + 1;
703     int ht = ibottom - itop + 1;
704     int cx, cy;
705     double maxcount = (double)wd * (double)ht;
706     int cntr = 0;
707     int v = 0;
708     bool abort = false;
709 
710     // copy (and erase if requested) live cells from given rect
711     // in source universe to same rect in destination universe
712     BeginProgress(progmsg);
713     for ( cy=itop; cy<=ibottom; cy++ ) {
714         for ( cx=ileft; cx<=iright; cx++ ) {
715             int skip = srcalgo->nextcell(cx, cy, v);
716             if (skip + cx > iright)
717                 skip = -1;           // pretend we found no more live cells
718             if (skip >= 0) {
719                 // found next live cell
720                 cx += skip;
721                 destalgo->setcell(cx, cy, v);
722                 if (erasesrc) srcalgo->setcell(cx, cy, 0);
723             } else {
724                 cx = iright + 1;     // done this row
725             }
726             cntr++;
727             if ((cntr % 4096) == 0) {
728                 double prog = ((cy - itop) * (double)(iright - ileft + 1) +
729                                (cx - ileft)) / maxcount;
730                 abort = AbortProgress(prog, "");
731                 if (abort) break;
732             }
733         }
734         if (abort) break;
735     }
736     if (erasesrc) srcalgo->endofpattern();
737     destalgo->endofpattern();
738     EndProgress();
739 
740     return !abort;
741 }
742 
743 // -----------------------------------------------------------------------------
744 
CopyAllRect(int itop,int ileft,int ibottom,int iright,lifealgo * srcalgo,lifealgo * destalgo,const char * progmsg)745 void CopyAllRect(int itop, int ileft, int ibottom, int iright,
746                  lifealgo* srcalgo, lifealgo* destalgo,
747                  const char* progmsg)
748 {
749     int wd = iright - ileft + 1;
750     int ht = ibottom - itop + 1;
751     int cx, cy;
752     double maxcount = (double)wd * (double)ht;
753     int cntr = 0;
754     bool abort = false;
755 
756     // copy all cells from given rect in srcalgo to same rect in destalgo
757     BeginProgress(progmsg);
758     for ( cy=itop; cy<=ibottom; cy++ ) {
759         for ( cx=ileft; cx<=iright; cx++ ) {
760             destalgo->setcell(cx, cy, srcalgo->getcell(cx, cy));
761             cntr++;
762             if ((cntr % 4096) == 0) {
763                 abort = AbortProgress((double)cntr / maxcount, "");
764                 if (abort) break;
765             }
766         }
767         if (abort) break;
768     }
769     destalgo->endofpattern();
770     EndProgress();
771 }
772 
773 // -----------------------------------------------------------------------------
774 
SelectionExists()775 bool SelectionExists()
776 {
777     return currlayer->currsel.Exists();
778 }
779 
780 // -----------------------------------------------------------------------------
781 
SelectAll()782 void SelectAll()
783 {
784     SaveCurrentSelection();
785     if (SelectionExists()) {
786         currlayer->currsel.Deselect();
787         UpdatePatternAndStatus();
788     }
789 
790     if (currlayer->algo->isEmpty()) {
791         ErrorMessage("All cells are dead.");
792         RememberNewSelection("Deselection");
793         return;
794     }
795 
796     bigint top, left, bottom, right;
797     currlayer->algo->findedges(&top, &left, &bottom, &right);
798     currlayer->currsel.SetEdges(top, left, bottom, right);
799 
800     RememberNewSelection("Select All");
801     DisplaySelectionSize();
802     UpdateEverything();
803 }
804 
805 // -----------------------------------------------------------------------------
806 
RemoveSelection()807 void RemoveSelection()
808 {
809     if (SelectionExists()) {
810         SaveCurrentSelection();
811         currlayer->currsel.Deselect();
812         RememberNewSelection("Deselection");
813         UpdateEverything();
814     }
815 }
816 
817 // -----------------------------------------------------------------------------
818 
FitSelection()819 void FitSelection()
820 {
821     if (!SelectionExists()) return;
822 
823     currlayer->currsel.Fit();
824 
825     TestAutoFit();
826     UpdateEverything();
827 }
828 
829 // -----------------------------------------------------------------------------
830 
DisplaySelectionSize()831 void DisplaySelectionSize()
832 {
833     currlayer->currsel.DisplaySize();
834 }
835 
836 // -----------------------------------------------------------------------------
837 
SaveCurrentSelection()838 void SaveCurrentSelection()
839 {
840     if (allowundo && !currlayer->stayclean) {
841         currlayer->savesel = currlayer->currsel;
842     }
843 }
844 
845 // -----------------------------------------------------------------------------
846 
RememberNewSelection(const char * action)847 void RememberNewSelection(const char* action)
848 {
849     if (allowundo && !currlayer->stayclean) {
850         currlayer->undoredo->RememberSelection(action);
851     }
852 }
853 
854 // -----------------------------------------------------------------------------
855 
ClearSelection()856 void ClearSelection()
857 {
858     currlayer->currsel.Clear();
859 }
860 
861 // -----------------------------------------------------------------------------
862 
ClearOutsideSelection()863 void ClearOutsideSelection()
864 {
865     currlayer->currsel.ClearOutside();
866 }
867 
868 // -----------------------------------------------------------------------------
869 
CutSelection()870 void CutSelection()
871 {
872     currlayer->currsel.CopyToClipboard(true);
873 }
874 
875 // -----------------------------------------------------------------------------
876 
CopySelection()877 void CopySelection()
878 {
879     currlayer->currsel.CopyToClipboard(false);
880 }
881 
882 // -----------------------------------------------------------------------------
883 
ShrinkSelection(bool fit)884 void ShrinkSelection(bool fit)
885 {
886     currlayer->currsel.Shrink(fit);
887 }
888 
889 // -----------------------------------------------------------------------------
890 
RandomFill()891 void RandomFill()
892 {
893     currlayer->currsel.RandomFill();
894 }
895 
896 // -----------------------------------------------------------------------------
897 
FlipSelection(bool topbottom,bool inundoredo)898 bool FlipSelection(bool topbottom, bool inundoredo)
899 {
900     return currlayer->currsel.Flip(topbottom, inundoredo);
901 }
902 
903 // -----------------------------------------------------------------------------
904 
RotateSelection(bool clockwise,bool inundoredo)905 bool RotateSelection(bool clockwise, bool inundoredo)
906 {
907     return currlayer->currsel.Rotate(clockwise, inundoredo);
908 }
909 
910 // -----------------------------------------------------------------------------
911 
GetClipboardPattern(Layer * templayer,bigint * t,bigint * l,bigint * b,bigint * r)912 bool GetClipboardPattern(Layer* templayer, bigint* t, bigint* l, bigint* b, bigint* r)
913 {
914     std::string data;
915     if ( !GetTextFromClipboard(data) ) return false;
916 
917     // copy clipboard data to temporary file so we can handle all formats supported by readclipboard
918     FILE* tmpfile = fopen(clipfile.c_str(), "w");
919     if (tmpfile) {
920         if (fputs(data.c_str(), tmpfile) == EOF) {
921             fclose(tmpfile);
922             Warning("Could not write clipboard text to temporary file!");
923             return false;
924         }
925     } else {
926         Warning("Could not create temporary file for clipboard data!");
927         return false;
928     }
929     fclose(tmpfile);
930 
931     // remember current rule
932     oldrule = currlayer->algo->getrule();
933 
934     const char* err = readclipboard(clipfile.c_str(), *templayer->algo, t, l, b, r);
935     if (err) {
936         // cycle thru all other algos until readclipboard succeeds
937         for (int i = 0; i < NumAlgos(); i++) {
938             if (i != currlayer->algtype) {
939                 delete templayer->algo;
940                 templayer->algo = CreateNewUniverse(i);
941                 err = readclipboard(clipfile.c_str(), *templayer->algo, t, l, b, r);
942                 if (!err) {
943                     templayer->algtype = i;
944                     break;
945                 }
946             }
947         }
948     }
949 
950     RemoveFile(clipfile);
951 
952     if (err) {
953         // error probably due to bad rule string in clipboard data
954         Warning("Could not load clipboard pattern\n(probably due to unknown rule).");
955         return false;
956     } else {
957         // set newrule for later use in PasteTemporaryToCurrent
958         newrule = templayer->algo->getrule();
959         return true;
960     }
961 }
962 
963 // -----------------------------------------------------------------------------
964 
ClipboardContainsRule()965 bool ClipboardContainsRule()
966 {
967     std::string data;
968     if (!GetTextFromClipboard(data)) return false;
969     if (strncmp(data.c_str(), "@RULE ", 6) != 0) return false;
970 
971     // extract rule name
972     std::string rulename;
973     int i = 6;
974     while (data[i] > ' ') {
975         rulename += data[i];
976         i++;
977     }
978 
979     // check if rulename.rule already exists in userrules
980     std::string rulepath = userrules + rulename;
981     rulepath += ".rule";
982     if (FileExists(rulepath)) {
983         std::string question = "Do you want to replace the existing " + rulename;
984         question += ".rule with the version in the clipboard?";
985         if (!YesNo(question.c_str())) {
986             // don't overwrite existing .rule file
987             return true;
988         }
989     }
990 
991     // create rulename.rule in userrules
992     FILE* rulefile = fopen(rulepath.c_str(), "w");
993     if (rulefile) {
994         if (fputs(data.c_str(), rulefile) == EOF) {
995             fclose(rulefile);
996             Warning("Could not write clipboard text to .rule file!");
997             return true;
998         }
999     } else {
1000         Warning("Could not open .rule file for writing!");
1001         return true;
1002     }
1003     fclose(rulefile);
1004 
1005     #ifdef WEB_GUI
1006         // ensure the .rule file persists beyond the current session
1007         CopyRuleToLocalStorage(rulepath.c_str());
1008     #endif
1009 
1010     // now switch to the newly created rule
1011     ChangeRule(rulename);
1012 
1013     std::string msg = "Created " + rulename + ".rule";
1014     DisplayMessage(msg.c_str());
1015 
1016     return true;
1017 }
1018 
1019 // -----------------------------------------------------------------------------
1020 
PasteClipboard()1021 void PasteClipboard()
1022 {
1023     // if clipboard text starts with "@RULE rulename" then install rulename.rule
1024     // and switch to that rule
1025     if (ClipboardContainsRule()) return;
1026 
1027     // create a temporary layer for storing the clipboard pattern
1028     if (pastelayer) {
1029         Warning("Bug detected in PasteClipboard!");
1030         delete pastelayer;
1031         // might as well continue
1032     }
1033     pastelayer = CreateTemporaryLayer();
1034     if (!pastelayer) return;
1035 
1036     // read clipboard pattern into pastelayer
1037     bigint top, left, bottom, right;
1038     if ( GetClipboardPattern(pastelayer, &top, &left, &bottom, &right) ) {
1039         // make sure given edges are within getcell/setcell limits
1040         if ( OutsideLimits(top, left, bottom, right) ) {
1041             ErrorMessage("Clipboard pattern is too big.");
1042         } else {
1043             // temporarily set currlayer to pastelayer so we can update the
1044             // paste pattern's colors and icons
1045             Layer* savelayer = currlayer;
1046             currlayer = pastelayer;
1047             UpdateLayerColors();
1048             currlayer = savelayer;
1049 
1050             #ifdef WEB_GUI
1051                 DisplayMessage("Drag paste image to desired location then right-click on it.");
1052             #else
1053                 // Android and iOS devices use a touch screen
1054                 DisplayMessage("Drag paste image to desired location then tap Paste button.");
1055             #endif
1056             waitingforpaste = true;
1057 
1058             // set initial position of pasterect's top left corner to near top left corner
1059             // of viewport so all of paste image is likely to be visble and it isn't far
1060             // to move finger from Paste button
1061             pastex = 128;
1062             pastey = 64;
1063 
1064             // create image for drawing the pattern to be pasted; note that pastebox
1065             // is not necessarily the minimal bounding box because clipboard pattern
1066             // might have blank borders (in fact it could be empty)
1067             int itop = top.toint();
1068             int ileft = left.toint();
1069             int ibottom = bottom.toint();
1070             int iright = right.toint();
1071             int wd = iright - ileft + 1;
1072             int ht = ibottom - itop + 1;
1073             SetRect(pastebox, ileft, itop, wd, ht);
1074             InitPaste(pastelayer, pastebox);
1075         }
1076     }
1077 
1078     // waitingforpaste will only be false if an error occurred
1079     if (!waitingforpaste) {
1080         delete pastelayer;
1081         pastelayer = NULL;
1082     }
1083 }
1084 
1085 // -----------------------------------------------------------------------------
1086 
PasteTemporaryToCurrent(bigint top,bigint left,bigint wd,bigint ht)1087 void PasteTemporaryToCurrent(bigint top, bigint left, bigint wd, bigint ht)
1088 {
1089     // reset waitingforpaste now to avoid paste image being displayed prematurely
1090     waitingforpaste = false;
1091 
1092     bigint bottom = top;   bottom += ht;   bottom -= 1;
1093     bigint right = left;   right += wd;    right -= 1;
1094 
1095     // check that paste rectangle is within edit limits
1096     if ( OutsideLimits(top, left, bottom, right) ) {
1097         ErrorMessage("Pasting is not allowed outside +/- 10^9 boundary.");
1098         return;
1099     }
1100 
1101     // set edges of pattern in pastelayer
1102     int itop = pastebox.y;
1103     int ileft = pastebox.x;
1104     int ibottom = pastebox.y + pastebox.height - 1;
1105     int iright = pastebox.x + pastebox.width - 1;
1106 
1107     // set pastex,pastey to top left cell of paste rectangle
1108     pastex = left.toint();
1109     pastey = top.toint();
1110 
1111     // selection might change if grid becomes smaller,
1112     // so save current selection for RememberRuleChange/RememberAlgoChange
1113     SaveCurrentSelection();
1114 
1115     // pasting clipboard pattern can cause a rule change
1116     int oldmaxstate = currlayer->algo->NumCellStates() - 1;
1117     if (canchangerule > 0 && currlayer->algo->isEmpty() && oldrule != newrule) {
1118         const char* err = currlayer->algo->setrule( newrule.c_str() );
1119         // setrule can fail if readclipboard loaded clipboard pattern into
1120         // a different type of algo
1121         if (err) {
1122             // allow rule change to cause algo change
1123             ChangeAlgorithm(pastelayer->algtype, newrule.c_str());
1124         } else {
1125 			// if pattern exists and is at starting gen then ensure savestart is true
1126 			// so that SaveStartingPattern will save pattern to suitable file
1127 			// (and thus undo/reset will work correctly)
1128 			if (currlayer->algo->getGeneration() == currlayer->startgen && !currlayer->algo->isEmpty()) {
1129 				currlayer->savestart = true;
1130 			}
1131 
1132             // if grid is bounded then remove any live cells outside grid edges
1133             if (currlayer->algo->gridwd > 0 || currlayer->algo->gridht > 0) {
1134                 ClearOutsideGrid();
1135             }
1136 
1137             // rule change might have changed the number of cell states;
1138             // if there are fewer states then pattern might change
1139             int newmaxstate = currlayer->algo->NumCellStates() - 1;
1140             if (newmaxstate < oldmaxstate && !currlayer->algo->isEmpty()) {
1141                 ReduceCellStates(newmaxstate);
1142             }
1143 
1144             // use colors for new rule
1145             UpdateLayerColors();
1146 
1147             if (allowundo && !currlayer->stayclean) {
1148                 currlayer->undoredo->RememberRuleChange(oldrule.c_str());
1149             }
1150         }
1151     }
1152 
1153     // save cell changes if undo/redo is enabled and script isn't constructing a pattern
1154     bool savecells = allowundo && !currlayer->stayclean;
1155     //!!! if (savecells && inscript) SavePendingChanges();
1156 
1157     // don't paste cells outside bounded grid
1158     int gtop = currlayer->algo->gridtop.toint();
1159     int gleft = currlayer->algo->gridleft.toint();
1160     int gbottom = currlayer->algo->gridbottom.toint();
1161     int gright = currlayer->algo->gridright.toint();
1162     if (currlayer->algo->gridwd == 0) {
1163         // grid has infinite width
1164         gleft = INT_MIN;
1165         gright = INT_MAX;
1166     }
1167     if (currlayer->algo->gridht == 0) {
1168         // grid has infinite height
1169         gtop = INT_MIN;
1170         gbottom = INT_MAX;
1171     }
1172 
1173     // copy pattern from temporary universe to current universe
1174     int tx, ty, cx, cy;
1175     double maxcount = wd.todouble() * ht.todouble();
1176     int cntr = 0;
1177     bool abort = false;
1178     bool pattchanged = false;
1179     bool reduced = false;
1180     lifealgo* pastealgo = pastelayer->algo;
1181     lifealgo* curralgo = currlayer->algo;
1182     int maxstate = curralgo->NumCellStates() - 1;
1183 
1184     BeginProgress("Pasting pattern");
1185 
1186     // we can speed up pasting sparse patterns by using nextcell in these cases:
1187     // - if using Or mode
1188     // - if current universe is empty
1189     // - if paste rect is outside current pattern edges
1190     bool usenextcell;
1191     if ( pmode == Or || curralgo->isEmpty() ) {
1192         usenextcell = true;
1193     } else {
1194         bigint ctop, cleft, cbottom, cright;
1195         curralgo->findedges(&ctop, &cleft, &cbottom, &cright);
1196         usenextcell = top > cbottom || bottom < ctop || left > cright || right < cleft;
1197     }
1198 
1199     if ( usenextcell && pmode == And ) {
1200         // current universe is empty or paste rect is outside current pattern edges
1201         // so don't change any cells
1202     } else if ( usenextcell ) {
1203         int newstate = 0;
1204         cy = pastey;
1205         for ( ty=itop; ty<=ibottom; ty++ ) {
1206             cx = pastex;
1207             for ( tx=ileft; tx<=iright; tx++ ) {
1208                 int skip = pastealgo->nextcell(tx, ty, newstate);
1209                 if (skip + tx > iright)
1210                     skip = -1;           // pretend we found no more live cells
1211                 if (skip >= 0) {
1212                     // found next live cell so paste it into current universe
1213                     tx += skip;
1214                     cx += skip;
1215                     if (cx >= gleft && cx <= gright && cy >= gtop && cy <= gbottom) {
1216                         int currstate = curralgo->getcell(cx, cy);
1217                         if (currstate != newstate) {
1218                             if (newstate > maxstate) {
1219                                 newstate = maxstate;
1220                                 reduced = true;
1221                             }
1222                             curralgo->setcell(cx, cy, newstate);
1223                             pattchanged = true;
1224                             if (savecells) currlayer->undoredo->SaveCellChange(cx, cy, currstate, newstate);
1225                         }
1226                     }
1227                     cx++;
1228                 } else {
1229                     tx = iright + 1;     // done this row
1230                 }
1231                 cntr++;
1232                 if ((cntr % 4096) == 0) {
1233                     double prog = ((ty - itop) * (double)(iright - ileft + 1) +
1234                                    (tx - ileft)) / maxcount;
1235                     abort = AbortProgress(prog, "");
1236                     if (abort) break;
1237                 }
1238             }
1239             if (abort) break;
1240             cy++;
1241         }
1242     } else {
1243         // have to use slower getcell/setcell calls
1244         int tempstate, currstate;
1245         int numstates = curralgo->NumCellStates();
1246         cy = pastey;
1247         for ( ty=itop; ty<=ibottom; ty++ ) {
1248             cx = pastex;
1249             for ( tx=ileft; tx<=iright; tx++ ) {
1250                 tempstate = pastealgo->getcell(tx, ty);
1251                 currstate = curralgo->getcell(cx, cy);
1252                 if (cx >= gleft && cx <= gright && cy >= gtop && cy <= gbottom) {
1253                     switch (pmode) {
1254                         case And:
1255                             if (tempstate != currstate && currstate > 0) {
1256                                 curralgo->setcell(cx, cy, 0);
1257                                 pattchanged = true;
1258                                 if (savecells) currlayer->undoredo->SaveCellChange(cx, cy, currstate, 0);
1259                             }
1260                             break;
1261                         case Copy:
1262                             if (tempstate != currstate) {
1263                                 if (tempstate > maxstate) {
1264                                     tempstate = maxstate;
1265                                     reduced = true;
1266                                 }
1267                                 curralgo->setcell(cx, cy, tempstate);
1268                                 pattchanged = true;
1269                                 if (savecells) currlayer->undoredo->SaveCellChange(cx, cy, currstate, tempstate);
1270                             }
1271                             break;
1272                         case Or:
1273                             // Or mode is done using above nextcell loop;
1274                             // we only include this case to avoid compiler warning
1275                             break;
1276                         case Xor:
1277                             if (tempstate == currstate) {
1278                                 if (currstate != 0) {
1279                                     curralgo->setcell(cx, cy, 0);
1280                                     pattchanged = true;
1281                                     if (savecells) currlayer->undoredo->SaveCellChange(cx, cy, currstate, 0);
1282                                 }
1283                             } else {
1284                                 // tempstate != currstate
1285                                 int newstate = tempstate ^ currstate;
1286                                 // if xor overflows then don't change current state
1287                                 if (newstate >= numstates) newstate = currstate;
1288                                 if (currstate != newstate) {
1289                                     curralgo->setcell(cx, cy, newstate);
1290                                     pattchanged = true;
1291                                     if (savecells) currlayer->undoredo->SaveCellChange(cx, cy, currstate, newstate);
1292                                 }
1293                             }
1294                             break;
1295                     }
1296                 }
1297                 cx++;
1298                 cntr++;
1299                 if ( (cntr % 4096) == 0 ) {
1300                     abort = AbortProgress((double)cntr / maxcount, "");
1301                     if (abort) break;
1302                 }
1303             }
1304             if (abort) break;
1305             cy++;
1306         }
1307     }
1308 
1309     if (pattchanged) curralgo->endofpattern();
1310     EndProgress();
1311 
1312     // tidy up and display result
1313     ClearMessage();
1314     if (pattchanged) {
1315         if (savecells) currlayer->undoredo->RememberCellChanges("Paste", currlayer->dirty);
1316         MarkLayerDirty();
1317         UpdatePatternAndStatus();
1318     }
1319 
1320     if (reduced) ErrorMessage("Some cell states were reduced.");
1321 }
1322 
1323 // -----------------------------------------------------------------------------
1324 
DoPaste(bool toselection)1325 void DoPaste(bool toselection)
1326 {
1327     bigint top, left;
1328     bigint wd = pastebox.width;
1329     bigint ht = pastebox.height;
1330 
1331     if (toselection) {
1332         // paste pattern into selection rectangle, if possible
1333         if (!SelectionExists()) {
1334             ErrorMessage("There is no selection.");
1335             return;
1336         }
1337         if (!currlayer->currsel.CanPaste(wd, ht, top, left)) {
1338             ErrorMessage("Clipboard pattern is bigger than selection.");
1339             return;
1340         }
1341         // top and left have been set to the selection's top left corner
1342         PasteTemporaryToCurrent(top, left, wd, ht);
1343 
1344     } else {
1345         // paste pattern into pasterect, if possible
1346         if ( // allow paste if any corner of pasterect is within grid
1347              !( PointInGrid(pastex, pastey) ||
1348                 PointInGrid(pastex+pasterect.width-1, pastey) ||
1349                 PointInGrid(pastex, pastey+pasterect.height-1) ||
1350                 PointInGrid(pastex+pasterect.width-1, pastey+pasterect.height-1) ) ) {
1351             ErrorMessage("Paste must be at least partially within grid.");
1352             return;
1353         }
1354         // get paste rectangle's top left cell coord
1355         pair<bigint, bigint> cellpos = currlayer->view->at(pastex, pastey);
1356         top = cellpos.second;
1357         left = cellpos.first;
1358         PasteTemporaryToCurrent(top, left, wd, ht);
1359     }
1360 
1361     AbortPaste();
1362 }
1363 
1364 // -----------------------------------------------------------------------------
1365 
AbortPaste()1366 void AbortPaste()
1367 {
1368     waitingforpaste = false;
1369     pastex = -1;
1370     pastey = -1;
1371     if (pastelayer) {
1372         delete pastelayer;
1373         pastelayer = NULL;
1374     }
1375 }
1376 
1377 // -----------------------------------------------------------------------------
1378 
FlipPastePattern(bool topbottom)1379 bool FlipPastePattern(bool topbottom)
1380 {
1381     bool result;
1382     Selection pastesel(pastebox.y, pastebox.x, pastebox.y + pastebox.height - 1, pastebox.x + pastebox.width - 1);
1383 
1384     // flip the pattern in pastelayer
1385     lifealgo* savealgo = currlayer->algo;
1386     int savetype = currlayer->algtype;
1387     currlayer->algo = pastelayer->algo;
1388     currlayer->algtype = pastelayer->algtype;
1389     // pass in true for inundoredo parameter so flip won't be remembered
1390     // and layer won't be marked as dirty; also set inscript temporarily
1391     // so that viewport won't be updated
1392     inscript = true;
1393     result = pastesel.Flip(topbottom, true);
1394     // currlayer->algo might point to a *different* universe
1395     pastelayer->algo = currlayer->algo;
1396     currlayer->algo = savealgo;
1397     currlayer->algtype = savetype;
1398     inscript = false;
1399 
1400     if (result) {
1401         InitPaste(pastelayer, pastebox);
1402     }
1403 
1404     return result;
1405 }
1406 
1407 // -----------------------------------------------------------------------------
1408 
RotatePastePattern(bool clockwise)1409 bool RotatePastePattern(bool clockwise)
1410 {
1411     Selection pastesel(pastebox.y, pastebox.x,
1412                        pastebox.y + pastebox.height - 1,
1413                        pastebox.x + pastebox.width - 1);
1414 
1415     // check if pastelayer->algo uses a finite grid
1416     if (!pastelayer->algo->unbounded) {
1417         // readclipboard has loaded the pattern into top left corner of grid,
1418         // so if pastebox isn't square we need to expand the grid to avoid the
1419         // rotated pattern being clipped (WARNING: this assumes the algo won't
1420         // change the pattern's cell coordinates when setrule expands the grid)
1421         int x, y, wd, ht;
1422         pastesel.GetRect(&x, &y, &wd, &ht);
1423         if (wd != ht) {
1424 
1425             // better solution would be to check if pastebox is small enough for
1426             // pattern to be safely rotated after shifting to center of grid and only
1427             // expand grid if it can't!!!??? (must also update pastesel edges)
1428 
1429             int newwd, newht;
1430             if (wd > ht) {
1431                 // expand grid vertically
1432                 newht = pastelayer->algo->gridht + wd;
1433                 newwd = pastelayer->algo->gridwd;
1434             } else {
1435                 // wd < ht so expand grid horizontally
1436                 newwd = pastelayer->algo->gridwd + ht;
1437                 newht = pastelayer->algo->gridht;
1438             }
1439             char rule[MAXRULESIZE];
1440             sprintf(rule, "%s", pastelayer->algo->getrule());
1441             char topology = 'T';
1442             char *suffix = strchr(rule, ':');
1443             if (suffix) {
1444                 topology = suffix[1];
1445                 suffix[0] = 0;
1446             }
1447             sprintf(rule, "%s:%c%d,%d", rule, topology, newwd, newht);
1448             if (pastelayer->algo->setrule(rule)) {
1449                 // unlikely, but could happen if the new grid size is too big
1450                 Warning("Sorry, but the clipboard pattern could not be rotated.");
1451                 return false;
1452             }
1453         }
1454     }
1455 
1456     // rotate the pattern in pastelayer
1457     lifealgo* savealgo = currlayer->algo;
1458     int savetype = currlayer->algtype;
1459     currlayer->algo = pastelayer->algo;
1460     currlayer->algtype = pastelayer->algtype;
1461     // pass in true for inundoredo parameter so rotate won't be remembered
1462     // and layer won't be marked as dirty; also set inscript temporarily
1463     // so that viewport won't be updated and selection size won't be displayed
1464     inscript = true;
1465     bool result = pastesel.Rotate(clockwise, true);
1466     // currlayer->algo might point to a *different* universe
1467     pastelayer->algo = currlayer->algo;
1468     currlayer->algo = savealgo;
1469     currlayer->algtype = savetype;
1470     inscript = false;
1471 
1472     if (result) {
1473         // get rotated selection and update pastebox
1474         int x, y, wd, ht;
1475         pastesel.GetRect(&x, &y, &wd, &ht);
1476         SetRect(pastebox, x, y, wd, ht);
1477         InitPaste(pastelayer, pastebox);
1478     }
1479 
1480     return result;
1481 }
1482 
1483 // -----------------------------------------------------------------------------
1484 
ToggleCellColors()1485 void ToggleCellColors()
1486 {
1487     InvertCellColors();
1488 
1489     if (pastelayer) {
1490         // invert colors used to draw paste pattern
1491         for (int n = 0; n <= pastelayer->numicons; n++) {
1492             pastelayer->cellr[n] = 255 - pastelayer->cellr[n];
1493             pastelayer->cellg[n] = 255 - pastelayer->cellg[n];
1494             pastelayer->cellb[n] = 255 - pastelayer->cellb[n];
1495         }
1496         InvertIconColors(pastelayer->atlas7x7, 8, pastelayer->numicons);
1497         InvertIconColors(pastelayer->atlas15x15, 16, pastelayer->numicons);
1498         InvertIconColors(pastelayer->atlas31x31, 32, pastelayer->numicons);
1499     }
1500 }
1501 
1502 // -----------------------------------------------------------------------------
1503 
ZoomInPos(int x,int y)1504 void ZoomInPos(int x, int y)
1505 {
1506     // zoom in to given point
1507     if (currlayer->view->getmag() < MAX_MAG) {
1508         TestAutoFit();
1509         currlayer->view->zoom(x, y);
1510         UpdateEverything();
1511     } else {
1512         Beep();   // can't zoom in any further
1513     }
1514 }
1515 
1516 // -----------------------------------------------------------------------------
1517 
ZoomOutPos(int x,int y)1518 void ZoomOutPos(int x, int y)
1519 {
1520     // zoom out from given point
1521     TestAutoFit();
1522     currlayer->view->unzoom(x, y);
1523     UpdateEverything();
1524 }
1525 
1526 // -----------------------------------------------------------------------------
1527 
PanUp(int amount)1528 void PanUp(int amount)
1529 {
1530     TestAutoFit();
1531     currlayer->view->move(0, -amount);
1532     UpdateEverything();
1533 }
1534 
1535 // -----------------------------------------------------------------------------
1536 
PanDown(int amount)1537 void PanDown(int amount)
1538 {
1539     TestAutoFit();
1540     currlayer->view->move(0, amount);
1541     UpdateEverything();
1542 }
1543 
1544 // -----------------------------------------------------------------------------
1545 
PanLeft(int amount)1546 void PanLeft(int amount)
1547 {
1548     TestAutoFit();
1549     currlayer->view->move(-amount, 0);
1550     UpdateEverything();
1551 }
1552 
1553 // -----------------------------------------------------------------------------
1554 
PanRight(int amount)1555 void PanRight(int amount)
1556 {
1557     TestAutoFit();
1558     currlayer->view->move(amount, 0);
1559     UpdateEverything();
1560 }
1561 
1562 // -----------------------------------------------------------------------------
1563 
PanNE()1564 void PanNE()
1565 {
1566     TestAutoFit();
1567     int xamount = SmallScroll(currlayer->view->getwidth());
1568     int yamount = SmallScroll(currlayer->view->getheight());
1569     int amount = (xamount < yamount) ? xamount : yamount;
1570     currlayer->view->move(amount, -amount);
1571     UpdateEverything();
1572 }
1573 
1574 // -----------------------------------------------------------------------------
1575 
PanNW()1576 void PanNW()
1577 {
1578     TestAutoFit();
1579     int xamount = SmallScroll(currlayer->view->getwidth());
1580     int yamount = SmallScroll(currlayer->view->getheight());
1581     int amount = (xamount < yamount) ? xamount : yamount;
1582     currlayer->view->move(-amount, -amount);
1583     UpdateEverything();
1584 }
1585 
1586 // -----------------------------------------------------------------------------
1587 
PanSE()1588 void PanSE()
1589 {
1590     TestAutoFit();
1591     int xamount = SmallScroll(currlayer->view->getwidth());
1592     int yamount = SmallScroll(currlayer->view->getheight());
1593     int amount = (xamount < yamount) ? xamount : yamount;
1594     currlayer->view->move(amount, amount);
1595     UpdateEverything();
1596 }
1597 
1598 // -----------------------------------------------------------------------------
1599 
PanSW()1600 void PanSW()
1601 {
1602     TestAutoFit();
1603     int xamount = SmallScroll(currlayer->view->getwidth());
1604     int yamount = SmallScroll(currlayer->view->getheight());
1605     int amount = (xamount < yamount) ? xamount : yamount;
1606     currlayer->view->move(-amount, amount);
1607     UpdateEverything();
1608 }
1609 
1610 // -----------------------------------------------------------------------------
1611 
SmallScroll(int xysize)1612 int SmallScroll(int xysize)
1613 {
1614     int amount;
1615     int mag = currlayer->view->getmag();
1616     if (mag > 0) {
1617         // scroll an integral number of cells (1 cell = 2^mag pixels)
1618         if (mag < 3) {
1619             amount = ((xysize >> mag) / 20) << mag;
1620             if (amount == 0) amount = 1 << mag;
1621             return amount;
1622         } else {
1623             // grid lines are visible so scroll by only 1 cell
1624             return 1 << mag;
1625         }
1626     } else {
1627         // scroll by approx 5% of current wd/ht
1628         amount = xysize / 20;
1629         if (amount == 0) amount = 1;
1630         return amount;
1631     }
1632 }
1633 
1634 // -----------------------------------------------------------------------------
1635 
BigScroll(int xysize)1636 int BigScroll(int xysize)
1637 {
1638     int amount;
1639     int mag = currlayer->view->getmag();
1640     if (mag > 0) {
1641         // scroll an integral number of cells (1 cell = 2^mag pixels)
1642         amount = ((xysize >> mag) * 9 / 10) << mag;
1643         if (amount == 0) amount = 1 << mag;
1644         return amount;
1645     } else {
1646         // scroll by approx 90% of current wd/ht
1647         amount = xysize * 9 / 10;
1648         if (amount == 0) amount = 1;
1649         return amount;
1650     }
1651 }
1652