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