This is Document.m in view mode; [Download] [Up]
/* * (a) (C) 1990 by Adobe Systems Incorporated. All rights reserved. * * (b) If this Sample Code is distributed as part of the Display PostScript * System Software Development Kit from Adobe Systems Incorporated, * then this copy is designated as Development Software and its use is * subject to the terms of the License Agreement attached to such Kit. * * (c) If this Sample Code is distributed independently, then the following * terms apply: * * (d) This file may be freely copied and redistributed as long as: * 1) Parts (a), (d), (e) and (f) continue to be included in the file, * 2) If the file has been modified in any way, a notice of such * modification is conspicuously indicated. * * (e) PostScript, Display PostScript, and Adobe are registered trademarks of * Adobe Systems Incorporated. * * (f) THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO * CHANGE WITHOUT NOTICE, AND SHOULD NOT BE CONSTRUED * AS A COMMITMENT BY ADOBE SYSTEMS INCORPORATED. * ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY * OR LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO * WARRANTY OF ANY KIND (EXPRESS, IMPLIED OR STATUTORY) * WITH RESPECT TO THIS INFORMATION, AND EXPRESSLY * DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, * FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. */ /* * Document.m * * Portions of the source code in this file are based on source code * from the Draw example application provided by NeXT. * * The Document class serves to keep track of the global information * concerning a particular file. It sets up the window and the views * (ScrollView, DocView and DrawingView) . It manages the name * of the file as well as save and print info information. * * The listenerId is used to allow the user to drag an icon representing * a PostScript file into the document. The iconPathList is the * list of files which was last dragged into the document. * * The saved variable marks whether a disk file has been * created for the document (i.e. if it has ever been saved). * * Version: 2.0 * Author: Ken Fromm */ #import "Document.h" #import "ImportApp.h" #import "DocView.h" #import "DrawingView.h" #import "ScrollingView.h" #import "ImportPanel.h" #import "SaveAsPanel.h" #import <appkit/Cursor.h> #import <appkit/Listener.h> #import <appkit/Matrix.h> #import <appkit/PageLayout.h> #import <appkit/PrintInfo.h> #import <appkit/ScrollView.h> #import <appkit/Speaker.h> #import <appkit/nextstd.h> #import <appkit/publicWraps.h> #import <objc/hashtable.h> #import <string.h> const NXRect DefaultContentRect = {0.0, 0.0, 575.0, 660.0}; static const char DefaultName[] = "Empty Window"; extern void resizeBuffer(); @implementation Document /* Factory methods */ /* * Creates a new, empty, document. * * Creates a PrintInfo object; creates a view whose size depends on the * default PrintInfo created; creates a window for that view; sets self * as the window's delegate; orders the window front; registers the window * with the Workspace Manager. The default margins are set to 1/4 inch. */ + new { self = [super new]; printinfoId = [PrintInfo new]; [printinfoId setMarginLeft:18.0 right:18.0 top:18.0 bottom:18.0]; [self createWindow]; [self setName:NULL andDirectory:NULL]; [self setDocument]; return self; } /* Opens an existing document from the specified file. */ + newFromFile:(const char *)file { NXStream *stream; self = [super new]; stream = NXMapFile(file, NX_READONLY); [self createWindow]; if ([self readFromStream:stream]) { [self setName:file]; [self setDocument]; } else { [self free]; self = nil; } return self; } /* Very private instance method needed by factory methods */ /* * This method loads an archived document from the * specified file name. The frame size is read first and then * the window is displayed before the rest of the document * is loaded (the print info and the drawing view). * * Does not place newView as the window's drawing view until * the read is complete. If a failure occurs for some reason * during the read, the previous drawing view is retained. * * An NX_DURING handler is needed around the NXTypedStream * operations because if the user has asked that a bogus file be * opened, the NXTypedStream will raise an error. To handle the * error, the NXTypedStream must be closed. */ - readFromStream:(NXStream *)stream { BOOL err = YES; NXTypedStream *volatile ts = NULL; if (stream) { NX_DURING ts = NXOpenTypedStream(stream, NX_READONLY); if (ts) { printinfoId = NXReadObject(ts); drawingviewId = NXReadObject(ts); NXCloseTypedStream(ts); err = NO; } NX_HANDLER NXCloseTypedStream(ts); NX_ENDHANDLER NXClose(stream); } if (!err) { [[docviewId setDrawView:drawingviewId] free]; [docviewId placeView:drawingviewId]; [[windowId contentView] display]; } else Notify("Open Error", "Cannot open file."); return self; } - setDocument { NXPoint location; NXRect winFrame; [docviewId placeView:drawingviewId]; [self registerWindow]; [windowId setDelegate:self]; [windowId display]; [windowId getFrame:&winFrame]; [NXApp getPosition:&location forSize:&winFrame.size]; [windowId moveTo:location.x :location.y]; [windowId makeKeyAndOrderFront:self]; return self; } - free { [printinfoId free]; [windowId free]; NX_FREE(name); NX_FREE(directory); NX_FREE(iconPathList); return [super free]; } /* * Create the drawing window and place a scrollview as the content view. * A DrawingView instance is placed as the DocView of the ScrollView. */ - createWindow { id scrollView; const NXRect *paperRect; windowId = [Window newContent:&DefaultContentRect style:NX_TITLEDSTYLE backing:NX_BUFFERED buttonMask:NX_ALLBUTTONS defer:NO]; [windowId addToEventMask:NX_FLAGSCHANGEDMASK]; scrollView = [ScrollingView newFrame:&DefaultContentRect]; [scrollView setBorderType:SCROLLVIEW_BORDER]; paperRect = [printinfoId paperRect]; drawingviewId = [DrawingView newFrame:paperRect]; docviewId = [[[[DocView new] setClipping:NO] setScale:1.0] setFlipped:NO]; [docviewId notifyAncestorWhenFrameChanged:YES]; [docviewId setDrawView:drawingviewId]; [scrollView setDocView:docviewId]; [[docviewId superview] allocateGState]; [[windowId setContentView:scrollView] free]; [windowId makeFirstResponder:drawingviewId]; return self; } - window { return windowId; } /* Returns the DrawingView associated with this document. */ - drawingView { return drawingviewId; } - docView { return docviewId; } - printInfo { return printinfoId; } /* Target/Action methods */ /* * Puts up a PageLayout panel and allows the user to pick a different * size paper to work on. The view is then resized to the * new paper size. The new DrawingView is placed in the DocView * and then scrolled to the previous rectangle. * The view is dirtied because the PrintInfo is part of the document. */ - changeLayout:sender { NXRect visibleRect; const NXRect *paperRect; if ([[PageLayout new] runModal] == NX_OKTAG) { [drawingviewId getVisibleRect:&visibleRect]; paperRect = [printinfoId paperRect]; [drawingviewId sizeTo:paperRect->size.width :paperRect->size.height]; [docviewId placeView:drawingviewId]; [drawingviewId scrollRectToVisible:&visibleRect]; [[windowId contentView] display]; [drawingviewId setDirty:YES]; } return self; } - print:sender { return [drawingviewId printPSCode:sender]; } /* Revert the document back to what is on the disk. */ - revertToSaved:sender { NXStream *stream; NXRect visibleRect; const NXRect *paperRect; if (!saved || ![drawingviewId isDirty] || (NXRunAlertPanel("Revert", "%s has been edited. Are you sure you want to undo changes?", "Revert", "Cancel", NULL, name) != NX_ALERTDEFAULT)) { return self; } [drawingviewId getVisibleRect:&visibleRect]; [windowId endEditingFor:self]; stream = NXMapFile([self filename], NX_READONLY); if (stream && [self readFromStream:stream]) { [windowId disableDisplay]; paperRect = [printinfoId paperRect]; [drawingviewId sizeTo:paperRect->size.width :paperRect->size.height]; [docviewId placeView:drawingviewId]; [drawingviewId scrollRectToVisible:&visibleRect]; [windowId reenableDisplay]; [[windowId contentView] display]; [windowId makeFirstResponder:drawingviewId]; [drawingviewId setDirty:NO]; NXClose(stream); } else { if (stream) NXClose(stream); Notify("Revert Error", "Cannot revert to saved file."); } return self; } /* * Bring up the import (open) panel to obtain the file and then pass * to the drawing view. */ - import:sender { id importpanel; static const char *const filetype[4] = {"ps", "eps", "tiff", NULL}; importpanel = [[ImportPanel new] setImport]; if ([importpanel runModalForTypes:filetype]) [drawingviewId importFile:[importpanel filename] at:NULL]; return self; } /* * Writes out the document in archive format. * (Saves the PrintInfo and DrawingView objects. See * DrawingView's write: methods for more details.) */ - saveFile:(const char *) file { BOOL error; NXTypedStream *typedstream; error = YES; typedstream = NXOpenTypedStreamForFile(file, NX_WRITEONLY); if (typedstream) { NXWriteRootObject(typedstream, printinfoId); NXWriteRootObject(typedstream, drawingviewId); NXCloseTypedStream(typedstream); error = NO; } else Notify("Save Error", "Cannot open a typed stream to the file."); return (error ? nil : self); } /* * Saves the file. If this document has never been saved to disk, * then a SavePanel is put up to ask the user what file name she * wishes to use to save the document. */ - save:sender { id savepanel; if ([drawingviewId isDirty] || !saved) { if (!saved) { savepanel = [[SaveAsPanel new] setSave]; if (![savepanel runModalForDirectory:directory file:NULL]) return self; [self setName:[savepanel filename]]; } if ([self saveFile:[self filename]]) { [drawingviewId setDirty:NO]; saved = YES; } } return self; } - saveAs:sender { id savepanel; char *tempname; if (strcmp(name, DefaultName) == 0) tempname = NULL; else tempname = name; savepanel = [[SaveAsPanel new] setSaveAs]; if ([savepanel runModalForDirectory:directory file:tempname]) { [drawingviewId setDirty:YES]; [self setName:[savepanel filename]]; if ([self saveFile:[self filename]]) { [drawingviewId setDirty:NO]; saved = YES; } } return self; } /* * Writes out the document in Epsf/Illustrator format. */ - saveTo:sender { id savepanel; NXStream *stream; savepanel = [[SaveAsPanel new] setSaveTo]; if ([savepanel runModal]) { stream = NXOpenMemory(NULL, 0, NX_WRITEONLY); if (stream) { [drawingviewId writePSToStream:stream]; NXSaveToFile(stream, [savepanel filename]); NXCloseMemory(stream, NX_FREEBUFFER); } else Notify("Save Error", "Cannot open a stream to the file."); } return self; } /* Methods related to naming/saving this document. */ /* * Gets the fully specified file name of the document. */ - (const char *)filename { static char filenamebuf[MAXPATHLEN+1]; if (!directory || !name) [self setName:name andDirectory:directory]; if (directory) { strcpy(filenamebuf, directory); strcat(filenamebuf, "/"); } else filenamebuf[0] = '\0'; if (name) strcat(filenamebuf, name); return filenamebuf; } - (const char *)directory { return directory; } - (const char *)name { return name; } /* * If file is a full path name, then both the name and directory of the * document is updated appropriately, otherwise, only the name is changed. */ - setName:(const char *)file { char *lastComponent; char path[MAXPATHLEN+1]; if (file) { strcpy(path, file); lastComponent = strrchr(path, '/'); if (lastComponent) { *lastComponent++ = '\0'; return [self setName:lastComponent andDirectory:path]; } else return [self setName:file andDirectory:NULL]; } return self; } /* * Updates the name and directory of the document. * newName or newDirectory can be NULL, in which case the name or directory * will not be changed (unless one is currently not set, in which case * a default name will be used). */ - setName:(const char *)newName andDirectory:(const char *)newDirectory { if ((newName && *newName) || !name) { if (!newName || !*newName) newName = DefaultName; NX_FREE(name); name = NXCopyStringBuffer(newName); } if ((newDirectory && (*newDirectory == '/')) || !directory) { if (!newDirectory || (*newDirectory != '/')) newDirectory = [NXApp currentDirectory]; NX_FREE(directory); directory = NXCopyStringBufferFromZone(newDirectory, [self zone]); } [windowId setTitleAsFilename:[self filename]]; return self; } /* Window delegate methods. */ /* * Switch the Application's PrintInfo to the document's when the document * window becomes the main window. Also set the cursor appropriately * depending on which tool is currently selected. */ - windowDidBecomeMain:sender { [NXApp setPrintInfo:printinfoId]; [self resetResponder]; return self; } /* * Prevents the window from getting too large or too small. */ - windowWillResize:sender toSize:(NXSize *)size { NXSize screenSize; [NXApp getScreenSize:&screenSize]; screenSize.width = screenSize.width * 0.90; screenSize.height = screenSize.height * 0.90; size->width = MIN(screenSize.width, size->width); size->height = MIN(screenSize.height, size->height); size->width = MAX(MIN_WINDOW_WIDTH, size->width); size->height = MAX(MIN_WINDOW_HEIGHT, size->height); return self; } /* * Resizes the doc view and repositions the drawing view inside the doc view. */ - windowDidResize:sender { NXRect frameRect, contRect; [windowId getFrame:&frameRect]; [Window getContentRect:&contRect forFrameRect:&frameRect style:[windowId style]]; resizeBuffer([drawingviewId buffer], &contRect.size); [docviewId placeView:drawingviewId]; return self; } /* * If the GraphicView has been edited, then this asks the user if she * wants to save the changes before closing the window. When the window * is closed, the DrawDocument itself must be freed. This is accomplished * via Application's delayedFree: mechanism. Unfortunately, by the time * delayedFree: frees the DrawDocument, the window and view instance variables * will already have automatically been freed by virtue of the window's being * closed. Thus, those instance variables must be set to nil to avoid their * being freed twice. * * Returning nil from this method informs the caller that the window should * NOT be closed. Anything else implies it should be closed. */ - windowWillClose:sender { int save; if ([drawingviewId isDirty] && (saved || ![drawingviewId isEmpty])) { save = NXRunAlertPanel("Close", "%s has changes. Save them?", "Save", "Don't Save", "Cancel", name); if (save == NX_ALERTDEFAULT || save == NX_ALERTALTERNATE) { [sender endEditingFor:self]; /* terminate any editing */ if (save == NX_ALERTDEFAULT) [self save:nil]; } else return nil; } [self unregisterWindow]; [NXApp setPrintInfo:nil]; return self; } /* Icon dragging methods */ /* * Registers the document window with the Workspace Manager so that when the * user picks up an icon in the Workspace and drags it over our document window * and lets go, iconEntered:... and iconReleasedAt::ok: messages will be * sent to the DrawDocument from the Workspace Manager. Allows the user to * drag PostScript and TIFF files into the document. */ - registerWindow { unsigned int windowNum; id speaker = [NXApp appSpeaker]; listenerId = [Listener new]; [listenerId setDelegate:self]; [listenerId usePrivatePort]; [listenerId addPort]; NXConvertWinNumToGlobal([windowId windowNum], &windowNum); [speaker setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)]; [speaker registerWindow:windowNum toPort:[listenerId listenPort]]; return self; } /* Undoes what registerWindow does. */ - unregisterWindow { unsigned int windowNum; id speaker = [NXApp appSpeaker]; if (listenerId) { [speaker setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)]; NXConvertWinNumToGlobal([windowId windowNum], &windowNum); [speaker unregisterWindow:windowNum]; [listenerId free]; } return self; } /* * Called whenever an icon is dragged from the Workspace over the document * window. At this point, all that is done is to salt away the list of files * represented by the icon. All the real work is done in iconReleasedAt::ok:. */ - (int)iconEntered:(int)windowNum at:(double)x :(double)y iconWindow:(int)iconWindowNum iconX:(double)iconX iconY:(double)iconY iconWidth:(double)iconWidth iconHeight:(double)iconHeight pathList:(char *)pathList { if (!iconPathList || strcmp(iconPathList, pathList)) { NX_FREE(iconPathList); NX_MALLOC(iconPathList, char, strlen(pathList)+1); strcpy(iconPathList, pathList); } return 0; } /* * Goes through the list of files associated with the icon dragged * from the Workspace and checks if any of them are PostScript or TIFF. * If any are, then the GraphicView is asked to load those in as objects. * Very important: an NX_DURING handler is required around all the processing * of this method since an uncaught raised error will cause this method not * to return and thus hang the Workspace Manager for a while. */ - (int)iconReleasedAt:(double)x :(double)y ok:(int *)flag { volatile int foundOne = NO; char *file, *tab, *extension; int format; id importpanel; NXPoint pt; NX_DURING importpanel = [ImportPanel new]; format = [importpanel format]; [importpanel setFormat:IMPORT_COPY]; pt.x = x; pt.y = y; [windowId convertScreenToBase:&pt]; [drawingviewId convertPoint:&pt fromView:nil]; file = iconPathList; while (file) { tab = strchr(file, '\t'); if (tab) *tab = '\0'; extension = strrchr(file, '.'); if (extension) { if (!strcmp(extension, ".ps") || !strcmp(extension, ".eps") || !strcmp(extension, ".tiff")) if ([drawingviewId importFile:file at:&pt]) foundOne = YES; } file = tab ? tab++ : NULL; } if (foundOne) { [NXApp activateSelf:YES]; [windowId makeKeyAndOrderFront:self]; } [importpanel setFormat:format]; NX_HANDLER NX_ENDHANDLER *flag = foundOne; return 0; } /* Validates whether a menu command makes sense now */ /* * Validates whether a menu command that DrawDocument responds to * is valid at the current time. */ - (BOOL)validateCommand:menuCell { SEL action = [menuCell action]; if (action == @selector(save:)) return ([drawingviewId isDirty] || !saved); else if (action == @selector(saveTo:)) return ([drawingviewId isSelected]); else if (action == @selector(revertToSaved:)) return ([drawingviewId isDirty] && saved); return YES; } /* Cursor-setting method */ /* * Resets the document's cursor rectangle to be the frame of the * drawing view. * Makes the drawing view the first responder if * there isn't one or if no tool is selected. */ - resetResponder { id responder; responder = [windowId firstResponder]; if (!responder || responder == windowId || [NXApp cursor] == NXArrow) [windowId makeFirstResponder:drawingviewId]; return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.