home *** CD-ROM | disk | FTP | other *** search
- ///////////////////////////////////////////////////////////////////////////////
- // FILENAME: eTImage.m
- // SUMMARY: Implementation of Image annotations with Action support
- // SUPERCLASS: Object
- // INTERFACE: None
- // PROTOCOLS: <Annotation,HTMDSupport,ASCIISupport,LaTeXSupport,Tool,
- // InspectableTarget,DocNotification>
- // AUTHOR: Rohit Khare
- // COPYRIGHT: (c) 1994 California Institure of Technology, eText Project
- ///////////////////////////////////////////////////////////////////////////////
- // IMPLEMENTATION COMMENTS
- // A whole lot of interesting delegation has been introduced between
- // eTImage and eTImageComponent.
- // Tips for subclass implementors:
- // * Feel free to access the ivars directly and avoid the extra messages...
- // * ... but make sure to call updateGraphics to commit your changes.
- // * YOU have to make sure the images are deallocated. Leave all of the other
- // ivars alone in this class, but if you want to free these images, YOU
- // have to free them in your subclass's -free.
- ///////////////////////////////////////////////////////////////////////////////
- // HISTORY
- // 02/14/95: In TestTwentyFourBitRGB on mono, it kept crashing on -backColor
- // 02/11/95: Attempted to modify captioning process to avoid redraw.
- // 10/30/94: Modified to support <InspectableTarget>
- // 07/16/94: Rewritten from scratch, integrating ImageAnnotation and Image
- ///////////////////////////////////////////////////////////////////////////////
-
- #import "eTImage.h"
- #import "eTImageUI.h"
- #define _eTImageVERSION 10
- #define _eTImageVERSION_captions 22
-
- @implementation eTImage
- // id theText
- // id etDoc
- //
- // id imageComponent
- // id image
- // id altImageComponent
- // id altImage
- // char *description
- // NXSize size
- // BOOL usesButtonStyle
- // BOOL isDraggable
- // BOOL highlighted
-
- // "Delegate" methods
- // User Interaction
-
- - click:(NXEvent*)e
- { return self; }
- - doubleClick:(NXEvent*)e
- {
- NXAtom fname = [imageComponent currentPath];
-
- if (*fname && !access(fname, F_OK|R_OK)) {
- // we need to flip the location y coord
- NXPoint pt;
- NXSize sz;
- id img;
-
- pt = e->location;
- img = [imageComponent icon];
- [theText convertPoint:&pt fromView:nil];
- [img getSize:&sz];
- pt.x -= sz.width/2;
- pt.y += sz.height/2;
- [[Application workspace]
- openFile:fname fromImage: img
- at: &pt inView:theText];
- }
- return self;
- }
-
- - inspect:(NXEvent*)e
- {
- [[NXApp inspector] inspect:self];
- return self;
- }
- - (id <Inspectable>) inspectableDelegate {
- return [[eTImageUI new] setAnnotation:self]; }
-
- - drag: (Pasteboard *)draggingPboard image:(NXImage **)proxyImage
- {
- NXAtom fname = [imageComponent currentPath];
-
- if (*fname && !access(fname, F_OK|R_OK)) {
- [imageComponent writeComponentToPboard:draggingPboard];
- *proxyImage = [imageComponent icon];
- } else {
- NXRunAlertPanel("eTImage","Cannot access %s. Try saving the document.","OK",NULL,NULL,fname?fname:"the image");
- draggingPboard=nil;
- *proxyImage=nil;
- }
- return self;
- }
-
- // "Client" methods -- feel free to access ivars directly.
- // Getter/Setter methods
-
- - setImageComponent: newImageComponent
- {
- if (![newImageComponent isKindOf:[eTImageComponent class]])
- return nil;
- if (imageComponent != newImageComponent) {
- [imageComponent free];
- imageComponent = newImageComponent;
- }
- image = [imageComponent theImage];
- if (image) [image getSize:&size];
- else size.width = size.height = 0.0;
- captionMode = NO;
- // don't drag out shared images. Conflict with subclasses that drag out other data.
- // Reveals design flaw.
- if ([imageComponent isShared] && ([self class] == [eTImage class])) isDraggable = NO;
- return self;
- }
- - setAltImageComponent: newAltImageComponent
- {
- if (![newAltImageComponent isKindOf:[eTImageComponent class]])
- return nil;
- altImageComponent = newAltImageComponent;
- altImage = [altImageComponent theImage];
- return self;
- }
- - setDescription: (const char *) newDescription
- {
- description=realloc(description,(strlen(newDescription)+1)*sizeof(char));
- strcpy(description,newDescription);
- if (captionMode) [self setCaptionMode];
- [etDoc touch];
- return self;
- }
- - setUsesButtonStyle:(BOOL)newState
- {
- usesButtonStyle = newState;
- return self;
- }
- - setDraggable:(BOOL)newState
- {
- isDraggable=newState;
- return self;
- }
- - setSize:(const NXSize *)newSize
- {
- // I don't quite know what to do with this.
- // How do we factor in the Component's isMutable semantics?
- // should this do a touch?
- if ([imageComponent isMutable] && !captionMode) {
- size = *newSize;
- [image setSize:&size];
- //[imageComponent touch];
- if ([altImageComponent isMutable]) {
- [altImage setSize:&size];
- //[altImageComponent touch];
- }
- [etDoc touch];
- }
- return self;
- }
-
- - setCaptionMode {
- if (description && theText) {
- NXRun *theRun = [theText runForAnnotation:self];
-
- if (theRun && description) {
- size.width = [theRun->font getWidthOf:description];
- size.height = [theRun->font pointSize];
- captionMode = YES;
- [self setUsesButtonStyle: YES];
- } else if (description) {
- size.width = [[theText font] getWidthOf:description];
- size.height = [[theText font] pointSize];
- captionMode = YES;
- [self setUsesButtonStyle: YES];
- }
- }
- return self;
- }
- - setState:(BOOL) newState {state = newState; return [self updateGraphics];}
- - eTextObj
- {return theText;}
- - etDoc
- {return etDoc;}
- - imageComponent
- {return imageComponent;}
- - altImageComponent
- {return altImageComponent;}
- - (char *) description
- {return description;}
- - (char **) getDescription // be careful of reallocing this
- {return &description;}
- - (BOOL) usesButtonStyle
- {return usesButtonStyle;}
- - (BOOL) isDraggable
- {return isDraggable;}
- - (NXSize *) size
- {return &size;}
- - (BOOL) state {return state;}
- - (BOOL) captionMode {return captionMode;}
- // State Flushing
- - updateGraphics
- {
- [theText perform:@selector(calcLine) with:nil afterDelay:0 cancelPrevious:YES];
- [[theText superview] perform:@selector(display) with:nil afterDelay:0 cancelPrevious:YES];
- return self;
- }
-
- // "Private" Methods; really, default implementations
- // Lifecycle
- + toolAwake:theApp
- {
- const char *const * types;
- int i;
-
- [theApp registerAnnotation: [eTImage class]
- name: "eTImage"
- RTFDirective: "eTImage"
- menuLabel: "Insert Image..."
- menuKey: 'I'
- menuIcon: (NXImage *) nil];
- types = [NXImage imagePasteboardTypes];
- for(i=0; types[i]; i++)
- [theApp registerType:types[i] for:[eTImage class]];
- types = [NXImage imageFileTypes];
- for(i=0; types[i]; i++)
- [theApp registerType:NXCreateFileContentsPboardType(types[i])
- for:[eTImage class]];
- return self;
- }
- - init
- {
- [super init];
- if (!description){
- description = (char *)malloc(2*sizeof(char));
- strcpy(description, "");
- }
- isDraggable = YES;
- // everything else is automatically nil or NO
- imageComponent = altImageComponent = nil;
- captionMode = NO;
- return self;
- }
- - free
- {
- [[NXApp inspector] inspect:nil];
- [etDoc unregisterNotification:self];
- etDoc=nil;
- [imageComponent free];
- [altImageComponent free];
- return self = [super free];
- }
-
- - initFromPboard:thePB inDoc:theDoc linked:(BOOL) linked
- {
- [self init]; // this is a wierd call-chain. check?
- etDoc = theDoc;
- theText = [[etDoc docUI] eTextObj]; // consistency checking
- [etDoc registerNotification:self];
-
- if ((!thePB) && !imageComponent) {
- // initialize as a null image as if from a menu selection
- imageComponent = [eTImageComponent newImageNamed:"eTImageComponentIcon"];
- [self setImageComponent:imageComponent];
- } else if (thePB) {
- imageComponent = [[eTImageComponent alloc]
- initInDoc:theDoc linked:linked];
- [self setImageComponent:
- [imageComponent readComponentFromPboard:thePB]];
- }
- return self;
- }
-
- // Drawing
- - calcCellSize:(NXSize *)theSize
- {
- if (captionMode) [self setCaptionMode]; //Recalcs font-sizes
- *theSize = size;
- if (usesButtonStyle) {
- theSize->width += 8;
- theSize->height += 8;
- }
- return self;
- }
-
- - drawSelf:(const NXRect *)cellFrame inView:view
- {
- NXPoint point;
- NXRect bounds;
- NXRun *theRun;
-
- if (!etDoc || !theText) {
- theText = view;
- etDoc = [theText etDoc];
- [etDoc registerNotification:self];
- }
-
- [view getBounds:&bounds];
- PSgsave();
- point = cellFrame->origin;
- point.y += cellFrame->size.height;
- if (usesButtonStyle) {
- NXDrawButton(cellFrame, &bounds);
- point.x += 4; point.y -= 4;
- } else {
- //if ([theText shouldDrawColor])
- // NXSetColor([theText backgroundColor]);
- //else
- PSsetgray([theText backgroundGray]);
- NXRectFill(cellFrame);
- }
- if (captionMode)
- theRun = [theText runForAnnotation:self];
- if (captionMode && theRun) {
- if ([theText shouldDrawColor])
- NXSetColor([theText runColor:theRun]);
- else
- PSsetgray([theText runGray:theRun]);
- PSmoveto(point.x, point.y + (([theRun->font metrics])->descender * [theRun->font pointSize]));
- [theRun->font set];
- PSshow(description);
- } else {
- [((state && !usesButtonStyle) ? altImage : image)
- composite:NX_SOVER toPoint:&point];
- }
- PSgrestore();
- return self;
- }
-
- - highlight:(const NXRect *)rect inView:view lit:(BOOL)flag
- {
- if (highlighted != flag) {
- highlighted = flag;
- //NXHighlightRect(cellFrame);
- }
- return self;
- }
-
- - (BOOL) trackMouse:(NXEvent *)theEvent
- inRect:(const NXRect *)cellFrame
- ofView:view
- { // See chapter 7, Modal Event Loops in Prog Dynamics
- NXPoint point,offset,mLoc;
- NXRect tracker, bounds;
- register int inside;
- int shouldLoop = YES;
- int oldMask;
- NXEvent *nextEvent,saveEvent;
- Pasteboard *dragPasteboard;
- NXImage *proxyImage;
- BOOL inTextSel; // you can't drag out of selected text.
- NXSelPt begin, end;
- int posn;
- NXRun *theRun;
-
- [view getBounds:&bounds];
- tracker = *cellFrame;
- NXIntersectionRect(&bounds, &tracker);
- tracker.size.width += 16; tracker.size.height += 16; tracker.origin.x -=8; tracker.origin.y -= 8;
- point = cellFrame->origin;
- point.y += cellFrame->size.height;
- saveEvent = *theEvent;
-
- PSgsave();
- if (usesButtonStyle) {
- NXDrawGrayBezel(cellFrame, &bounds);
- point.x += 4; point.y -= 4;
- }
-
- if (captionMode)
- theRun = [theText runForAnnotation:self];
- if (captionMode && theRun) {
- if ([theText shouldDrawColor])
- NXSetColor([theText runColor:theRun]);
- else
- PSsetgray([theText runGray:theRun]);
- PSmoveto(point.x, point.y + (([theRun->font metrics])->descender * [theRun->font pointSize]));
- [theRun->font set];
- PSshow(description);
- NXHighlightRect(cellFrame);
- } else {
- if (usesButtonStyle)
- NXHighlightRect(cellFrame);
- else
- [(state ? image : altImage) composite:NX_SOVER toPoint:&point];
- }
-
- PSgrestore();
- NXPing(); [[view window] flushWindow];
-
- [view getSel:&begin :&end];
- posn = [view positionForAnnotation:self];
- if ((posn >= begin.cp) && (posn <= end.cp)) inTextSel = YES;
- else inTextSel = NO;
- oldMask = [[view window] addToEventMask:NX_LMOUSEDRAGGEDMASK];
- while (shouldLoop) {
- nextEvent =
- [NXApp getNextEvent:(NX_LMOUSEUPMASK|NX_LMOUSEDRAGGEDMASK)];
- mLoc = nextEvent->location;
- [view convertPoint:&mLoc fromView:nil];
- inside = [view mouse:&mLoc inRect:&tracker];
- if ((!inside) && isDraggable && !inTextSel) {
- NXPoint imgLoc;
- NXSize imgSize;
-
- dragPasteboard = [Pasteboard newName: NXDragPboard];
- [self drag:dragPasteboard image: &proxyImage];
- [proxyImage getSize:&imgSize];
- // proxyImage's (fictional) origin in Text coords
- imgLoc = saveEvent.location;
- [view convertPoint:&imgLoc fromView:nil];
- imgLoc.x -= imgSize.width/2;
- imgLoc.y += imgSize.height/2;
-
- offset.x = nextEvent->location.x - saveEvent.location.x;
- offset.y = nextEvent->location.y - saveEvent.location.y;
-
- if (dragPasteboard && proxyImage) {
- [view dragImage:proxyImage
- at:&imgLoc offset:&offset
- event:&saveEvent pasteboard:dragPasteboard
- source:self slideBack: YES];
- shouldLoop = NO;
- }
-
- [self inspect:nextEvent];
- }
- // ok, now is it inside the _actual_ bounds?
- inside = [view mouse:&mLoc inRect:cellFrame];
- if ((nextEvent->type == NX_LMOUSEUP) && shouldLoop && inside) {
- // command-click only inspects
- // single-click inspects AND click:s
- // double click inspects AND click:s AND doubleClick:s
- if (nextEvent->data.mouse.click == 2)
- [self doubleClick:nextEvent];
- else if (nextEvent->data.mouse.click == 1) {
- [self inspect:nextEvent];
- if (!((nextEvent->flags) & NX_COMMANDMASK))
- [self click:nextEvent];
- }
- shouldLoop = NO;
- }
- if (nextEvent->type == NX_LMOUSEUP)
- shouldLoop = NO; // mouse up outside rects.
- }
- [[view window] setEventMask:oldMask];
-
- PSgsave();
- if (usesButtonStyle)
- NXDrawButton(cellFrame,&bounds);
- else
- NXEraseRect(cellFrame);
-
- if (captionMode)
- theRun = [theText runForAnnotation:self];
- if (captionMode && theRun) {
- if ([theText shouldDrawColor])
- NXSetColor([theText runColor:theRun]);
- else
- PSsetgray([theText runGray:theRun]);
- PSmoveto(point.x, point.y + (([theRun->font metrics])->descender * [theRun->font pointSize]));
- [theRun->font set];
- PSshow(description);
- } else {
- [((state || usesButtonStyle) ? altImage : image)
- composite:NX_SOVER toPoint:&point];
- }
-
- PSgrestore();
- NXPing();
- [[view window] flushWindow];
-
- return YES;
- }
-
- // File I/O
- // Here is a curious and inexplicable design decision:
- // altImageComponents are never written.
- // Why? I don't know. The implication is that the altImage is never the
- // explicitly set (or desired) by the user.
- - readRichText:(NXStream *)stream forView:view
- {
- int i,ver;
- char shouldCap = 0;
- NXSize temp;
-
- if (!etDoc || !theText) {
- theText = view;
- etDoc = [theText etDoc];
- [etDoc registerNotification:self];
- }
- NXScanf(stream, "%d ", &ver);
- if ((ver != _eTImageVERSION) && (ver != _eTImageVERSION_captions)) {
- // bad version block.
- NXLogError("eTImage found unparseable version %d at position %d",
- ver, NXTell(stream));
- return nil;
- }
- NXScanf(stream, "%f %f %d", &(temp.height), &(temp.width), &i);
- NXGetc(stream); // space-eater
- if (i) {
- if (description) free(description);
- description = malloc(sizeof(char)*(i+1));
- NXRead(stream, description, i);
- description[i] = '\0';
- }
- NXGetc(stream); // balances out the space at the end of %s
- if (ver >= _eTImageVERSION_captions) {
- NXScanf(stream, "%c ", &shouldCap);
- shouldCap -= 'A';
- }
- [self setImageComponent:[[[eTImageComponent alloc] init]
- readRichText:stream forView:view]];
- if (shouldCap) {
- //[self perform:@selector(setCaptionMode) with:nil afterDelay:0 cancelPrevious:NO];
- [self setCaptionMode];
- captionMode = YES;
- }
- return self;
- }
- - writeRichText:(NXStream *)stream forView:view
- {
- if (!(size.height && size.width)) {
- //sanity check failed. try reaquiring size from the image
- image = [imageComponent theImage];
- [image getSize:&size];
- if (!(size.height && size.width)) {
- // now we have real problems. go undercover here:
- [imageComponent registerError];
- image = [imageComponent theImage];
- [image getSize:&size];
- }
- }
- NXPrintf(stream, "%d %f %f %d %s %c ", _eTImageVERSION_captions,
- size.height, size.width, strlen(description), description, captionMode + 'A');
- [imageComponent writeRichText:stream forView:view];
- return self;
- }
-
- - writeHTML:(NXStream *)stream forView:view {
- if (captionMode) {
- NXPrintf(stream, "%v", description);
- } else if (([self class] == [eTImage class]) && ![imageComponent isShared]) {
- // Make the .gif point to the original file
- NXPrintf(stream, "<A HREF=\"%V\">", [imageComponent componentName]);
- [imageComponent writeHTML:stream forView:view withCaption:description];
- NXPrintf(stream,"</A>");
- } else {
- [imageComponent writeHTML:stream forView:view withCaption:description];
- }
- return self;
- }
- - writeLaTeX:(NXStream *)stream forView:view
- {
- if (captionMode) {
- NXPrintf(stream, "%W", description);
- } else {
- NXPrintf(stream,"\\begin{figure}\n\\hrule\n\\vspace{12 pt}\n");
- [imageComponent writeLaTeX:stream forView:view];
- NXPrintf(stream,"\n\\vspace{12 pt}\n\\hrule\n\\caption{\\em %w.}\n\\label{%w}\n\\end{figure}\n[Figure \\ref{%w}]", description, [imageComponent componentName],[imageComponent componentName]);
- }
- return self;
- }
- - writeASCIIRef:(NXStream *)stream forView:view
- {return [imageComponent writeASCIIRef:stream forView:view];}
-
- // Format encoding and support
- - writeComponentToPath:(NXAtom)path inFormat:(int) theFormat
- {
- if(!etDoc) NXLogError("etDoc is nil at %s %u",__FILE__,__LINE__);
- if ((theFormat == ETFD_FMT) || !captionMode)
- [imageComponent writeComponentToPath:path inFormat:theFormat];
- if (([self class] == [eTImage class]) && (theFormat == HTMD_FMT) && !captionMode)
- [imageComponent writeComponentToPath:path inFormat:ETFD_FMT];
- return self;
- }
- // Drag-and-Drop
- - addToPboard:pboard
- {
- [imageComponent addToPboard:pboard];
- return self;
- }
- - (NXDragOperation)draggingSourceOperationMaskForLocal:(BOOL)flag
- {
- return (flag ? NX_DragOperationLink : NX_DragOperationAll);
- }
- - draggedImage:(NXImage *)image endedAt:(NXPoint *)screenPoint
- deposited:(BOOL)flag
- {
- return self;
- }
- @end