Bit different approach to load & save


#1

Hello,

I’m trying to complete the challenge for this chapter, but I can’t get the load and save working. I’ve chosen to store all NSBezierPaths of all ovals in NSArray in the PictureView class, so I have… kind of… vector editor, instead of bitmap one, actually. My problem is, I can’t figure out how to pass the NSArray from PictureView class to MyDocument to store it. May someone tell me what am I missing, please? Here is the code:

MyDocument.h

[code]#import <Cocoa/Cocoa.h>

@interface MyDocument: NSDocument {
IBOutlet NSView *pictureView;
}
@end[/code]

MyDocument.m

[code]#import “MyDocument.h”

@implementation MyDocument

  • (id) init {
    self = [super init];

    if (self) {
    }

    return (self);
    }

  • (NSString *) windowNibName {
    return (@“MyDocument”);
    }

  • (void) windowControllerDidLoadNib: (NSWindowController *) aController {
    [super windowControllerDidLoadNib: aController];
    }

  • (NSData *) dataOfType: (NSString *) typeName error: (NSError **) outError {
    NSBitmapImageRep *bitmap;
    NSData *data;

    // Ignore this part, it just… stores the PictureView as TIFF for the moment. Not optimal.
    [pictureView lockFocus];
    bitmap = [pictureView bitmapImageRepForCachingDisplayInRect: [pictureView bounds]];
    [pictureView cacheDisplayInRect: [pictureView bounds] toBitmapImageRep: bitmap];
    data = [bitmap TIFFRepresentation];
    [pictureView unlockFocus];

    return (data);
    }

  • (BOOL) readFromData: (NSData *) data ofType: (NSString *) typeName error: (NSError **) outError {
    return (NO);
    }
    @end[/code]

PictureView.h

[code]#import <Cocoa/Cocoa.h>

@interface PictureView: NSView {
BOOL eventDone;
NSBezierPath *path;
NSMutableArray *operations;
NSPoint downPoint, currentPoint;
}

  • (NSRect) currentRect;
    @end
    [/code]

PictureView.m

[code]#import “PictureView.h”

@implementation PictureView

  • (id) initWithFrame: (NSRect) frame {
    self = [super initWithFrame: frame];

    if (self) {
    operations = [[NSMutableArray alloc] init];
    path = [[NSBezierPath alloc] init];
    }

    return (self);
    }

  • (void) dealloc {
    [path release];
    [super dealloc];
    }

  • (void) drawRect: (NSRect) dirtyRect {
    NSRect bounds = [self bounds];

    [[NSColor whiteColor] set];
    [NSBezierPath fillRect: bounds];

    [[NSColor blackColor] set];

    if ((!eventDone) && ([operations count] > 0)) {
    [operations removeLastObject];
    }

    path = [NSBezierPath bezierPathWithOvalInRect: [self currentRect]];
    [operations addObject: path];

    for (NSBezierPath *operation in operations) {
    [operation setLineWidth: 2.0];
    [operation stroke];
    }
    }

#pragma mark Events

  • (void) mouseDown: (NSEvent *) theEvent {
    NSPoint p = [theEvent locationInWindow];

    downPoint = [self convertPoint: p fromView: nil];
    currentPoint = downPoint;
    eventDone = NO;

    [self setNeedsDisplay: YES];
    }

  • (void) mouseDragged: (NSEvent *) theEvent {
    NSPoint p = [theEvent locationInWindow];

    currentPoint = [self convertPoint: p fromView: nil];

    [self autoscroll: theEvent];
    [self setNeedsDisplay: YES];
    }

  • (void) mouseUp: (NSEvent *) theEvent {
    NSPoint p = [theEvent locationInWindow];

    currentPoint = [self convertPoint: p fromView: nil];
    eventDone = YES;

    [self setNeedsDisplay: YES];
    }

  • (NSRect) currentRect {
    float minX = MIN (downPoint.x, currentPoint.x);
    float maxX = MAX (downPoint.x, currentPoint.x);
    float minY = MIN (downPoint.y, currentPoint.y);
    float maxY = MAX (downPoint.y, currentPoint.y);

    return (NSMakeRect (minX, minY, maxX - minX, maxY - minY));
    }
    @end[/code]


#2

Right, that was a silly thing to miss. This version now works, although you might consider it a spoiler. Continue on your own risk, then!

