This is IGraphicImage.m in view mode; [Download] [Up]
/*$Copyright: * Copyright (C) 1992.5.22. Recruit Co.,Ltd. * Institute for Supercomputing Research * All rights reserved. * NewsBase by ISR, Kazuto MIYAI, Gary ARAKAKI, Katsunori SUZUKI, Kok-meng Lue * * You may freely copy, distribute and reuse the code in this program under * following conditions. * - to include this notice in the source code, if it is to be distributed * with source code. * - to add the file named "COPYING" within the code, which shall include * GNU GENERAL PUBLIC LICENSE(*). * - to display an acknowledgement in binary code as follows: "This product * includes software developed by Recruit Co.,Ltd., ISR." * - to display a notice which shall state that the users may freely copy, * distribute and reuse the code in this program under GNU GENERAL PUBLIC * LICENSE(*) * - to indicate the way to access the copy of GNU GENERAL PUBLIC LICENSE(*) * * (*)GNU GENERAL PUBLIC LICENSE is stored in the file named COPYING * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. $*/ //***************************************************************************** // IGraphicImage is used to implement an embedded graphic object as specified // by Text class. An embedded grahic image object is implemented by two // classes IGraphicImage and a media class. The IGraphicImage class implements // the generic methods and the media class implements the media specific // methods. The IGraphicImage object has a link to the supporting media // object and will delegate media specific operations to the media object. // Note that more than one IGraphicImage object can share the same media // object. Thw IGraphicImage class also has methods for supporting mouse // events over their corresponding images. These include mouse events to // resize/subimage an image and drag out media objects. This class now // also allows documents to be expanded and then replace the selection. //***************************************************************************** #import <dpsclient/psops.h> #import <dpsclient/wraps.h> #import <appkit/NXImage.h> #import <appkit/NXBitmapImageRep.h> #import <appkit/Text.h> #import <appkit/Window.h> #import <appkit/Application.h> #import <sys/param.h> /* added in for MAXPATHLEN */ #import <streams/streams.h> #import <sys/types.h> #import <sys/stat.h> #import <c.h> #import <libc.h> #import <strings.h> #import <objc/zone.h> #import <mach/mach.h> #import <ctype.h> #import <objc/HashTable.h> #import <appkit/timer.h> #import <appkit/Panel.h> #import <appkit/tiff.h> #import "Object1.h" #import "IGraphicImage.h" #import "JFIFBitmap.h" #import "IArticleD.h" #import "IMMEditor.h" #import "IMediaTable.h" #import "IMediaD.h" #import "INewsBaseText.h" #import "IFileIconButton.h" #import "IBinaryD.h" #import "IExternalD.h" #import "ILocalFileD.h" #import "IJfifD.h" #import "ITiffD.h" #import "IConvertMIME.h" #import "data_types.h" #import "drawRect.h" #import "errdebug.h" #import "Localization.h" #define LoStr(key) doLocalString(NULL,key,NULL) // ## REWRITE! ## might be better to send a message than use extern extern HashTable *listOfTempFiles; // list points to a list that will contain the media objects written by // writeRichText: message static List *list = nil; static char fileExtensions[128]; static int *counter = NULL; #define SIZE(N) (((N + 2) / 3 * 4 + 75) / 76 * 78) @implementation IGraphicImage + initialize { int i; Class mediaClass; const char *sptr; char *tptr; tptr = fileExtensions; for (i = 0; (mediaClass = [IMediaTable mediaClassAt:i]) != Nil; ++i) { sptr = [(id)mediaClass fileExtension]; if (strcmp(sptr, "rtf") == 0 || strcmp(sptr, "tbl") == 0) { continue; } for (; *sptr != '\0' && tptr < &fileExtensions[sizeof(fileExtensions) - 1]; ++sptr, ++tptr) { *tptr = *sptr; } *tptr++ = ','; } *--tptr = '\0'; return(self); } + setCounter:(int *)aCounter { counter = aCounter; return(self); } // list contains the media objects used by the writeRichText: message + setList:aList { list = aList; return(self); } //***************************************************************************** // Create IGraphicImage from file //***************************************************************************** - (IGraphicImage *)initFromFile:(const char *)pathName forView:(View *)view withIcon:(NXImage *)icon // Be careful because ownership of icon is transferred to IGraphicImage object. { const char *fileName, *extension; char mediaObjectName[MAXPATHLEN + 16]; Class mediaClass; IArticleD *tempArticle; int i; IMediaD *insertObject; NXStream *stream; ITextD *textObject; IConvertMIME *conv; enum {UNDEFINED, EXPAND, EMBED, EXTERNAL} option = UNDEFINED; text = (INewsBaseText *)view; if ((fileName = rindex(pathName, '/')) == 0) { fileName = pathName; } else { ++fileName; } if ((extension = rindex(pathName, '/'), extension = rindex(extension, '.')) != 0) { ++extension; } else { extension = [IBinaryD fileExtension]; } // document files can be expanded to replace selection or be embedded and // displayed as an icon or be made into a external reference and displayed // as an icon. if (strcmp(extension, RTF_FILE_EXTENSION) == 0 || strcmp(extension, MM_FILE_EXTENSION) == 0 || strcmp(extension, MIME_FILE_EXTENSION) == 0 || strcmp(extension, NEWS_FILE_EXTENSION) == 0) { switch(NXRunAlertPanel(LoStr(MMEDITOR), LoStr("How should object be included?"), LoStr("Expand"), LoStr("Embed"), LoStr("External"))) { case NX_ALERTDEFAULT: // Expand and Replace selection is a special case option = EXPAND; if (strcmp(extension, RTF_FILE_EXTENSION) == 0) { stream = NXMapFile(pathName, NX_READONLY); [text replaceSelWithRichText:stream]; NXCloseMemory(stream, NX_FREEBUFFER); } else { // .mmd directory or .news file so make a temporary article tempArticle = [ILocalFileD loadFromName:pathName inZone:[[text article] zone]]; if ((textObject = [tempArticle dataForKey:INDEX_RTF]) != nil) { // loop over media objects in temporary article for (i = [tempArticle count] - 1; i >= 0 && (insertObject = [tempArticle objectAt:i]) != nil; --i) { if ([insertObject isKindOf:[IMediaD class]] == TRUE && [[text article] dataForKey:[insertObject key]] == nil) { // put media object in new article and remove from // temporary article [[text article] insertKeyedObject:insertObject]; [tempArticle removeObject:insertObject]; } } // now replace selection with text stream = NXOpenMemory([textObject textData], [textObject size], NX_READONLY); [text replaceSelWithRichText:stream]; NXCloseMemory(stream, NX_SAVEBUFFER); } else { // replace selection with plain text textObject = [tempArticle dataForKey:PLAINTEXT]; [text replaceSel:[textObject textData] length:[textObject size]]; } // free temporary article [tempArticle free]; } // in this special case no graphic object is returned [icon free]; return([self free]); case NX_ALERTALTERNATE: // Embed or External is handled later option = EMBED; break; case NX_ALERTOTHER: // Embed or External is handled later option = EXTERNAL; break; } } if (strcmp(extension, "tbl") == 0) { NXRunAlertPanel(LoStr("NewsBase"), LoStr("Filename %s has bad extension"),NULL,NULL,NULL, fileName); return([self free]); } strncpy(mediaObjectName, fileName, sizeof(mediaObjectName)); STRDBG(mediaObjectName); // check if media object is already defined and create only if not defined if ((mediaObject = [[text article] dataForKey:mediaObjectName]) == nil) { if (option == UNDEFINED) { switch(NXRunAlertPanel(LoStr(MMEDITOR), LoStr("How is object to be included?"), LoStr("Embed"), LoStr("External"), NULL)) { case NX_ALERTDEFAULT: option = EMBED; break; case NX_ALERTALTERNATE: option = EXTERNAL; break; } } switch (option) { case EMBED: // embed as binary and show icon /* if (strcmp(extension, MM_FILE_EXTENSION) == 0 || strcmp(extension, MIME_FILE_EXTENSION) == 0 || strcmp(extension, NEWS_FILE_EXTENSION) == 0) { NXRunAlertPanel(LoStr(EDITOR_APP_NAME), LoStr("This capability currently not implemented."), NULL,NULL,NULL); [icon free]; return([self free]); } */ // rtf, mmd, mime and news files must be handled as binary if (strcmp(extension, "rtf") != 0 && strcmp(extension, MM_FILE_EXTENSION) != 0 && strcmp(extension, MIME_FILE_EXTENSION) != 0 && strcmp(extension, NEWS_FILE_EXTENSION) != 0 && (mediaClass = [IMediaTable mediaClassForFileExtension: extension]) != Nil && mediaClass != (Class)[IBinaryD class]) { mediaObject = [(id)mediaClass allocFromZone: [[text article] zone]]; [mediaObject initWithKey:mediaObjectName]; if ([mediaObject readFromFile:pathName] == YES) { [[text article] insertKeyedObject:mediaObject]; } else { [icon free]; return([self free]); } [icon free]; } else { mediaObject = [IBinaryD allocFromZone: [[text article] zone]]; // ownership of icon transferred to mediaObject [mediaObject setImage:icon]; // mmd requires special handling since it is not a file if (strcmp(extension, MM_FILE_EXTENSION) == 0) { // change extension from .mmd to .mime strcpy(rindex(mediaObjectName, '.') + 1, MIME_FILE_EXTENSION); [mediaObject initWithKey:mediaObjectName]; // convert mmd directory to MIME file tempArticle = [ILocalFileD loadFromName:pathName inZone:[[text article] zone]]; [tempArticle setObjectCount:[tempArticle count] - 2]; conv = [[IConvertMIME alloc] init]; stream = NXOpenMemory(NULL, 0, NX_READWRITE); [conv convertToMIME:tempArticle stream:stream]; [conv free]; [tempArticle free]; NXSeek(stream, (long)0, NX_FROMSTART); if ([mediaObject readFromStream:stream] == YES) { [[text article] insertKeyedObject:mediaObject]; NXCloseMemory(stream, NX_FREEBUFFER); } else { NXCloseMemory(stream, NX_FREEBUFFER); [icon free]; return([self free]); } } else { [mediaObject initWithKey:mediaObjectName]; if ([mediaObject readFromFile:pathName] == YES) { [[text article] insertKeyedObject:mediaObject]; } else { [icon free]; return([self free]); } } } break; case EXTERNAL: // make an external reference and show icon if (strcmp(extension, MM_FILE_EXTENSION) != 0 && strcmp(extension, MIME_FILE_EXTENSION) != 0 && strcmp(extension, NEWS_FILE_EXTENSION) != 0) { NXRunAlertPanel(LoStr(EDITOR_APP_NAME), LoStr("This capability currently not implemented."), NULL,NULL,NULL); [icon free]; return([self free]); } mediaObject = [[IExternalD allocFromZone:NXCreateZone(vm_page_size, vm_page_size, YES)] initWithDomain:[ILocalFileD domain] andPath:pathName]; [[[text article] externalsList] addObjectIfAbsent:mediaObject]; [icon free]; break; case EXPAND: case UNDEFINED: // needed to supress compiler warnings only break; } } else { [icon free]; } [mediaObject incrementReferenceCount]; [text addToArticleSize:SIZE([mediaObject size])]; // if ([mediaObject incrementReferenceCount] == 1) { // [text addToArticleSize:SIZE([mediaObject size])]; // } image = [mediaObject image]; [[[text article] objectsList] addObject:self]; return(self); } //***************************************************************************** // Create IGraphicImage from pasteboard //***************************************************************************** - (IGraphicImage *)initFromPasteboard:(Pasteboard *)pasteboard forView:(INewsBaseText *)view { const NXAtom *pasteboardType; char mediaObjectName[1024]; Class mediaClass; char *ptr; BOOL status; id loadingAlertPanel; NXModalSession loadingModalSession; text = view; for (pasteboardType = [pasteboard types]; *pasteboardType != NULL; ++pasteboardType) { if ((mediaClass = [IMediaTable mediaClassForPasteboardType: *pasteboardType]) != Nil) { break; } } if (*pasteboardType == NULL) { return([self free]); } if ([text isMultimedia] == NO) { switch(NXRunAlertPanel(LoStr("NewsBase"),LoStr("This is not a multimedia article. Change to multimedia?"),LoStr("YES"),LoStr("NO"),NULL)) { case NX_ALERTDEFAULT: [text setIsMultimedia:YES]; break; case NX_ALERTALTERNATE: return([self free]); } } sprintf(mediaObjectName, "%.256s_%d.%s", [pasteboard name], [pasteboard changeCount], [(id)mediaClass fileExtension]); for (ptr = mediaObjectName; *ptr != '\0'; ++ptr) { if (isalnum(*ptr) == 0 && *ptr != '.') { *ptr = '_'; } } if ((mediaObject = [[text article] dataForKey:mediaObjectName]) == nil) { mediaObject = [[(id)mediaClass allocFromZone:[[text article] zone]] initWithKey:mediaObjectName]; loadingAlertPanel = NXGetAlertPanel(LoStr("NewsBase"), LoStr("Loading from %s pasteboard..., please wait") , NULL, NULL, NULL,[(id)mediaClass pasteboardType]); [NXApp beginModalSession:&loadingModalSession for:loadingAlertPanel]; status = [mediaObject readFromPasteboard:pasteboard]; [NXApp runModalSession:&loadingModalSession]; [NXApp endModalSession:&loadingModalSession]; [loadingAlertPanel orderOut:self]; NXFreeAlertPanel(loadingAlertPanel); if (status == YES && [mediaObject class] != [IExternalD class]) { [[text article] insertKeyedObject:mediaObject]; } else { return([self free]); } } [mediaObject incrementReferenceCount]; [text addToArticleSize:SIZE([mediaObject size])]; // if ([mediaObject incrementReferenceCount] == 1) { // [text addToArticleSize:SIZE([mediaObject size])]; // } if ([mediaObject class] == [IExternalD class]) { [[[text article] externalsList] addObjectIfAbsent:mediaObject]; } image = [mediaObject image]; [[[text article] objectsList] addObject:self]; return(self); } - free { List *objectsList; id object; int i; BOOL multipleReferenceExists; [[[text article] objectsList] removeObject:self]; [text addToArticleSize:-SIZE([mediaObject size])]; // ## REWRITE! ## multiple external references will not be handled // correctly but this yet another reference count (must find a better way!) // Handle multiple references for external objects. if ([mediaObject class] == [IExternalD class]) { // check if there is another reference to this media object multipleReferenceExists = NO; objectsList = [[text article] objectsList]; for (i = 0; (object = [objectsList objectAt:i]) != nil; ++i) { if (mediaObject == [object mediaObject]) { multipleReferenceExists = YES; break; } } if (multipleReferenceExists == NO) { [[[text article] externalsList] removeObject:mediaObject]; [mediaObject free]; mediaObject = nil; } } // There are two cases where the graphics image object is freeded. // The graphic image object is being deleted using the editor and // the underlying text object is being freeded. In the first case // the underlying media object should be freeded in the second // case the underlying media object should be retained with a zero // reference count as the article may be externally referenced. // The only exception are externals because they are not in the // IArticleD. if (mediaObject != nil && [text textIsFreeing] == NO && [mediaObject decrementReferenceCount] == 0) { // [text addToArticleSize:-SIZE([mediaObject size])]; [[text article] removeObject:mediaObject]; [mediaObject free]; } return [super free]; } - calcCellSize:(NXSize *)theSize { /* our graphic image determines our size */ [image getSize:theSize]; return self; } - highlight:(const NXRect *)cellFrame inView:controlView lit:(BOOL)flag { if (highlighted != flag) { highlighted = flag; /* toggle highlighting */ // NXHighlightRect(cellFrame); /* make change visible */ [[controlView window] flushWindow]; } return self; } //***************************************************************************** // draw image in text view //***************************************************************************** #define RESIZE_BUTTON_SIZE 15 - drawSelf:(const NXRect *)cellFrame inView:controlView { NXPoint point; NXRect resizeButtonRect; char buffer[64]; NXSize size; origin = cellFrame->origin; origin.y += cellFrame->size.height; /* * the text object expects us not to modify the current graphics state, so * we'll save it */ PSgsave(); /* clear our rect using the text object's background gray and transparancy */ PSsetgray([controlView backgroundGray]); if ([controlView isOpaque]) { PSsetalpha(1); } else { PSsetalpha(0); } NXRectFill(cellFrame); /* we're in a flipped coordinate system */ point = cellFrame->origin; point.y += cellFrame->size.height; /* draw the image */ [image composite:NX_SOVER toPoint:&point]; // check if this image is currently selected for resizing or subimaging if (imageResizingEnabled == YES) { // resizing mode on so draw resizing button // first draw larger white square in lower right corner resizeButtonRect.origin.x = cellFrame->origin.x + cellFrame->size.width - RESIZE_BUTTON_SIZE; resizeButtonRect.origin.y = cellFrame->origin.y + cellFrame->size.height - RESIZE_BUTTON_SIZE; resizeButtonRect.size.width = resizeButtonRect.size.height = RESIZE_BUTTON_SIZE; PSsetgray(NX_WHITE); NXRectFill(&resizeButtonRect); // then draw smaller black square in lower right corner resizeButtonRect.origin.x = cellFrame->origin.x + cellFrame->size.width - (2 * RESIZE_BUTTON_SIZE / 3); resizeButtonRect.origin.y = cellFrame->origin.y + cellFrame->size.height - (2 * RESIZE_BUTTON_SIZE / 3); resizeButtonRect.size.width = resizeButtonRect.size.height = 2 * RESIZE_BUTTON_SIZE / 3; PSsetgray(NX_BLACK); NXRectFill(&resizeButtonRect); // now display image charecteristics in upper left corner [image getSize:&size]; if (size.width > 100.0 && size.height > 100.0) { // paint background white PSsetgray(NX_WHITE); // PSsetrgbcolor(0.0, 255.0, 0.0); PSrectfill(cellFrame->origin.x, cellFrame->origin.y, 72.0, 48.0); // show file type sprintf(buffer, "%s", [[mediaObject class] fileExtension]); PSmoveto((float)(cellFrame->origin.x + 2.0), (float)(cellFrame->origin.y + 14.0)); PSsetgray(NX_BLACK); PSshow(buffer); // file size sprintf(buffer, "%dbytes", [mediaObject size]); PSmoveto((float)(cellFrame->origin.x + 2.0), (float)(cellFrame->origin.y + 31.0)); PSshow(buffer); // image dimensions sprintf(buffer, "%.0f x %.0f", size.width, size.height); PSmoveto((float)(cellFrame->origin.x + 2.0), (float)(cellFrame->origin.y + 48.0)); PSshow(buffer); PSsetrgbcolor(0.0, 0.0, 0.0); } } if (isActiveEmbeddedView) { drawRect((float)(cellFrame->origin.x), (float)(cellFrame->origin.y), (float)(cellFrame->size.width), (float)(cellFrame->size.height), 0.5, 10); } /* restore the graphics state */ PSgrestore(); return self; } //***************************************************************************** // Handle mouse events over image of IGraphicImage //***************************************************************************** // Mouse events can specify: // 1) toggle on/off resize/subimage mode - double click on graphic // 2) play/open object - double click on icon // 3) drag and drop from text - mouse down and drag // 4) resize (in resize/subimage mode) - mouse down and drag on button // 5) subimage (in resize/subimage mode) - mouse down and drag in image - trackMouse:(NXEvent *)theEvent inRect:(const NXRect *)cellFrame ofView:controlView { NXEvent initialMouseEvent; NXPoint initialMouseLocation; int oldMask; NXEvent *event, *nextEvent; NXPoint mouseLocation; BOOL mouseInCell = NO; NXRect rect; char stringBuffer[512]; const char *filename; NXStream *stream; NXTrackingTimer timer; NXRect resizeButtonRect; double x0, y0; enum {RESIZE, EDIT} operation; NXRect sourceRect, targetRect; NXBitmapImageRep *sourceBitmap; JFIFBitmap *tempBitmap; NXImage *targetImage; NXStream *targetStream; ITiffD *tiffObject; char tiffObjectName[512], *ptr; static int resizeCount = 0; NXRect frame; float heightToWidthRatio; NXPoint displayPoint; const char *option; const char *factorString; int factor; id compressingAlertPanel; NXModalSession compressingModalSession; char buffer[64]; initialMouseEvent = *theEvent; initialMouseLocation = initialMouseEvent.location; [controlView convertPoint:&initialMouseLocation fromView:NULL]; mouseLocation = initialMouseLocation; if (initialMouseEvent.type == NX_MOUSEDOWN && initialMouseEvent.data.mouse.click == 1) { if (imageResizingEnabled == YES) { // Image resize/subimage mode has been toggled on so // this is a resize or subimage operation // check if mouse down is in resize rectangle or elsewhere in // image to determine operation type resizeButtonRect.origin.x = cellFrame->origin.x + cellFrame->size.width - RESIZE_BUTTON_SIZE; resizeButtonRect.origin.y = cellFrame->origin.y + cellFrame->size.height - RESIZE_BUTTON_SIZE; resizeButtonRect.size.width = resizeButtonRect.size.height = RESIZE_BUTTON_SIZE; if (NXPointInRect(&mouseLocation, &resizeButtonRect) == YES) { // mouse down in resize button and drag is a resize operation operation = RESIZE; x0 = cellFrame->origin.x; y0 = cellFrame->origin.y; heightToWidthRatio = cellFrame->size.height / cellFrame->size.width; } else { // mouse down in rest of image is a subimage operation operation = EDIT; x0 = mouseLocation.x; y0 = mouseLocation.y; } oldMask = [[controlView window] addToEventMask:NX_MOUSEDRAGGEDMASK]; [controlView lockFocus]; event = [NXApp getNextEvent:NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK]; while (event->type != NX_MOUSEUP) { [controlView display:NULL :0]; mouseLocation = event->location; [controlView convertPoint:&mouseLocation fromView:NULL]; if (mouseLocation.x - x0 > 0.0 && mouseLocation.y - y0 > 0.0) { if (operation == RESIZE) { // draw a gray rectangle that preserves the aspect // (height / width) ratio with new width drawRect((float)x0, (float)y0, (float)(mouseLocation.x - x0), (float)(heightToWidthRatio * (mouseLocation.x - x0)), 0.5, 5); } // draw new rectangle in black with white border drawRect((float)x0, (float)y0, (float)(mouseLocation.x - x0), (float)(mouseLocation.y - y0), 1.0, 5); drawRect((float)x0, (float)y0, (float)(mouseLocation.x - x0), (float)(mouseLocation.y - y0), 0.0, 3); // show new length and width in purple PSsetgray(NX_WHITE); // PSsetrgbcolor(0.0, 255.0, 0.0); PSrectfill(mouseLocation.x - 62.0, mouseLocation.y - 17.0, 62.0, 17.0); sprintf(buffer, "%.0f x %.0f", mouseLocation.x - x0, mouseLocation.y - y0); PSmoveto((float)(mouseLocation.x - 60.0), (float)(mouseLocation.y - 5.0)); PSsetgray(NX_BLACK); // PSsetrgbcolor(255.0, 0.0, 255.0); PSshow(buffer); PSsetrgbcolor(0.0, 0.0, 0.0); [[controlView window] flushWindow]; } NXPing(); event = [NXApp getNextEvent:NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK]; // skip some mouse dragged events for better performance while (event->type != NX_MOUSEUP && (nextEvent = [NXApp getNextEvent:NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK waitFor:0.0 threshold:NX_MODALRESPTHRESHOLD]) != NULL) { event = nextEvent; } } [controlView unlockFocus]; // determine the new size or subimage from initial and final // mouse location switch (operation) { case RESIZE: sourceRect.origin.x = sourceRect.origin.y = 0.0; [image getSize:&sourceRect.size]; targetRect.size.width = mouseLocation.x - cellFrame->origin.x; targetRect.size.height = mouseLocation.y - cellFrame->origin.y; break; case EDIT: sourceRect.origin.x = initialMouseLocation.x - cellFrame->origin.x; sourceRect.origin.y = cellFrame->size.height - (mouseLocation.y - cellFrame->origin.y); sourceRect.size.width = mouseLocation.x - initialMouseLocation.x; sourceRect.size.height = mouseLocation.y - initialMouseLocation.y; targetRect.size = sourceRect.size; break; } targetRect.origin.x = targetRect.origin.y = 0.0; // if mouse moved to left or up interpret that as a cancel // request by the user if (targetRect.size.width <= 0.0 || targetRect.size.height <= 0.0 || sourceRect.size.width <= 0.0 || sourceRect.size.height <= 0.0) { return(self); } imageResizingEnabled = NO; // get source image [image lockFocus]; sourceBitmap = [[NXBitmapImageRep allocFromZone: [[text article] zone]] initData:NULL fromRect:&sourceRect]; [image unlockFocus]; // resize (maybe) targetImage = [[NXImage allocFromZone:[[text article] zone]] initSize:&targetRect.size]; [targetImage useCacheWithDepth:NX_TwentyFourBitRGBDepth]; [targetImage lockFocus]; [sourceBitmap drawIn:&targetRect]; [targetImage unlockFocus]; [sourceBitmap free]; // display temporary image [controlView lockFocus]; switch (operation) { case RESIZE: displayPoint.x = cellFrame->origin.x; displayPoint.y = mouseLocation.y; break; case EDIT: displayPoint.x = initialMouseLocation.x; displayPoint.y = mouseLocation.y; break; } PSsetgray([controlView backgroundGray]); if ([controlView isOpaque]) { PSsetalpha(1); } else { PSsetalpha(0); } NXRectFill(cellFrame); [targetImage composite:NX_SOVER toPoint:&displayPoint]; drawRect((float)x0, (float)y0, (float)(mouseLocation.x - x0), (float)(mouseLocation.y - y0), 0.0, 5); [controlView unlockFocus]; [[controlView window] flushWindow]; // now make compressed stream [targetImage lockFocus]; tempBitmap = [[JFIFBitmap allocFromZone:[[text article] zone]] initData:NULL fromRect:&targetRect]; [targetImage unlockFocus]; [targetImage free]; targetStream = NXOpenMemory(NULL, 0, NX_READWRITE); if ((option = NXGetDefaultValue(OWNER, JFIF_OR_TIFF)) != 0 && strcmp(option, "TIFF") == 0) { if ([tempBitmap bitsPerSample] >= 4) { // JPEG in TIFF if ((factorString = NXGetDefaultValue(OWNER, JPEGCOMPRESSIONFACTOR)) != 0) { factor = atoi(factorString); } else { factor = 10; } compressingAlertPanel = NXGetAlertPanel(LoStr(MMEDITOR), LoStr("Compressing using JPEG to TIFF with factor" " %s... please wait"), NULL, NULL, NULL, factorString); [NXApp beginModalSession:&compressingModalSession for:compressingAlertPanel]; [tempBitmap writeTIFF:targetStream usingCompression:NX_TIFF_COMPRESSION_JPEG andFactor:(float)factor]; } else { // not enough depth for jpeg do lzw instead compressingAlertPanel = NXGetAlertPanel(LoStr(MMEDITOR), LoStr("Compressing using LZW to TIFF..." " please wait"), NULL, NULL, NULL); [NXApp beginModalSession:&compressingModalSession for:compressingAlertPanel]; [tempBitmap writeTIFF:targetStream usingCompression:NX_TIFF_COMPRESSION_LZW andFactor:0.0]; } tiffObject = [ITiffD allocFromZone:[[text article] zone]]; } else { if ([tempBitmap bitsPerSample] == 8) { // JPEG if ((factorString = NXGetDefaultValue(OWNER, JPEGCOMPRESSIONFACTOR)) != 0) { factor = atoi(factorString); } else { factor = 75; } compressingAlertPanel = NXGetAlertPanel(LoStr(MMEDITOR), LoStr("Compressing to JPEG with factor" " %s... please wait"), NULL, NULL, NULL, factorString); [NXApp beginModalSession:&compressingModalSession for:compressingAlertPanel]; [tempBitmap writeJFIF:targetStream usingQuality:factor]; tiffObject = [IJfifD allocFromZone:[[text article] zone]]; } else { // not enough depth for jpeg do lzw instead compressingAlertPanel = NXGetAlertPanel(LoStr(MMEDITOR), LoStr("Compressing using LZW to TIFF..." " please wait"), NULL, NULL, NULL); [NXApp beginModalSession:&compressingModalSession for:compressingAlertPanel]; [tempBitmap writeTIFF:targetStream usingCompression:NX_TIFF_COMPRESSION_LZW andFactor:0.0]; tiffObject = [ITiffD allocFromZone:[[text article] zone]]; } } [tempBitmap free]; strncpy(tiffObjectName, [mediaObject key], sizeof(tiffObjectName) - 32); tiffObjectName[sizeof(tiffObjectName) - 32] = '\0'; if ((ptr = rindex(tiffObjectName, '.')) != 0) { *ptr = '\0'; } if ((ptr = rindex(tiffObjectName, '#')) != 0) { *ptr = '\0'; } ptr = index(tiffObjectName, '\0'); // must give this a object a new name; use sequence no to insure // uniqueness sprintf(ptr, "#%d.%.16s", resizeCount++, [[tiffObject class] fileExtension]); [tiffObject initWithKey:tiffObjectName]; if ([tiffObject readFromStream:targetStream] == YES) { [text addToArticleSize:-SIZE([mediaObject size])]; if (mediaObject != nil && [mediaObject decrementReferenceCount] == 0) { // [text addToArticleSize:-SIZE([mediaObject size])]; [[text article] removeObject:mediaObject]; [mediaObject free]; } mediaObject = tiffObject; [[text article] insertKeyedObject:mediaObject]; [mediaObject incrementReferenceCount]; [text addToArticleSize:SIZE([mediaObject size])]; image = [mediaObject image]; [controlView getFrame:&frame]; [controlView calcLine]; [compressingAlertPanel orderOut:self]; [NXApp endModalSession:&compressingModalSession]; NXFreeAlertPanel(compressingAlertPanel); [controlView display:NULL :0]; [controlView setNeedsSaving:YES]; } else { imageResizingEnabled = YES; [controlView display:NULL :0]; } NXCloseMemory(targetStream, NX_FREEBUFFER); return(self); } else { // drag & drop or first click of double click /* we want to grab mouse dragged events */ oldMask = [[controlView window] addToEventMask:NX_MOUSEDRAGGEDMASK]; // look for mouse-up, mouse-dragged or tiemout events // need artificial events for timeout to work NXBeginTimer(&timer, 0.25, 0.1); event = [NXApp getNextEvent:NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK | NX_TIMERMASK]; while (event->type != NX_MOUSEUP && event->type != NX_TIMER) { /* mouse-dragged event; highlight if mouse is in cell bounds */ mouseLocation = event->location; [controlView convertPoint:&mouseLocation fromView:NULL]; mouseInCell = NXPointInRect(&mouseLocation, cellFrame); if (mouseInCell != highlighted) { /* we have to lock focus before calling hightlight:inView:lit:*/ [controlView lockFocus]; [self highlight:cellFrame inView:controlView lit:mouseInCell]; [controlView unlockFocus]; } if (mouseInCell == NO) { break; } event = [NXApp getNextEvent:NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK | NX_TIMERMASK]; } NXEndTimer(&timer); /* turn off any highlighting */ [controlView lockFocus]; [self highlight:cellFrame inView:controlView lit:NO]; [controlView unlockFocus]; /* reset the event mask */ [[controlView window] setEventMask:oldMask]; if (event->type != NX_MOUSEUP) { // drag & drop if ([mediaObject class] == [IExternalD class]) { return(self); } sprintf(stringBuffer, "/tmp/%.506s", [mediaObject key]); filename = NXCopyStringBuffer(stringBuffer); stream = NXOpenMemory(NULL, 0, NX_WRITEONLY); [mediaObject writeToStream:stream]; NXSaveToFile(stream, filename); // Add executable permission if neccessary sprintf(stringBuffer, "file %s | grep executable > /dev/null", filename); if (system(stringBuffer) == 0) { chmod(filename, 0700); } else { chmod(filename, 0600); } NXCloseMemory(stream, NX_FREEBUFFER); rect.origin.x = mouseLocation.x - 24.0; rect.origin.y = mouseLocation.y - 24.0; rect.size.width = 48.0; rect.size.height = 48.0; [controlView dragFile:filename fromRect:&rect slideBack:YES event:&initialMouseEvent]; [controlView setOwnerOfDraggedIconToMe:YES]; // ## REWRITE! ## very bad to use externs // schedule file for deletion [listOfTempFiles insertKey:filename value:nil]; [IFileIconButton perform:@selector(unlink:) with:(id)filename afterDelay:600000 cancelPrevious:NO]; } return(self); } } else if (NXPointInRect(&mouseLocation, cellFrame) && initialMouseEvent.data.mouse.click == 2) { /* if a double-click and the mouse is over us, do something */ if ([mediaObject performDoubleClickAction:self] == nil) { // this is an image so toggle resizing if (imageResizingEnabled == NO) { if ([controlView isEditable] == NO || [controlView hasEmbeddedView] == YES) { if ([[controlView delegate] textWillChange:self] == YES) { return(self); } } imageResizingEnabled = YES; [controlView display:NULL :0]; } else { imageResizingEnabled = NO; [controlView display:NULL :0]; } } return(self); } return(self); } //***************************************************************************** // create IGraphicImage from rtf stream //***************************************************************************** - readRichText:(NXStream *)stream forView:view { char mediaObjectName[MAXPATHLEN + 16]; const char *fileName; char path[MAXPATHLEN]; Class MediaClass; const char *extension; text = view; NXScanf(stream, "%[^\n]", mediaObjectName); STRDBG(mediaObjectName); NXScanf(stream, "%*[\n]"); if ([text isKindOf:[INewsBaseText class]] == NO) { NXRunAlertPanel(LoStr("NewsBase"), LoStr("Paste failed.Text object cannot read graphic object."), NULL,NULL,NULL); mediaObject = nil; image = nil; return(self); } if ((fileName = rindex(mediaObjectName, '/')) == 0) { fileName = mediaObjectName; } else { ++fileName; } // Internal media objects must already exists but external objects may not // already exists so check if external object already exists and create // if doesn't. External Objects do not respond to readFromStream // message and this may be used to differentiate them from internal // media objects. if ((mediaObject = [[text article] dataForKey:fileName]) == nil) { // object doesn't exists extension = rindex(mediaObjectName, '.'); if (extension != 0 && (MediaClass = [IMediaTable mediaClassForFileExtension: NXUniqueString(extension + 1)]) != Nil && [(id)MediaClass instancesRespondTo:@selector(readFromStream:)] == NO) { // non-existant external object so create it strcpy(path, mediaObjectName); *rindex(path, '.') = '\0'; mediaObject = [[IExternalD allocFromZone:NXCreateZone(vm_page_size, vm_page_size, YES)] initWithDomain:[(id)MediaClass domain] andPath:path]; [[[text article] externalsList] addObjectIfAbsent:mediaObject]; } else { // non-existant internal object is an error NXRunAlertPanel(LoStr("NewsBase"),LoStr("no Media object for %s"), NULL,NULL,NULL, fileName); mediaObject = nil; image = nil; return(self); } } [mediaObject incrementReferenceCount]; [text addToArticleSize:SIZE([mediaObject size])]; // if ([mediaObject incrementReferenceCount] == 1) { // [text addToArticleSize:SIZE([mediaObject size])]; // } image = [mediaObject image]; [[[text article] objectsList] addObject:self]; return(self); } // writeRichText:forView: Since the data for the object is separately stored // just write out the name of the object. - writeRichText:(NXStream *)stream forView:view { const char *key; if (mediaObject == nil) { NXPrintf(stream, "nil\n"); return(self); } if ((key = [mediaObject key]) != NULL) { NXPrintf(stream, "%s\n", key); } else { NXPrintf(stream, "nil\n"); } // maintain list of objects written if (list != nil) { [list addObjectIfAbsent:mediaObject]; } if (counter != NULL) { ++*counter; } return(self); } - mediaObject { return(mediaObject); } - (View *)view { return((View *)text); } - getOrigin:(NXPoint *)anOrigin { *anOrigin = origin; return self; } - setIsActiveEmbeddedView:(BOOL)flag { isActiveEmbeddedView = flag; return(self); } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.