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;
}
@endThese are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.