MyDocument.h

[code]#import <Cocoa/Cocoa.h>

@interface MyDocument: NSDocument {
NSMutableArray *ovals;

IBOutlet NSView *pictureView;

}

@property (retain) NSMutableArray *ovals;
@end
[/code]

MyDocument.m

[code]#import “MyDocument.h”

@implementation MyDocument
@synthesize ovals;

  • (id) init {
    self = [super init];

    if (self) {
    ovals = [[NSMutableArray alloc] init];
    }

    return (self);
    }

  • (NSString *) windowNibName {
    return (@“MyDocument”);
    }

  • (void) windowControllerDidLoadNib: (NSWindowController *) aController {
    [super windowControllerDidLoadNib: aController];
    }

  • (NSData *) dataOfType: (NSString *) typeName error: (NSError **) outError {
    return ([NSKeyedArchiver archivedDataWithRootObject: ovals]);
    }

  • (BOOL) readFromData: (NSData *) data ofType: (NSString *) typeName error: (NSError **) outError {
    NSMutableArray *newArray = nil;

    @try {
    newArray = [NSKeyedUnarchiver unarchiveObjectWithData: data];
    }
    @catch (NSException *exception) {
    if (outError) {
    NSDictionary *d = [NSDictionary dictionaryWithObject: @"The data is corrupted."
    forKey: NSLocalizedFailureReasonErrorKey];

      	*outError = [NSError errorWithDomain: NSOSStatusErrorDomain
      									code: unimpErr
      								userInfo: d];
      }
      
      return (NO);
    

    }

    [self setOvals: newArray];

    return (YES);
    }
    @end
    [/code]

PictureView.h

[code]#import <Cocoa/Cocoa.h>
@class MyDocument;

@interface PictureView: NSView {
BOOL eventDone;
NSBezierPath *path;
NSPoint downPoint, currentPoint;

IBOutlet MyDocument *document;

}

  • (NSRect) currentRect;
    @end
    [/code]

PictureView.m

[code]#import “PictureView.h”
#import “MyDocument.h”

@implementation PictureView

  • (id) initWithFrame: (NSRect) frame {
    self = [super initWithFrame: frame];

    if (self) {
    eventDone = NO;
    }

    return (self);
    }

  • (void) dealloc {
    [super dealloc];
    }

  • (void) drawRect: (NSRect) dirtyRect {
    NSRect bounds = [self bounds];

    [[NSColor whiteColor] set];
    [NSBezierPath fillRect: bounds];

    [[NSColor blackColor] set];

    if ((!eventDone) && ([[document ovals] count] > 0)) {
    [[document ovals] removeLastObject];
    }

    [[document ovals] addObject: [NSBezierPath bezierPathWithOvalInRect: [self currentRect]]];

    for (NSBezierPath *oval in [document ovals]) {
    [oval setLineWidth: 2.0];
    [oval stroke];
    }
    }

#pragma mark Events

  • (void) mouseDown: (NSEvent *) theEvent {
    NSPoint p = [theEvent locationInWindow];

    downPoint = [self convertPoint: p fromView: nil];
    currentPoint = downPoint;
    eventDone = NO;

    [self setNeedsDisplay: YES];
    }

  • (void) mouseDragged: (NSEvent *) theEvent {
    NSPoint p = [theEvent locationInWindow];

    currentPoint = [self convertPoint: p fromView: nil];

    [self autoscroll: theEvent];
    [self setNeedsDisplay: YES];
    }

  • (void) mouseUp: (NSEvent *) theEvent {
    NSPoint p = [theEvent locationInWindow];

    currentPoint = [self convertPoint: p fromView: nil];
    eventDone = YES;

    [self setNeedsDisplay: YES];
    }

  • (NSRect) currentRect {
    float minX = MIN (downPoint.x, currentPoint.x);
    float maxX = MAX (downPoint.x, currentPoint.x);
    float minY = MIN (downPoint.y, currentPoint.y);
    float maxY = MAX (downPoint.y, currentPoint.y);

    return (NSMakeRect (minX, minY, maxX - minX, maxY - minY));
    }
    @end
    [/code]

Not sure if it’s the correct or most optimal solution, but it sure works. Now to do the undo/redo mechanism! :slight_smile: