1// This file is part of Golly. 2// See docs/License.html for the copyright notice. 3 4#include "viewport.h" // for MAX_MAG 5 6#include "utils.h" // for Warning, event_checker 7#include "status.h" // for SetMessage, DisplayMessage 8#include "algos.h" // for InitAlgorithms, algoinfo 9#include "prefs.h" // for GetPrefs, SavePrefs, userdir, etc 10#include "layer.h" // for AddLayer, currlayer 11#include "file.h" // for NewPattern 12#include "control.h" // for generating, StartGenerating, etc 13#include "view.h" // for nopattupdate, SmallScroll, drawingcells 14#include "undo.h" // for currlayer->undoredo->... 15 16#import "GollyAppDelegate.h" // for EnableTabBar 17#import "InfoViewController.h" 18#import "SaveViewController.h" 19#import "RuleViewController.h" 20#import "PatternViewController.h" 21 22@implementation PatternViewController 23 24// ----------------------------------------------------------------------------- 25 26// global stuff needed in UpdatePattern, UpdateStatus, etc 27 28static PatternViewController *globalController = nil; 29static PatternView *globalPatternView = nil; 30static StatusView *globalStatusView = nil; 31static StateView *globalStateView = nil; 32static UIView *globalProgress = nil; 33static UILabel *globalTitle = nil; 34 35static bool cancelProgress = false; // cancel progress dialog? 36static double progstart, prognext; // for progress timing 37static int progresscount = 0; // if > 0 then BeginProgress has been called 38static int pausecount = 0; // if > 0 then genTimer needs to be restarted 39 40// ----------------------------------------------------------------------------- 41 42- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 43{ 44 self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 45 if (self) { 46 self.title = @"Pattern"; 47 self.tabBarItem.image = [UIImage imageNamed:@"pattern.png"]; 48 } 49 return self; 50} 51 52// ----------------------------------------------------------------------------- 53 54- (void)didReceiveMemoryWarning 55{ 56 [super didReceiveMemoryWarning]; 57 58 // free up as much memory as possible 59 if (numlayers > 0) DeleteOtherLayers(); 60 if (waitingforpaste) AbortPaste(); 61 [self doNew:self]; 62 63 // Warning hangs if called here, so use DisplayMessage for now!!! 64 // (may need non-modal version via extra flag???) 65 // Warning("Memory warning occurred, so empty universe created."); 66 DisplayMessage("Memory warning occurred, so empty universe created."); 67} 68 69// ----------------------------------------------------------------------------- 70 71static void CreateDocSubdir(NSString *subdirname) 72{ 73 // create given subdirectory inside Documents 74 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 75 NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:subdirname]; 76 if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { 77 // do nothing if subdir already exists 78 } else { 79 NSError *error; 80 if (![[NSFileManager defaultManager] createDirectoryAtPath:path 81 withIntermediateDirectories:NO 82 attributes:nil 83 error:&error]) { 84 NSLog(@"Error creating %@ subdirectory: %@", subdirname, error); 85 } 86 } 87} 88 89// ----------------------------------------------------------------------------- 90 91static void InitPaths() 92{ 93 // init userdir to directory containing user's data 94 userdir = [NSHomeDirectory() cStringUsingEncoding:NSUTF8StringEncoding]; 95 userdir += "/"; 96 97 // init savedir to userdir/Documents/Saved/ 98 savedir = userdir + "Documents/Saved/"; 99 CreateDocSubdir(@"Saved"); 100 101 // init downloaddir to userdir/Documents/Downloads/ 102 downloaddir = userdir + "Documents/Downloads/"; 103 CreateDocSubdir(@"Downloads"); 104 105 // init userrules to userdir/Documents/Rules/ 106 userrules = userdir + "Documents/Rules/"; 107 CreateDocSubdir(@"Rules"); 108 109 // supplied patterns, rules, help are bundled inside Golly.app 110 NSString *appdir = [[NSBundle mainBundle] resourcePath]; 111 supplieddir = [appdir cStringUsingEncoding:NSUTF8StringEncoding]; 112 supplieddir += "/"; 113 patternsdir = supplieddir + "Patterns/"; 114 rulesdir = supplieddir + "Rules/"; 115 helpdir = supplieddir + "Help/"; 116 117 // init tempdir to userdir/tmp/ 118 tempdir = userdir + "tmp/"; 119 120 // init path to file that stores clipboard data 121 clipfile = tempdir + "golly_clipboard"; 122 123 // init path to file that stores user preferences 124 prefsfile = userdir + "Library/Preferences/GollyPrefs"; 125 126#if 0 127 NSLog(@"userdir = %s", userdir.c_str()); 128 NSLog(@"savedir = %s", savedir.c_str()); 129 NSLog(@"downloaddir = %s", downloaddir.c_str()); 130 NSLog(@"userrules = %s", userrules.c_str()); 131 NSLog(@"supplieddir = %s", supplieddir.c_str()); 132 NSLog(@"patternsdir = %s", patternsdir.c_str()); 133 NSLog(@"rulesdir = %s", rulesdir.c_str()); 134 NSLog(@"helpdir = %s", helpdir.c_str()); 135 NSLog(@"tempdir = %s", tempdir.c_str()); 136 NSLog(@"clipfile = %s", clipfile.c_str()); 137 NSLog(@"prefsfile = %s", prefsfile.c_str()); 138#endif 139} 140 141// ----------------------------------------------------------------------------- 142 143- (void)viewDidLoad 144{ 145 [super viewDidLoad]; 146 147 // now do additional setup after loading the view from the xib file 148 149 // init global pointers 150 globalController = self; 151 globalPatternView = pattView; 152 globalStatusView = statView; 153 globalStateView = stateView; 154 globalProgress = progressView; 155 globalTitle = progressTitle; 156 157 static bool firstload = true; 158 if (firstload) { 159 firstload = false; // only do the following once 160 SetMessage("This is Golly 1.2 for iOS. Copyright 2017 The Golly Gang."); 161 MAX_MAG = 5; // maximum cell size = 32x32 162 InitAlgorithms(); // must initialize algoinfo first 163 InitPaths(); // init userdir, etc (must be before GetPrefs) 164 GetPrefs(); // load user's preferences 165 SetMinimumStepExponent(); // for slowest speed 166 AddLayer(); // create initial layer (sets currlayer) 167 NewPattern(); // create new, empty universe 168 } 169 170 // ensure pattView is composited underneath toolbar, otherwise 171 // bottom toolbar can be obscured when device is rotated left/right 172 // (no longer needed) 173 // [[pattView layer] setZPosition:-1]; 174 175 // we draw background areas of pattView and statView so set to transparent (presumably a tad faster) 176 [pattView setBackgroundColor:nil]; 177 [statView setBackgroundColor:nil]; 178 179 progressView.hidden = YES; 180 181 genTimer = nil; 182 183 // init buttons 184 [self toggleStartStopButton]; 185 resetButton.enabled = NO; 186 undoButton.enabled = NO; 187 redoButton.enabled = NO; 188 actionButton.enabled = NO; 189 infoButton.enabled = NO; 190 restoreButton.hidden = YES; 191 192 // deselect step/scale controls (these have Momentary attribute set) 193 [stepControl setSelectedSegmentIndex:-1]; 194 [scaleControl setSelectedSegmentIndex:-1]; 195 196 // init touch mode 197 [modeControl setSelectedSegmentIndex:currlayer->touchmode]; 198 199 [self updateDrawingState]; 200} 201 202// ----------------------------------------------------------------------------- 203 204- (void)viewDidUnload 205{ 206 [super viewDidUnload]; 207 208 SavePrefs(); 209 210 // release all outlets 211 pattView = nil; 212 statView = nil; 213 startStopButton = nil; 214 restoreButton = nil; 215 resetButton = nil; 216 undoButton = nil; 217 redoButton = nil; 218 actionButton = nil; 219 infoButton = nil; 220 stepControl = nil; 221 scaleControl = nil; 222 modeControl = nil; 223 stateLabel = nil; 224 stateView = nil; 225 topBar = nil; 226 editBar = nil; 227 bottomBar = nil; 228 progressView = nil; 229 progressTitle = nil; 230 progressMessage = nil; 231 progressBar = nil; 232 cancelButton = nil; 233} 234 235// ----------------------------------------------------------------------------- 236 237- (void)viewWillDisappear:(BOOL)animated 238{ 239 [super viewWillDisappear:animated]; 240 241 SetMessage(""); 242 243 // stop generating, but only if PauseGenTimer() hasn't been called 244 if (pausecount == 0) [self stopIfGenerating]; 245} 246 247// ----------------------------------------------------------------------------- 248 249- (void)viewDidAppear:(BOOL)animated 250{ 251 [super viewDidAppear:animated]; 252 253 UpdateEverything(); // calls [pattView refreshPattern] 254 255 // restart genTimer if PauseGenTimer() was called earlier 256 if (pausecount > 0) RestartGenTimer(); 257} 258 259// ----------------------------------------------------------------------------- 260 261- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 262{ 263 // return YES for supported orientations 264 return YES; 265} 266 267// ----------------------------------------------------------------------------- 268 269- (void)updateDrawingState; 270{ 271 // reset drawing state if it's no longer valid (due to algo/rule change) 272 if (currlayer->drawingstate >= currlayer->algo->NumCellStates()) { 273 currlayer->drawingstate = 1; 274 } 275 276 [stateLabel setText:[NSString stringWithFormat:@"State=%d", currlayer->drawingstate]]; 277 [stateView setNeedsDisplay]; 278} 279 280// ----------------------------------------------------------------------------- 281 282- (void)updateButtons 283{ 284 resetButton.enabled = currlayer->algo->getGeneration() > currlayer->startgen; 285 undoButton.enabled = currlayer->undoredo->CanUndo(); 286 redoButton.enabled = currlayer->undoredo->CanRedo(); 287 actionButton.enabled = SelectionExists(); 288 infoButton.enabled = currlayer->currname != "untitled"; 289 [modeControl setSelectedSegmentIndex:currlayer->touchmode]; 290} 291 292// ----------------------------------------------------------------------------- 293 294- (void)toggleStartStopButton 295{ 296 if (generating) { 297 [startStopButton setTitle:@"STOP" 298 forState:UIControlStateNormal]; 299 [startStopButton setTitleColor:[UIColor redColor] 300 forState:UIControlStateNormal]; 301 } else { 302 [startStopButton setTitle:@"START" 303 forState:UIControlStateNormal]; 304 [startStopButton setTitleColor:[UIColor colorWithRed:0.2 green:0.7 blue:0.0 alpha:1.0] 305 forState:UIControlStateNormal]; 306 } 307} 308 309// ----------------------------------------------------------------------------- 310 311- (void)stopIfGenerating 312{ 313 if (generating) { 314 [self stopGenTimer]; 315 StopGenerating(); 316 // generating now false 317 [self toggleStartStopButton]; 318 } 319} 320 321// ----------------------------------------------------------------------------- 322 323- (IBAction)doNew:(id)sender 324{ 325 if (drawingcells) return; 326 327 // undo/redo history is about to be cleared so no point calling RememberGenFinish 328 // if we're generating a (possibly large) pattern 329 bool saveundo = allowundo; 330 allowundo = false; 331 [self stopIfGenerating]; 332 allowundo = saveundo; 333 334 if (event_checker > 0) { 335 // try again after a short delay that gives time for NextGeneration() to terminate 336 [self performSelector:@selector(doNew:) withObject:sender afterDelay:0.01]; 337 return; 338 } 339 340 ClearMessage(); 341 NewPattern(); 342 343 [modeControl setSelectedSegmentIndex:currlayer->touchmode]; 344 345 [self updateDrawingState]; 346 [self updateButtons]; 347 348 [statView setNeedsDisplay]; 349 [pattView refreshPattern]; 350} 351 352// ----------------------------------------------------------------------------- 353 354- (IBAction)doInfo:(id)sender 355{ 356 if (drawingcells) return; 357 358 // if generating then just pause the timer so doGeneration won't be called; 359 // best not to call PauseGenerating() because it calls StopGenerating() 360 // which might result in a lengthy file save for undo/redo 361 PauseGenTimer(); 362 363 ClearMessage(); 364 365 // display contents of current pattern file in a modal view 366 InfoViewController *modalInfoController = [[InfoViewController alloc] initWithNibName:nil bundle:nil]; 367 368 [modalInfoController setModalPresentationStyle:UIModalPresentationFullScreen]; 369 [self presentViewController:modalInfoController animated:YES completion:nil]; 370 371 modalInfoController = nil; 372 373 // RestartGenTimer() will be called in viewDidAppear 374} 375 376// ----------------------------------------------------------------------------- 377 378- (IBAction)doSave:(id)sender 379{ 380 if (drawingcells) return; 381 382 [self stopIfGenerating]; 383 384 if (event_checker > 0) { 385 // try again after a short delay 386 [self performSelector:@selector(doSave:) withObject:sender afterDelay:0.01]; 387 return; 388 } 389 390 ClearMessage(); 391 392 // let user save current pattern in a file via a modal view 393 SaveViewController *modalSaveController = [[SaveViewController alloc] initWithNibName:nil bundle:nil]; 394 395 [modalSaveController setModalPresentationStyle:UIModalPresentationFormSheet]; 396 [self presentViewController:modalSaveController animated:YES completion:nil]; 397 398 modalSaveController = nil; 399} 400 401// ----------------------------------------------------------------------------- 402 403- (IBAction)doUndo:(id)sender 404{ 405 if (drawingcells) return; 406 407 [self stopIfGenerating]; 408 409 if (event_checker > 0) { 410 // try again after a short delay 411 [self performSelector:@selector(doUndo:) withObject:sender afterDelay:0.01]; 412 return; 413 } 414 415 ClearMessage(); 416 currlayer->undoredo->UndoChange(); 417 UpdateEverything(); 418} 419 420// ----------------------------------------------------------------------------- 421 422- (IBAction)doRedo:(id)sender 423{ 424 if (drawingcells) return; 425 426 [self stopIfGenerating]; 427 428 if (event_checker > 0) { 429 // try again after a short delay 430 [self performSelector:@selector(doRedo:) withObject:sender afterDelay:0.01]; 431 return; 432 } 433 434 ClearMessage(); 435 currlayer->undoredo->RedoChange(); 436 UpdateEverything(); 437} 438 439// ----------------------------------------------------------------------------- 440 441- (IBAction)doReset:(id)sender 442{ 443 if (drawingcells) return; 444 445 [self stopIfGenerating]; 446 447 if (event_checker > 0) { 448 // try again after a short delay 449 [self performSelector:@selector(doReset:) withObject:sender afterDelay:0.01]; 450 return; 451 } 452 453 ClearMessage(); 454 ResetPattern(); 455 UpdateEverything(); 456} 457 458// ----------------------------------------------------------------------------- 459 460- (IBAction)doStartStop:(id)sender 461{ 462 if (drawingcells) return; 463 // this can happen on iPad if user taps Start/Stop button while another finger 464 // is currently drawing cells 465 466 ClearMessage(); 467 if (generating) { 468 [self stopGenTimer]; 469 StopGenerating(); 470 // generating is now false 471 [self toggleStartStopButton]; 472 // can't call [self updateButtons] here because if event_checker is > 0 473 // then StopGenerating hasn't called RememberGenFinish, and so CanUndo/CanRedo 474 // might not return correct results 475 bool canreset = currlayer->algo->getGeneration() > currlayer->startgen; 476 resetButton.enabled = canreset; 477 undoButton.enabled = allowundo && (canreset || currlayer->undoredo->CanUndo()); 478 redoButton.enabled = NO; 479 480 } else if (StartGenerating()) { 481 // generating is now true 482 [self toggleStartStopButton]; 483 [self startGenTimer]; 484 // don't call [self updateButtons] here because we want user to 485 // be able to stop generating by tapping Reset/Undo buttons 486 resetButton.enabled = YES; 487 undoButton.enabled = allowundo; 488 redoButton.enabled = NO; 489 } 490 pausecount = 0; // play safe 491} 492 493// ----------------------------------------------------------------------------- 494 495- (void)startGenTimer 496{ 497 float interval = 1.0f/60.0f; 498 499 // increase interval if user wants a delay 500 if (currlayer->currexpo < 0) { 501 interval = GetCurrentDelay() / 1000.0f; 502 } 503 504 // create a repeating timer 505 genTimer = [NSTimer scheduledTimerWithTimeInterval:interval 506 target:self 507 selector:@selector(doGeneration:) 508 userInfo:nil 509 repeats:YES]; 510} 511 512// ----------------------------------------------------------------------------- 513 514- (void)stopGenTimer 515{ 516 [genTimer invalidate]; 517 genTimer = nil; 518} 519 520// ----------------------------------------------------------------------------- 521 522// called after genTimer fires 523 524- (void)doGeneration:(NSTimer*)timer 525{ 526 if (event_checker > 0 || progresscount > 0) return; 527 528 // advance by current step size 529 NextGeneration(true); 530 531 [statView setNeedsDisplay]; 532 [pattView refreshPattern]; 533} 534 535// ----------------------------------------------------------------------------- 536 537- (IBAction)doNext:(id)sender 538{ 539 if (drawingcells) return; 540 541 [self stopIfGenerating]; 542 543 if (event_checker > 0) { 544 // try again after a short delay 545 [self performSelector:@selector(doNext:) withObject:sender afterDelay:0.01]; 546 return; 547 } 548 549 ClearMessage(); 550 NextGeneration(false); 551 552 [statView setNeedsDisplay]; 553 [pattView refreshPattern]; 554 [self updateButtons]; 555} 556 557// ----------------------------------------------------------------------------- 558 559- (IBAction)doStep:(id)sender 560{ 561 if (drawingcells) return; 562 563 [self stopIfGenerating]; 564 565 if (event_checker > 0) { 566 // try again after a short delay 567 [self performSelector:@selector(doStep:) withObject:sender afterDelay:0.01]; 568 return; 569 } 570 571 ClearMessage(); 572 NextGeneration(true); 573 574 [statView setNeedsDisplay]; 575 [pattView refreshPattern]; 576 [self updateButtons]; 577} 578 579// ----------------------------------------------------------------------------- 580 581- (IBAction)doFit:(id)sender 582{ 583 ClearMessage(); 584 FitInView(1); 585 586 [statView setNeedsDisplay]; 587 [pattView refreshPattern]; 588} 589 590// ----------------------------------------------------------------------------- 591 592- (IBAction)doChangeStep:(id)sender 593{ 594 ClearMessage(); 595 switch([sender selectedSegmentIndex]) 596 { 597 case 0: 598 { 599 // go slower by decrementing step exponent 600 if (currlayer->currexpo > minexpo) { 601 currlayer->currexpo--; 602 SetGenIncrement(); 603 if (generating && currlayer->currexpo < 0) { 604 // increase timer interval 605 [self stopGenTimer]; 606 [self startGenTimer]; 607 } 608 } else { 609 Beep(); 610 } 611 } break; 612 613 case 1: 614 { 615 // reset step exponent to 0 616 currlayer->currexpo = 0; 617 SetGenIncrement(); 618 if (generating) { 619 // reset timer interval to max speed 620 [self stopGenTimer]; 621 [self startGenTimer]; 622 } 623 } break; 624 625 case 2: 626 { 627 // go faster by incrementing step exponent 628 currlayer->currexpo++; 629 SetGenIncrement(); 630 if (generating && currlayer->currexpo <= 0) { 631 // reduce timer interval 632 [self stopGenTimer]; 633 [self startGenTimer]; 634 } 635 } break; 636 } 637 638 // only need to update status info 639 [statView setNeedsDisplay]; 640} 641 642// ----------------------------------------------------------------------------- 643 644- (IBAction)doChangeScale:(id)sender 645{ 646 ClearMessage(); 647 switch([sender selectedSegmentIndex]) 648 { 649 case 0: 650 { 651 // zoom out 652 currlayer->view->unzoom(); 653 [statView setNeedsDisplay]; 654 [pattView refreshPattern]; 655 } break; 656 657 case 1: 658 { 659 // set scale to 1:1 660 if (currlayer->view->getmag() != 0) { 661 currlayer->view->setmag(0); 662 [statView setNeedsDisplay]; 663 [pattView refreshPattern]; 664 } 665 } break; 666 667 case 2: 668 { 669 // zoom in 670 if (currlayer->view->getmag() < MAX_MAG) { 671 currlayer->view->zoom(); 672 [statView setNeedsDisplay]; 673 [pattView refreshPattern]; 674 } else { 675 Beep(); 676 } 677 } break; 678 } 679} 680 681// ----------------------------------------------------------------------------- 682 683- (IBAction)doChangeMode:(id)sender 684{ 685 ClearMessage(); 686 switch([sender selectedSegmentIndex]) 687 { 688 case 0: currlayer->touchmode = drawmode; break; 689 case 1: currlayer->touchmode = pickmode; break; 690 case 2: currlayer->touchmode = selectmode; break; 691 case 3: currlayer->touchmode = movemode; break; 692 } 693} 694 695// ----------------------------------------------------------------------------- 696 697- (IBAction)doMiddle:(id)sender 698{ 699 ClearMessage(); 700 if (currlayer->originx == bigint::zero && currlayer->originy == bigint::zero) { 701 currlayer->view->center(); 702 } else { 703 // put cell saved by ChangeOrigin (not yet implemented!!!) in middle 704 currlayer->view->setpositionmag(currlayer->originx, currlayer->originy, 705 currlayer->view->getmag()); 706 } 707 [statView setNeedsDisplay]; 708 [pattView refreshPattern]; 709} 710 711// ----------------------------------------------------------------------------- 712 713- (IBAction)doSelectAll:(id)sender 714{ 715 ClearMessage(); 716 SelectAll(); 717} 718 719// ----------------------------------------------------------------------------- 720 721- (IBAction)doAction:(id)sender 722{ 723 ClearMessage(); 724 [pattView doSelectionAction]; 725} 726 727// ----------------------------------------------------------------------------- 728 729- (IBAction)doPaste:(id)sender 730{ 731 if (drawingcells) return; 732 733 [self stopIfGenerating]; 734 735 if (event_checker > 0) { 736 // try again after a short delay 737 [self performSelector:@selector(doPaste:) withObject:sender afterDelay:0.01]; 738 return; 739 } 740 741 ClearMessage(); 742 if (waitingforpaste) { 743 [pattView doPasteAction]; 744 } else { 745 PasteClipboard(); 746 [pattView refreshPattern]; 747 } 748} 749 750// ----------------------------------------------------------------------------- 751 752- (IBAction)doRule:(id)sender 753{ 754 if (drawingcells) return; 755 756 [self stopIfGenerating]; 757 758 if (event_checker > 0) { 759 // try again after a short delay 760 [self performSelector:@selector(doRule:) withObject:sender afterDelay:0.01]; 761 return; 762 } 763 764 ClearMessage(); 765 766 // let user change current algo/rule in a modal view 767 RuleViewController *modalRuleController = [[RuleViewController alloc] initWithNibName:nil bundle:nil]; 768 769 [modalRuleController setModalPresentationStyle:UIModalPresentationFullScreen]; 770 [self presentViewController:modalRuleController animated:YES completion:nil]; 771 772 modalRuleController = nil; 773} 774 775// ----------------------------------------------------------------------------- 776 777- (IBAction)toggleFullScreen:(id)sender 778{ 779 if (fullscreen) { 780 ShowTabBar(YES); 781 topBar.hidden = NO; 782 editBar.hidden = NO; 783 bottomBar.hidden = NO; 784 statView.hidden = NO; 785 // recalculate pattView's frame from scratch (in case device was rotated) 786 CGFloat barht = topBar.bounds.size.height; 787 CGFloat topht = barht * 2 + statView.bounds.size.height; 788 CGFloat x = pattView.superview.frame.origin.x; 789 CGFloat y = pattView.superview.frame.origin.y + topht; 790 CGFloat wd = pattView.superview.frame.size.width; 791 CGFloat ht = pattView.superview.frame.size.height - (topht + barht + TabBarHeight()); 792 pattView.frame = CGRectMake(x, y, wd, ht); 793 [statView setNeedsDisplay]; 794 } else { 795 ShowTabBar(NO); 796 topBar.hidden = YES; 797 editBar.hidden = YES; 798 bottomBar.hidden = YES; 799 statView.hidden = YES; 800 pattView.frame = pattView.superview.frame; 801 } 802 803 fullscreen = !fullscreen; 804 restoreButton.hidden = !fullscreen; 805 [pattView refreshPattern]; 806 UpdateEditBar(); 807} 808 809// ----------------------------------------------------------------------------- 810 811- (IBAction)doCancel:(id)sender 812{ 813 cancelProgress = true; 814} 815 816// ----------------------------------------------------------------------------- 817 818- (void)enableInteraction:(BOOL)enable 819{ 820 topBar.userInteractionEnabled = enable; 821 editBar.userInteractionEnabled = enable; 822 bottomBar.userInteractionEnabled = enable; 823 pattView.userInteractionEnabled = enable; 824 EnableTabBar(enable); 825} 826 827// ----------------------------------------------------------------------------- 828 829- (void)updateProgressBar:(float)fraction 830{ 831 progressBar.progress = fraction; 832 833 // show estimate of time remaining 834 double elapsecs = TimeInSeconds() - progstart; 835 double remsecs = fraction > 0.0 ? (elapsecs / fraction) - elapsecs : 999.0; 836 [progressMessage setText:[NSString stringWithFormat:@"Estimated time remaining (in secs): %d", int(remsecs+0.5)]]; 837 838 // need following to see progress view and bar update 839 event_checker++; 840 [[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]]; 841 event_checker--; 842} 843 844@end 845 846// ============================================================================= 847 848// global routines used by external code: 849 850void UpdatePattern() 851{ 852 [globalPatternView refreshPattern]; 853} 854 855// ----------------------------------------------------------------------------- 856 857void UpdateStatus() 858{ 859 if (inscript || currlayer->undoredo->doingscriptchanges) return; 860 if (!fullscreen) { 861 [globalStatusView setNeedsDisplay]; 862 } 863} 864 865// ----------------------------------------------------------------------------- 866 867void UpdateEditBar() 868{ 869 if (!fullscreen) { 870 [globalController updateButtons]; 871 [globalController updateDrawingState]; 872 } 873} 874 875// ----------------------------------------------------------------------------- 876 877void CloseStatePicker() 878{ 879 [globalStateView dismissStatePopover]; 880} 881 882// ----------------------------------------------------------------------------- 883 884void PauseGenTimer() 885{ 886 if (generating) { 887 // use pausecount to handle nested calls 888 if (pausecount == 0) [globalController stopGenTimer]; 889 pausecount++; 890 } 891} 892 893// ----------------------------------------------------------------------------- 894 895void RestartGenTimer() 896{ 897 if (pausecount > 0) { 898 pausecount--; 899 if (pausecount == 0) [globalController startGenTimer]; 900 } 901} 902 903// ----------------------------------------------------------------------------- 904 905static bool wasgenerating = false; // generating needs to be resumed? 906 907void PauseGenerating() 908{ 909 if (generating) { 910 // stop generating without changing STOP button 911 [globalController stopGenTimer]; 912 StopGenerating(); 913 // generating now false 914 wasgenerating = true; 915 } 916} 917 918// ----------------------------------------------------------------------------- 919 920void ResumeGenerating() 921{ 922 if (wasgenerating) { 923 // generating should be false but play safe 924 if (!generating) { 925 if (StartGenerating()) { 926 // generating now true 927 [globalController startGenTimer]; 928 } else { 929 // this can happen if pattern is empty 930 [globalController toggleStartStopButton]; 931 [globalController updateButtons]; 932 } 933 } 934 wasgenerating = false; 935 } 936} 937 938// ----------------------------------------------------------------------------- 939 940void StopIfGenerating() 941{ 942 [globalController stopIfGenerating]; 943} 944 945// ----------------------------------------------------------------------------- 946 947void BeginProgress(const char* title) 948{ 949 if (progresscount == 0) { 950 // disable interaction with all views but don't show progress view just yet 951 [globalController enableInteraction:NO]; 952 [globalTitle setText:[NSString stringWithCString:title encoding:NSUTF8StringEncoding]]; 953 cancelProgress = false; 954 progstart = TimeInSeconds(); 955 } 956 progresscount++; // handles nested calls 957} 958 959// ----------------------------------------------------------------------------- 960 961bool AbortProgress(double fraction_done, const char* message) 962{ 963 if (progresscount <= 0) Fatal("Bug detected in AbortProgress!"); 964 double secs = TimeInSeconds() - progstart; 965 if (!globalProgress.hidden) { 966 if (secs < prognext) return false; 967 prognext = secs + 0.1; // update progress bar about 10 times per sec 968 if (fraction_done < 0.0) { 969 // show indeterminate progress gauge??? 970 } else { 971 [globalController updateProgressBar:fraction_done]; 972 } 973 return cancelProgress; 974 } else { 975 // note that fraction_done is not always an accurate estimator for how long 976 // the task will take, especially when we use nextcell for cut/copy 977 if ( (secs > 1.0 && fraction_done < 0.3) || secs > 2.0 ) { 978 // task is probably going to take a while so show progress view 979 globalProgress.hidden = NO; 980 [globalController updateProgressBar:fraction_done]; 981 } 982 prognext = secs + 0.01; // short delay until 1st progress update 983 } 984 return false; 985} 986 987// ----------------------------------------------------------------------------- 988 989void EndProgress() 990{ 991 if (progresscount <= 0) Fatal("Bug detected in EndProgress!"); 992 progresscount--; 993 if (progresscount == 0) { 994 // hide the progress view and enable interaction with other views 995 globalProgress.hidden = YES; 996 [globalController enableInteraction:YES]; 997 } 998} 999