1 // This file is part of Golly.
2 // See docs/License.html for the copyright notice.
3 
4 #include "wx/wxprec.h"     // for compilers that support precompilation
5 #ifndef WX_PRECOMP
6     #include "wx/wx.h"     // for all others include the necessary headers
7 #endif
8 
9 #include "bigint.h"
10 #include "lifealgo.h"
11 #include "viewport.h"
12 
13 #include "wxgolly.h"       // for wxGetApp, mainptr, viewptr, statusptr, insideYield
14 #include "wxutils.h"       // for Warning
15 #include "wxprefs.h"       // for randomfill, etc
16 #include "wxmain.h"        // for mainptr->...
17 #include "wxstatus.h"      // for statusptr->...
18 #include "wxscript.h"      // for inscript
19 #include "wxview.h"        // for viewptr->...
20 #include "wxundo.h"        // for currlayer->undoredo->...
21 #include "wxalgos.h"       // for *_ALGO, CreateNewUniverse
22 #include "wxlayer.h"       // for currlayer, MarkLayerDirty, etc
23 #include "wxselect.h"
24 
25 // This module implements operations on selections.
26 
27 // -----------------------------------------------------------------------------
28 
Selection()29 Selection::Selection()
30 {
31     exists = false;
32 }
33 
34 // -----------------------------------------------------------------------------
35 
Selection(int t,int l,int b,int r)36 Selection::Selection(int t, int l, int b, int r)
37 {
38     // create rectangular selection if given edges are valid
39     exists = (t <= b && l <= r);
40     if (exists) {
41         seltop = t;
42         selleft = l;
43         selbottom = b;
44         selright = r;
45     }
46 }
47 
48 // -----------------------------------------------------------------------------
49 
~Selection()50 Selection::~Selection()
51 {
52     // no need to do anything at the moment
53 }
54 
55 // -----------------------------------------------------------------------------
56 
operator ==(const Selection & s) const57 bool Selection::operator==(const Selection& s) const
58 {
59     if (!exists && !s.exists) {
60         // neither selection exists
61         return true;
62     } else if (exists && s.exists) {
63         // check if edges match
64         return (seltop == s.seltop && selleft == s.selleft &&
65                 selbottom == s.selbottom && selright == s.selright);
66     } else {
67         // one selection exists but not the other
68         return false;
69     }
70 }
71 
72 // -----------------------------------------------------------------------------
73 
operator !=(const Selection & s) const74 bool Selection::operator!=(const Selection& s) const
75 {
76     return !(*this == s);
77 }
78 
79 // -----------------------------------------------------------------------------
80 
Exists()81 bool Selection::Exists()
82 {
83     return exists;
84 }
85 
86 // -----------------------------------------------------------------------------
87 
Deselect()88 void Selection::Deselect()
89 {
90     exists = false;
91 }
92 
93 // -----------------------------------------------------------------------------
94 
TooBig()95 bool Selection::TooBig()
96 {
97     return viewptr->OutsideLimits(seltop, selleft, selbottom, selright);
98 }
99 
100 // -----------------------------------------------------------------------------
101 
DisplaySize()102 void Selection::DisplaySize()
103 {
104     bigint wd = selright;    wd -= selleft;   wd += bigint::one;
105     bigint ht = selbottom;   ht -= seltop;    ht += bigint::one;
106     wxString msg = _("Selection wd x ht = ");
107     msg += statusptr->Stringify(wd);
108     msg += _(" x ");
109     msg += statusptr->Stringify(ht);
110     statusptr->SetMessage(msg);
111 }
112 
113 // -----------------------------------------------------------------------------
114 
SetRect(int x,int y,int wd,int ht)115 void Selection::SetRect(int x, int y, int wd, int ht)
116 {
117     exists = (wd > 0 && ht > 0);
118     if (exists) {
119         seltop = y;
120         selleft = x;
121         // avoid int overflow
122         ht--;
123         wd--;
124         selbottom = y;
125         selbottom += ht;
126         selright = x;
127         selright += wd;
128     }
129 }
130 
131 // -----------------------------------------------------------------------------
132 
GetRect(int * x,int * y,int * wd,int * ht)133 void Selection::GetRect(int* x, int* y, int* wd, int* ht)
134 {
135     *x = selleft.toint();
136     *y = seltop.toint();
137     *wd = selright.toint() - *x + 1;
138     *ht = selbottom.toint() - *y + 1;
139 }
140 
141 // -----------------------------------------------------------------------------
142 
SetEdges(bigint & t,bigint & l,bigint & b,bigint & r)143 void Selection::SetEdges(bigint& t, bigint& l, bigint& b, bigint& r)
144 {
145     exists = true;
146     seltop = t;
147     selleft = l;
148     selbottom = b;
149     selright = r;
150     CheckGridEdges();
151 }
152 
153 // -----------------------------------------------------------------------------
154 
CheckGridEdges()155 void Selection::CheckGridEdges()
156 {
157     if (exists) {
158         // change selection edges if necessary to ensure they are inside a bounded grid
159         if (currlayer->algo->gridwd > 0) {
160             if (selleft > currlayer->algo->gridright || selright < currlayer->algo->gridleft) {
161                 exists = false;   // selection is outside grid
162                 return;
163             }
164             if (selleft < currlayer->algo->gridleft) selleft = currlayer->algo->gridleft;
165             if (selright > currlayer->algo->gridright) selright = currlayer->algo->gridright;
166         }
167         if (currlayer->algo->gridht > 0) {
168             if (seltop > currlayer->algo->gridbottom || selbottom < currlayer->algo->gridtop) {
169                 exists = false;   // selection is outside grid
170                 return;
171             }
172             if (seltop < currlayer->algo->gridtop) seltop = currlayer->algo->gridtop;
173             if (selbottom > currlayer->algo->gridbottom) selbottom = currlayer->algo->gridbottom;
174         }
175     }
176 }
177 
178 // -----------------------------------------------------------------------------
179 
Contains(bigint & t,bigint & l,bigint & b,bigint & r)180 bool Selection::Contains(bigint& t, bigint& l, bigint& b, bigint& r)
181 {
182     return ( seltop <= t && selleft <= l && selbottom >= b && selright >= r );
183 }
184 
185 // -----------------------------------------------------------------------------
186 
Outside(bigint & t,bigint & l,bigint & b,bigint & r)187 bool Selection::Outside(bigint& t, bigint& l, bigint& b, bigint& r)
188 {
189     return ( seltop > b || selleft > r || selbottom < t || selright < l );
190 }
191 
192 // -----------------------------------------------------------------------------
193 
ContainsCell(int x,int y)194 bool Selection::ContainsCell(int x, int y)
195 {
196     return ( x >= selleft.toint() && x <= selright.toint() &&
197             y >= seltop.toint() && y <= selbottom.toint() );
198 }
199 
200 // -----------------------------------------------------------------------------
201 
SaveDifferences(lifealgo * oldalgo,lifealgo * newalgo,int itop,int ileft,int ibottom,int iright)202 bool Selection::SaveDifferences(lifealgo* oldalgo, lifealgo* newalgo,
203                                 int itop, int ileft, int ibottom, int iright)
204 {
205     int wd = iright - ileft + 1;
206     int ht = ibottom - itop + 1;
207     int cx, cy;
208     double maxcount = (double)wd * (double)ht;
209     int cntr = 0;
210     bool abort = false;
211 
212     // compare patterns in given algos and call SaveCellChange for each different cell
213     BeginProgress(_("Saving cell changes"));
214     for ( cy=itop; cy<=ibottom; cy++ ) {
215         for ( cx=ileft; cx<=iright; cx++ ) {
216             int oldstate = oldalgo->getcell(cx, cy);
217             int newstate = newalgo->getcell(cx, cy);
218             if ( oldstate != newstate ) {
219                 // assume this is only called if allowundo && !currlayer->stayclean
220                 currlayer->undoredo->SaveCellChange(cx, cy, oldstate, newstate);
221             }
222             cntr++;
223             if ((cntr % 4096) == 0) {
224                 abort = AbortProgress((double)cntr / maxcount, wxEmptyString);
225                 if (abort) break;
226             }
227         }
228         if (abort) break;
229     }
230     EndProgress();
231 
232     return !abort;
233 }
234 
235 // -----------------------------------------------------------------------------
236 
Advance()237 void Selection::Advance()
238 {
239     if (mainptr->generating || viewptr->drawingcells || viewptr->waitingforclick) return;
240 
241     if (!exists) {
242         statusptr->ErrorMessage(no_selection);
243         return;
244     }
245 
246     if (currlayer->algo->isEmpty()) {
247         statusptr->ErrorMessage(empty_selection);
248         return;
249     }
250 
251     bigint top, left, bottom, right;
252     currlayer->algo->findedges(&top, &left, &bottom, &right);
253 
254     // check if selection is completely outside pattern edges
255     if (Outside(top, left, bottom, right)) {
256         statusptr->ErrorMessage(empty_selection);
257         return;
258     }
259 
260     // save cell changes if undo/redo is enabled and script isn't constructing a pattern
261     bool savecells = allowundo && !currlayer->stayclean;
262     if (savecells && inscript) SavePendingChanges();
263 
264     bool boundedgrid = currlayer->algo->unbounded && (currlayer->algo->gridwd > 0 || currlayer->algo->gridht > 0);
265 
266     // check if selection encloses entire pattern;
267     // can't do this if qlife because it uses gen parity to decide which bits to draw;
268     // also avoid this if undo/redo is enabled (too messy to remember cell changes)
269     if ( currlayer->algtype != QLIFE_ALGO && !savecells && Contains(top, left, bottom, right) ) {
270         mainptr->generating = true;
271         wxGetApp().PollerReset();
272 
273         // step by one gen without changing gen count
274         bigint savegen = currlayer->algo->getGeneration();
275         bigint saveinc = currlayer->algo->getIncrement();
276         currlayer->algo->setIncrement(1);
277         if (boundedgrid) currlayer->algo->CreateBorderCells();
278         currlayer->algo->step();
279         if (boundedgrid) currlayer->algo->DeleteBorderCells();
280         currlayer->algo->setIncrement(saveinc);
281         currlayer->algo->setGeneration(savegen);
282 
283         mainptr->generating = false;
284 
285         // clear 1-cell thick strips just outside selection
286         ClearOutside();
287         MarkLayerDirty();
288         mainptr->UpdateEverything();
289         return;
290     }
291 
292     // find intersection of selection and pattern to minimize work
293     if (seltop > top) top = seltop;
294     if (selleft > left) left = selleft;
295     if (selbottom < bottom) bottom = selbottom;
296     if (selright < right) right = selright;
297 
298     // check that intersection is within setcell/getcell limits
299     if ( viewptr->OutsideLimits(top, left, bottom, right) ) {
300         statusptr->ErrorMessage(selection_too_big);
301         return;
302     }
303 
304     // create a temporary universe of same type as current universe
305     lifealgo* tempalgo = CreateNewUniverse(currlayer->algtype);
306     if (tempalgo->setrule(currlayer->algo->getrule()))
307         tempalgo->setrule(tempalgo->DefaultRule());
308 
309     // copy live cells in selection to temporary universe
310     if ( !viewptr->CopyRect(top.toint(), left.toint(), bottom.toint(), right.toint(),
311                             currlayer->algo, tempalgo, false, _("Saving selection")) ) {
312         delete tempalgo;
313         return;
314     }
315 
316     if ( tempalgo->isEmpty() ) {
317         statusptr->ErrorMessage(empty_selection);
318         delete tempalgo;
319         return;
320     }
321 
322     // advance temporary universe by one gen
323     mainptr->generating = true;
324     wxGetApp().PollerReset();
325     tempalgo->setIncrement(1);
326     if (boundedgrid) tempalgo->CreateBorderCells();
327     tempalgo->step();
328     if (boundedgrid) tempalgo->DeleteBorderCells();
329     mainptr->generating = false;
330 
331     if ( !tempalgo->isEmpty() ) {
332         // temporary pattern might have expanded
333         bigint temptop, templeft, tempbottom, tempright;
334         tempalgo->findedges(&temptop, &templeft, &tempbottom, &tempright);
335         if (temptop < top) top = temptop;
336         if (templeft < left) left = templeft;
337         if (tempbottom > bottom) bottom = tempbottom;
338         if (tempright > right) right = tempright;
339 
340         // but ignore live cells created outside selection edges
341         if (top < seltop) top = seltop;
342         if (left < selleft) left = selleft;
343         if (bottom > selbottom) bottom = selbottom;
344         if (right > selright) right = selright;
345     }
346 
347     if (savecells) {
348         // compare selection rect in currlayer->algo and tempalgo and call SaveCellChange
349         // for each cell that has a different state
350         if ( SaveDifferences(currlayer->algo, tempalgo,
351                              top.toint(), left.toint(), bottom.toint(), right.toint()) ) {
352             if ( !currlayer->undoredo->RememberCellChanges(_("Advance Selection"), currlayer->dirty) ) {
353                 // pattern inside selection didn't change
354                 delete tempalgo;
355                 return;
356             }
357         } else {
358             currlayer->undoredo->ForgetCellChanges();
359             delete tempalgo;
360             return;
361         }
362     }
363 
364     // copy all cells in new selection from tempalgo to currlayer->algo
365     viewptr->CopyAllRect(top.toint(), left.toint(), bottom.toint(), right.toint(),
366                          tempalgo, currlayer->algo, _("Copying advanced selection"));
367 
368     delete tempalgo;
369     MarkLayerDirty();
370     mainptr->UpdateEverything();
371 }
372 
373 // -----------------------------------------------------------------------------
374 
AdvanceOutside()375 void Selection::AdvanceOutside()
376 {
377     if (mainptr->generating || viewptr->drawingcells || viewptr->waitingforclick) return;
378 
379     if (!exists) {
380         statusptr->ErrorMessage(no_selection);
381         return;
382     }
383 
384     if (currlayer->algo->isEmpty()) {
385         statusptr->ErrorMessage(empty_outside);
386         return;
387     }
388 
389     bigint top, left, bottom, right;
390     currlayer->algo->findedges(&top, &left, &bottom, &right);
391 
392     // check if selection encloses entire pattern
393     if (Contains(top, left, bottom, right)) {
394         statusptr->ErrorMessage(empty_outside);
395         return;
396     }
397 
398     // save cell changes if undo/redo is enabled and script isn't constructing a pattern
399     bool savecells = allowundo && !currlayer->stayclean;
400     if (savecells && inscript) SavePendingChanges();
401 
402     bool boundedgrid = currlayer->algo->unbounded && (currlayer->algo->gridwd > 0 || currlayer->algo->gridht > 0);
403 
404     // check if selection is completely outside pattern edges;
405     // can't do this if qlife because it uses gen parity to decide which bits to draw;
406     // also avoid this if undo/redo is enabled (too messy to remember cell changes)
407     if ( currlayer->algtype != QLIFE_ALGO && !savecells && Outside(top, left, bottom, right) ) {
408         mainptr->generating = true;
409         wxGetApp().PollerReset();
410 
411         // step by one gen without changing gen count
412         bigint savegen = currlayer->algo->getGeneration();
413         bigint saveinc = currlayer->algo->getIncrement();
414         currlayer->algo->setIncrement(1);
415         if (boundedgrid) currlayer->algo->CreateBorderCells();
416         currlayer->algo->step();
417         if (boundedgrid) currlayer->algo->DeleteBorderCells();
418         currlayer->algo->setIncrement(saveinc);
419         currlayer->algo->setGeneration(savegen);
420 
421         mainptr->generating = false;
422 
423         // clear selection in case pattern expanded into it
424         Clear();
425         MarkLayerDirty();
426         mainptr->UpdateEverything();
427         return;
428     }
429 
430     // check that pattern is within setcell/getcell limits
431     if ( viewptr->OutsideLimits(top, left, bottom, right) ) {
432         statusptr->ErrorMessage(_("Pattern is outside +/- 10^9 boundary."));
433         return;
434     }
435 
436     lifealgo* oldalgo = NULL;
437     if (savecells) {
438         // copy current pattern to oldalgo, using same type and gen count
439         // so we can switch to oldalgo if user decides to abort below
440         oldalgo = CreateNewUniverse(currlayer->algtype);
441         if (oldalgo->setrule(currlayer->algo->getrule()))
442             oldalgo->setrule(oldalgo->DefaultRule());
443         oldalgo->setGeneration( currlayer->algo->getGeneration() );
444         if ( !viewptr->CopyRect(top.toint(), left.toint(), bottom.toint(), right.toint(),
445                                 currlayer->algo, oldalgo, false, _("Saving pattern")) ) {
446             delete oldalgo;
447             return;
448         }
449     }
450 
451     // create a new universe of same type
452     lifealgo* newalgo = CreateNewUniverse(currlayer->algtype);
453     if (newalgo->setrule(currlayer->algo->getrule()))
454         newalgo->setrule(newalgo->DefaultRule());
455     newalgo->setGeneration( currlayer->algo->getGeneration() );
456 
457     // copy (and kill) live cells in selection to new universe
458     int iseltop    = seltop.toint();
459     int iselleft   = selleft.toint();
460     int iselbottom = selbottom.toint();
461     int iselright  = selright.toint();
462     if ( !viewptr->CopyRect(iseltop, iselleft, iselbottom, iselright,
463                             currlayer->algo, newalgo, true,
464                             _("Saving and erasing selection")) ) {
465         // aborted, so best to restore selection
466         if ( !newalgo->isEmpty() ) {
467             newalgo->findedges(&top, &left, &bottom, &right);
468             viewptr->CopyRect(top.toint(), left.toint(), bottom.toint(), right.toint(),
469                               newalgo, currlayer->algo, false,
470                               _("Restoring selection"));
471         }
472         delete newalgo;
473         if (savecells) delete oldalgo;
474         mainptr->UpdateEverything();
475         return;
476     }
477 
478     // advance current universe by 1 generation
479     mainptr->generating = true;
480     wxGetApp().PollerReset();
481     currlayer->algo->setIncrement(1);
482     if (boundedgrid) currlayer->algo->CreateBorderCells();
483     currlayer->algo->step();
484     if (boundedgrid) currlayer->algo->DeleteBorderCells();
485     mainptr->generating = false;
486 
487     if ( !currlayer->algo->isEmpty() ) {
488         // find new edges and copy current pattern to new universe,
489         // except for any cells that were created in selection
490         // (newalgo contains the original selection)
491         bigint t, l, b, r;
492         currlayer->algo->findedges(&t, &l, &b, &r);
493         int itop = t.toint();
494         int ileft = l.toint();
495         int ibottom = b.toint();
496         int iright = r.toint();
497         int ht = ibottom - itop + 1;
498         int cx, cy;
499 
500         // for showing accurate progress we need to add pattern height to pop count
501         // in case this is a huge pattern with many blank rows
502         double maxcount = currlayer->algo->getPopulation().todouble() + ht;
503         double accumcount = 0;
504         int currcount = 0;
505         int v = 0;
506         bool abort = false;
507         BeginProgress(_("Copying advanced pattern"));
508 
509         lifealgo* curralgo = currlayer->algo;
510         for ( cy=itop; cy<=ibottom; cy++ ) {
511             currcount++;
512             for ( cx=ileft; cx<=iright; cx++ ) {
513                 int skip = curralgo->nextcell(cx, cy, v);
514                 if (skip >= 0) {
515                     // found next live cell in this row
516                     cx += skip;
517 
518                     // only copy cell if outside selection
519                     if ( cx < iselleft || cx > iselright ||
520                         cy < iseltop || cy > iselbottom ) {
521                         newalgo->setcell(cx, cy, v);
522                     }
523 
524                     currcount++;
525                 } else {
526                     cx = iright;  // done this row
527                 }
528                 if (currcount > 1024) {
529                     accumcount += currcount;
530                     currcount = 0;
531                     abort = AbortProgress(accumcount / maxcount, wxEmptyString);
532                     if (abort) break;
533                 }
534             }
535             if (abort) break;
536         }
537 
538         newalgo->endofpattern();
539         EndProgress();
540 
541         if (abort && savecells) {
542             // revert back to pattern saved in oldalgo
543             delete newalgo;
544             delete currlayer->algo;
545             currlayer->algo = oldalgo;
546             mainptr->SetGenIncrement();
547             mainptr->UpdateEverything();
548             return;
549         }
550     }
551 
552     // switch to new universe (best to do this even if aborted)
553     delete currlayer->algo;
554     currlayer->algo = newalgo;
555     mainptr->SetGenIncrement();
556 
557     if (savecells) {
558         // compare patterns in oldalgo and currlayer->algo and call SaveCellChange
559         // for each cell that has a different state; note that we need to compare
560         // the union of the original pattern's rect and the new pattern's rect
561         int otop = top.toint();
562         int oleft = left.toint();
563         int obottom = bottom.toint();
564         int oright = right.toint();
565         if (!currlayer->algo->isEmpty()) {
566             currlayer->algo->findedges(&top, &left, &bottom, &right);
567             int ntop = top.toint();
568             int nleft = left.toint();
569             int nbottom = bottom.toint();
570             int nright = right.toint();
571             if (ntop < otop) otop = ntop;
572             if (nleft < oleft) oleft = nleft;
573             if (nbottom > obottom) obottom = nbottom;
574             if (nright > oright) oright = nright;
575         }
576         if ( SaveDifferences(oldalgo, currlayer->algo, otop, oleft, obottom, oright) ) {
577             delete oldalgo;
578             if ( !currlayer->undoredo->RememberCellChanges(_("Advance Outside"), currlayer->dirty) ) {
579                 // pattern outside selection didn't change
580                 mainptr->UpdateEverything();
581                 return;
582             }
583         } else {
584             // revert back to pattern saved in oldalgo
585             currlayer->undoredo->ForgetCellChanges();
586             delete currlayer->algo;
587             currlayer->algo = oldalgo;
588             mainptr->SetGenIncrement();
589             mainptr->UpdateEverything();
590             return;
591         }
592     }
593 
594     MarkLayerDirty();
595     mainptr->UpdateEverything();
596 }
597 
598 // -----------------------------------------------------------------------------
599 
Modify(const bigint & xclick,const bigint & yclick,bigint & anchorx,bigint & anchory,bool * forceh,bool * forcev)600 void Selection::Modify(const bigint& xclick, const bigint& yclick,
601                        bigint& anchorx, bigint& anchory,
602                        bool* forceh, bool* forcev)
603 {
604     // note that we include "=" in following tests to get sensible
605     // results when modifying small selections (ht or wd <= 3)
606     if ( yclick <= seltop && xclick <= selleft ) {
607         // click is in or outside top left corner
608         seltop = yclick;
609         selleft = xclick;
610         anchory = selbottom;
611         anchorx = selright;
612 
613     } else if ( yclick <= seltop && xclick >= selright ) {
614         // click is in or outside top right corner
615         seltop = yclick;
616         selright = xclick;
617         anchory = selbottom;
618         anchorx = selleft;
619 
620     } else if ( yclick >= selbottom && xclick >= selright ) {
621         // click is in or outside bottom right corner
622         selbottom = yclick;
623         selright = xclick;
624         anchory = seltop;
625         anchorx = selleft;
626 
627     } else if ( yclick >= selbottom && xclick <= selleft ) {
628         // click is in or outside bottom left corner
629         selbottom = yclick;
630         selleft = xclick;
631         anchory = seltop;
632         anchorx = selright;
633 
634     } else if (yclick <= seltop) {
635         // click is in or above top edge
636         *forcev = true;
637         seltop = yclick;
638         anchory = selbottom;
639 
640     } else if (yclick >= selbottom) {
641         // click is in or below bottom edge
642         *forcev = true;
643         selbottom = yclick;
644         anchory = seltop;
645 
646     } else if (xclick <= selleft) {
647         // click is in or left of left edge
648         *forceh = true;
649         selleft = xclick;
650         anchorx = selright;
651 
652     } else if (xclick >= selright) {
653         // click is in or right of right edge
654         *forceh = true;
655         selright = xclick;
656         anchorx = selleft;
657 
658     } else {
659         // click is somewhere inside selection
660         double wd = selright.todouble() - selleft.todouble() + 1.0;
661         double ht = selbottom.todouble() - seltop.todouble() + 1.0;
662         double onethirdx = selleft.todouble() + wd / 3.0;
663         double twothirdx = selleft.todouble() + wd * 2.0 / 3.0;
664         double onethirdy = seltop.todouble() + ht / 3.0;
665         double twothirdy = seltop.todouble() + ht * 2.0 / 3.0;
666         double midy = seltop.todouble() + ht / 2.0;
667         double x = xclick.todouble();
668         double y = yclick.todouble();
669 
670         if ( y < onethirdy && x < onethirdx ) {
671             // click is near top left corner
672             seltop = yclick;
673             selleft = xclick;
674             anchory = selbottom;
675             anchorx = selright;
676 
677         } else if ( y < onethirdy && x > twothirdx ) {
678             // click is near top right corner
679             seltop = yclick;
680             selright = xclick;
681             anchory = selbottom;
682             anchorx = selleft;
683 
684         } else if ( y > twothirdy && x > twothirdx ) {
685             // click is near bottom right corner
686             selbottom = yclick;
687             selright = xclick;
688             anchory = seltop;
689             anchorx = selleft;
690 
691         } else if ( y > twothirdy && x < onethirdx ) {
692             // click is near bottom left corner
693             selbottom = yclick;
694             selleft = xclick;
695             anchory = seltop;
696             anchorx = selright;
697 
698         } else if ( x < onethirdx ) {
699             // click is near middle of left edge
700             *forceh = true;
701             selleft = xclick;
702             anchorx = selright;
703 
704         } else if ( x > twothirdx ) {
705             // click is near middle of right edge
706             *forceh = true;
707             selright = xclick;
708             anchorx = selleft;
709 
710         } else if ( y < midy ) {
711             // click is below middle section of top edge
712             *forcev = true;
713             seltop = yclick;
714             anchory = selbottom;
715 
716         } else {
717             // click is above middle section of bottom edge
718             *forcev = true;
719             selbottom = yclick;
720             anchory = seltop;
721         }
722     }
723 }
724 
725 // -----------------------------------------------------------------------------
726 
SetLeftRight(const bigint & x,const bigint & anchorx)727 void Selection::SetLeftRight(const bigint& x, const bigint& anchorx)
728 {
729     if (x <= anchorx) {
730         selleft = x;
731         selright = anchorx;
732     } else {
733         selleft = anchorx;
734         selright = x;
735     }
736     exists = true;
737 }
738 
739 // -----------------------------------------------------------------------------
740 
SetTopBottom(const bigint & y,const bigint & anchory)741 void Selection::SetTopBottom(const bigint& y, const bigint& anchory)
742 {
743     if (y <= anchory) {
744         seltop = y;
745         selbottom = anchory;
746     } else {
747         seltop = anchory;
748         selbottom = y;
749     }
750     exists = true;
751 }
752 
753 // -----------------------------------------------------------------------------
754 
Fit()755 void Selection::Fit()
756 {
757     bigint newx = selright;
758     newx -= selleft;
759     newx += bigint::one;
760     newx.div2();
761     newx += selleft;
762 
763     bigint newy = selbottom;
764     newy -= seltop;
765     newy += bigint::one;
766     newy.div2();
767     newy += seltop;
768 
769     int mag = MAX_MAG;
770     while (true) {
771         currlayer->view->setpositionmag(newx, newy, mag);
772         if (currlayer->view->contains(selleft, seltop) &&
773             currlayer->view->contains(selright, selbottom) )
774             break;
775         mag--;
776     }
777 }
778 
779 // -----------------------------------------------------------------------------
780 
Shrink(bool fit,bool remove_if_empty)781 void Selection::Shrink(bool fit, bool remove_if_empty)
782 {
783     if (!exists) return;
784 
785     if (mainptr->generating) {
786         mainptr->command_pending = true;
787         mainptr->cmdevent.SetId(fit ? ID_SHRINKFIT : ID_SHRINK);
788         mainptr->Stop();
789         return;
790     }
791 
792     // check if there is no pattern
793     if (currlayer->algo->isEmpty()) {
794         if (remove_if_empty) {
795             viewptr->RemoveSelection();
796         } else {
797             statusptr->ErrorMessage(empty_selection);
798             if (fit) viewptr->FitSelection();
799         }
800         return;
801     }
802 
803     // check if selection encloses entire pattern
804     bigint top, left, bottom, right;
805     currlayer->algo->findedges(&top, &left, &bottom, &right);
806     if (Contains(top, left, bottom, right)) {
807         // shrink edges
808         viewptr->SaveCurrentSelection();
809         seltop = top;
810         selleft = left;
811         selbottom = bottom;
812         selright = right;
813         viewptr->RememberNewSelection(_("Shrink Selection"));
814         viewptr->DisplaySelectionSize();
815         if (fit)
816             viewptr->FitSelection();
817         else
818             mainptr->UpdatePatternAndStatus();
819         return;
820     }
821 
822     // check if selection is completely outside pattern edges
823     if (Outside(top, left, bottom, right)) {
824         if (remove_if_empty) {
825             viewptr->RemoveSelection();
826         } else {
827             statusptr->ErrorMessage(empty_selection);
828             if (fit) viewptr->FitSelection();
829         }
830         return;
831     }
832 
833     // find intersection of selection and pattern to minimize work
834     if (seltop > top) top = seltop;
835     if (selleft > left) left = selleft;
836     if (selbottom < bottom) bottom = selbottom;
837     if (selright < right) right = selright;
838 
839     // check that selection is small enough to save
840     if ( viewptr->OutsideLimits(top, left, bottom, right) ) {
841         statusptr->ErrorMessage(selection_too_big);
842         if (fit) viewptr->FitSelection();
843         return;
844     }
845 
846     if ( insideYield ) {
847         // we've been called from checkevents() so we don't attempt to shrink a very
848         // large selection because the progress dialog can't be cancelled, presumably
849         // because normal event handling isn't available inside Yield()
850         double wd = right.todouble() - left.todouble() + 1.0;
851         double ht = bottom.todouble() - top.todouble() + 1.0;
852         if ( wd * ht > 1.0e12 ) {
853             statusptr->ErrorMessage(_("Selection is too big to shrink."));
854             if (fit) viewptr->FitSelection();
855             return;
856         }
857     }
858 
859     // the easy way to shrink selection is to create a new temporary universe,
860     // copy selection into new universe and then call findedges;
861     // if only 2 cell states then use qlife because its findedges call is faster
862     lifealgo* tempalgo = CreateNewUniverse(currlayer->algo->NumCellStates() > 2 ?
863                                            currlayer->algtype :
864                                            QLIFE_ALGO);
865     // make sure temporary universe has same # of cell states
866     if (currlayer->algo->NumCellStates() > 2)
867         if (tempalgo->setrule(currlayer->algo->getrule()))
868             tempalgo->setrule(tempalgo->DefaultRule());
869 
870     // copy live cells in selection to temporary universe
871     if ( viewptr->CopyRect(top.toint(), left.toint(), bottom.toint(), right.toint(),
872                            currlayer->algo, tempalgo, false, _("Copying selection")) ) {
873         if (tempalgo->isEmpty()) {
874             if (remove_if_empty) {
875                 viewptr->RemoveSelection();
876                 delete tempalgo;
877                 return;
878             } else {
879                 statusptr->ErrorMessage(empty_selection);
880             }
881         } else {
882             viewptr->SaveCurrentSelection();
883             tempalgo->findedges(&seltop, &selleft, &selbottom, &selright);
884             viewptr->RememberNewSelection(_("Shrink Selection"));
885             viewptr->DisplaySelectionSize();
886             if (!fit) mainptr->UpdatePatternAndStatus();
887         }
888     }
889 
890     delete tempalgo;
891     if (fit) viewptr->FitSelection();
892 }
893 
894 // -----------------------------------------------------------------------------
895 
Visible(wxRect * visrect)896 bool Selection::Visible(wxRect* visrect)
897 {
898     if (!exists) return false;
899 
900     pair<int,int> lt = currlayer->view->screenPosOf(selleft, seltop, currlayer->algo);
901     pair<int,int> rb = currlayer->view->screenPosOf(selright, selbottom, currlayer->algo);
902 
903     if (lt.first > currlayer->view->getxmax() || rb.first < 0 ||
904         lt.second > currlayer->view->getymax() || rb.second < 0) {
905         // no part of selection is visible
906         return false;
907     }
908 
909     // all or some of selection is visible in viewport;
910     // only set visible rectangle if requested
911     if (visrect) {
912         // first we must clip coords to viewport
913         if (lt.first < 0) lt.first = 0;
914         if (lt.second < 0) lt.second = 0;
915         if (rb.first > currlayer->view->getxmax()) rb.first = currlayer->view->getxmax();
916         if (rb.second > currlayer->view->getymax()) rb.second = currlayer->view->getymax();
917 
918         if (currlayer->view->getmag() > 0) {
919             // move rb to pixel at bottom right corner of cell
920             rb.first += (1 << currlayer->view->getmag()) - 1;
921             rb.second += (1 << currlayer->view->getmag()) - 1;
922             if (currlayer->view->getmag() > 1) {
923                 // avoid covering gaps at scale 1:4 and above
924                 rb.first--;
925                 rb.second--;
926             }
927             // clip to viewport again
928             if (rb.first > currlayer->view->getxmax()) rb.first = currlayer->view->getxmax();
929             if (rb.second > currlayer->view->getymax()) rb.second = currlayer->view->getymax();
930         }
931 
932         visrect->SetX(lt.first);
933         visrect->SetY(lt.second);
934         visrect->SetWidth(rb.first - lt.first + 1);
935         visrect->SetHeight(rb.second - lt.second + 1);
936     }
937     return true;
938 }
939 
940 // -----------------------------------------------------------------------------
941 
EmptyUniverse()942 void Selection::EmptyUniverse()
943 {
944     // save current step, scale, position and gen count
945     int savebase = currlayer->currbase;
946     int saveexpo = currlayer->currexpo;
947     int savemag = currlayer->view->getmag();
948     bigint savex = currlayer->view->x;
949     bigint savey = currlayer->view->y;
950     bigint savegen = currlayer->algo->getGeneration();
951 
952     // kill all live cells by replacing the current universe with a
953     // new, empty universe which also uses the same rule
954     mainptr->CreateUniverse();
955 
956     // restore step, scale, position and gen count
957     currlayer->currbase = savebase;
958     mainptr->SetStepExponent(saveexpo);
959     // SetStepExponent calls SetGenIncrement
960     currlayer->view->setpositionmag(savex, savey, savemag);
961     currlayer->algo->setGeneration(savegen);
962 
963     mainptr->UpdatePatternAndStatus();
964 }
965 
966 // -----------------------------------------------------------------------------
967 
Clear()968 void Selection::Clear()
969 {
970     if (!exists) return;
971 
972     // no need to do anything if there is no pattern
973     if (currlayer->algo->isEmpty()) return;
974 
975     if (mainptr->generating) {
976         mainptr->command_pending = true;
977         mainptr->cmdevent.SetId(ID_CLEAR);
978         mainptr->Stop();
979         return;
980     }
981 
982     // save cell changes if undo/redo is enabled and script isn't constructing a pattern
983     bool savecells = allowundo && !currlayer->stayclean;
984     if (savecells && inscript) SavePendingChanges();
985 
986     bigint top, left, bottom, right;
987     currlayer->algo->findedges(&top, &left, &bottom, &right);
988 
989     if ( !savecells && Contains(top, left, bottom, right) ) {
990         // selection encloses entire pattern so just create empty universe
991         EmptyUniverse();
992         MarkLayerDirty();
993         return;
994     }
995 
996     // no need to do anything if selection is completely outside pattern edges
997     if (Outside(top, left, bottom, right)) {
998         return;
999     }
1000 
1001     // find intersection of selection and pattern to minimize work
1002     if (seltop > top) top = seltop;
1003     if (selleft > left) left = selleft;
1004     if (selbottom < bottom) bottom = selbottom;
1005     if (selright < right) right = selright;
1006 
1007     // can only use setcell in limited domain
1008     if ( viewptr->OutsideLimits(top, left, bottom, right) ) {
1009         statusptr->ErrorMessage(selection_too_big);
1010         return;
1011     }
1012 
1013     // clear all live cells in selection
1014     int itop = top.toint();
1015     int ileft = left.toint();
1016     int ibottom = bottom.toint();
1017     int iright = right.toint();
1018     int wd = iright - ileft + 1;
1019     int ht = ibottom - itop + 1;
1020     int cx, cy;
1021     double maxcount = (double)wd * (double)ht;
1022     int cntr = 0;
1023     int v = 0;
1024     bool abort = false;
1025     bool selchanged = false;
1026     BeginProgress(_("Clearing selection"));
1027     lifealgo* curralgo = currlayer->algo;
1028     for ( cy=itop; cy<=ibottom; cy++ ) {
1029         for ( cx=ileft; cx<=iright; cx++ ) {
1030             int skip = curralgo->nextcell(cx, cy, v);
1031             if (skip + cx > iright)
1032                 skip = -1;           // pretend we found no more live cells
1033             if (skip >= 0) {
1034                 // found next live cell
1035                 cx += skip;
1036                 curralgo->setcell(cx, cy, 0);
1037                 selchanged = true;
1038                 if (savecells) currlayer->undoredo->SaveCellChange(cx, cy, v, 0);
1039             } else {
1040                 cx = iright + 1;     // done this row
1041             }
1042             cntr++;
1043             if ((cntr % 4096) == 0) {
1044                 double prog = ((cy - itop) * (double)(iright - ileft + 1) +
1045                                (cx - ileft)) / maxcount;
1046                 abort = AbortProgress(prog, wxEmptyString);
1047                 if (abort) break;
1048             }
1049         }
1050         if (abort) break;
1051     }
1052     if (selchanged) curralgo->endofpattern();
1053     EndProgress();
1054 
1055     if (selchanged) {
1056         if (savecells) currlayer->undoredo->RememberCellChanges(_("Clear"), currlayer->dirty);
1057         MarkLayerDirty();
1058         mainptr->UpdatePatternAndStatus();
1059     }
1060 }
1061 
1062 // -----------------------------------------------------------------------------
1063 
SaveOutside(bigint & t,bigint & l,bigint & b,bigint & r)1064 bool Selection::SaveOutside(bigint& t, bigint& l, bigint& b, bigint& r)
1065 {
1066     if ( viewptr->OutsideLimits(t, l, b, r) ) {
1067         statusptr->ErrorMessage(pattern_too_big);
1068         return false;
1069     }
1070 
1071     int itop = t.toint();
1072     int ileft = l.toint();
1073     int ibottom = b.toint();
1074     int iright = r.toint();
1075 
1076     // save ALL cells if selection is completely outside pattern edges
1077     bool saveall = Outside(t, l, b, r);
1078 
1079     // integer selection edges must not be outside pattern edges
1080     int stop = itop;
1081     int sleft = ileft;
1082     int sbottom = ibottom;
1083     int sright = iright;
1084     if (!saveall) {
1085         if (seltop > t)    stop = seltop.toint();
1086         if (selleft > l)   sleft = selleft.toint();
1087         if (selbottom < b) sbottom = selbottom.toint();
1088         if (selright < r)  sright = selright.toint();
1089     }
1090 
1091     int wd = iright - ileft + 1;
1092     int ht = ibottom - itop + 1;
1093     int cx, cy;
1094     int v = 0;
1095     double maxcount = (double)wd * (double)ht;
1096     int cntr = 0;
1097     bool abort = false;
1098     BeginProgress(_("Saving outside selection"));
1099     lifealgo* curralgo = currlayer->algo;
1100     for ( cy=itop; cy<=ibottom; cy++ ) {
1101         for ( cx=ileft; cx<=iright; cx++ ) {
1102             int skip = curralgo->nextcell(cx, cy, v);
1103             if (skip + cx > iright)
1104                 skip = -1;           // pretend we found no more live cells
1105             if (skip >= 0) {
1106                 // found next live cell
1107                 cx += skip;
1108                 if (saveall || cx < sleft || cx > sright || cy < stop || cy > sbottom) {
1109                     // cell is outside selection edges
1110                     currlayer->undoredo->SaveCellChange(cx, cy, v, 0);
1111                 }
1112             } else {
1113                 cx = iright + 1;     // done this row
1114             }
1115             cntr++;
1116             if ((cntr % 4096) == 0) {
1117                 double prog = ((cy - itop) * (double)(iright - ileft + 1) +
1118                                (cx - ileft)) / maxcount;
1119                 abort = AbortProgress(prog, wxEmptyString);
1120                 if (abort) break;
1121             }
1122         }
1123         if (abort) break;
1124     }
1125     EndProgress();
1126 
1127     if (abort) currlayer->undoredo->ForgetCellChanges();
1128     return !abort;
1129 }
1130 
1131 // -----------------------------------------------------------------------------
1132 
ClearOutside()1133 void Selection::ClearOutside()
1134 {
1135     if (!exists) return;
1136 
1137     // no need to do anything if there is no pattern
1138     if (currlayer->algo->isEmpty()) return;
1139 
1140     // no need to do anything if selection encloses entire pattern
1141     bigint top, left, bottom, right;
1142     currlayer->algo->findedges(&top, &left, &bottom, &right);
1143     if (Contains(top, left, bottom, right)) {
1144         return;
1145     }
1146 
1147     if (mainptr->generating) {
1148         mainptr->command_pending = true;
1149         mainptr->cmdevent.SetId(ID_OUTSIDE);
1150         mainptr->Stop();
1151         return;
1152     }
1153 
1154     // save cell changes if undo/redo is enabled and script isn't constructing a pattern
1155     bool savecells = allowundo && !currlayer->stayclean;
1156     if (savecells && inscript) SavePendingChanges();
1157 
1158     if (savecells) {
1159         // save live cells outside selection
1160         if ( !SaveOutside(top, left, bottom, right) ) {
1161             return;
1162         }
1163     } else {
1164         // create empty universe if selection is completely outside pattern edges
1165         if (Outside(top, left, bottom, right)) {
1166             EmptyUniverse();
1167             MarkLayerDirty();
1168             return;
1169         }
1170     }
1171 
1172     // find intersection of selection and pattern to minimize work
1173     if (seltop > top) top = seltop;
1174     if (selleft > left) left = selleft;
1175     if (selbottom < bottom) bottom = selbottom;
1176     if (selright < right) right = selright;
1177 
1178     // check that selection is small enough to save
1179     if ( viewptr->OutsideLimits(top, left, bottom, right) ) {
1180         statusptr->ErrorMessage(selection_too_big);
1181         return;
1182     }
1183 
1184     // create a new universe of same type
1185     lifealgo* newalgo = CreateNewUniverse(currlayer->algtype);
1186     if (newalgo->setrule(currlayer->algo->getrule()))
1187         newalgo->setrule(newalgo->DefaultRule());
1188 
1189     // set same gen count
1190     newalgo->setGeneration( currlayer->algo->getGeneration() );
1191 
1192     // copy live cells in selection to new universe
1193     if ( viewptr->CopyRect(top.toint(), left.toint(), bottom.toint(), right.toint(),
1194                            currlayer->algo, newalgo, false, _("Saving selection")) ) {
1195         // delete old universe and point currlayer->algo at new universe
1196         delete currlayer->algo;
1197         currlayer->algo = newalgo;
1198         mainptr->SetGenIncrement();
1199         if (savecells) currlayer->undoredo->RememberCellChanges(_("Clear Outside"), currlayer->dirty);
1200         MarkLayerDirty();
1201         mainptr->UpdatePatternAndStatus();
1202     } else {
1203         // CopyRect was aborted, so don't change current universe
1204         delete newalgo;
1205         if (savecells) currlayer->undoredo->ForgetCellChanges();
1206     }
1207 }
1208 
1209 // -----------------------------------------------------------------------------
1210 
AddEOL(char * & chptr)1211 void Selection::AddEOL(char* &chptr)
1212 {
1213 #ifdef __WXMSW__
1214     // use DOS line ending (CR+LF) on Windows
1215     *chptr = '\r';
1216     chptr += 1;
1217     *chptr = '\n';
1218     chptr += 1;
1219 #else
1220     // use LF on Linux or Mac
1221     *chptr = '\n';
1222     chptr += 1;
1223 #endif
1224 }
1225 
1226 // -----------------------------------------------------------------------------
1227 
1228 const int WRLE_NONE = -3;
1229 const int WRLE_EOP = -2;
1230 const int WRLE_NEWLINE = -1;
1231 
AddRun(int state,int multistate,unsigned int & run,unsigned int & linelen,char * & chptr)1232 void Selection::AddRun(int state,                // in: state of cell to write
1233                        int multistate,           // true if #cell states > 2
1234                        unsigned int &run,        // in and out
1235                        unsigned int &linelen,    // ditto
1236                        char* &chptr)             // ditto
1237 {
1238     // output of RLE pattern data is channelled thru here to make it easier to
1239     // ensure all lines have <= maxrleline characters
1240     const unsigned int maxrleline = 70;
1241     unsigned int i, numlen;
1242     char numstr[32];
1243 
1244     if ( run > 1 ) {
1245         sprintf(numstr, "%u", run);
1246         numlen = (unsigned int)strlen(numstr);
1247     } else {
1248         numlen = 0;                      // no run count shown if 1
1249     }
1250     // keep linelen <= maxrleline
1251     if ( linelen + numlen + 1 + multistate > maxrleline ) {
1252         AddEOL(chptr);
1253         linelen = 0;
1254     }
1255     i = 0;
1256     while (i < numlen) {
1257         *chptr = numstr[i];
1258         chptr += 1;
1259         i++;
1260     }
1261     if (multistate) {
1262         if (state <= 0)
1263             *chptr = ".$!"[-state];
1264         else {
1265             if (state > 24) {
1266                 int hi = (state - 25) / 24;
1267                 *chptr = hi + 'p';
1268                 chptr += 1;
1269                 linelen += 1;
1270                 state -= (hi + 1) * 24;
1271             }
1272             *chptr = 'A' + state - 1;
1273         }
1274     } else
1275         *chptr = "!$bo"[state+2];
1276     chptr += 1;
1277     linelen += numlen + 1;
1278     run = 0;                           // reset run count
1279 }
1280 
1281 // -----------------------------------------------------------------------------
1282 
CopyToClipboard(bool cut)1283 void Selection::CopyToClipboard(bool cut)
1284 {
1285     // can only use getcell/setcell in limited domain
1286     if (TooBig()) {
1287         statusptr->ErrorMessage(selection_too_big);
1288         return;
1289     }
1290 
1291     int itop = seltop.toint();
1292     int ileft = selleft.toint();
1293     int ibottom = selbottom.toint();
1294     int iright = selright.toint();
1295     unsigned int wd = iright - ileft + 1;
1296     unsigned int ht = ibottom - itop + 1;
1297 
1298     // convert cells in selection to RLE data in textptr
1299     char* textptr;
1300     char* etextptr;
1301     int cursize = 4096;
1302 
1303     textptr = (char*)malloc(cursize);
1304     if (textptr == NULL) {
1305         statusptr->ErrorMessage(_("Not enough memory for clipboard data!"));
1306         return;
1307     }
1308     etextptr = textptr + cursize;
1309 
1310     // add RLE header line
1311     sprintf(textptr, "x = %u, y = %u, rule = %s", wd, ht, currlayer->algo->getrule());
1312     char* chptr = textptr;
1313     chptr += strlen(textptr);
1314     AddEOL(chptr);
1315     // save start of data in case livecount is zero
1316     int datastart = chptr - textptr;
1317 
1318     // add RLE pattern data
1319     unsigned int livecount = 0;
1320     unsigned int linelen = 0;
1321     unsigned int brun = 0;
1322     unsigned int orun = 0;
1323     unsigned int dollrun = 0;
1324     int laststate;
1325     int cx, cy;
1326     int v = 0;
1327 
1328     // save cell changes if undo/redo is enabled and script isn't constructing a pattern
1329     bool savecells = allowundo && !currlayer->stayclean;
1330     if (savecells && inscript) SavePendingChanges();
1331 
1332     double maxcount = (double)wd * (double)ht;
1333     int cntr = 0;
1334     bool abort = false;
1335     if (cut)
1336         BeginProgress(_("Cutting selection"));
1337     else
1338         BeginProgress(_("Copying selection"));
1339 
1340     lifealgo* curralgo = currlayer->algo;
1341     int multistate = curralgo->NumCellStates() > 2;
1342     for ( cy=itop; cy<=ibottom; cy++ ) {
1343         laststate = WRLE_NONE;
1344         for ( cx=ileft; cx<=iright; cx++ ) {
1345             int skip = curralgo->nextcell(cx, cy, v);
1346             if (skip + cx > iright)
1347                 skip = -1;           // pretend we found no more live cells
1348             if (skip > 0) {
1349                 // have exactly "skip" empty cells here
1350                 if (laststate == 0) {
1351                     brun += skip;
1352                 } else {
1353                     if (orun > 0) {
1354                         // output current run of live cells
1355                         AddRun(laststate, multistate, orun, linelen, chptr);
1356                     }
1357                     laststate = 0;
1358                     brun = skip;
1359                 }
1360             }
1361             if (skip >= 0) {
1362                 // found next live cell
1363                 cx += skip;
1364                 livecount++;
1365                 if (cut) {
1366                     curralgo->setcell(cx, cy, 0);
1367                     if (savecells) currlayer->undoredo->SaveCellChange(cx, cy, v, 0);
1368                 }
1369                 if (laststate == v) {
1370                     orun++;
1371                 } else {
1372                     if (dollrun > 0)
1373                         // output current run of $ chars
1374                         AddRun(WRLE_NEWLINE, multistate, dollrun, linelen, chptr);
1375                     if (brun > 0)
1376                         // output current run of dead cells
1377                         AddRun(0, multistate, brun, linelen, chptr);
1378                     if (orun > 0)
1379                         // output current run of other live cells
1380                         AddRun(laststate, multistate, orun, linelen, chptr);
1381                     laststate = v;
1382                     orun = 1;
1383                 }
1384             } else {
1385                 cx = iright + 1;     // done this row
1386             }
1387             cntr++;
1388             if ((cntr % 4096) == 0) {
1389                 double prog = ((cy - itop) * (double)(iright - ileft + 1) +
1390                                (cx - ileft)) / maxcount;
1391                 abort = AbortProgress(prog, wxEmptyString);
1392                 if (abort) break;
1393             }
1394             if (chptr + 60 >= etextptr) {
1395                 // nearly out of space; try to increase allocation
1396                 char* ntxtptr = (char*) realloc(textptr, 2*cursize);
1397                 if (ntxtptr == 0) {
1398                     statusptr->ErrorMessage(_("No more memory for clipboard data!"));
1399                     // don't return here -- best to set abort flag and break so that
1400                     // partially cut/copied portion gets saved to clipboard
1401                     abort = true;
1402                     break;
1403                 }
1404                 chptr = ntxtptr + (chptr - textptr);
1405                 cursize *= 2;
1406                 etextptr = ntxtptr + cursize;
1407                 textptr = ntxtptr;
1408             }
1409         }
1410         if (abort) break;
1411         // end of current row
1412         if (laststate == 0)
1413             // forget dead cells at end of row
1414             brun = 0;
1415         else if (laststate >= 0)
1416             // output current run of live cells
1417             AddRun(laststate, multistate, orun, linelen, chptr);
1418         dollrun++;
1419     }
1420 
1421     if (livecount == 0) {
1422         // no live cells in selection so simplify RLE data to "!"
1423         chptr = textptr + datastart;
1424         *chptr = '!';
1425         chptr++;
1426     } else {
1427         // terminate RLE data
1428         dollrun = 1;
1429         AddRun(WRLE_EOP, multistate, dollrun, linelen, chptr);
1430         if (cut) currlayer->algo->endofpattern();
1431     }
1432     AddEOL(chptr);
1433     *chptr = 0;
1434 
1435     EndProgress();
1436 
1437     if (cut && livecount > 0) {
1438         if (savecells) currlayer->undoredo->RememberCellChanges(_("Cut"), currlayer->dirty);
1439         // update currlayer->dirty AFTER RememberCellChanges
1440         MarkLayerDirty();
1441         mainptr->UpdatePatternAndStatus();
1442     }
1443 
1444     wxString text = wxString(textptr,wxConvLocal);
1445     mainptr->CopyTextToClipboard(text);
1446     free(textptr);
1447 }
1448 
1449 // -----------------------------------------------------------------------------
1450 
CanPaste(const bigint & wd,const bigint & ht,bigint & top,bigint & left)1451 bool Selection::CanPaste(const bigint& wd, const bigint& ht, bigint& top, bigint& left)
1452 {
1453     bigint selht = selbottom;  selht -= seltop;   selht += 1;
1454     bigint selwd = selright;   selwd -= selleft;  selwd += 1;
1455     if ( ht > selht || wd > selwd ) return false;
1456 
1457     // set paste rectangle's top left cell coord
1458     top = seltop;
1459     left = selleft;
1460 
1461     return true;
1462 }
1463 
1464 // -----------------------------------------------------------------------------
1465 
RandomFill()1466 void Selection::RandomFill()
1467 {
1468     if (!exists) return;
1469 
1470     // can only use getcell/setcell in limited domain
1471     if (TooBig()) {
1472         statusptr->ErrorMessage(selection_too_big);
1473         return;
1474     }
1475 
1476     if (mainptr->generating) {
1477         mainptr->command_pending = true;
1478         mainptr->cmdevent.SetId(ID_RANDOM);
1479         mainptr->Stop();
1480         return;
1481     }
1482 
1483     // save cell changes if undo/redo is enabled and script isn't constructing a pattern
1484     bool savecells = allowundo && !currlayer->stayclean;
1485     if (savecells && inscript) SavePendingChanges();
1486 
1487     // no need to kill cells if selection is empty
1488     bool killcells = !currlayer->algo->isEmpty();
1489     if ( killcells ) {
1490         // find pattern edges and compare with selection edges
1491         bigint top, left, bottom, right;
1492         currlayer->algo->findedges(&top, &left, &bottom, &right);
1493         if (Contains(top, left, bottom, right)) {
1494             // selection encloses entire pattern so create empty universe
1495             if (savecells) {
1496                 // don't kill pattern otherwise we can't use SaveCellChange below
1497             } else {
1498                 EmptyUniverse();
1499                 killcells = false;
1500             }
1501         } else if (Outside(top, left, bottom, right)) {
1502             // selection is completely outside pattern edges
1503             killcells = false;
1504         }
1505     }
1506 
1507     int itop = seltop.toint();
1508     int ileft = selleft.toint();
1509     int ibottom = selbottom.toint();
1510     int iright = selright.toint();
1511     int wd = iright - ileft + 1;
1512     int ht = ibottom - itop + 1;
1513     double maxcount = (double)wd * (double)ht;
1514     int cntr = 0;
1515     bool abort = false;
1516     BeginProgress(_("Randomly filling selection"));
1517     int cx, cy;
1518     lifealgo* curralgo = currlayer->algo;
1519     int livestates = curralgo->NumRandomizedCellStates() - 1;    // don't count dead state
1520 
1521     for ( cy=itop; cy<=ibottom; cy++ ) {
1522         for ( cx=ileft; cx<=iright; cx++ ) {
1523             // randomfill is from 1..100
1524             if (savecells) {
1525                 // remember cell change only if state changes
1526                 int oldstate = curralgo->getcell(cx, cy);
1527                 if ((rand() % 100) < randomfill) {
1528                     int newstate = livestates < 2 ? 1 : 1 + (rand() % livestates);
1529                     if (oldstate != newstate) {
1530                         curralgo->setcell(cx, cy, newstate);
1531                         currlayer->undoredo->SaveCellChange(cx, cy, oldstate, newstate);
1532                     }
1533                 } else if (killcells && oldstate > 0) {
1534                     curralgo->setcell(cx, cy, 0);
1535                     currlayer->undoredo->SaveCellChange(cx, cy, oldstate, 0);
1536                 }
1537             } else {
1538                 if ((rand() % 100) < randomfill) {
1539                     if (livestates < 2) {
1540                         curralgo->setcell(cx, cy, 1);
1541                     } else {
1542                         curralgo->setcell(cx, cy, 1 + (rand() % livestates));
1543                     }
1544                 } else if (killcells) {
1545                     curralgo->setcell(cx, cy, 0);
1546                 }
1547             }
1548             cntr++;
1549             if ((cntr % 4096) == 0) {
1550                 abort = AbortProgress((double)cntr / maxcount, wxEmptyString);
1551                 if (abort) break;
1552             }
1553         }
1554         if (abort) break;
1555     }
1556 
1557     currlayer->algo->endofpattern();
1558     EndProgress();
1559 
1560     if (savecells) currlayer->undoredo->RememberCellChanges(_("Random Fill"), currlayer->dirty);
1561 
1562     // update currlayer->dirty AFTER RememberCellChanges
1563     MarkLayerDirty();
1564     mainptr->UpdatePatternAndStatus();
1565 }
1566 
1567 // -----------------------------------------------------------------------------
1568 
FlipRect(bool topbottom,lifealgo * srcalgo,lifealgo * destalgo,bool erasesrc,int itop,int ileft,int ibottom,int iright)1569 bool Selection::FlipRect(bool topbottom, lifealgo* srcalgo, lifealgo* destalgo, bool erasesrc,
1570                          int itop, int ileft, int ibottom, int iright)
1571 {
1572     int wd = iright - ileft + 1;
1573     int ht = ibottom - itop + 1;
1574     double maxcount = (double)wd * (double)ht;
1575     int cntr = 0;
1576     bool abort = false;
1577     int v = 0;
1578     int cx, cy, newx, newy, newxinc, newyinc;
1579 
1580     if (topbottom) {
1581         BeginProgress(_("Flipping top-bottom"));
1582         newy = ibottom;
1583         newyinc = -1;
1584         newxinc = 1;
1585     } else {
1586         BeginProgress(_("Flipping left-right"));
1587         newy = itop;
1588         newyinc = 1;
1589         newxinc = -1;
1590     }
1591 
1592     for ( cy=itop; cy<=ibottom; cy++ ) {
1593         newx = topbottom ? ileft : iright;
1594         for ( cx=ileft; cx<=iright; cx++ ) {
1595             int skip = srcalgo->nextcell(cx, cy, v);
1596             if (skip + cx > iright)
1597                 skip = -1;           // pretend we found no more live cells
1598             if (skip >= 0) {
1599                 // found next live cell
1600                 cx += skip;
1601                 if (erasesrc) srcalgo->setcell(cx, cy, 0);
1602                 newx += newxinc * skip;
1603                 destalgo->setcell(newx, newy, v);
1604             } else {
1605                 cx = iright + 1;     // done this row
1606             }
1607             cntr++;
1608             if ((cntr % 4096) == 0) {
1609                 double prog = ((cy - itop) * (double)(iright - ileft + 1) +
1610                                (cx - ileft)) / maxcount;
1611                 abort = AbortProgress(prog, wxEmptyString);
1612                 if (abort) break;
1613             }
1614             newx += newxinc;
1615         }
1616         if (abort) break;
1617         newy += newyinc;
1618     }
1619 
1620     if (erasesrc) srcalgo->endofpattern();
1621     destalgo->endofpattern();
1622     EndProgress();
1623 
1624     return !abort;
1625 }
1626 
1627 // -----------------------------------------------------------------------------
1628 
Flip(bool topbottom,bool inundoredo)1629 bool Selection::Flip(bool topbottom, bool inundoredo)
1630 {
1631     if (!exists) return false;
1632 
1633     if (mainptr->generating) {
1634         mainptr->command_pending = true;
1635         mainptr->cmdevent.SetId(topbottom ? ID_FLIPTB : ID_FLIPLR);
1636         mainptr->Stop();
1637         return true;
1638     }
1639 
1640     if (topbottom) {
1641         if (seltop == selbottom) return true;
1642     } else {
1643         if (selleft == selright) return true;
1644     }
1645 
1646     if (currlayer->algo->isEmpty()) return true;
1647 
1648     bigint top, left, bottom, right;
1649     currlayer->algo->findedges(&top, &left, &bottom, &right);
1650 
1651     bigint stop = seltop;
1652     bigint sleft = selleft;
1653     bigint sbottom = selbottom;
1654     bigint sright = selright;
1655 
1656     bool simpleflip;
1657     if (Contains(top, left, bottom, right)) {
1658         // selection encloses entire pattern so we may only need to flip a smaller rectangle
1659         if (topbottom) {
1660             bigint tdiff = top; tdiff -= stop;
1661             bigint bdiff = sbottom; bdiff -= bottom;
1662             bigint mindiff = tdiff;
1663             if (bdiff < mindiff) mindiff = bdiff;
1664             stop += mindiff;
1665             sbottom -= mindiff;
1666             sleft = left;
1667             sright = right;
1668         } else {
1669             bigint ldiff = left; ldiff -= sleft;
1670             bigint rdiff = sright; rdiff -= right;
1671             bigint mindiff = ldiff;
1672             if (rdiff < mindiff) mindiff = rdiff;
1673             sleft += mindiff;
1674             sright -= mindiff;
1675             stop = top;
1676             sbottom = bottom;
1677         }
1678         simpleflip = true;
1679     } else {
1680         // selection encloses part of pattern so we can clip some selection edges
1681         // if they are outside the pattern edges
1682         if (topbottom) {
1683             if (sleft < left) sleft = left;
1684             if (sright > right) sright = right;
1685         } else {
1686             if (stop < top) stop = top;
1687             if (sbottom > bottom) sbottom = bottom;
1688         }
1689         simpleflip = false;
1690     }
1691 
1692     // can only use getcell/setcell in limited domain
1693     if ( viewptr->OutsideLimits(stop, sbottom, sleft, sright) ) {
1694         statusptr->ErrorMessage(selection_too_big);
1695         return false;
1696     }
1697 
1698     int itop = stop.toint();
1699     int ileft = sleft.toint();
1700     int ibottom = sbottom.toint();
1701     int iright = sright.toint();
1702 
1703     if (simpleflip) {
1704         // selection encloses all of pattern so we can flip into new universe
1705         // (must be same type) without killing live cells in selection
1706         lifealgo* newalgo = CreateNewUniverse(currlayer->algtype);
1707         if (newalgo->setrule(currlayer->algo->getrule()))
1708             newalgo->setrule(newalgo->DefaultRule());
1709         newalgo->setGeneration( currlayer->algo->getGeneration() );
1710 
1711         if ( FlipRect(topbottom, currlayer->algo, newalgo, false, itop, ileft, ibottom, iright) ) {
1712             // switch to newalgo
1713             delete currlayer->algo;
1714             currlayer->algo = newalgo;
1715             mainptr->SetGenIncrement();
1716         } else {
1717             // user aborted flip
1718             delete newalgo;
1719             return false;
1720         }
1721     } else {
1722         // flip into temporary universe and kill all live cells in selection;
1723         // if only 2 cell states then use qlife because its setcell/getcell calls are faster
1724         lifealgo* tempalgo = CreateNewUniverse(currlayer->algo->NumCellStates() > 2 ?
1725                                                currlayer->algtype :
1726                                                QLIFE_ALGO);
1727         // make sure temporary universe has same # of cell states
1728         if (currlayer->algo->NumCellStates() > 2)
1729             if (tempalgo->setrule(currlayer->algo->getrule()))
1730                 tempalgo->setrule(tempalgo->DefaultRule());
1731 
1732         if ( FlipRect(topbottom, currlayer->algo, tempalgo, true, itop, ileft, ibottom, iright) ) {
1733             // find pattern edges in temporary universe (could be much smaller)
1734             // and copy temporary pattern into current universe
1735             tempalgo->findedges(&top, &left, &bottom, &right);
1736             viewptr->CopyRect(top.toint(), left.toint(), bottom.toint(), right.toint(),
1737                               tempalgo, currlayer->algo, false, _("Adding flipped selection"));
1738             delete tempalgo;
1739         } else {
1740             // user aborted flip so flip tempalgo pattern back into current universe
1741             FlipRect(topbottom, tempalgo, currlayer->algo, false, itop, ileft, ibottom, iright);
1742             delete tempalgo;
1743             return false;
1744         }
1745     }
1746 
1747     // flips are always reversible so no need to use SaveCellChange and RememberCellChanges
1748     if (allowundo && !currlayer->stayclean && !inundoredo) {
1749         if (inscript) SavePendingChanges();
1750         currlayer->undoredo->RememberFlip(topbottom, currlayer->dirty);
1751     }
1752 
1753     // update currlayer->dirty AFTER RememberFlip
1754     if (!inundoredo) MarkLayerDirty();
1755     mainptr->UpdatePatternAndStatus();
1756 
1757     return true;
1758 }
1759 
1760 // -----------------------------------------------------------------------------
1761 
1762 const wxString rotate_clockwise =      _("Rotating selection +90 degrees");
1763 const wxString rotate_anticlockwise =  _("Rotating selection -90 degrees");
1764 
RotateRect(bool clockwise,lifealgo * srcalgo,lifealgo * destalgo,bool erasesrc,int itop,int ileft,int ibottom,int iright,int ntop,int nleft,int nbottom,int nright)1765 bool Selection::RotateRect(bool clockwise,
1766                            lifealgo* srcalgo, lifealgo* destalgo, bool erasesrc,
1767                            int itop, int ileft, int ibottom, int iright,
1768                            int ntop, int nleft, int nbottom, int nright)
1769 {
1770     int wd = iright - ileft + 1;
1771     int ht = ibottom - itop + 1;
1772     double maxcount = (double)wd * (double)ht;
1773     int cntr = 0;
1774     bool abort = false;
1775     int cx, cy, newx, newy, newxinc, newyinc, v=0;
1776 
1777     if (clockwise) {
1778         BeginProgress(rotate_clockwise);
1779         newx = nright;
1780         newyinc = 1;
1781         newxinc = -1;
1782     } else {
1783         BeginProgress(rotate_anticlockwise);
1784         newx = nleft;
1785         newyinc = -1;
1786         newxinc = 1;
1787     }
1788 
1789     for ( cy=itop; cy<=ibottom; cy++ ) {
1790         newy = clockwise ? ntop : nbottom;
1791         for ( cx=ileft; cx<=iright; cx++ ) {
1792             int skip = srcalgo->nextcell(cx, cy, v);
1793             if (skip + cx > iright)
1794                 skip = -1;           // pretend we found no more live cells
1795             if (skip >= 0) {
1796                 // found next live cell
1797                 cx += skip;
1798                 if (erasesrc) srcalgo->setcell(cx, cy, 0);
1799                 newy += newyinc * skip;
1800                 destalgo->setcell(newx, newy, v);
1801             } else {
1802                 cx = iright + 1;     // done this row
1803             }
1804             cntr++;
1805             if ((cntr % 4096) == 0) {
1806                 double prog = ((cy - itop) * (double)(iright - ileft + 1) +
1807                                (cx - ileft)) / maxcount;
1808                 abort = AbortProgress(prog, wxEmptyString);
1809                 if (abort) break;
1810             }
1811             newy += newyinc;
1812         }
1813         if (abort) break;
1814         newx += newxinc;
1815     }
1816 
1817     if (erasesrc) srcalgo->endofpattern();
1818     destalgo->endofpattern();
1819     EndProgress();
1820 
1821     return !abort;
1822 }
1823 
1824 // -----------------------------------------------------------------------------
1825 
RotatePattern(bool clockwise,bigint & newtop,bigint & newbottom,bigint & newleft,bigint & newright,bool inundoredo)1826 bool Selection::RotatePattern(bool clockwise,
1827                               bigint& newtop, bigint& newbottom,
1828                               bigint& newleft, bigint& newright,
1829                               bool inundoredo)
1830 {
1831     // create new universe of same type as current universe
1832     lifealgo* newalgo = CreateNewUniverse(currlayer->algtype);
1833     if (newalgo->setrule(currlayer->algo->getrule()))
1834         newalgo->setrule(newalgo->DefaultRule());
1835 
1836     // set same gen count
1837     newalgo->setGeneration( currlayer->algo->getGeneration() );
1838 
1839     // copy all live cells to new universe, rotating the coords by +/- 90 degrees
1840     int itop    = seltop.toint();
1841     int ileft   = selleft.toint();
1842     int ibottom = selbottom.toint();
1843     int iright  = selright.toint();
1844     int wd = iright - ileft + 1;
1845     int ht = ibottom - itop + 1;
1846     double maxcount = (double)wd * (double)ht;
1847     int cntr = 0;
1848     bool abort = false;
1849     int cx, cy, newx, newy, newxinc, newyinc, firstnewy, v=0;
1850 
1851     if (clockwise) {
1852         BeginProgress(rotate_clockwise);
1853         firstnewy = newtop.toint();
1854         newx = newright.toint();
1855         newyinc = 1;
1856         newxinc = -1;
1857     } else {
1858         BeginProgress(rotate_anticlockwise);
1859         firstnewy = newbottom.toint();
1860         newx = newleft.toint();
1861         newyinc = -1;
1862         newxinc = 1;
1863     }
1864 
1865     lifealgo* curralgo = currlayer->algo;
1866     for ( cy=itop; cy<=ibottom; cy++ ) {
1867         newy = firstnewy;
1868         for ( cx=ileft; cx<=iright; cx++ ) {
1869             int skip = curralgo->nextcell(cx, cy, v);
1870             if (skip + cx > iright)
1871                 skip = -1;           // pretend we found no more live cells
1872             if (skip >= 0) {
1873                 // found next live cell
1874                 cx += skip;
1875                 newy += newyinc * skip;
1876                 newalgo->setcell(newx, newy, v);
1877             } else {
1878                 cx = iright + 1;     // done this row
1879             }
1880             cntr++;
1881             if ((cntr % 4096) == 0) {
1882                 double prog = ((cy - itop) * (double)(iright - ileft + 1) +
1883                                (cx - ileft)) / maxcount;
1884                 abort = AbortProgress(prog, wxEmptyString);
1885                 if (abort) break;
1886             }
1887             newy += newyinc;
1888         }
1889         if (abort) break;
1890         newx += newxinc;
1891     }
1892 
1893     newalgo->endofpattern();
1894     EndProgress();
1895 
1896     if (abort) {
1897         delete newalgo;
1898     } else {
1899         // rotate the selection edges
1900         seltop    = newtop;
1901         selbottom = newbottom;
1902         selleft   = newleft;
1903         selright  = newright;
1904 
1905         // switch to new universe and display results
1906         delete currlayer->algo;
1907         currlayer->algo = newalgo;
1908         mainptr->SetGenIncrement();
1909         viewptr->DisplaySelectionSize();
1910 
1911         // rotating entire pattern is easily reversible so no need to use
1912         // SaveCellChange and RememberCellChanges in this case
1913         if (allowundo && !currlayer->stayclean && !inundoredo) {
1914             if (inscript) SavePendingChanges();
1915             currlayer->undoredo->RememberRotation(clockwise, currlayer->dirty);
1916         }
1917 
1918         // update currlayer->dirty AFTER RememberRotation
1919         if (!inundoredo) MarkLayerDirty();
1920         mainptr->UpdatePatternAndStatus();
1921     }
1922 
1923     return !abort;
1924 }
1925 
1926 // -----------------------------------------------------------------------------
1927 
Rotate(bool clockwise,bool inundoredo)1928 bool Selection::Rotate(bool clockwise, bool inundoredo)
1929 {
1930     if (!exists) return false;
1931 
1932     if (mainptr->generating) {
1933         mainptr->command_pending = true;
1934         mainptr->cmdevent.SetId(clockwise ? ID_ROTATEC : ID_ROTATEA);
1935         mainptr->Stop();
1936         return true;
1937     }
1938 
1939     // determine rotated selection edges
1940     bigint halfht = selbottom;   halfht -= seltop;    halfht.div2();
1941     bigint halfwd = selright;    halfwd -= selleft;   halfwd.div2();
1942     bigint midy = seltop;    midy += halfht;
1943     bigint midx = selleft;   midx += halfwd;
1944     bigint newtop    = midy;   newtop    += selleft;     newtop    -= midx;
1945     bigint newbottom = midy;   newbottom += selright;    newbottom -= midx;
1946     bigint newleft   = midx;   newleft   += seltop;      newleft   -= midy;
1947     bigint newright  = midx;   newright  += selbottom;   newright  -= midy;
1948 
1949     if (!inundoredo) {
1950         // check if rotated selection edges are outside bounded grid
1951         if ( (currlayer->algo->gridwd > 0 &&
1952                 (newleft < currlayer->algo->gridleft || newright > currlayer->algo->gridright)) ||
1953              (currlayer->algo->gridht > 0 &&
1954                 (newtop < currlayer->algo->gridtop || newbottom > currlayer->algo->gridbottom)) ) {
1955             statusptr->ErrorMessage(_("New selection would be outside grid boundary."));
1956             return false;
1957         }
1958     }
1959 
1960     // if there is no pattern then just rotate the selection edges
1961     if (currlayer->algo->isEmpty()) {
1962         viewptr->SaveCurrentSelection();
1963         seltop    = newtop;
1964         selbottom = newbottom;
1965         selleft   = newleft;
1966         selright  = newright;
1967         viewptr->RememberNewSelection(_("Rotation"));
1968         viewptr->DisplaySelectionSize();
1969         mainptr->UpdatePatternAndStatus();
1970         return true;
1971     }
1972 
1973     // if the current selection and the rotated selection are both outside the
1974     // pattern edges (ie. both are empty) then just rotate the selection edges
1975     bigint top, left, bottom, right;
1976     currlayer->algo->findedges(&top, &left, &bottom, &right);
1977     if ( (seltop > bottom || selbottom < top || selleft > right || selright < left) &&
1978          (newtop > bottom || newbottom < top || newleft > right || newright < left) ) {
1979         viewptr->SaveCurrentSelection();
1980         seltop    = newtop;
1981         selbottom = newbottom;
1982         selleft   = newleft;
1983         selright  = newright;
1984         viewptr->RememberNewSelection(_("Rotation"));
1985         viewptr->DisplaySelectionSize();
1986         mainptr->UpdatePatternAndStatus();
1987         return true;
1988     }
1989 
1990     // can only use nextcell/getcell/setcell in limited domain
1991     if (TooBig()) {
1992         statusptr->ErrorMessage(selection_too_big);
1993         return false;
1994     }
1995 
1996     // make sure rotated selection edges are also within limits
1997     if ( viewptr->OutsideLimits(newtop, newbottom, newleft, newright) ) {
1998         statusptr->ErrorMessage(_("New selection would be outside +/- 10^9 boundary."));
1999         return false;
2000     }
2001 
2002     // use faster method if selection encloses entire pattern
2003     if (Contains(top, left, bottom, right)) {
2004         return RotatePattern(clockwise, newtop, newbottom, newleft, newright, inundoredo);
2005     }
2006 
2007     int itop    = seltop.toint();
2008     int ileft   = selleft.toint();
2009     int ibottom = selbottom.toint();
2010     int iright  = selright.toint();
2011 
2012     int ntop    = newtop.toint();
2013     int nleft   = newleft.toint();
2014     int nbottom = newbottom.toint();
2015     int nright  = newright.toint();
2016 
2017     // save cell changes if undo/redo is enabled and script isn't constructing a pattern
2018     // and we're not undoing/redoing an earlier rotation
2019     bool savecells = allowundo && !currlayer->stayclean && !inundoredo;
2020     if (savecells && inscript) SavePendingChanges();
2021 
2022     lifealgo* oldalgo = NULL;
2023     int otop = itop;
2024     int oleft = ileft;
2025     int obottom = ibottom;
2026     int oright = iright;
2027 
2028     if (savecells) {
2029         // copy current pattern to oldalgo using union of old and new selection rects
2030         if (otop > ntop) otop = ntop;
2031         if (oleft > nleft) oleft = nleft;
2032         if (obottom < nbottom) obottom = nbottom;
2033         if (oright < nright) oright = nright;
2034         oldalgo = CreateNewUniverse(currlayer->algo->NumCellStates() > 2 ?
2035                                     currlayer->algtype :
2036                                     QLIFE_ALGO);
2037         // make sure universe has same # of cell states
2038         if (currlayer->algo->NumCellStates() > 2)
2039             if (oldalgo->setrule(currlayer->algo->getrule()))
2040                 oldalgo->setrule(oldalgo->DefaultRule());
2041         if ( !viewptr->CopyRect(otop, oleft, obottom, oright, currlayer->algo, oldalgo,
2042                                 false, _("Saving part of pattern")) ) {
2043             delete oldalgo;
2044             return false;
2045         }
2046     }
2047 
2048     // create temporary universe; doesn't need to match current universe so
2049     // if only 2 cell states then use qlife because its setcell/getcell calls are faster
2050     lifealgo* tempalgo = CreateNewUniverse(currlayer->algo->NumCellStates() > 2 ?
2051                                            currlayer->algtype :
2052                                            QLIFE_ALGO);
2053     // make sure temporary universe has same # of cell states
2054     if (currlayer->algo->NumCellStates() > 2)
2055         if (tempalgo->setrule(currlayer->algo->getrule()))
2056             tempalgo->setrule(tempalgo->DefaultRule());
2057 
2058     // copy (and kill) live cells in selection to temporary universe,
2059     // rotating the new coords by +/- 90 degrees
2060     if ( !RotateRect(clockwise, currlayer->algo, tempalgo, true,
2061                      itop, ileft, ibottom, iright,
2062                      ntop, nleft, nbottom, nright) ) {
2063         // user aborted rotation
2064         if (savecells) {
2065             // use oldalgo to restore erased selection
2066             viewptr->CopyRect(itop, ileft, ibottom, iright, oldalgo, currlayer->algo,
2067                               false, _("Restoring selection"));
2068             delete oldalgo;
2069         } else {
2070             // restore erased selection by rotating tempalgo in opposite direction
2071             // back into the current universe
2072             RotateRect(!clockwise, tempalgo, currlayer->algo, false,
2073                        ntop, nleft, nbottom, nright,
2074                        itop, ileft, ibottom, iright);
2075         }
2076         delete tempalgo;
2077         mainptr->UpdatePatternAndStatus();
2078         return false;
2079     }
2080 
2081     // copy rotated selection from temporary universe to current universe;
2082     // check if new selection rect is outside modified pattern edges
2083     currlayer->algo->findedges(&top, &left, &bottom, &right);
2084     if ( newtop > bottom || newbottom < top || newleft > right || newright < left ) {
2085         // safe to use fast nextcell calls
2086         viewptr->CopyRect(ntop, nleft, nbottom, nright,
2087                           tempalgo, currlayer->algo, false, _("Adding rotated selection"));
2088     } else {
2089         // have to use slow getcell calls
2090         viewptr->CopyAllRect(ntop, nleft, nbottom, nright,
2091                              tempalgo, currlayer->algo, _("Pasting rotated selection"));
2092     }
2093     // don't need temporary universe any more
2094     delete tempalgo;
2095 
2096     // rotate the selection edges
2097     seltop    = newtop;
2098     selbottom = newbottom;
2099     selleft   = newleft;
2100     selright  = newright;
2101 
2102     if (savecells) {
2103         // compare patterns in oldalgo and currlayer->algo and call SaveCellChange
2104         // for each cell that has a different state
2105         if ( SaveDifferences(oldalgo, currlayer->algo, otop, oleft, obottom, oright) ) {
2106             Selection oldsel(itop, ileft, ibottom, iright);
2107             Selection newsel(ntop, nleft, nbottom, nright);
2108             currlayer->undoredo->RememberRotation(clockwise, oldsel, newsel, currlayer->dirty);
2109         } else {
2110             currlayer->undoredo->ForgetCellChanges();
2111             Warning(_("You can't undo this change!"));
2112         }
2113         delete oldalgo;
2114     }
2115 
2116     // display results
2117     viewptr->DisplaySelectionSize();
2118     if (!inundoredo) MarkLayerDirty();
2119     mainptr->UpdatePatternAndStatus();
2120 
2121     return true;
2122 }
2123