/*$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
