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.