ftp.nice.ch/pub/next/connectivity/news/NewsBase.3.02.s.tar.gz#/NewsBase302.source/MMEdit/IMMEditor.m

This is IMMEditor.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.
$*/

//*****************************************************************************
// IMMEditor manages the editor window, the header view and body view.  The
// editor window is used both for viewing and editing.  IMMEditor is
// responsible for receiving articles to be viewed and sending articles to be
// posted.  The article is an IArticleD and is owned by IMMEditor.  There are
// two types of of articles: plain text and rich text with embedded objects.
// A plain text article has exactly two elements in the IArticleD.  They
// are InfoD object containing the header and a ITextD object containing the
// body as plain text.  An article with rich text with embedded objects has
// an InfoD object containing the header, a ITextD object containing the body 
// as rich text and media objects which are referenced by control words in
// the rich text.  Each media object is identified by a name.  The control
// words in the rich text body references media object by their names.
// The editor uses an INewsBaseText object and an IHeaderFormScrollView 
// object to operate on the body view and header view, respectively.
// Most of the editing capabilities are implemented by the INewsBaseText
// class.  
//
// The important entry points into IMMEditor are:
//      - displayArticle             used to display existing articles
//      -editNewArticle              used to edit a new article
//*****************************************************************************

#import "IMMEditor.h"
#import <appkit/publicWraps.h>		// for NXConvertWinNumToGlobal()
#import <appkit/Listener.h>
#import <appkit/Speaker.h>
#import <appkit/Application.h>
#import <appkit/Text.h>
#import <appkit/Window.h>
#import <appkit/Font.h>
#import <appkit/graphics.h>
#import <appkit/Panel.h>
#import <appkit/PrintInfo.h>
#import <defaults/defaults.h>
#import <mach/mach.h>
#import <objc/zone.h>
#import <libc.h>
#import <time.h>
#import <ctype.h>
#import <sys/time.h>
#import <sys/types.h>
#import <stdio.h>
#import <strings.h>
#import <appkit/Form.h>
#import <streams/streams.h>
#import <appkit/NXImage.h>
#import <appkit/Matrix.h>
#import <objc/hashtable.h>
#import "IAppDelegate.h"
#import "IMediaTable.h"
#import "IGraphicImage.h"
#import "ITextD.h"
#import "InfoD.h"
#import "data_types.h"
#import "INewScroller.h"
#import "IHeaderFormScrollView.h"
#import "ILocalFileD.h"
#import <appkit/nextstd.h>
#import "errdebug.h"

#import "Localization.h"

#define LoStr(key)      doLocalString(NULL,key,NULL)

// Position of most recent editor window; negative means undefined
static NXCoord topLeftX = - 1.0, topLeftY = -1.0;

@implementation IMMEditor

/*
+ initialize
{
    [Text registerDirective:RTF_CONTROL_WORD forClass:[IGraphicImage class]];
    return(self);
}
*/

- init
{
    selectionList = [[IArticleD allocFromZone:[self zone]] initWithKey:""];
    selectionRTFText = nil;
    selectionPlainText = nil;
    selectionHeader = nil;
    strcpy(selectionFileName, TMP_DIR);
    return(self);
}

// [IMMEditor displayArticle:] displays the article.  the ownership of the
// article is transferred to the receiver.

- displayArticle:theArticle
{
    DBG(1, ;);
    article = theArticle;
    [self displayBody];
    [self displayHeader];
    [[window contentView] displayFromOpaqueAncestor:NULL :0 :YES];
    [window makeKeyAndOrderFront:self];
    return(self);
}

//****************************************************************************
// determine size and location of window
//****************************************************************************
- setSizeAndPosition
{
    const char* widthAndHeight;
    NXCoord width, height;
    const char* location;
    NXSize screen;
    const char* editorViewSizes;
    NXCoord attributeWidth, attributeHeight, headerWidth, headerHeight,
            bodyWidth, bodyHeight;

    if ((widthAndHeight = NXGetDefaultValue(OWNER, EDITOR_SIZE)) != 0 &&
        sscanf(widthAndHeight, "%f:%f", &width, &height) == 2) {
        [window sizeWindow:width :height];
    }
    // if previous window exists offset new window so both are visible
    // topLeftX is top left position of previously created window
    // topLeftX == -1.0 means no previous window
    if (topLeftX >= 0.0) {
        [NXApp getScreenSize:&screen];
        // offset new window; if window goes off the screen then move
        // window to opposite edge
        if ((topLeftX += 30) > screen.width - width - 100) {
            topLeftX = 100.0;
        }
        if ((topLeftY -= 30) < height + 10) {
            topLeftY = screen.height - 10.0;
        }
   } else if ((location = NXGetDefaultValue(OWNER, EDITOR_LOCATION)) != 0 &&
        sscanf(location, "%f:%f", &topLeftX, &topLeftY) == 2) {
    }
    [window moveTopLeftTo:topLeftX :topLeftY];
    if ((editorViewSizes = NXGetDefaultValue(OWNER, EDITOR_VIEW_SIZES)) != 0 &&
        sscanf(editorViewSizes, "(%f,%f):(%f,%f):(%f,%f)",
            &attributeWidth, &attributeHeight, &headerWidth, &headerHeight,
            &bodyWidth, &bodyHeight) == 6) {
        [oAttributeView sizeTo:attributeWidth :attributeHeight];
        [oHeaderView sizeTo:headerWidth :headerHeight];
        [oBodyView sizeTo:bodyWidth :bodyHeight];
    }
    return(self);
}

//****************************************************************************
// display body for retrieved article
//****************************************************************************

