/* |
|
File: PDFViewEdit.m |
|
Abstract: <Description, Points of interest, Algorithm approach> |
|
Version: 1.0 |
|
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Computer, Inc. ("Apple") in consideration of your agreement to the |
following terms, and your use, installation, modification or |
redistribution of this Apple software constitutes acceptance of these |
terms. If you do not agree with these terms, please do not use, |
install, modify or redistribute this Apple software. |
|
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Computer, |
Inc. may be used to endorse or promote products derived from the Apple |
Software without specific prior written permission from Apple. Except |
as expressly stated in this notice, no other rights or licenses, express |
or implied, are granted by Apple herein, including but not limited to |
any patent rights that may be infringed by your derivative works or by |
other works in which the Apple Software may be incorporated. |
|
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
|
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
|
Copyright © 2006 Apple Computer, Inc., All Rights Reserved |
|
*/ |
|
// ===================================================================================================================== |
// PDFViewEdit.m |
// ===================================================================================================================== |
|
|
#import "AnnotationPanel.h" |
#import "MyStampAnnotation.h" |
#import "PDFViewEdit.h" |
|
|
static NSRect RectPlusScale (NSRect aRect, float scale); |
|
|
@implementation PDFViewEdit |
// ========================================================================================================= PDFViewEdit |
// -------------------------------------------------------------------------------------------------------- saveDocument |
|
- (void) saveDocument: (id) sender |
{ |
[self saveDocumentAs: sender]; |
} |
|
// ------------------------------------------------------------------------------------------------------ saveDocumentAs |
|
- (void) saveDocumentAs: (id) sender |
{ |
NSSavePanel *panel; |
|
panel = [NSSavePanel savePanel]; |
[panel setRequiredFileType: @"pdf"]; |
|
// Run. |
if ([panel runModal] == NSFileHandlingPanelOKButton) |
{ |
PDFDocument *document; |
|
// Save file. |
[[self document] writeToURL: [panel URL]]; |
|
// Clear active annotation. |
_activeAnnotation = NULL; |
|
// Set new file. |
document = [[[PDFDocument alloc] initWithURL: [panel URL]] autorelease]; |
[self setDocument: document]; |
} |
} |
|
// ------------------------------------------------------------------------------------------------------- printDocument |
|
- (void) printDocument: (id) sender |
{ |
// Pass to PDF view. |
[self printWithInfo: [[[[self window] windowController] document] printInfo] autoRotate: YES]; |
} |
|
// ------------------------------------------------------------------------------------------------------------ drawPage |
|
- (void) drawPage: (PDFPage *) pdfPage |
{ |
NSArray *annotations; |
NSUInteger annotCount; |
NSUInteger i; |
|
// Let PDFView do most of the hard work. |
[super drawPage: pdfPage]; |
|
// Skip out unless we are in 'edit mode'. |
if (_editMode == NO) |
return; |
|
// Save. |
[NSGraphicsContext saveGraphicsState]; |
|
// Tranform. |
[self transformContextForPage: pdfPage]; |
|
// Frame all annotations in gray. |
[[NSColor colorWithDeviceRed: 0.0 green: 0.0 blue: 0.0 alpha: 0.3] set]; |
|
// Walk array of annotations. |
annotations = [pdfPage annotations]; |
annotCount = [annotations count]; |
for (i = 0; i < annotCount; i++) |
NSFrameRectWithWidthUsingOperation([[annotations objectAtIndex: i] bounds], 1.0, NSCompositeSourceOver); |
|
// Handle the selected annotation. |
if ((_activeAnnotation) && ([_activeAnnotation page] == pdfPage)) |
{ |
NSRect bounds; |
NSBezierPath *path; |
|
bounds = [_activeAnnotation bounds]; |
|
path = [NSBezierPath bezierPathWithRect: bounds]; |
[path setLineJoinStyle: NSRoundLineJoinStyle]; |
[[NSColor colorWithDeviceRed: 1.0 green: 0.0 blue: 0.0 alpha: 0.1] set]; |
[path fill]; |
[[NSColor redColor] set]; |
[path stroke]; |
|
// Draw resize handle. |
NSRectFill(NSIntegralRect([self resizeThumbForRect: bounds rotation: [pdfPage rotation]])); |
} |
|
// Restore. |
[NSGraphicsContext restoreGraphicsState]; |
} |
|
// --------------------------------------------------------------------------------------------- transformContextForPage |
|
- (void) transformContextForPage: (PDFPage *) page |
{ |
NSAffineTransform *transform; |
NSRect boxRect; |
int rotation; |
|
// Identity. |
transform = [NSAffineTransform transform]; |
|
// Bounds for page. |
boxRect = [page boundsForBox: [self displayBox]]; |
|
// Handle rotation. |
rotation = [page rotation]; |
switch (rotation) |
{ |
case 90: |
[transform rotateByDegrees: -90]; |
[transform translateXBy: -boxRect.size.width yBy: 0.0]; |
break; |
|
case 180: |
[transform rotateByDegrees: 180]; |
[transform translateXBy: -boxRect.size.height yBy: -boxRect.size.width]; |
break; |
|
case 270: |
[transform rotateByDegrees: 90]; |
[transform translateXBy: 0.0 yBy: -boxRect.size.height]; |
break; |
} |
|
// Origin. |
[transform translateXBy: -boxRect.origin.x yBy: -boxRect.origin.y]; |
|
// Concatenate. |
[transform concat]; |
} |
|
// ---------------------------------------------------------------------------------------------------- selectAnnotation |
|
- (void) selectAnnotation: (PDFAnnotation *) annotation |
{ |
// Deselect old annotation when appropriate. |
if ((_activeAnnotation != NULL) && (_activeAnnotation != annotation)) |
{ |
[self setNeedsDisplayInRect: RectPlusScale([self convertRect: [_activeAnnotation bounds] |
fromPage: [_activeAnnotation page]], [self scaleFactor])]; |
} |
|
// Assign. |
_activeAnnotation = annotation; |
|
// Display in panel. |
[[AnnotationPanel sharedAnnotationPanel] setAnnotation: _activeAnnotation]; |
|
if (_activeAnnotation) |
{ |
// Old (current) annotation location. |
_wasBounds = [_activeAnnotation bounds]; |
|
// Force redisplay. |
[self setNeedsDisplayInRect: RectPlusScale([self convertRect: [_activeAnnotation bounds] |
fromPage: [_activeAnnotation page]], [self scaleFactor])]; |
} |
} |
|
// --------------------------------------------------------------------------------------------------- annotationChanged |
|
- (void) annotationChanged |
{ |
NSRect bounds; |
|
// NOP. |
if (_activeAnnotation == NULL) |
return; |
|
// Get bounds. |
bounds = [_activeAnnotation bounds]; |
|
// Handle line start and end points. |
if ([_activeAnnotation isKindOfClass: [PDFAnnotationLine class]]) |
{ |
PDFBorder *border = [_activeAnnotation border]; |
float inset = 1.0; |
|
if (border) |
inset = ceilf([border lineWidth] * 2.2); |
[(PDFAnnotationLine *)_activeAnnotation setStartPoint: NSMakePoint(inset, inset)]; |
[(PDFAnnotationLine *)_activeAnnotation setEndPoint: NSMakePoint(bounds.size.width - inset, bounds.size.height - inset)]; |
} |
else if ([_activeAnnotation isKindOfClass: [PDFAnnotationMarkup class]]) |
{ |
[(PDFAnnotationMarkup *)_activeAnnotation setQuadrilateralPoints: [NSArray arrayWithObjects: |
[NSValue valueWithPoint: NSMakePoint(0.0, bounds.size.height)], |
[NSValue valueWithPoint: NSMakePoint(bounds.size.width, bounds.size.height)], |
[NSValue valueWithPoint: NSMakePoint(0.0, 0.0)], |
[NSValue valueWithPoint: NSMakePoint(bounds.size.width, 0.0)], |
NULL]]; |
} |
} |
|
// --------------------------------------------------------------------------------------------------------- setEditMode |
|
- (void) setEditMode: (BOOL) edit |
{ |
// Assign. |
_editMode = edit; |
|
// Redraw. |
[self setNeedsDisplay: YES]; |
} |
|
#pragma mark -------- event overrides |
// ------------------------------------------------------------------------------------------ setCursorForAreaOfInterest |
/* |
- (void) setCursorForAreaOfInterest: (PDFAreaOfInterest) area |
{ |
[[NSCursor arrowCursor] set]; |
} |
*/ |
// ----------------------------------------------------------------------------------------------------------- mouseDown |
|
- (void) mouseDown: (NSEvent *) theEvent |
{ |
PDFPage *activePage; |
PDFAnnotation *newActiveAnnotation = NULL; |
NSArray *annotations; |
int numAnnotations, i; |
NSPoint pagePoint; |
|
// Defer to super for locked PDF or if not in 'edit mode'. |
if (([[self document] isLocked]) || (_editMode == NO)) |
{ |
[super mouseDown: theEvent]; |
return; |
} |
|
// Mouse in display view coordinates. |
_mouseDownLoc = [self convertPoint: [theEvent locationInWindow] fromView: NULL]; |
|
// Page we're on. |
activePage = [self pageForPoint: _mouseDownLoc nearest: YES]; |
|
// Get mouse in "page space". |
pagePoint = [self convertPoint: _mouseDownLoc toPage: activePage]; |
|
// Hit test for annotation. |
annotations = [activePage annotations]; |
numAnnotations = [annotations count]; |
for (i = 0; i < numAnnotations; i++) |
{ |
NSRect annotationBounds; |
|
// Hit test annotation. |
annotationBounds = [[annotations objectAtIndex: i] bounds]; |
if (NSPointInRect(pagePoint, annotationBounds)) |
{ |
// New annotation. |
newActiveAnnotation = [annotations objectAtIndex: i]; |
|
// Update font panel. |
[self reflectFont]; |
|
// Remember click point relative to annotation origin. |
_clickDelta.x = pagePoint.x - annotationBounds.origin.x; |
_clickDelta.y = pagePoint.y - annotationBounds.origin.y; |
break; |
} |
} |
|
// Select annotation. |
[self selectAnnotation: newActiveAnnotation]; |
|
if (_activeAnnotation == NULL) |
{ |
[super mouseDown: theEvent]; |
} |
else |
{ |
_mouseDownInAnnotation = YES; |
|
// Hit-test for resize box. |
_resizing = NSPointInRect(pagePoint, [self resizeThumbForRect: _wasBounds |
rotation: [[_activeAnnotation page] rotation]]); |
} |
} |
|
// -------------------------------------------------------------------------------------------------------- mouseDragged |
|
- (void) mouseDragged: (NSEvent *) theEvent |
{ |
// Defer to super for locked PDF or if not in 'edit mode'. |
if (([[self document] isLocked]) || (_editMode == NO)) |
{ |
[super mouseDragged: theEvent]; |
return; |
} |
|
_dragging = YES; |
|
// Handle link-edit mode. |
if (_mouseDownInAnnotation) |
{ |
NSRect newBounds; |
NSRect currentBounds; |
NSRect dirtyRect; |
NSPoint mouseLoc; |
NSPoint endPt; |
|
// Where is annotation now? |
currentBounds = [_activeAnnotation bounds]; |
|
// Mouse in display view coordinates. |
mouseLoc = [self convertPoint: [theEvent locationInWindow] fromView: NULL]; |
|
// Convert end point to page space. |
endPt = [self convertPoint: mouseLoc toPage: [_activeAnnotation page]]; |
|
if (_resizing) |
{ |
NSPoint startPoint; |
|
// Convert start point to page space. |
startPoint = [self convertPoint: _mouseDownLoc toPage: [_activeAnnotation page]]; |
|
// Resize the annotation. |
switch ([[_activeAnnotation page] rotation]) |
{ |
case 0: |
newBounds.origin.x = _wasBounds.origin.x; |
newBounds.origin.y = _wasBounds.origin.y + (endPt.y - startPoint.y); |
newBounds.size.width = _wasBounds.size.width + (endPt.x - startPoint.x); |
newBounds.size.height = _wasBounds.size.height - (endPt.y - startPoint.y); |
break; |
|
case 90: |
newBounds.origin.x = _wasBounds.origin.x; |
newBounds.origin.y = _wasBounds.origin.y; |
newBounds.size.width = _wasBounds.size.width + (endPt.x - startPoint.x); |
newBounds.size.height = _wasBounds.size.height + (endPt.y - startPoint.y); |
break; |
|
case 180: |
newBounds.origin.x = _wasBounds.origin.x + (endPt.x - startPoint.x); |
newBounds.origin.y = _wasBounds.origin.y; |
newBounds.size.width = _wasBounds.size.width - (endPt.x - startPoint.x); |
newBounds.size.height = _wasBounds.size.height + (endPt.y - startPoint.y); |
break; |
|
case 270: |
newBounds.origin.x = _wasBounds.origin.x + (endPt.x - startPoint.x); |
newBounds.origin.y = _wasBounds.origin.y + (endPt.y - startPoint.y); |
newBounds.size.width = _wasBounds.size.width - (endPt.x - startPoint.x); |
newBounds.size.height = _wasBounds.size.height - (endPt.y - startPoint.y); |
break; |
} |
|
// Keep integer. |
newBounds = NSIntegralRect(newBounds); |
} |
else |
{ |
// Move annotation. |
// Hit test, is mouse still within page bounds? |
if (NSPointInRect([self convertPoint: mouseLoc toPage: [_activeAnnotation page]], |
[[_activeAnnotation page] boundsForBox: [self displayBox]])) |
{ |
// Calculate new bounds for annotation. |
newBounds = currentBounds; |
newBounds.origin.x = roundf(endPt.x - _clickDelta.x); |
newBounds.origin.y = roundf(endPt.y - _clickDelta.y); |
} |
else |
{ |
// Snap back to initial location. |
newBounds = _wasBounds; |
} |
} |
|
// Change annotation's location. |
[_activeAnnotation setBounds: newBounds]; |
|
// Call our method to handle updating annotation geometry. |
[self annotationChanged]; |
|
// Force redraw. |
dirtyRect = NSUnionRect(currentBounds, newBounds); |
[self setNeedsDisplayInRect: |
RectPlusScale([self convertRect: dirtyRect fromPage: [_activeAnnotation page]], [self scaleFactor])]; |
} |
else |
{ |
[super mouseDragged: theEvent]; |
} |
} |
|
// ------------------------------------------------------------------------------------------------------------- mouseUp |
|
- (void) mouseUp: (NSEvent *) theEvent |
{ |
// Defer to super for locked PDF or if not in 'edit mode'. |
if (([[self document] isLocked]) || (_editMode == NO)) |
{ |
[super mouseUp: theEvent]; |
return; |
} |
|
_dragging = NO; |
|
// Handle link-edit mode. |
if (_mouseDownInAnnotation) |
{ |
_mouseDownInAnnotation = NO; |
} |
else |
{ |
[super mouseUp: theEvent]; |
} |
} |
|
// ------------------------------------------------------------------------------------------------------------- keyDown |
|
- (void) keyDown: (NSEvent *) theEvent |
{ |
unichar oneChar; |
unsigned int theModifiers; |
BOOL noModifier; |
|
// Skip out if not in 'edit mode'. |
if (_editMode == NO) |
{ |
[super keyDown: theEvent]; |
return; |
} |
|
// Get the character from the keyDown event. |
oneChar = [[theEvent charactersIgnoringModifiers] characterAtIndex: 0]; |
theModifiers = [theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask; |
noModifier = ((theModifiers & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask)) == 0); |
|
// Delete? |
if ((oneChar == NSDeleteCharacter) || (oneChar == NSDeleteFunctionKey)) |
[self delete: self]; |
else |
[super keyDown: theEvent]; |
} |
|
// -------------------------------------------------------------------------------------------------------------- delete |
|
- (void) delete: (id) sender |
{ |
if (_activeAnnotation != NULL) |
{ |
// Remove annotation from page. |
[[_activeAnnotation page] removeAnnotation: _activeAnnotation]; |
_activeAnnotation = NULL; |
|
// Lazy, redraw entire view. |
[self setNeedsDisplay: YES]; |
|
// No annotation selected. |
[[AnnotationPanel sharedAnnotationPanel] setAnnotation: NULL]; |
|
// Set edited flag. |
[[self window] setDocumentEdited: YES]; |
} |
} |
|
// ------------------------------------------------------------------------------------------------------- newAnnotation |
|
- (void) newAnnotation: (id) sender |
{ |
PDFSelection *selection; |
PDFAnnotation *annotation; |
NSRect annotationBounds; |
|
// Get bounds for selection if available, otherwise, create an arbitrary rectangle. |
selection = [self currentSelection]; |
if (selection) |
{ |
annotationBounds = [selection boundsForPage: [[selection pages] objectAtIndex: 0]]; |
[self setCurrentSelection: NULL]; |
} |
else |
{ |
NSRect pageBounds; |
|
pageBounds = [[self currentPage] boundsForBox: [self displayBox]]; |
annotationBounds = NSMakeRect(pageBounds.origin.x + 20.0, pageBounds.origin.y + 20.0, 200.0, 80.0); |
} |
|
// Which annotation to create.... |
switch ([sender tag]) |
{ |
case 0: |
annotation = [[PDFAnnotationButtonWidget alloc] initWithBounds: annotationBounds]; |
break; |
|
case 1: |
annotation = [[PDFAnnotationChoiceWidget alloc] initWithBounds: annotationBounds]; |
break; |
|
case 2: |
annotation = [[PDFAnnotationCircle alloc] initWithBounds: annotationBounds]; |
break; |
|
case 3: |
annotation = [[PDFAnnotationFreeText alloc] initWithBounds: annotationBounds]; |
break; |
|
case 4: |
annotation = [[PDFAnnotationInk alloc] initWithBounds: annotationBounds]; |
// CREATE INK |
break; |
|
case 5: |
annotation = [[PDFAnnotationLine alloc] initWithBounds: annotationBounds]; |
break; |
|
case 6: |
annotation = [[PDFAnnotationLink alloc] initWithBounds: annotationBounds]; |
break; |
|
case 7: |
annotation = [[PDFAnnotationMarkup alloc] initWithBounds: annotationBounds]; |
break; |
|
case 8: |
annotation = [[PDFAnnotationSquare alloc] initWithBounds: annotationBounds]; |
break; |
|
case 9: |
annotation = [[MyStampAnnotation alloc] initWithBounds: annotationBounds]; |
break; |
|
case 10: |
// Special case bounds for Text annotation - we want something small and icon-sized. |
annotationBounds.size.width = 20.0; |
annotationBounds.size.height = 20.0; |
annotation = [[PDFAnnotationText alloc] initWithBounds: annotationBounds]; |
break; |
|
case 11: |
annotation = [[PDFAnnotationTextWidget alloc] initWithBounds: annotationBounds]; |
break; |
} |
|
[[self currentPage] addAnnotation: annotation]; |
[self setNeedsDisplay: YES]; |
|
// Select. |
[self selectAnnotation: annotation]; |
} |
|
#pragma mark -------- font |
// -------------------------------------------------------------------------------------------------------- showFontPanel |
|
- (void) showFontPanel: (id) sender |
{ |
[[NSFontPanel sharedFontPanel] makeKeyAndOrderFront: self]; |
[[NSFontManager sharedFontManager] setDelegate: self]; |
[self reflectFont]; |
} |
|
// ---------------------------------------------------------------------------------------------------------- reflectFont |
|
- (void) reflectFont |
{ |
if ([NSFontPanel sharedFontPanelExists] == NO) |
return; |
|
if (_activeAnnotation == NULL) |
return; |
|
if ([_activeAnnotation isKindOfClass: [PDFAnnotationFreeText class]]) |
[[NSFontPanel sharedFontPanel] setPanelFont: [(PDFAnnotationFreeText *)_activeAnnotation font] isMultiple: NO]; |
} |
|
// ----------------------------------------------------------------------------------------------------------- changeFont |
|
- (void) changeFont: (id) sender |
{ |
NSFont *newFont; |
|
if (_activeAnnotation == NULL) |
return; |
|
if ([_activeAnnotation isKindOfClass: [PDFAnnotationFreeText class]]) |
{ |
newFont = [sender convertFont: [(PDFAnnotationFreeText *)_activeAnnotation font]]; |
[(PDFAnnotationFreeText *)_activeAnnotation setFont: newFont]; |
} |
|
// Lazy. |
[self setNeedsDisplay: YES]; |
} |
|
// --------------------------------------------------------------------------------------------------- resizeThumbForRect |
|
- (NSRect) resizeThumbForRect: (NSRect) rect rotation: (int) rotation |
{ |
NSRect thumb; |
|
// Start with rect. |
thumb = rect; |
|
// Use rotation to determine thumb origin. |
switch (rotation) |
{ |
case 0: |
thumb.origin.x += rect.size.width - 8.0; |
break; |
|
case 90: |
thumb.origin.x += rect.size.width - 8.0; |
thumb.origin.y += rect.size.height - 8.0; |
break; |
|
case 180: |
thumb.origin.y += rect.size.height - 8.0; |
break; |
} |
|
thumb.size.width = 8.0; |
thumb.size.height = 8.0; |
|
return thumb; |
} |
|
@end |
|
// -------------------------------------------------------------------------------------------------------- RectPlusScale |
|
static NSRect RectPlusScale (NSRect aRect, float scale) |
{ |
float maxX; |
float maxY; |
NSPoint origin; |
|
// Determine edges. |
maxX = ceilf(aRect.origin.x + aRect.size.width) + scale; |
maxY = ceilf(aRect.origin.y + aRect.size.height) + scale; |
origin.x = floorf(aRect.origin.x) - scale; |
origin.y = floorf(aRect.origin.y) - scale; |
|
return NSMakeRect(origin.x, origin.y, maxX - origin.x, maxY - origin.y); |
} |