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.