- displayBody
{
    ITextD *bodyITextD;
    NXRect rect;
    BOOL isMultimedia;
    const char *fontNameAndSize;
    char fontName[128];
    float fontSize;
    Font *fontObj;

    if ((bodyITextD = [article objectWithKey:INDEX_RTF]) != nil) {
        isMultimedia = YES;
    } else if ((bodyITextD = [article objectWithKey:PLAINTEXT]) != nil) {
        isMultimedia = NO;
    } else {
        return(self);
    }
    rect.origin.x = rect.origin.y = 0.0;
    [oBodyView getContentSize:&rect.size];
    if ([bodyITextD width] != 0) {
        rect.size.width = [bodyITextD width];
    }
    bodyText = [[INewsBaseText allocFromZone:[self zone]] initFrame:&rect];
    [[oBodyView setDocView:bodyText] free];
    [bodyText setHorizResizable:NO];
    [[bodyText superview] setAutoresizeSubviews:YES];
    [bodyText setAutosizing:NX_WIDTHSIZABLE];
    [bodyText notifyAncestorWhenFrameChanged: YES];
    [bodyText setVertResizable:YES];
    [bodyText setMinSize:&rect.size];
    rect.size.width = 1.0e30;
    rect.size.height = 1.0e30;
    [bodyText setMaxSize:&rect.size];
    [bodyText setDelegate:self];
    [bodyText setOpaque:YES];
    [bodyText setBackgroundGray:NX_WHITE];
    [oBodyView setBackgroundGray:NX_WHITE];
    [bodyText setIsMultimedia:isMultimedia];
    [bodyText setIsEditable:NO];
    [bodyText setNeedsSaving:NO];
    [bodyText setArticleSizeField:oArticleSizeField];
    [bodyText setPlainMultimediaButton:oPlainMultimediaButton];
    [bodyText setArticle:article];
    [bodyText setIsMultimedia:isMultimedia];
    if (isMultimedia == YES) {
        [bodyITextD writeRichTextStreamTo:bodyText];
    } else {
        if ((fontNameAndSize = NXGetDefaultValue(OWNER, FONT)) != 0 &&
            sscanf(fontNameAndSize, "%127[^:]:%f", fontName, &fontSize) == 2 &&
            (fontObj = [Font newFont:fontName size:fontSize]) != nil) {
        } else {
            fontObj = [Font newFont:"FixedRyuminCourier-Light" size:14.0];
        }
        [bodyText setFont:fontObj];
        [bodyText setMonoFont:YES];
        [bodyITextD writeTextStreamTo:bodyText];
    }
    [bodyText setSelectionFileName:selectionFileName];
    [self registerEditWindow];
    [bodyText displaySize];
    return(self);
}

//****************************************************************************
// display header for retrieved article
//****************************************************************************

- displayHeader
{
    InfoD *header;
    NXHashState state;
    char *key, *value;
    char title[64];
    const char *infoptr;
    static char pictureFile[MAXPATHLEN];    

    headerForm = [[IHeaderFormScrollView allocFromZone:[self zone]] init];
    if ((header = [article dataForKey:HEADER_INFO]) == nil) {
        [headerForm setStringValue:"THIS ARTICLE HAS NO HEADER!"
        atTitle:"Warning" setEnabled:NO];
        [headerForm setStringValue:"Untitled" atTitle:SUBJECT setEnabled:NO];
        sprintf(title, "Editor:%.21s...", "Untitled");
    } else {
        [headerForm setStringValue:infoptr = [header infoForKey:SUBJECT]
            atTitle:SUBJECT setEnabled:NO];
        [headerForm setStringValue:infoptr = [header infoForKey:FROM]
            atTitle:FROM setEnabled:NO];
        [headerForm setStringValue:infoptr = [header infoForKey:ORGANIZATION]
            atTitle:ORGANIZATION setEnabled:NO];
        [headerForm setStringValue:infoptr = [header infoForKey:DATE]
            atTitle:DATE setEnabled:NO];

        [headerForm setStringValue:infoptr = [header infoForKey:GROUPNAME]
            atTitle:GROUPNAME setEnabled:NO];
        [headerForm setStringValue:infoptr = [header infoForKey:REFERENCES]
            atTitle:REFERENCES setEnabled:NO];
        state = [header initState];
        while ([header nextState:&state key:(const void **)&key
            value:(void **)&value]) {
            [headerForm setStringValue:value atTitle:key setEnabled:NO];
            DBG(10, fprintf(stderr, "%s %s\n", key, value));
        }
        sprintf(title, "MMEdit:%.21s...", (char *)[header infoForKey:SUBJECT]);
    }
    [headerForm initScrollView:oHeaderView];
    [headerForm showForm:self];
    // show sender's picture
    sprintf(pictureFile, "/LocalLibrary/Images/People/%s.tiff",
        [header infoForKey:FROM]);
    picture = [[NXImage allocFromZone:[self zone]] initFromFile:pictureFile];
    [oPicture setImage:picture];
    [window setTitle:title];
    return(self);
}

- redisplayArticle
{
    [oBodyView display];
    return(self);
}

//****************************************************************************
//print article
//****************************************************************************

