This is eTImage.m in view mode; [Download] [Up]
/////////////////////////////////////////////////////////////////////////////// // 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
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.