1// 2// PXDocument.m 3// Pixen 4// 5// Created by Joe Osborn on Thu Sep 11 2003. 6// Copyright (c) 2003 Open Sword Group. All rights reserved. 7// 8 9#import "PXDocument.h" 10#import "PXCanvasController.h" 11#import "PXCanvas.h" 12#import "PXPSDHandler.h" 13#import "PXPalette.h" 14#import "PXCanvasView.h" 15#import "PXCanvasPrintView.h" 16#ifdef __COCOA__ 17#import "gif_lib.h" 18#endif 19#import "PXGifExporter.h" 20#import "PXLayer.h" 21#import "PXImage.h" 22#import "PXPixel.h" 23#ifdef __COCOA__ 24#import <AppKit/NSAlert.h> 25#endif 26 27#ifndef __COCOA__ 28#include "math.h" 29#endif 30 31 32NSString * PXDocumentOpened = @"PXDocumentOpenedNotificationName"; 33NSString * PXDocumentClosed = @"PXDocumentClosedNotificationName"; 34 35 36@interface NSData(GOLAdditions) 37 38- (NSArray *)getLines; 39 40@end 41 42@implementation NSData(GOLAdditions) 43 44- (NSArray *)getLines 45{ 46 NSRange charRange, lineRange = NSMakeRange(0, 0); 47 char character; 48 char line[4096]; 49 NSMutableArray *lines = [NSMutableArray array]; 50 for (charRange = NSMakeRange(0, 1); charRange.location<[self length]; charRange.location++) { 51 [self getBytes:&character range:charRange]; 52 if (character == '\n') { 53 lineRange.length = charRange.location - lineRange.location; 54 if (lineRange.length >= 4096) { 55 NSLog(@"-[NSData getLines]: Line longer than 4K, truncating..."); 56 lineRange.length = 4095; 57 } 58 [self getBytes:&line range:lineRange]; 59 line[lineRange.length] = '\0'; 60 lineRange.location += lineRange.length + 1; 61 [lines addObject:[NSString stringWithUTF8String:line]]; 62 } 63 } 64 return [[lines copy] autorelease]; 65} 66 67@end 68 69@implementation PXDocument 70 71- (BOOL)rescheduleAutosave 72{ 73 NSTimeInterval repeatTime = [[NSUserDefaults standardUserDefaults] floatForKey:@"PXAutosaveInterval"]; 74 if (repeatTime == 0.0f) { 75 [[NSUserDefaults standardUserDefaults] setFloat:180.0 forKey:@"PXAutosaveInterval"]; 76 repeatTime = 180.0f; 77 } 78 if (repeatTime <= 0 || ![[NSUserDefaults standardUserDefaults] boolForKey:@"PXAutosaveEnabled"]) { 79 return NO; 80 } 81 82 [[self retain] autorelease]; 83 [autosaveTimer invalidate]; 84 [autosaveTimer release]; 85 autosaveTimer = [[NSTimer scheduledTimerWithTimeInterval:repeatTime target:self selector:@selector(autosave:) userInfo:nil repeats:NO] retain]; 86 return YES; 87} 88 89- (id)init 90{ 91 [super init]; 92 canSave = YES; 93 canvas = [[PXCanvas alloc] init]; 94 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanUpAutosaveFile:) name:NSApplicationWillTerminateNotification object:[NSApplication sharedApplication]]; 95 [self autosave:nil]; 96 return self; 97} 98 99- (void)updateChangeCount:(NSDocumentChangeType)changeType 100{ 101 if (!canSave) { 102 [super updateChangeCount:NSChangeCleared]; 103 } else { 104 [super updateChangeCount:changeType]; 105 } 106} 107 108- (void)setCanSave:(BOOL)saveable 109{ 110 canSave = saveable; 111 if (!canSave) { 112 [self updateChangeCount:NSChangeCleared]; 113 } 114} 115 116- (void)canCloseDocumentWithDelegate:(id)delegate shouldCloseSelector:(SEL)shouldCloseSelector contextInfo:(void *)contextInfo 117{ 118 if (!canSave) { 119 NSInvocation *closedInvocation; 120 BOOL shouldClose = YES; 121 closedInvocation = [NSInvocation invocationWithMethodSignature:[delegate methodSignatureForSelector:shouldCloseSelector]]; 122 [closedInvocation setSelector:shouldCloseSelector]; 123 [closedInvocation setArgument:&self atIndex:2]; 124 [closedInvocation setArgument:&shouldClose atIndex:3]; 125 [closedInvocation setArgument:&contextInfo atIndex:4]; 126 [closedInvocation invokeWithTarget:delegate]; 127 } else { 128 [super canCloseDocumentWithDelegate:delegate shouldCloseSelector:shouldCloseSelector contextInfo:contextInfo]; 129 } 130} 131 132- (IBAction)saveDocument:(id)sender 133{ 134 if (!canSave) { 135 [self close]; 136 } else { 137 [super saveDocument:sender]; 138 } 139} 140 141- (IBAction)saveDocumentAs:(id)sender 142{ 143 if (!canSave) { 144#ifdef __COCOA__ 145 NSBeep(); 146#endif 147 } else { 148 [super saveDocumentAs:sender]; 149 } 150} 151 152- (IBAction)saveDocumentTo:(id)sender 153{ 154 if (!canSave) { 155#ifdef __COCOA__ 156 NSBeep(); 157#endif 158 } else { 159 [super saveDocumentTo:sender]; 160 } 161} 162 163- (NSString *)displayName 164{ 165 if (!canSave) { 166 return @""; 167 } 168 return [super displayName]; 169} 170 171- (void)changeDirtyFileFromFilename:(NSString *)from toFilename:(NSString *)to 172{ 173 NSMutableArray *dirtyFiles = [[[[NSUserDefaults standardUserDefaults] objectForKey:@"PXDirtyFiles"] mutableCopy] autorelease]; 174 175 if (dirtyFiles == nil) { 176 dirtyFiles = [NSMutableArray arrayWithCapacity:8]; 177 } 178 179 if ((from != nil) && !NSEqualSizes([canvas size], NSZeroSize)) { 180 if (![[NSFileManager defaultManager] removeFileAtPath:from handler:nil]) { 181 NSLog(@"Could not delete backup file \"%@\"", from); 182 } 183 [dirtyFiles removeObject:from]; 184 } 185 186 if (to != nil) { 187 [dirtyFiles addObject:to]; 188 } 189 190 [[NSUserDefaults standardUserDefaults] setObject:dirtyFiles forKey:@"PXDirtyFiles"]; 191 [[NSUserDefaults standardUserDefaults] synchronize]; 192} 193 194- (void)cleanUpAutosaveFile:(NSNotification *)aNotification 195{ 196 [self changeDirtyFileFromFilename:autosaveFilename toFilename:nil]; // this will remove our dirty autosave file from the user defaults, so it doesn't complain on the next start of Pixen and freak out the user 197 autosaveFilename = nil; 198} 199 200 201- (void)updateAutosaveFilename 202{ 203 NSString *oldFilename = autosaveFilename; 204 205 if ([self fileName] != nil) { 206 autosaveFilename = [[[[[self fileName] stringByDeletingPathExtension] stringByAppendingString:@"~"] stringByAppendingPathExtension:@"pxi"] retain]; 207 } else { 208 autosaveFilename = [@"/tmp/PixenAutosave.pxi" retain]; 209 } 210 if (![oldFilename isEqualToString:autosaveFilename]) { 211 [self changeDirtyFileFromFilename:oldFilename toFilename:autosaveFilename]; 212 } 213 214 [oldFilename release]; 215} 216 217- (void)setFileName:(NSString *)path 218{ 219 [super setFileName:path]; 220 [self autosave:nil]; 221} 222 223- (void)autosave:(NSTimer *)timer 224{ 225 if (![self rescheduleAutosave]) { 226 return; 227 } 228 [self updateAutosaveFilename]; 229 if (canvas && !NSEqualSizes([canvas size], NSZeroSize)) { 230 [[self dataRepresentationOfType:@"Pixen Image"] writeToFile:autosaveFilename atomically:YES]; 231 } 232} 233 234- (void)dealloc 235{ 236 [autosaveFilename release]; 237 [[NSNotificationCenter defaultCenter] removeObserver:self]; 238 [autosaveTimer invalidate]; 239 [autosaveTimer release]; 240 [[self windowControllers] makeObjectsPerformSelector:@selector(close)]; 241 [canvasController release]; 242 [canvas release]; 243 [super dealloc]; 244} 245 246- (void)makeWindowControllers 247{ 248 // Override returning the nib file name of the document 249 // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead. 250 canvasController = [[PXCanvasController alloc] init]; 251 [canvasController setCanvas:canvas]; 252 [self addWindowController:canvasController]; 253 [canvasController window]; 254 [[NSNotificationCenter defaultCenter] postNotificationName:PXDocumentOpened object:self]; 255} 256 257- (void)windowControllerDidLoadNib:aController 258{ 259 [super windowControllerDidLoadNib:aController]; 260 // Add any code here that needs to be executed once the windowController has loaded the document's window. 261} 262 263- (void)close 264{ 265 [[NSNotificationCenter defaultCenter] postNotificationName:PXDocumentClosed object:self]; 266 [autosaveTimer invalidate]; 267 [self cleanUpAutosaveFile:nil]; 268 [super close]; 269} 270 271BOOL isPowerOfTwo(int num) 272{ 273 double logResult = log2(num); 274 return (logResult == (int)logResult); 275} 276 277- (NSData *)dataRepresentationOfType:(NSString *)aType 278{ 279 if (!canSave) { 280 return nil; 281 } 282 283 if([aType isEqualToString:@"Pixen Image"]) 284 { 285 return [NSKeyedArchiver archivedDataWithRootObject:canvas]; 286 } 287 if([aType isEqualToString:@"Portable Network Graphic (PNG)"]) 288 { 289 return [canvas imageDataWithType:NSPNGFileType properties:nil]; 290 } 291 if([aType isEqualToString:@"Tagged Image File Format (TIFF)"]) 292 { 293 return [canvas imageDataWithType:NSTIFFFileType properties:nil]; 294 } 295 296#ifdef __COCOA__ 297 if([aType isEqualToString:@"Compuserve Graphic (GIF)"]) 298 { 299 id image = [[NSImage alloc] initWithSize:[canvas size]]; 300 [image lockFocus]; 301 [canvas drawRect:NSMakeRect(0,0,[canvas size].width,[canvas size].height) fixBug:YES]; 302 [image unlockFocus]; 303 return [PXGifExporter gifDataForImage:image]; 304 } 305 if([aType isEqualToString:@"Windows Bitmap (BMP)"]) 306 { 307 return [canvas imageDataWithType:NSBMPFileType properties:nil]; 308 } 309 if([aType isEqualToString:@"Apple PICT Graphic"]) 310 { 311 return [canvas PICTData]; 312 } 313 if([aType isEqualToString:@"Encapsulated PostScript (EPS)"]) 314 { 315 return [[(PXCanvasController *)canvasController view] dataWithEPSInsideRect:[[(PXCanvasController *)canvasController view] frame]]; 316 } 317 318 if([aType isEqualToString:@"Game of Life File (LIF)"]) 319 { 320 NSMutableString *fileString = [NSMutableString stringWithString:@"#Life 1.05\r\n#D Generated by "]; 321 [fileString appendString:[[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:@"CFBundleName"]]; 322 [fileString appendString:@" "]; 323 [fileString appendString:[[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:@"CFBundleShortVersionString"]]; 324 [fileString appendString:@"\r\n#N\r\n#P 0 0\r\n"]; 325 NSPoint point; 326 int spaces=0, i; 327 BOOL lineFilled; 328 for (point.y=0; point.y<[canvas size].height; point.y++) { 329 lineFilled = NO; 330 for (point.x=0; point.x<[canvas size].width; point.x++) { 331 if ([[canvas colorAtPoint:point] alphaComponent] > .5) { 332 for (i=0; i<spaces; i++) { 333 [fileString appendString:@"."]; 334 } 335 spaces = 0; 336 [fileString appendString:@"*"]; 337 lineFilled = YES; 338 } else { 339 spaces++; 340 } 341 } 342 spaces = 0; 343 if (!lineFilled) { 344 [fileString appendString:@"."]; 345 } 346 [fileString appendString:@"\r\n"]; 347 } 348 349 return [fileString dataUsingEncoding:NSUTF8StringEncoding]; 350 } 351#else 352#warning implement that without Quicktime !! 353#endif 354 355 return nil; 356} 357 358- (BOOL)checkSize:(NSSize)size 359{ 360 if (size.width * size.height <= 256 * 256) { 361 return YES; 362 } 363#ifdef __COCOA__ 364 return [[NSAlert alertWithMessageText:@"Large Image Warning" defaultButton:@"Yes" alternateButton:@"No" otherButton:nil informativeTextWithFormat:@"This image is %d by %d pixels in size, which is large enough that manipulation might be noticably slow. Pixen is designed for images under 256 by 256 pixels. Would you still like to open this image?", (int)size.width, (int)size.height] runModal] == NSOKButton; 365#else 366#warning GNUstep TODO 367 return YES; 368#endif 369 370} 371 372 373- (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)aType 374{ 375 // Insert code here to read your document from the given data. You can also choose to override -loadFileWrapperRepresentation:ofType: or -readFromFile:ofType: instead. 376 if([aType isEqualToString:@"Pixen Image"]) 377 { 378 PXCanvas *tempCanvas = [NSKeyedUnarchiver unarchiveObjectWithData:data]; 379 if (![self checkSize:[tempCanvas size]]) { 380 return NO; 381 } 382 [canvas release]; 383 canvas = [tempCanvas retain]; 384 } 385 /* else if([aType isEqualToString:@"Photoshop Graphic (PSD)"]) 386 { 387 canvas = [[PXCanvas alloc] initWithPSDData:data]; 388 } */ 389 else if([aType isEqualToString:@"Game of Life File (LIF)"]) { 390#ifdef __COCOA__ 391 NSMutableSet *points = [NSMutableSet set]; 392#else 393 NSMutableSet *points = [[NSMutableSet alloc] init]; 394#warning GNUstep strange error during compilation 395#endif 396 NSRect rect = NSZeroRect; 397 NSArray *lines = [data getLines]; 398 NSPoint startingPoint; 399 NSPoint offset; 400 NSEnumerator *lineEnumerator = [lines objectEnumerator]; 401 NSString *line; 402 BOOL firstLine = YES; 403 while ( ( line = [lineEnumerator nextObject] ) ) { 404 if (firstLine) { 405 if (![line isEqualToString:@"#Life 1.05\r"]) { 406#ifdef __COCOA__ 407 [[NSAlert alertWithMessageText:@"Invalid file!" defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@"Only life v1.05 files are supported. This is a v%@ life file.", [line substringFromIndex:6]] runModal]; 408#else 409#warning GNUstep : TODO 410#endif 411 return NO; 412 } 413 firstLine = NO; 414 continue; 415 } 416 417 if ([line length] <= 0) { 418 continue; 419 } 420 421 NSScanner *lineScanner = [NSScanner scannerWithString:line]; 422 423 if ([lineScanner scanString:@"#" intoString:NULL]) { 424 if ([lineScanner scanString:@"P" intoString:NULL]) { 425 if (![lineScanner scanFloat:&startingPoint.x]) { 426 return NO; 427 } 428 if (![lineScanner scanFloat:&startingPoint.y]) { 429 return NO; 430 } 431 offset.y = startingPoint.y; 432 } 433 continue; 434 } 435 436 int i=0; 437 offset.x=startingPoint.x; 438 for (i=0; i<[line length]-1; i++) { 439 if ([line characterAtIndex:i]=='*') { 440 [points addObject:[NSValue valueWithPoint:offset]]; 441 rect = NSUnionRect(rect, NSMakeRect(offset.x, offset.y, 1, 1)); 442 } 443 offset.x++; 444 } 445 offset.y++; 446 } 447 [canvas setSize:rect.size]; 448 NSEnumerator *pointEnumerator = [points objectEnumerator]; 449 NSValue *point; 450 NSPoint pointValue; 451 while ( (point = [pointEnumerator nextObject]) ) { 452 pointValue = [point pointValue]; 453 pointValue.x -= rect.origin.x; 454 pointValue.y -= rect.origin.y; 455 [[[canvas activeLayer] image] setPixel:[PXPixel withColor:[NSColor blackColor]] atPoint:pointValue]; // so the notification doesn't get sent 456 } 457 } 458 else 459 { 460 NSImage *image = [[[NSImage alloc] initWithData:data] autorelease]; 461 if (![self checkSize:[image size]]) { 462 return NO; 463 } 464 [canvas release]; 465 canvas = [[PXCanvas alloc] initWithImage:image]; 466 } 467 if(canvas) 468 { 469 [canvasController setCanvas:canvas]; 470 return YES; 471 } 472 return NO; 473} 474 475- (void)setLayers:layers fromLayers:oldLayers 476{ 477 [canvas setLayers:layers fromLayers:oldLayers]; 478 //[[[self undoManager] prepareWithInvocationTarget:self] setLayers:oldLayers fromLayers:layers]; 479 //[canvas setLayers:layers]; 480 //[canvas setSize:[[layers objectAtIndex:0] size]]; 481} 482 483- (IBAction)cut:sender 484{ 485 [[self undoManager] beginUndoGrouping]; 486 [self setLayers:[[canvas layers] deepMutableCopy] fromLayers:[canvas layers]]; 487 [self copy:sender]; 488 [self delete:sender]; 489 [[self undoManager] setActionName:@"Cut"]; 490 [[self undoManager] endUndoGrouping]; 491 [canvas changedInRect:NSMakeRect(0, 0, [canvas size].width, [canvas size].height)]; 492} 493 494- (IBAction)copy:sender 495{ 496 id board = [NSPasteboard generalPasteboard]; 497 [board declareTypes:[NSArray arrayWithObject:@"PXLayer"] owner:self]; 498 if(![[board types] containsObject:@"PXLayer"]) 499 { 500 [board addTypes:[NSArray arrayWithObject:@"PXLayer"] owner:self]; 501 } 502 [board setData:[canvas selectionData] forType:@"PXLayer"]; 503} 504 505- (IBAction)paste:sender 506{ 507 [[self undoManager] beginUndoGrouping]; 508 [[self undoManager] setActionName:@"Paste"]; 509 [self setLayers:[[canvas layers] deepMutableCopy] fromLayers:[canvas layers]]; 510 [[[self undoManager] prepareWithInvocationTarget:canvasController] canvasSizeDidChange:nil]; 511 [[self undoManager] endUndoGrouping]; 512 id board = [NSPasteboard generalPasteboard]; 513 if([[board types] containsObject:@"PXLayer"]) 514 { 515 [canvas pasteFromPasteboard:board type:@"PXLayer"]; 516 } 517 id enumerator = [[NSImage imagePasteboardTypes] objectEnumerator], current; 518 while (( current = [enumerator nextObject] ) ) 519 { 520 if ([[board types] containsObject:current]) 521 { 522 [canvas pasteFromPasteboard:board type:@"NSImage"]; 523 } 524 } 525 [canvas changedInRect:NSMakeRect(0, 0, [canvas size].width, [canvas size].height)]; 526} 527 528- (IBAction)delete:sender 529{ 530 if (![canvas hasSelection]) { return; } 531 [[self undoManager] beginUndoGrouping]; 532 [[self undoManager] setActionName:@"Delete"]; 533 [self setLayers:[[canvas layers] deepMutableCopy] fromLayers:[canvas layers]]; 534 [[self undoManager] endUndoGrouping]; 535 [canvas deleteSelection]; 536 [canvas changedInRect:NSMakeRect(0, 0, [canvas size].width, [canvas size].height)]; 537} 538 539- (IBAction)selectAll:sender 540{ 541 [canvas selectAll]; 542 [canvas changedInRect:NSMakeRect(0, 0, [canvas size].width, [canvas size].height)]; 543} 544 545- (IBAction)selectNone:sender 546{ 547 [[self undoManager] beginUndoGrouping]; 548 [[self undoManager] setActionName:@"Deselect"]; 549 [self setLayers:[[canvas layers] deepMutableCopy] fromLayers:[canvas layers]]; 550 [[self undoManager] endUndoGrouping]; 551 [canvas deselect]; 552 [canvas changedInRect:NSMakeRect(0, 0, [canvas size].width, [canvas size].height)]; 553} 554 555- (void)printShowingPrintPanel:(BOOL)showPanels 556{ 557 if(printableView == nil) { printableView = [[PXCanvasPrintView viewForCanvas:[self canvas]] retain]; } 558 559 float scale = [[[[self printInfo] dictionary] objectForKey:NSPrintScalingFactor] floatValue]; 560 id transform = [NSAffineTransform transform]; 561 [transform scaleXBy:scale yBy:scale]; 562 [printableView setBoundsOrigin:[transform transformPoint:[printableView frame].origin]]; 563 [printableView setBoundsSize:[transform transformSize:[printableView frame].size]]; 564 565 NSPrintOperation *op = [NSPrintOperation printOperationWithView:printableView printInfo:[self printInfo]]; 566 [op setShowPanels:showPanels]; 567 568#ifdef __COCOA__ 569 [self runModalPrintOperation:op delegate:nil didRunSelector:NULL contextInfo:NULL]; 570#else 571#warning GNUstep TODO 572#endif 573} 574 575- canvas 576{ 577 return canvas; 578} 579 580@end 581