- printArticle
{
	id	headerView, bodyView, offScreenWindow, compositeView;
	NXRect	windowRect, headerRect, bodyRect, contentRect, *pageRect;
	id	textD_index;
	id	pinfo;
	NXStream 	*stream;
	InfoD	*headerInfo;
	NXHashState	state;
	char		*key,	*value;
        const char *fontNameAndSize;
        char fontName[128];
        float fontSize;
        id fontObj;
        char buffer[32];
	
        sprintf(buffer, "%f", [[NXApp printInfo] scalingFactor]);
        NXWriteDefault(OWNER, PRINT_INFO_SCALING_FACTOR, buffer);
        [self sendArticle];
	pinfo = [NXApp printInfo];
	pageRect = (NXRect *)[pinfo paperRect];
	[pinfo setVertCentered: NO];
	windowRect.origin.x = 0.0;
	windowRect.origin.y = 0.0;
	windowRect.size.width = pageRect->size.width/[pinfo scalingFactor];
	windowRect.size.height = pageRect->size.height/[pinfo scalingFactor];
	headerView = [[Text allocFromZone: [self zone]] initFrame: &windowRect];
	bodyView = [[INewsBaseText allocFromZone:[self zone]]
            initFrame:&windowRect];
	[bodyView setArticle:article];
	[headerView setVertResizable: YES];
	[bodyView setVertResizable: YES];
	offScreenWindow = [[Window allocFromZone:[self zone]]
            initContent:&windowRect style:NX_PLAINSTYLE backing:NX_BUFFERED
            buttonMask:NX_CLOSEBUTTONMASK | NX_MINIATURIZEBUTTONMASK defer:NO];
	[offScreenWindow setBackgroundGray: NX_WHITE];
	
	/* Load header into stream */
	stream = NXOpenMemory(NULL, 0, NX_READWRITE);
	headerInfo = [article dataForKey: HEADER_INFO];
	state = [headerInfo initState];
	while ([headerInfo nextState: &state key:(const void **)&key
            value:(const void **)&value]) {
            NXPrintf(stream, "%s: %s\n", key, value);
	}
	/* Load header viewer from stream */
	NXSeek(stream, 0, NX_FROMSTART);
	[headerView readText: stream];
	[headerView sizeToFit];
	NXCloseMemory(stream, NX_FREEBUFFER);
	
	if ((textD_index = [article dataForKey: PLAINTEXT]) != nil) {
                if ((fontNameAndSize = NXGetDefaultValue(OWNER, FONT)) != 0 &&
                    sscanf(fontNameAndSize, "%127[^:]:%f", fontName, &fontSize) == 2 &&
                    (fontObj = [Font newFont:fontName size:fontSize]) != nil) {
                } else {
                    fontObj = [Font newFont:"FixedRyuminCourier-Light" size:10.0];
                }
		[bodyView setFont:fontObj];
		[bodyView setMonoFont:YES];
		[textD_index writeTextStreamTo: bodyView];
		[bodyView sizeToFit];
	} else if ((textD_index = [article dataForKey:INDEX_RTF]) != nil) {
		/* if multimedia document */
		[bodyView setMonoFont: NO];
		[textD_index writeRichTextStreamTo: bodyView];
		[bodyView sizeToFit];
	} else {
            return(self);
        }
	
	compositeView = [offScreenWindow contentView];
	[compositeView setAutosizing: NX_HEIGHTSIZABLE];
	[compositeView setFlipped: YES];
	
	[headerView getFrame: &headerRect];
	[bodyView getFrame: &bodyRect];
	bodyRect.origin.y = headerRect.size.height;
	contentRect.size.width = windowRect.size.width;
	contentRect.size.height = bodyRect.size.height + headerRect.size.height;
	contentRect.origin.x = windowRect.origin.x;
	contentRect.origin.y = windowRect.origin.y;
	[bodyView setFrame: &bodyRect];
	[offScreenWindow sizeWindow:contentRect.size.width
            :contentRect.size.height];
	[compositeView setFrame: &contentRect];
	[compositeView addSubview: headerView];
	[compositeView addSubview: bodyView];
	
	DBG(20,{	
		char	inputChar=' ';
		while(inputChar!='y' && inputChar!='n') {
		    printf(" output to printer(y or n)=");
		    fscanf(stdin,"%c",&inputChar);
		}
		if(inputChar=='n') {
		    [offScreenWindow display];
		    [offScreenWindow orderFront:self];
		    return self;
		}
	    });
	[offScreenWindow printPSCode: self];
//	[offScreenWindow free];
	
	return self;
	
}

/* outlet initialization */

- setOSplitView:splitView
{
    oSplitView = splitView;
    if (oAttributeView != nil && oHeaderView != nil && oBodyView != nil) {
        [oSplitView addSubview:oAttributeView];
        [oSplitView addSubview:oHeaderView];
        [oSplitView addSubview:oBodyView];
    }
    return(self);
}

- setOBodyView:bodyView
{
    oBodyView = bodyView;
    [oBodyView setBorderType: NX_BEZEL];
    [oBodyView setVertScrollerRequired:YES];
    [oBodyView setHorizScrollerRequired:YES];
    if (oSplitView != nil && oAttributeView != nil && oHeaderView != nil) {
        [oSplitView addSubview:oAttributeView];
        [oSplitView addSubview:oHeaderView];
        [oSplitView addSubview:oBodyView];
    }
    window = [oBodyView window];
    [window setDepthLimit:NX_TwentyFourBitRGBDepth];
    return(self);
}

- setOHeaderView:headerView
{
    oHeaderView = headerView;
    if (oSplitView != nil && oAttributeView != nil && oBodyView != nil) {
        [oSplitView addSubview:oAttributeView];
        [oSplitView addSubview:oHeaderView];
        [oSplitView addSubview:oBodyView];
    }
    return(self);
}

- setOAttributeView:attributeView
{
    oAttributeView = attributeView;
    if (oSplitView != nil && oHeaderView != nil && oBodyView != nil) {
        [oSplitView addSubview:oAttributeView];
        [oSplitView addSubview:oHeaderView];
        [oSplitView addSubview:oBodyView];
    }
    return(self);
}

- setOPlainMultimediaButton:button
{
    oPlainMultimediaButton = button;
    [oPlainMultimediaButton setTag:0];
    return(self);
}

- setOSelectionButton:button
{
    oSelectionButton = button;
    [oSelectionButton setTag:1];
    return(self);
}


- editNewArticle
{
    DBG(1, ;);
    [self makeNewArticleHeader];
    [self makeNewArticleBody];
    [headerForm setTarget:bodyText andAction:@selector(setNeedsSaving:)];
    [self setSizeAndPosition];
    [[window contentView] displayFromOpaqueAncestor:NULL :0 :YES];
    [window makeKeyAndOrderFront:self];
    [headerForm selectCellAtTitle:GROUPNAME];
    return(self);
}

- makeNewArticleHeader
{
    [self makeNewArticleHeaderWithNewsGroup:"" withSubject:""
        withReferences:""];
    return(self);
}

- makeNewArticleBody
{
    NXRect rect;
    const char *fontNameAndSize;
    char fontName[128];
    float fontSize;
    Font *fontObj;
    IExternalD *external;

    external = [[IExternalD allocFromZone:
        NXCreateZone(vm_page_size, vm_page_size, YES)] init];
    article = [[IArticleD allocFromZone:[external zone]]
         initWithKey:"editArticle"];
    [external setObject:article];
    [article setExternal:external];
    [article setEditor:self];
    [external incrementReferenceCount];
    rect.origin.x = rect.origin.y = 0.0;
    [oBodyView getContentSize:&rect.size];
    bodyText = [[INewsBaseText allocFromZone:[self zone]]
        initFrame:&rect];
    [[oBodyView setDocView:bodyText] free];
    [bodyText setHorizResizable:NO];
    [[bodyText superview] setAutoresizeSubviews:YES];
    [bodyText setAutosizing:NX_WIDTHSIZABLE];
    [bodyText notifyAncestorWhenFrameChanged: YES];
    [bodyText setVertResizable:YES];
    [bodyText setMinSize:&rect.size];
    rect.size.width = 1.0e30;
    rect.size.height = 1.0e30;
    [bodyText setMaxSize:&rect.size];
    [bodyText setDelegate:self];
    [bodyText setOpaque:YES];
    [bodyText setBackgroundGray: NX_WHITE];
    [bodyText setArticleSizeField:oArticleSizeField];
    [bodyText setPlainMultimediaButton:oPlainMultimediaButton];
    [bodyText setArticle:article];
    [bodyText setIsMultimedia:NO];
    if ((fontNameAndSize = NXGetDefaultValue(OWNER, FONT)) != 0 &&
        sscanf(fontNameAndSize, "%127[^:]:%f", fontName, &fontSize) == 2 &&
        (fontObj = [Font newFont:fontName size:fontSize]) != nil) {
    } else {
        fontObj = [Font newFont:"FixedRyuminCourier-Light" size:14.0];
    }
    [bodyText setFont:fontObj];
    [bodyText setMonoFont:YES];
    [bodyText setIsEditable:YES];
    [bodyText setNeedsSaving:YES];
    [bodyText setSelectionFileName:selectionFileName];
    [self registerEditWindow];
    [bodyText setSel:0 :0];
    return(self);
}

