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