- registerEditWindow
{
    unsigned int	windowNum;
    id			speaker;
    
  /* register our app icon window with the workspace */
    DBG(1, fprintf(stderr, "window to register %d(%x)\n", [window windowNum],
        window);)
    NXConvertWinNumToGlobal([window windowNum], &windowNum);
    speaker = [NXApp appSpeaker];
    [speaker setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
    [speaker registerWindow:windowNum toPort:[[NXApp appListener] listenPort]];
//  [[NXApp appListener] setDelegate:[NXApp delegate]]; 
    return(self);
}

//*****************************************************************************
// construct an article for posting or saving
//*****************************************************************************

// sendArticle constructs an IArticleD representation of the article being
// edited.  Note that while an article is being edited there is no valid
// IArticleD representation of an article as the changes are reflected only
// the IHeaderFormScrollView and INewsBaseText objects only.  sendArticle
// constructs a new IArticleD from IHeaderFormScrollView and INewsBaseText.

- (IArticleD *)sendArticle
{
    NXStream	*stream;
    NXRect	rect;
    char	*data;
    int		len, maxlen;
    List	*mediaObjectList;
    ITextD	*oldTextD, *textD;
    InfoD	*oldInfoD, *infoD;
    NXStream  *headerStream;
    char	machine[256];	/* for message ID */
    char	domain[257];	/* for message ID */
    struct	tm *timestamp;	/* for message ID */
    struct	timeval	tv;
    struct	timezone tzp;
    char	messageid[256];
    char	path[256];
    char	datetime[256];
    static	char *month[12]={"Jan","Feb","Mar","Apr","May",
    				"Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
    const char	*value;
    int counter;    

    if ([bodyText needsSaving] == NO) { 
        goto constructBody;
    }
    /* check header field contents, newsgroup, subject */
    if ((value = [headerForm stringValueAtTitle:SUBJECT]) == NULL) {
        NXRunAlertPanel(LoStr("NewsBase"),LoStr("Subject field is missing, Please enter."),NULL,NULL,NULL);
        return(nil);
    }
//    if ((value = [headerForm stringValueAtTitle:GROUPNAME]) == NULL) {
//        NXRunAlertPanel(LoStr("NewsBase"),LoStr("Newsgroups field is missing, Please enter."),NULL,NULL,NULL);
//        return(nil);
//    }
    if ((value = [headerForm stringValueAtTitle:FROM]) == NULL) {
        NXRunAlertPanel(LoStr("NewsBase"),LoStr("From field is missing, Please enter."),NULL,NULL,NULL);
        return(nil);
    }
    /* create InfoD object */
    infoD = [[InfoD allocFromZone:[article zone]] initWithKey:HEADER_INFO];
    
    /* reading in header field for infoD construction */
    headerStream = NXOpenMemory(NULL, 0, NX_READWRITE);
    [headerForm writeToStream:headerStream];
    NXSeek(headerStream,0,NX_FROMSTART);
    [infoD readFromStream:headerStream];
    NXCloseMemory(headerStream,NX_FREEBUFFER);

    /* building message ID, path, and Date */
    gettimeofday(&tv, &tzp);
    timestamp = localtime(&tv.tv_sec);
    gethostname(machine, sizeof(machine));
    machine[sizeof(machine) - 1] = '\0';
    getdomainname(domain + 1, sizeof(domain) - 1);
    domain[sizeof(domain) - 1] = '\0';
    if (domain[1] != '\0') {
        domain[0] = '.';
    } else {
        domain[0] = '\0';
    }
    (void)sprintf(path, "%.191s!%.63s", machine, getenv("USER"));
    [infoD addInfoString: path key:PATH];
    (void) sprintf(messageid, "<%.63s.%d%s%d%d%d%d@%s%.127s>", getenv("USER"),
        timestamp->tm_year,month[timestamp->tm_mon], timestamp->tm_mday,
        timestamp->tm_hour,timestamp->tm_min,timestamp->tm_sec,machine,
           domain);
   [infoD addInfoString: messageid key:MESSAGE_ID];
    /* Date Field format is DD Mon YY HH:MM:SS */
   (void)sprintf(datetime,"%02d %s %d %02d:%02d:%02d",timestamp->tm_mday,
        month[timestamp->tm_mon],timestamp->tm_year,timestamp->tm_hour,
        timestamp->tm_min,timestamp->tm_sec);
    [infoD addInfoString: datetime key:DATE];
    if ((oldInfoD = [article objectWithKey:HEADER_INFO]) != nil) {
        [article removeObjectWithKey:HEADER_INFO];
        [oldInfoD free];
    }
    [article insertKeyedObject:(IKeyedObject *)infoD];

constructBody:
    // do the body 
    if ([bodyText isMultimedia] == NO) { 
        // plain text
      	textD = [[ITextD allocFromZone:[article zone]] initWithKey:PLAINTEXT];
        stream = NXOpenMemory(NULL, 0, NX_READWRITE);
	[bodyText writeText:stream];
        NXPutc(stream, '\0');
        NXGetMemoryBuffer(stream, &data, &len, &maxlen);
        len = NXTell(stream);
        NXCloseMemory(stream, NX_SAVEBUFFER);
        [textD setTextData:data size:len maxSize:maxlen];    // textD owns data
    } else {
        // multimedia
   	textD = [[ITextD allocFromZone:[article zone]] initWithKey:INDEX_RTF];
   	stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
        mediaObjectList = [List allocFromZone:[article zone]];
        [IGraphicImage setList:mediaObjectList];
        [oBodyView getFrame:&rect];
        NXPrintf(stream, "{\\info{\\width %f}}", rect.size.width);
        // count media objects and externals
        counter = 0;
        [IGraphicImage setCounter:&counter];
   	[bodyText writeRichText:stream];
        [article setObjectCount:counter];
        [IGraphicImage setCounter:NULL];
        NXPutc(stream, '\0');
   	NXGetMemoryBuffer(stream, &data, &len, &maxlen);
        len = NXTell(stream);
   	NXCloseMemory(stream, NX_SAVEBUFFER);
        [textD setTextData:data size:len maxSize:maxlen];    // textD owns data
    }
    // remove previous text object if it exists
    if ((oldTextD = [article objectWithKey:INDEX_RTF]) != nil) {
        [article removeObjectWithKey:INDEX_RTF];
        [oldTextD free];
    }
    if ((oldTextD = [article objectWithKey:PLAINTEXT]) != nil) {
        [article removeObjectWithKey:PLAINTEXT];
        [oldTextD free];
    }
    [bodyText setNeedsSaving:NO];
    [article insertKeyedObject:textD];
    return(article);
}

- clear
{
    [bodyText setText:""];
    [article freeObjects];
    [bodyText setArticleSize:0];
    return(self);
}

- changeToMultimedia:sender
{
    [bodyText setIsMultimedia:YES];
    return(self);
}

- postToNewsgroup:(const char *)newsgroup
{
    [self makeNewArticleHeaderWithNewsGroup:newsgroup
        withSubject:"" withReferences:""];
    [self makeNewArticleBody];
    [window makeKeyAndOrderFront:self];
    [headerForm selectCellAtTitle:GROUPNAME];
    return(self);
}

- postResponseToArticle:currentArticleItem
{
    char	newsgroup[256];
    char	*sub_p, subject[256];
    char	*ref_p, references[511], *message_id;
    id		headerInfo;
    int		len;

    if ((headerInfo=[currentArticleItem dataForKey:HEADER_INFO])==NULL) {
	NXRunAlertPanel(LoStr("NewsBase"),LoStr("select an article"),LoStr("OK"),NULL,NULL);
	return self;
    }
    (void)sprintf(newsgroup,"%.256s", (char *)[headerInfo infoForKey:GROUPNAME]);
    sub_p = (char *)[headerInfo infoForKey:SUBJECT];
    if (sub_p != NULL) {
	if (strncmp(sub_p,"Re: ",4)==0) {
	    sprintf(subject,"%.255s",sub_p);
	} else {
	    sprintf(subject,"Re: %.251s",sub_p);
	}
    }
    message_id = [headerInfo infoForKey:MESSAGE_ID];
    if ((ref_p=[headerInfo infoForKey:REFERENCES])!=NULL) {
    /* original article has References: */
	/* field sould be under 510 */
	while((len = strlen(ref_p)+strlen(message_id)+1)>511) {
	    ref_p = strchr(ref_p,'<');
	}
	sprintf(references,"%s %s",ref_p,message_id);
    } else {
	sprintf(references,"%.510s",message_id);
    }
    [self makeNewArticleHeaderWithNewsGroup:newsgroup
        withSubject:subject withReferences:references];
    [self makeNewArticleBody];
    [headerForm setTarget:bodyText andAction:@selector(setNeedsSaving:)];
    [[window contentView] displayFromOpaqueAncestor:NULL :0 :YES];
    [window makeKeyAndOrderFront:self];
    [headerForm selectCellAtTitle:GROUPNAME];
    return(self);
}
//*****************************************************************************
// make new article header
//*****************************************************************************

- makeNewArticleHeaderWithNewsGroup:(const char *)newsgroup
    withSubject:(const char *)subject withReferences:(const char *)references
{
    static char pictureFile[MAXPATHLEN];

    headerForm = [[IHeaderFormScrollView allocFromZone:[self zone]] init];
    [headerForm setStringValue:newsgroup atTitle:GROUPNAME setEnabled:YES];
    [headerForm setStringValue:subject atTitle:SUBJECT setEnabled:YES];
    [headerForm setStringValue:NXGetDefaultValue(OWNER, DISTRIBUTION)
        atTitle:DISTRIBUTION setEnabled:YES];
    [headerForm setStringValue:NXGetDefaultValue(OWNER, FROM)
        atTitle:FROM setEnabled:YES];
    [headerForm setStringValue:NXGetDefaultValue(OWNER, REPLYTO)
        atTitle:REPLY_TO setEnabled:YES];
    [headerForm setStringValue:"" atTitle:FOLLOWUP_TO setEnabled:YES];
    [headerForm setStringValue:references atTitle:REFERENCES setEnabled:YES];
    [headerForm setStringValue:"" atTitle:KEYWORDS setEnabled:YES];
    [headerForm addCellForNewEntry];
    [headerForm addCellForNewEntry];
    [headerForm addCellForNewEntry];
    [headerForm addCellForNewEntry];
    [headerForm addCellForNewEntry];
    [headerForm addCellForNewEntry];
    [headerForm addCellForNewEntry];
    [headerForm addCellForNewEntry];
    [headerForm addCellForNewEntry];
    [headerForm addCellForNewEntry];
    [headerForm initScrollView:oHeaderView];
    [headerForm showForm:self];
    // show sender's picture
    sprintf(pictureFile, "/LocalLibrary/Images/People/%s.tiff",
        NXGetDefaultValue(OWNER, FROM));
    picture = [[NXImage allocFromZone:[self zone]] initFromFile:pictureFile];
    [oPicture setImage:picture];
    return(self);
}

- bodyView
{
    return(oBodyView);
}

- (INewsBaseText *)text
{
    return(bodyText);
}

- (INewsBaseText *)view
{
    return(bodyText);
}

- windowDidBecomeKey:sender
{
    [[NXApp delegate] editorDidBecomeKey:self];

#ifdef KANJI
    // when the editor will become key, kanji FEP will not catch
    // command key first. if FEP will catch first command key equivalence
    // then text class will get textDidChanged message.
    // so the user can not use command+c.
    [[NXApp inputManager] imGetCommandKeyFirst:NO];
#endif /* KANJI */

    return(self);
}

- windowDidResignKey:sender
{
    [window endEditingFor:self];
    [[NXApp delegate] editorDidResignKey:self];
    
#ifdef KANJI
    [[NXApp inputManager] imGetCommandKeyFirst:YES];
#endif /* KANJI */
    return(self);
}

- windowWillClose:sender
{
    NXRect frameRect, attributeRect, headerRect, bodyRect;
    NXRect contentRect;
    char buffer[64];

    if ([self detachArticle] == nil) {
        return(nil);
    }
    [IArticleD editorWillBeFreed:self];
    [window getFrame:&frameRect];
    [oAttributeView getFrame:&attributeRect];
    [oHeaderView getFrame:&headerRect];
    [oBodyView getFrame:&bodyRect];
    sprintf(buffer, "%.0f:%.0f", frameRect.origin.x,
        frameRect.origin.y + frameRect.size.height);
    NXWriteDefault(OWNER, EDITOR_LOCATION, buffer);
    [Window getContentRect:&contentRect forFrameRect:&frameRect
        style:NX_RESIZEBARSTYLE];
    sprintf(buffer, "%.0f:%.0f", contentRect.size.width,
        contentRect.size.height);
    NXWriteDefault(OWNER, EDITOR_SIZE, buffer);
    sprintf(buffer, "(%.0f,%.0f):(%.0f,%.0f):(%.0f,%.0f)",
        attributeRect.size.width, attributeRect.size.height,
        headerRect.size.width, headerRect.size.height,
        bodyRect.size.width, bodyRect.size.height);
    NXWriteDefault(OWNER, EDITOR_VIEW_SIZES, buffer);
    [NXApp delayedFree:self];
    return(self);
}

- toggleFirstResponder
{
    Responder *firstResponder;

    firstResponder = [window firstResponder];
    [window makeFirstResponder:oHeaderView];
    [window makeFirstResponder:firstResponder];
    return(self);
}
//*****************************************************************************
// detach article to reuse viewer/editor
//*****************************************************************************

// detaching an article from the editor allows the editor to be reused.
// This is useful since creating an editor is quite expensive.

- detachArticle
{
    if ([bodyText needsSaving] == YES) {
        [window makeKeyAndOrderFront:self];
//      [self windowDidBecomeKey:self];
        if (NXRunAlertPanel(LoStr("NewsBase"),
            LoStr("Article will be destroyed."), LoStr("OK"),
            LoStr("Cancel"), NULL) != NX_ALERTDEFAULT) {
            return(nil);
        }
    }
    if (selectionRTFText != nil) {
        [selectionRTFText free];
        selectionRTFText = nil;
    }
    if (selectionHeader != nil) {
        [selectionHeader free];
        selectionHeader = nil;
    }
    if (selectionPlainText != nil) {
        [selectionPlainText free];
        selectionPlainText = nil;
    }
    [selectionList empty];
    [bodyText setTextIsFreeing];
    [bodyText setText:""];
    [headerForm free];
    if (picture != nil) {
        [picture free];
    }
    [article editorWillClose:self];
    article = nil;
    return(self);
}

- free
{
    NXZone *zone;

    [selectionList free];
    [window free];
    zone = [self zone];
    [super free];
//  NXDestroyZone(zone);
    return(nil);
}
//*****************************************************************************
// change readonly article to writable
//*****************************************************************************

// textWillChange changes a read only article to a writable article.
// As this is commonly used for posting a response the neccessary 
// changes are made to the header fields and the old header is
// copied into the new body


- (BOOL)textWillChange:sender
{
    InfoD *header;
    char *oldSubject, *oldMessageId, *oldReferences, *ptr;
    char subject[512];
    char references[512];
    List *embeddedViewControllers;
    IGraphicImage *controller;
    NXStream *headerStream;
    const char *headerBuffer;
    int headerBufferLen, headerBufferMaxLen;
    static char pictureFile[MAXPATHLEN];

    if ([bodyText hasEmbeddedView]) {
        embeddedViewControllers = [bodyText embeddedViewControllers];
        while ((controller = [embeddedViewControllers objectAt:0]) != nil) {
            [[controller mediaObject] performDoubleClickAction:controller];
            [embeddedViewControllers removeObject:controller];
        }
    }
    if ([bodyText isEditable] == YES) {
        [[article external] markAsDirty];
        return(NO);
    } else {
        switch(NXRunAlertPanel(LoStr(MMEDITOR),LoStr("Text is not editable, "
            "change to editable?"),LoStr("YES"),LoStr("NO"),NULL)) {
        case NX_ALERTDEFAULT:
            // change text to editable
            [bodyText setIsEditable:YES];
            [bodyText setNeedsSaving:YES];
            // Does user want the header of old article to be included in body
            if(NXRunAlertPanel(LoStr(MMEDITOR),LoStr("Include header?"),
                LoStr("YES"),LoStr("NO"),NULL) == NX_ALERTDEFAULT) {
                // dump the header to the body
                headerStream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
                NXPrintf(headerStream, "\n\n");
                [headerForm writeToStream:headerStream];
                NXPrintf(headerStream, "\n\n");
                NXGetMemoryBuffer(headerStream, &headerBuffer,
                    &headerBufferLen, &headerBufferMaxLen);
                [bodyText setSel:0 :0];
                [bodyText replaceSel:headerBuffer];
            }
            // rebuild the header for response
            [headerForm free];
             headerForm = [[IHeaderFormScrollView allocFromZone:[self zone]]
                 init];
            if ((header = [article dataForKey:HEADER_INFO]) == nil) {
            }
            [headerForm setStringValue:[header infoForKey:GROUPNAME]
                atTitle:GROUPNAME setEnabled:YES];
            if ((oldSubject = [header infoForKey:SUBJECT]) != NULL) {
                if (strncmp(oldSubject, "Re: ", 4) == 0 ||
                    strncmp(oldSubject, "re: ", 4) == 0) {
                    sprintf(subject, "%.255s", oldSubject);
                } else {
                    sprintf(subject, "Re: %.251s",oldSubject);
                }
            } else {
                subject[0] = '\0';
            }
            [headerForm setStringValue:subject atTitle:SUBJECT setEnabled:YES];
            [headerForm setStringValue:[header infoForKey:DISTRIBUTION]
                atTitle:DISTRIBUTION setEnabled:YES];
            [headerForm setStringValue:NXGetDefaultValue(OWNER, FROM)
                atTitle:FROM setEnabled:YES];
            [headerForm setStringValue:NXGetDefaultValue(OWNER, REPLYTO)
                atTitle:REPLY_TO setEnabled:YES];
            [headerForm setStringValue:subject atTitle:SUBJECT setEnabled:YES];
            [headerForm setStringValue:"" atTitle:REFERENCES setEnabled:YES];
            if ((oldMessageId = [header infoForKey:MESSAGE_ID]) != NULL) {
                if (strlen(oldMessageId) < sizeof(references) - 2 &&
                    (oldReferences = [header infoForKey:REFERENCES]) != NULL) {
                    /* original article has References: */
                    /* field should be under 510 */
                    ptr = index(oldReferences, '\0') - 
                        (sizeof(references) - (strlen(oldMessageId) + 2));
                    if (ptr < oldReferences) {
                        ptr = oldReferences;
                    }
                    for (; *ptr != '<' && *ptr != '\0'; ++ptr);
                    sprintf(references,"%s %s", ptr, oldMessageId);
                } else {
                    sprintf(references,"%.511s", oldMessageId);
                }
                [headerForm setStringValue:references atTitle:REFERENCES
                    setEnabled:YES];
            }
            [headerForm setStringValue:"" atTitle:KEYWORDS setEnabled:YES];
            [headerForm addCellForNewEntry];
            [headerForm addCellForNewEntry];
            [headerForm addCellForNewEntry];
            [headerForm addCellForNewEntry];
            [headerForm addCellForNewEntry];
            [headerForm addCellForNewEntry];
            [headerForm addCellForNewEntry];
            [headerForm addCellForNewEntry];
            [headerForm addCellForNewEntry];
            [headerForm addCellForNewEntry];
            [headerForm initScrollView:oHeaderView];
            [headerForm showForm:self];
            [headerForm setTarget:bodyText
                andAction:@selector(setNeedsSaving:)];
/*
            [bodyText setSelColor:NXConvertRGBAToColor(1.0, 0.0, 1.0, 1.0)];
            [bodyText selectAll:self];
            [bodyText selectNull];
            [bodyText setSelColor:NXConvertRGBAToColor(0.0, 0.0, 0.0, 1.0)];
*/
            // show sender's picture
            sprintf(pictureFile, "/LocalLibrary/Images/People/%s.tiff",
                NXGetDefaultValue(OWNER, FROM));
            picture = [[NXImage allocFromZone:[self zone]]
                initFromFile:pictureFile];
            [oPicture setImage:picture];
            [[article external] markAsDirty];
            return(NO);
        case NX_ALERTALTERNATE:
            return(YES);
        }
        return(YES);
    }
}

// getReferenceList returns the reference list of the article
// used to maintain the menu cells of the getReference menu item

- (const char *)getReferenceList
{
    if (article != nil) {
        return([[article  dataForKey:HEADER_INFO] infoForKey:REFERENCES]);
    } else {
        return(NULL);
    }
}
//*****************************************************************************
// drag and drop of article or selection in article
//*****************************************************************************
static int selectionNo = 1;

- (const char *)getFilename:sender
{
    IArticleD *saveArticle;
    id object;
    NXStream *stream;
    const char *extension;

    switch ([sender tag]) {
    case 0:
        // file button
        if ((saveArticle = [self sendArticle]) != nil) {
            // create temporary disk copy of article
            return([self temporaryMultiMediaArticle:saveArticle]);
        } else {
            return(NULL);
        }
    case 1:
        // selection button
        if ((object = [self getObjectInSelection]) != nil) {
            if ([object class] == [IExternalD class]) {
                NXRunAlertPanel(LoStr(MMEDITOR),
                    LoStr("This capability currently not implemented."),
                    NULL,NULL,NULL);
                return(NULL);
            } else if ([object class] == [ITextD class]) {
                if (strcmp([object key], INDEX_RTF) == 0) {
                    if ([selectionList count] > 3) {
                        // there are media objects
                        [selectionList removeObject:selectionPlainText];
                        [self temporaryMultiMediaArticle:selectionList];
                        [selectionList insertKeyedObject:selectionPlainText];
                        return(selectionFileName);
                    } else {
                        // just ordinary rtf
                        stream = NXOpenMemory(NULL, 0, NX_READWRITE);
                        [object writeToStream:stream];
                        sprintf(selectionFileName + sizeof(TMP_DIR) - 1,
                            "Untitled%d.rtf", selectionNo++);
                        NXSaveToFile(stream, selectionFileName);
                        NXCloseMemory(stream, NX_FREEBUFFER);
                        return(selectionFileName);
                    }
                } else {
                    stream = NXOpenMemory(NULL, 0, NX_READWRITE);
                    [object writeToStream:stream];
                    sprintf(selectionFileName + sizeof(TMP_DIR) - 1,
                        "Untitled%d", selectionNo++);
                    NXSaveToFile(stream, selectionFileName);
                    NXCloseMemory(stream, NX_FREEBUFFER);
                    return(selectionFileName);
                }
            } else {
                stream = NXOpenMemory(NULL, 0, NX_READWRITE);
                [object writeToStream:stream];
                if ((extension = rindex([object key], '.')) != 0) {
                    ++extension;
                } else {
                    extension = "bin";
                }
                sprintf(selectionFileName + sizeof(TMP_DIR) - 1,
                    "Untitled%d.%.230s", selectionNo++, extension);
                NXSaveToFile(stream, selectionFileName);
                NXCloseMemory(stream, NX_FREEBUFFER);
                return(selectionFileName);
            }
        } else {
            NXRunAlertPanel(LoStr("NewsBase"),
                LoStr("There is no selection"),NULL,NULL,NULL);
            return(NULL);
        }
    }
    return(NULL);
}

- (const char *)temporaryMultiMediaArticle:(IArticleD *)saveArticle
{
    const char *path;
    BOOL flag;
    static char buffer[2 * MAXPATHLEN];
    InfoD *header;
    char *ptr;
    const char *subject;
    struct stat statBuffer;
    char command[512];

    // First check if there is already a clean copy on the disk
    if ((path = [[saveArticle external] keyForDomain:[ILocalFileD domain]
        isDirty:&flag]) != NULL && flag == NO) {
        strncpy(buffer, path, sizeof(buffer));
        *rindex(buffer, '.') = '\0';
        return(buffer);
    }

    // remove previous attempt if there is one
    if (strcmp(selectionFileName, TMP_DIR) != 0 &&
        stat(selectionFileName, &statBuffer) == 0) {
        sprintf(command, "rm -r %.505s", (const char *)selectionFileName);
        system(command);
    }

    if ((header = [saveArticle objectWithKey:HEADER_INFO]) != nil &&
        (subject = (const char *)[header infoForKey:SUBJECT]) != NULL &&
        subject[0] != '\0') {
        strncpy(selectionFileName + sizeof(TMP_DIR) - 1, subject,
        sizeof(selectionFileName) - (sizeof(TMP_DIR) - 1
        + sizeof(char /* '.' */) + sizeof(MM_FILE_EXTENSION) - 1
        + sizeof(char /* '\0' */)));
        // replace non-alphanumerics with '_'
        for (ptr = selectionFileName + sizeof(TMP_DIR) - 1; *ptr != '\0';
            ++ptr) {
            if (isalnum(*ptr) == 0) {
                *ptr = '_';
            }
        }
    } else {
        sprintf(selectionFileName + sizeof(TMP_DIR) - 1, "Untitled%d",
            selectionNo++);
    }
    STRDBG(selectionFileName);
    ptr = index(selectionFileName, '\0');
    *ptr++ = '.';
    if ([saveArticle objectWithKey:INDEX_RTF] == nil) {
        // plain text
        strcpy(ptr, NEWS_FILE_EXTENSION);
    } else {
        // this is a multimedia article so add multimedia extension
        strcpy(ptr, MM_FILE_EXTENSION);
    }
    [ILocalFileD save:saveArticle toName:selectionFileName];
    STRDBG(selectionFileName);
    return(selectionFileName);
}

- getSelection
{
    NXSelPt start, end;
    NXStream *stream;
    char *data;
    int len, maxLen, realLen;
    BOOL writePlainText;
    char subject[32];

    [bodyText getSel:&start :&end];
    if (start.cp == selectionStart && end.cp == selectionEnd) {
        return(self);
    }
    selectionStart = start.cp;
    selectionEnd = end.cp;
    [selectionList empty];
    if (selectionRTFText != nil) {
        [selectionRTFText free];
        selectionRTFText = nil;
    }
    if (selectionHeader != nil) {
        [selectionHeader free];
        selectionHeader = nil;
    }
    if (selectionPlainText != nil) {
        [selectionPlainText free];
        selectionPlainText = nil;
    }
    selectionIndex = 0;
    if (selectionStart == -1 || selectionStart == selectionEnd) {
        return(self);
    }
    if ([bodyText isMultimedia] == YES) {
        stream = NXOpenMemory(NULL, 0, NX_READWRITE);
        [IGraphicImage setList:selectionList];
        [bodyText writeRichText:stream from:start.cp to:end.cp];
        if ([selectionList count] < end.cp - start.cp) {
            writePlainText = YES;
            NXPutc(stream, '\0');
            NXGetMemoryBuffer(stream, &data, &len, &maxLen);
            len = NXTell(stream);
            NXCloseMemory(stream, NX_SAVEBUFFER);
            selectionRTFText = [[ITextD allocFromZone:[self zone]]
                initWithKey:INDEX_RTF];
            [selectionRTFText setTextData:data size:len maxSize:maxLen];  
            selectionHeader = [[InfoD allocFromZone:[self zone]]
                initWithKey:HEADER_INFO];
            sprintf(subject, "Untitled%d", selectionNo++);
            [selectionHeader addInfoString:subject key:SUBJECT];
            [selectionList insertKeyedObject:(IKeyedObject *)selectionHeader];
            [selectionList insertKeyedObject:selectionRTFText];
            selectionIndex = [selectionList indexOf:selectionRTFText];
        } else {
            writePlainText = NO;
        }
    }
    if ([bodyText isMultimedia] == NO || writePlainText == YES) {
        len = end.cp - start.cp;
        vm_allocate(task_self(), (vm_address_t *)&data, 3 * len + 1, TRUE);
        realLen = [bodyText getSubstring:data start:start.cp length:len];
        NX_ASSERT(realLen <= 3 * len, "[Text getSubstring:start:length:]"
            "copied more than specified length");
        selectionPlainText = [[ITextD allocFromZone:[self zone]]
            initWithKey:PLAINTEXT];
        [selectionPlainText setTextData:data size:realLen maxSize:3 * len + 1];
        // important! this is inserted after selectionRTFText so 
        // selectionIndex is still valid
        [selectionList insertKeyedObject:selectionPlainText];
    }
    return(self);
}

- setObjectInSelection:sender
{
    id object;

    [self getSelection];
    if ([selectionList count] == 0) {
        return(self);
    }
tryAgain:
    if ((object = [selectionList objectAt:++selectionIndex]) == nil) {
         selectionIndex = 0;
         object = [selectionList objectAt:selectionIndex];
    }
    if (strcmp([object key], HEADER_INFO) == 0) {
        // if there is a header in the selectionList there must be a index_rtf
        // also so cannot loop infinitely!
        goto tryAgain;
    }
    if ([object class] != [ITextD class]) {
        [oSelectionButton setImage:(NXImage *)[[object class] icon]];
    } else {
        if (strcmp([object key], INDEX_RTF) == 0) {
             [oSelectionButton setIcon:"multi_icon"];
        } else {
             [oSelectionButton setIcon:"plain_icon"];
        }
    }
    return(self);
}

- getObjectInSelection
{
    id object;

    [self getSelection];
    if ((object = [selectionList objectAt:selectionIndex]) == nil) {
        return(nil);
    }
    [oSelectionButton setIcon:NULL];

/*
    if ([object class] != [ITextD class]) {
        [oSelectionButton setImage:[[object class] icon]];
    } else {
        if (strcmp([object key], INDEX_RTF) == 0) {
             [oSelectionButton setIcon:"multi_icon"];
        } else {
             [oSelectionButton setIcon:"plain_icon"];
        }
    }
*/
    return(object);
}

- window
{
    return(window);
}

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.