This is INewsBaseText.m in view mode; [Download] [Up]
// // INewsBaseText extends Text with the following capabilities: // // 1. drag and drop of media files. e.g. .tiff, .snd and .mtif files. // 2. copy and paste of NewsBasePboardType pasteboard type // NewsBasePboardType is a new pasteboard type that allows you to // copy and paste the NewsBase Rich Text Format with embedded objects // 3. support of selected Emacs-style editing commands // 4. size dynamically updated. // 5. scrolling using the space bar // 6. find modified to work with embedded media objects // // The NewsBasePboardType is a new private pasteboard type. The format // is header, body, header, body, ... header, body. The header is a // struct mediaObjectDescriptor. The body is the media object data as // written by the writeFromStream: method of the media object. Struct // mediaObjectDescriptor is defined by: // // struct mediaObjectDescriptor { // int magic; // char key[256]; // char type[16]; // int size; // }; // // where type is the same as the file extension fof the media object. // #import "INewsBaseText.h" #import <appkit/nextstd.h> #import <appkit/Application.h> #import <appkit/Pasteboard.h> #import <appkit/Panel.h> #import <appkit/Font.h> #import <streams/streams.h> #import <libc.h> #import <strings.h> #import <mach/mach.h> #import "IMMEditor.h" #import "IGraphicImage.h" #import "IMediaTable.h" #import "IMediaD.h" #import "CopyIcon.h" #import "INewScroller.h" #import "IAppDelegate.h" #import <ctype.h> #import "data_types.h" #import "errdebug.h" #import "Localization.h" #define LoStr(key) doLocalString(NULL,key,NULL) @implementation INewsBaseText NXAtom NewsBasePboardType; // new pasteboard type for NewsBase // RTF with embedded objects static NXAtom *types; // NULL terminated list of pasteboard // types in order by preference. // initialize registers the rtf control word for embedded media objects and // initializes the list of pasteboard types that InewsBaseText will write to // the pasteboard. + initialize { NXAtom *typesPtr; Class MediaClass; int i; [Text registerDirective:RTF_CONTROL_WORD forClass:[IGraphicImage class]]; NewsBasePboardType = NXUniqueString(NEWSBASE_PBOARD_TYPE); typesPtr = types = malloc(([IMediaTable count] + 4) * sizeof(NXAtom)); *typesPtr++ = NewsBasePboardType; // add types for supported media classes for (i = 0; (MediaClass = [IMediaTable mediaClassAt:i]) != Nil; ++i) { if ((*typesPtr = [(id)MediaClass pasteboardType]) != NULL) { ++typesPtr; } } *typesPtr++ = NXRTFPboardType; *typesPtr++ = NXAsciiPboardType; *typesPtr = NULL; return(self); } // [INewsBaseText initFrame:text:aligment:] sets special character and text // filters then calls [Text initFrame:text:aligment:]. - initFrame:(const NXRect *)frameRect text:(const char *)theText alignment:(int)mode { unsigned short emacsEditorFilter(unsigned short charCode, int flags, unsigned short charSet); char *textFilter(id sender, unsigned char *insertText, int *insertLength, int position); [super initFrame:frameRect text:theText alignment:mode]; [NXApp registerServicesMenuSendTypes:types andReturnTypes:types]; #ifdef KANJI inputManager = [NXApp inputManager]; #endif [self setTextFilter:textFilter]; // must keep track of embedded views in order to free them when text // is deleted first embeddedViewControllers = [List allocFromZone:[self zone]]; return(self); } //***************************************************************************** // Pasteboard methods //***************************************************************************** // validRequestorForSendType:andReturnType: will always return self. // This is incorrect but neccessary as determining the correct answer is // computationally intensive and this false answer does not turn out to // be a problem in reality. - validRequestorForSendType:(NXAtom)sendType andReturnType:(NXAtom)returnType { return(self); } - copy:sender { [self writeSelectionToPasteboard:[Pasteboard new] types:NULL]; return(self); } // writeSelectionToPasteboard: will write the current selection to the // pasteboard in the following types: NewsbasePboardType, media // types (e.g., tiff, jpeg, snd, ...) for media objects present in the // selection, NXRTFPboardType and NXAsciiPboardType - (BOOL)writeSelectionToPasteboard:(Pasteboard *)pasteboard types:(NXAtom *)requestedTypes { int typesNum; NXAtom types[16] = {NewsBasePboardType}, *typesPtr, type; NXSelPt start, end; NXStream *rtfStream, *stream; int length, realLength, maxLength; char *data; List *list; int n; IMediaD *mediaObject; struct mediaObjectDescriptor mediaObjectDescriptor; [self getSel:&start :&end]; rtfStream = NXOpenMemory(NULL, 0, NX_READWRITE); // list will contain media objects in the selection [IGraphicImage setList:list = [[List alloc] init]]; [self writeRichText:rtfStream from:start.cp to:end.cp]; // add types for media objects in selection typesNum = 1; typesPtr = &types[1]; for (n = 0; (mediaObject = [list objectAt:n]) != nil; ++n) { if ((*typesPtr = [[mediaObject class] pasteboardType]) != NULL) { ++typesNum; ++typesPtr; } } // add NXRTFPboardType and NXAsciiPboardType *typesPtr = NXRTFPboardType; *++typesPtr = NXAsciiPboardType; typesNum += 2; [pasteboard declareTypes:types num:typesNum owner:NULL]; // write NewsbasePboardType to pasteboard stream = NXOpenMemory(NULL, 0, NX_READWRITE); mediaObjectDescriptor.magic = MEDIA_OBJECT_DESCRIPTOR_MAGIC; NXGetMemoryBuffer(rtfStream, &data, &length, &maxLength); NXSeek(rtfStream, (long)0, NX_FROMEND); strncpy(mediaObjectDescriptor.key, NewsBasePboardType, sizeof(mediaObjectDescriptor.key)); mediaObjectDescriptor.key[sizeof(mediaObjectDescriptor.key) - 1] = '\0'; strncpy(mediaObjectDescriptor.type, "rtf", sizeof(mediaObjectDescriptor.type)); mediaObjectDescriptor.type[sizeof(mediaObjectDescriptor.type) - 1] = '\0'; length = NXTell(rtfStream); mediaObjectDescriptor.size = length; NXWrite(stream, &mediaObjectDescriptor, sizeof(mediaObjectDescriptor)); NXWrite(stream, data, length); // now write all media objects to pasteboard in NewsbasePboardType for (n = 0; (mediaObject = [list objectAt:n]) != nil; ++n) { strncpy(mediaObjectDescriptor.key, [mediaObject key], sizeof(mediaObjectDescriptor.key)); mediaObjectDescriptor.key[sizeof(mediaObjectDescriptor.key) - 1] = '\0'; strncpy(mediaObjectDescriptor.type, [[mediaObject class] fileExtension], sizeof(mediaObjectDescriptor.type)); mediaObjectDescriptor.type[sizeof(mediaObjectDescriptor.type) - 1] = '\0'; mediaObjectDescriptor.size = [mediaObject size]; NXWrite(stream, &mediaObjectDescriptor, sizeof(mediaObjectDescriptor)); if (mediaObjectDescriptor.size > 0) { length = NXTell(stream); [mediaObject writeToStream:stream]; NX_ASSERT(NXTell(stream) - length == mediaObjectDescriptor.size, "[mediaObject size] does not give correct length of output of " "[mediaObject writeToStream:]"); } } NXGetMemoryBuffer(stream, &data, &length, &maxLength); NXSeek(stream, (long)0, NX_FROMEND); length = NXTell(stream); [pasteboard writeType:NewsBasePboardType data:data length:length]; NXCloseMemory(stream, NX_FREEBUFFER); // write media objects to pasteboard in there natural pasteboard type for (n = 0; (mediaObject = [list objectAt:n]) != nil; ++n) { if ((type = [[mediaObject class] pasteboardType]) != NULL) { stream = NXOpenMemory(NULL, 0, NX_READWRITE); [mediaObject writeToStream:stream]; NXGetMemoryBuffer(stream, &data, &length, &maxLength); NXSeek(stream, (long)0, NX_FROMEND); length = NXTell(stream); [pasteboard writeType:type data:data length:length]; NXCloseMemory(stream, NX_FREEBUFFER); } } [IGraphicImage setList:nil]; [list free]; // do NXRTFPboardType NXGetMemoryBuffer(rtfStream, &data, &length, &maxLength); NXSeek(rtfStream, (long)0, NX_FROMEND); length = NXTell(rtfStream); NXPutc(rtfStream, '\0'); [pasteboard writeType:NXRTFPboardType data:data length:length]; NXCloseMemory(rtfStream, NX_FREEBUFFER); // finally do NXAsciiPboardType length = end.cp - start.cp; data = NXZoneMalloc([self zone], 3 * length + 1); realLength = [self getSubstring:data start:start.cp length:length]; NX_ASSERT(realLength <= 3 * length, "[Text getSubstring:start:length:]" "copied more than specified length"); [pasteboard writeType:NXAsciiPboardType data:data length:realLength]; NXZoneFree([self zone], data); return(TRUE); } - paste:sender { return([self readSelectionFromPasteboard:[Pasteboard new]]); } // readSelectionFromPasteboard reads the pasteboard. It will read all // standard types as well as the proprietary NewsbasePboardType. If there // are multiple representations on the pasteboard then the priority is // arranged as follows NewsbasePboardType, media types, NXRTFPboardType // and finally NXAsciiPboardType - readSelectionFromPasteboard:(Pasteboard *)pasteboard { const NXAtom *type; NXStream *stream; char *data, *dataPtr, *dataEnd; int length; IGraphicImage *graphicObject; id mediaObject; Class mediaClass; id loadingAlertPanel; NXModalSession loadingModalSession; if (isEditable == NO || [self hasEmbeddedView] == YES) { if ([[self delegate] textWillChange:self] == YES) { return(nil); } } // If NewsBasePboardType is on the pasteboard then use that. for (type = [pasteboard types]; *type != NULL; ++type) { if (*type == NewsBasePboardType) { if ([pasteboard readType:NewsBasePboardType data:&data length:&length] != nil) { if (((struct mediaObjectDescriptor *)data)->magic != MEDIA_OBJECT_DESCRIPTOR_MAGIC) { NXRunAlertPanel(LoStr("NewsBase"), LoStr("Paste of %s failed.Bad magic Number"), NULL,NULL,NULL, NewsBasePboardType); vm_deallocate(task_self(), (vm_address_t)data, length); return(nil); } dataPtr = data + sizeof(struct mediaObjectDescriptor) + ((struct mediaObjectDescriptor *)data)->size; dataEnd = data + length; // show alert panel for long paste if (length > 4096) { loadingAlertPanel = NXGetAlertPanel(LoStr("NewsBase"), LoStr("Loading from %s pasteboard..., please wait"), NULL, NULL, NULL, NewsBasePboardType); [NXApp beginModalSession:&loadingModalSession for:loadingAlertPanel]; } else { loadingAlertPanel = nil; } // loop over all objects while (dataPtr < dataEnd) { if (((struct mediaObjectDescriptor *)dataPtr)->magic != MEDIA_OBJECT_DESCRIPTOR_MAGIC) { NXRunAlertPanel(LoStr("NewsBase"), LoStr("Paste of %s failed.Bad magic Number"), NULL,NULL,NULL, NewsBasePboardType); vm_deallocate(task_self(), (vm_address_t)data, length); if (loadingAlertPanel != nil) { [NXApp endModalSession:&loadingModalSession]; [loadingAlertPanel orderOut:self]; NXFreeAlertPanel(loadingAlertPanel); } return(nil); } if (isMultimedia == NO) { switch(NXRunAlertPanel(LoStr("NewsBase"), LoStr("This is not a multimedia article. Change to multimedia?"), LoStr("YES"),LoStr("NO"),NULL)) { case NX_ALERTDEFAULT: isMultimedia = YES; [plainMultimediaButton setIcon:"multi_icon"]; [self setMonoFont:NO]; break; case NX_ALERTALTERNATE: vm_deallocate(task_self(), (vm_address_t)data, length); if (loadingAlertPanel != nil) { [NXApp endModalSession:&loadingModalSession]; [loadingAlertPanel orderOut:self]; NXFreeAlertPanel(loadingAlertPanel); } return(nil); } } // create object if it doesn't already exists mediaClass = [IMediaTable mediaClassForFileExtension: ((struct mediaObjectDescriptor *)dataPtr)->type]; if ([[self article] dataForKey: ((struct mediaObjectDescriptor *)dataPtr)->key] == nil) { mediaObject = [[(id)mediaClass allocFromZone: [[self article] zone]] initWithKey: ((struct mediaObjectDescriptor *)dataPtr)->key]; // ## REWRITE! ## need to implement our own stream // package which will map arbritrary memory block // not just vm_allocate memory block stream = NXOpenMemory(NULL, 0, NX_READWRITE); NXWrite(stream, dataPtr + sizeof(struct mediaObjectDescriptor), ((struct mediaObjectDescriptor *)dataPtr)->size); NXSeek(stream, (long)0, NX_FROMSTART); if ([mediaObject readFromStream:stream] == YES) { [[self article] insertKeyedObject:mediaObject]; } else { [mediaObject free]; } NXCloseMemory(stream, NX_FREEBUFFER); } dataPtr += sizeof(struct mediaObjectDescriptor) + ((struct mediaObjectDescriptor *)dataPtr)->size; } stream = NXOpenMemory(data, sizeof(struct mediaObjectDescriptor) + ((struct mediaObjectDescriptor *)data)->size, NX_READONLY); NXSeek(stream, sizeof(struct mediaObjectDescriptor), NX_FROMSTART); [self replaceSelWithRichText:stream]; NXCloseMemory(stream, NX_SAVEBUFFER); needsSaving = YES; [[self window] setDocEdited:needsSaving == YES]; [[article external] markAsDirty]; vm_deallocate(task_self(), (vm_address_t)data, length); if (loadingAlertPanel != nil) { [NXApp runModalSession:&loadingModalSession]; [NXApp endModalSession:&loadingModalSession]; [loadingAlertPanel orderOut:self]; NXFreeAlertPanel(loadingAlertPanel); } [self drainEventQueue]; [self display]; return(self); } else { NXRunAlertPanel(LoStr("NewsBase"),LoStr("Paste of %s failed.") ,NULL,NULL,NULL, NewsBasePboardType); return(nil); } } } // If no NewsBasePboardType is on the pasteboard then try media objects. // This responsibility is delegated to IGraphicImage which will return // IGraphicImage if a valid media pasteboard type exists if ((graphicObject = [[IGraphicImage allocFromZone:[article zone]] initFromPasteboard:pasteboard forView:self]) != nil) { [self replaceSelWithCell:graphicObject]; needsSaving = YES; [[self window] setDocEdited:needsSaving == YES]; [[article external] markAsDirty]; NXPing(); [self drainEventQueue]; return(self); } // If no NewsBasePboardType and no media objects try NXRTFPboardType // then NXAsciiPboardType for (type = [pasteboard types]; *type != NULL; ++type) { if (*type == NXRTFPboardType) { if ([pasteboard readType:NXRTFPboardType data:&data length:&length] != nil) { stream = NXOpenMemory(data, length, NX_READONLY); [self replaceSelWithRichText:stream]; NXCloseMemory(stream, NX_FREEBUFFER); [articleSizeField setIntValue:[self byteLength] + size]; needsSaving = YES; [[self window] setDocEdited:needsSaving == YES]; [[article external] markAsDirty]; return(self); } else { NXRunAlertPanel(LoStr("NewsBase"), LoStr("Paste of %s failed."), NULL,NULL,NULL,NXRTFPboardType); return(nil); } } } for (type = [pasteboard types]; *type != NULL; ++type) { if (*type == NXAsciiPboardType) { if ([pasteboard readType:NXAsciiPboardType data:&data length:&length] != nil) { [self replaceSel:data length:length]; vm_deallocate(task_self(), (vm_address_t)data, length); [articleSizeField setIntValue:[self byteLength] + size]; needsSaving = YES; [[self window] setDocEdited:needsSaving == YES]; [[article external] markAsDirty]; return(self); } else { NXRunAlertPanel(LoStr("NewsBase"),LoStr("Paste of %s failed."), NULL,NULL,NULL,NXAsciiPboardType); return(nil); } } } return(nil); } //***************************************************************************** // Drag and Drop Interface methods //***************************************************************************** static const char *pathList = NULL; // path of dragged file static NXImage *icon = nil; // icon of dragged file - (int)iconEntered:(int)windowNum at:(double)x :(double)y iconWindow:(int)iconWindowNum iconX:(double)iconX iconY:(double)iconY iconWidth:(double)iconWidth iconHeight:(double)iconHeight pathList:(const char *)iconPathList { NXSize iconSize = {48.0, 48.0}; DBG(1, ;) // free previous path if any if (pathList != NULL) { NXZoneFree([self zone], (void *)pathList); } // save path pathList = NXCopyStringBufferFromZone(iconPathList, [self zone]); // free previous icon if any if (icon != nil) { [icon free]; } // save icon icon = [[NXImage allocFromZone:[article zone]] initSize:&iconSize]; [icon lockFocus]; copyIconPicture(iconWindowNum, (float)iconX, (float)iconY, (float)iconWidth, (float)iconHeight); [icon unlockFocus]; return(0); } - (int)iconReleasedAt:(double)x :(double)y ok:(int *)flag { const char *stringPosition, *ptr; int files=1; struct stat statData; NXSelPt start, end; IGraphicImage *newGraphic; if (icon == nil || pathList == NULL) { // this should not happen *flag = 0; return(0); } if (draggedIconBelongsToMe == YES) { draggedIconBelongsToMe = NO; NXZoneFree([self zone], (void *)pathList); pathList = NULL; [icon free]; icon = nil; *flag = 1; return(0); } // The dragged and dropped file will replace the current selection or be // inserted at the current cursor; If there is no selection or cursor // the operation is aborted. [self getSel:&start :&end]; DBG(1, fprintf(stderr, "%d - %d", start.cp, end.cp);) if (start.cp == -1) { if ([self textLength] == 0) { [self setSel:0 :0]; } else { NXRunAlertPanel(LoStr("NewsBase"), LoStr("Please specify destination by making a selection in article body.") ,NULL,NULL,NULL); NXZoneFree([self zone], (void *)pathList); pathList = NULL; [icon free]; icon = nil; *flag = 0; return(0); } } // check if article is editable if (isEditable == NO || [self hasEmbeddedView] == YES) { if ([[self delegate] textWillChange:self] == YES) { NXZoneFree([self zone], (void *)pathList); pathList = NULL; [icon free]; icon = nil; *flag = 0; return(0); } } // check if article is multimedia if (isMultimedia == NO) { switch(NXRunAlertPanel(LoStr("NewsBase"), LoStr("This is not a multimedia article.change to multimedia?"), LoStr("YES"),LoStr("NO"),NULL)) { case NX_ALERTDEFAULT: isMultimedia = YES; [plainMultimediaButton setIcon:"multi_icon"]; [self setMonoFont:NO]; break; case NX_ALERTALTERNATE: NXZoneFree([self zone], (void *)pathList); pathList = NULL; [icon free]; icon = nil; *flag = 0; return(0); } } /* the number of tabs + 1 equals the number of files dragged in */ stringPosition = pathList; while (stringPosition = index(stringPosition, '\t')) { files++; stringPosition++; } /* make sure the user dragged one file in (and it wasn't root) */ if ((files > 1) || !(*pathList)) { NXZoneFree([self zone], (void *)pathList); pathList = NULL; [icon free]; icon = nil; *flag = 0; return(0); } /* punt if a directory */ stat(pathList, &statData); if (statData.st_mode & S_IFDIR && ((ptr = rindex(pathList, '/'), ptr = rindex(ptr, '.')) == 0 || strcmp(ptr + 1, MM_FILE_EXTENSION) != 0)) { NXZoneFree([self zone], (void *)pathList); pathList = NULL; [icon free]; icon = nil; *flag = 0; return(0); } // create a new IGraphicImage object and initialize that object from // the dragged and dropped file. The IGraphicImage object is // responsible for providing the image to be displayed if ((newGraphic = [[IGraphicImage allocFromZone:[article zone]] initFromFile:pathList forView:self withIcon:icon]) != nil) { [self replaceSelWithCell:newGraphic]; needsSaving = YES; [[self window] setDocEdited:needsSaving == YES]; [[article external] markAsDirty]; } // ownership of icon transferred to newGraphic icon = nil; NXZoneFree([self zone], (void *)pathList); pathList = NULL; *flag = 1; return(0); } - (int)iconExitedAt:(double)x :(double)y { DBG(1, ;) if (pathList != NULL) { NXZoneFree([self zone], (void *)pathList); pathList = NULL; } if (icon != nil) { [icon free]; icon = nil; } draggedIconBelongsToMe = NO; return(0); } - setArticle:(IArticleD *)anArticle { article = anArticle; return(self); } - (IOrderedListD *)article { return(article); } - setIsMultimedia:(BOOL)flag { id font_obj; isMultimedia = flag; if (isMultimedia == YES) { [plainMultimediaButton setIcon:"multi_icon"]; [self setMonoFont:NO]; } else { [plainMultimediaButton setIcon:"plain_icon"]; font_obj = [Font newFont:"RyuminTimes-Light" size:14.0]; [self setFont:font_obj]; } return(self); } - (BOOL)isMultimedia { return(isMultimedia); } - setIsEditable:(BOOL)flag { isEditable = flag; return(self); } - (BOOL)isEditable { return(isEditable); } //***************************************************************************** // Embedded View methods //***************************************************************************** // An embedded view is view that is overlayed on a portion of the text view. // Currently the only embedded view belongs to a video view. - (BOOL)hasEmbeddedView { if ([embeddedViewControllers count] > 0) { return(YES); } else { return(NO); } } - addEmbeddedViewController:controller { [embeddedViewControllers addObject:controller]; // setup the textWillChange: message [[[self window] delegate] toggleFirstResponder]; return(self); } - removeEmbeddedViewController:controller { [embeddedViewControllers removeObject:controller]; return(self); } - (List *)embeddedViewControllers { return(embeddedViewControllers); } - setNeedsSaving:(BOOL)flag; { if (flag != 0) { flag = YES; } needsSaving = flag; [[self window] setDocEdited:needsSaving == YES]; [[article external] markAsDirty]; return(self); } - (BOOL)needsSaving { return(needsSaving); } - setArticleSize:(int)newSize { size = newSize; [articleSizeField setIntValue:[self byteLength] + size]; return(self); } - (int)addToArticleSize:(int)increment { size += increment; [articleSizeField setIntValue:[self byteLength] + size]; return(size); } - displaySize { [articleSizeField setIntValue:[self byteLength] + size]; return(self); } - displaySize:dummy { [articleSizeField setIntValue:[self byteLength] + size]; return(self); } - setArticleSizeField:(Form *)aForm { articleSizeField = aForm; return(self); } - setPlainMultimediaButton:(Button *)aButton { plainMultimediaButton = aButton; return(self); } - drainEventQueue { NXEvent *event; while ((event = [NXApp getNextEvent:NX_ALLEVENTS waitFor:0.0 threshold:0]) != NULL) { DBG(1, fprintf(stderr, "event type = %d", event->type);) } return(self); } - setSelectionFileName:(const char *)fileName { selectionFileName = fileName; return(self); } - setOwnerOfDraggedIconToMe:(BOOL)flag { draggedIconBelongsToMe = YES; return(self); } //***************************************************************************** // Editing methods (also support for scroll by space bar) //***************************************************************************** // The following Emacs-style commands are supported. #define CONTROL_A 0x01 // Emacs command to move to beginning of line #define CONTROL_B 0x02 // Emacs command to move back one character #define CONTROL_D 0x04 // Emacs command to delete next character #define CONTROL_E 0x05 // Emacs command to move to end of line #define CONTROL_F 0x06 // Emacs command to move back one character #define CONTROL_H 0x08 // Emacs command to delete previous character #define CONTROL_K 0x0b // Emacs command to delete to end of line #define CONTROL_N 0x0e // Emacs command to move down one line #define CONTROL_P 0x10 // Emacs command to move up one line #define CONTROL_Y 0x19 // Emacs command to paste line #define ALTERNATE_B 0xe5 // Emacs command to move back one word #define ALTERNATE_D 0x44 // Emacs command to delete to end of word #define ALTERNATE_F 0xa6 // Emacs command to move forward one word #define ALTERNATE_H 0xe3 // Emacs command to delete to beginning of word #define ALTERNATE_LT 0xa3 // Emacs command to move beginning of text #define ALTERNATE_GT 0xb3 // Emacs command to move end of text // emacs key command // - keyDown:(NXEvent *)theEvent { NXSelPt start, end; int textEnd; int line; char buffer[8]; Scroller *vertScroller; // First, catch scroll by space bar events and switch to broswer return if (isEditable == NO && theEvent->data.key.charCode == '\r') { [[NXApp delegate] setMyContext:[NXApp activate:[[NXApp delegate] previousContext]]]; return(self); } if (isEditable == NO && (theEvent->data.key.charCode == ' ' || theEvent->data.key.charCode == 128)) { vertScroller = [[[self superview] superview] vertScroller]; // alternate key specifies page or line mode // alpha shift specifies up or down mode if ((theEvent->flags & NX_ALTERNATEMASK) == 0) { if ((theEvent->flags & NX_ALPHASHIFTMASK) == 0) { [vertScroller setHitPart:NX_INCPAGE]; } else { [vertScroller setHitPart:NX_DECPAGE]; } } else { // [[[self superview] superview] setLineScroll:1.0]; if ((theEvent->flags & NX_ALPHASHIFTMASK) == 0) { [vertScroller setHitPart:NX_INCLINE]; } else { [vertScroller setHitPart:NX_DECLINE]; } } [[vertScroller target] perform:[vertScroller action] with:vertScroller]; return(self); } // 1. ClientInputManager will take the key event and handle it // 2. if ClientInputManager process it, return // 3. if not process, check the key event is equivalent to emacs key // 4. if emacs key binding, process and return // 5. hand the event to -keyDown of "super" class #ifdef KANJI if ([inputManager imProcessEvent:theEvent from:self] != nil) { return self; } #endif /* KANJI */ // alt+space(128) will be input by mistake often, and it cause // problem in kanji converter(EUC->JIS). so we remove it. if (theEvent->data.key.charCode == 128) { return self; } // the key event is not processed in InputManager switch (theEvent->data.key.charCode) { case CONTROL_F: [self moveCaret:NX_RIGHT]; return self; break; case CONTROL_B: [self moveCaret:NX_LEFT]; return self; break; case CONTROL_P: [self moveCaret:NX_UP]; return self; break; case CONTROL_N: [self moveCaret:NX_DOWN]; return self; break; case CONTROL_A: // move to head of line [self getSel:&start :&end]; start.cp = [self positionFromLine: [self lineFromPosition:start.cp]]; [self setSel:start.cp :start.cp]; return self; break; case CONTROL_D: // delete next character textEnd = [self textLength]; [self getSel:&start :&end]; if (start.cp < textEnd) { [self setSel:start.cp :start.cp + 1]; [self delete:nil]; } return self; break; case CONTROL_E: // move to end of line [self getSel:&start :&end]; line = [self lineFromPosition:start.cp]; textEnd = [self textLength]; if (line < [self lineFromPosition:textEnd]) { start.cp = [self positionFromLine:line + 1] - 1; } else { start.cp = textEnd; } [self setSel:start.cp :start.cp]; return self; break; case CONTROL_K: // delete to end of line [self getSel:&start :&end]; line = [self lineFromPosition:start.cp]; textEnd = [self textLength]; if (line < [self lineFromPosition:textEnd]) { // this line is not the last line, so can calculate next line [self getSubstring:buffer start:start.cp length:1]; if (*buffer == '\n') { // if next char == '\n', delete '\n' end.cp = [self positionFromLine:line + 1]; } else { // cut this line end.cp = [self positionFromLine:line + 1] - 1; } } else { end.cp = textEnd; } [self setSel:start.cp :end.cp]; [self cut:nil]; return self; break; case CONTROL_Y: // paste line [self paste:nil]; return self; break; case ALTERNATE_B: // move back one word [self getSel:&start :&end]; if (start.cp > 0) { --start.cp; } for (; start.cp > 0; --start.cp) { [self getSubstring:buffer start:start.cp length:1]; if (*buffer != ' ' && *buffer != '\t' && *buffer != '\n') { break; } } for (; start.cp > 0; --start.cp) { [self getSubstring:buffer start:start.cp length:1]; if (*buffer == ' ' || *buffer == '\t' || *buffer == '\n') { ++start.cp; break; } } [self setSel:start.cp :start.cp]; return self; break; case ALTERNATE_D: if (theEvent->data.key.charSet != 1) { break; } // delete to end of current word textEnd = [self textLength]; [self getSel:&start :&end]; for (end.cp = start.cp; end.cp < textEnd; ++end.cp) { [self getSubstring:buffer start:end.cp length:1]; if (*buffer != ' ' && *buffer != '\t' && *buffer != '\n') { break; } } for (; end.cp < textEnd; ++end.cp) { [self getSubstring:buffer start:end.cp length:1]; if (*buffer == ' ' || *buffer == '\t' || *buffer == '\n') { break; } } [self setSel:start.cp :end.cp]; [self delete:nil]; return self; break; case ALTERNATE_F: // move forward one word textEnd = [self textLength]; [self getSel:&start :&end]; for (; start.cp < textEnd; ++start.cp) { [self getSubstring:buffer start:start.cp length:1]; if (*buffer == ' ' || *buffer == '\t' || *buffer == '\n') { break; } } for (; start.cp < textEnd; ++start.cp) { [self getSubstring:buffer start:start.cp length:1]; if (*buffer != ' ' && *buffer != '\t' && *buffer != '\n') { break; } } [self setSel:start.cp :start.cp]; return self; break; case ALTERNATE_H: // delete to beginning of current word [self getSel:&start :&end]; end.cp = start.cp; for (--start.cp; start.cp >= 0; --start.cp) { [self getSubstring:buffer start:start.cp length:1]; if (*buffer != ' ' && *buffer != '\t' && *buffer != '\n') { break; } } for (; start.cp >= 0; --start.cp) { [self getSubstring:buffer start:start.cp length:1]; if (*buffer == ' ' || *buffer == '\t' || *buffer == '\n') { break; } } ++start.cp; [self setSel:start.cp :end.cp]; [self delete:nil]; return self; break; case ALTERNATE_LT: // Emacs command to move beggining of text start.cp = 0; [self setSel:start.cp :start.cp]; return self; break; case ALTERNATE_GT: // Emacs command to move end of text textEnd = [self textLength]; start.cp = textEnd; [self setSel:start.cp :start.cp]; return self; break; default: break; } return ([super keyDown:theEvent]); } // textIsFreeing should be set just before self is freeded. // It is neccessary to control the freeing of media objects // which should not be freed when the self is freeded. - setTextIsFreeing { textIsFreeing = YES; return(self); } - (BOOL)textIsFreeing { return(textIsFreeing); } //***************************************************************************** // Find methods //***************************************************************************** // find is messy because of the embedded graphics - find:(const char *)string forward:(BOOL)dirFlag ignoreCase:(BOOL)caseFlag { NXStream *stream; char *buffer; int len, maxlen; NXSelPt start, end; char *ptr, *bufferEnd, *ptr1; int stringLen; BOOL found; int graphicImageCount; int kanjiCount; int startPtr; stream = NXOpenMemory(NULL, 0, NX_WRITEONLY); [self writeText:stream]; NXGetMemoryBuffer(stream, &buffer, &len, &maxlen); bufferEnd = buffer + NXTell(stream); // translate all to lower if ignore case if (caseFlag == YES) { for (ptr = buffer; ptr < bufferEnd; ++ ptr) { if (isupper(*ptr) != 0) { *ptr = tolower(*ptr); } } } [self getSel:&start :&end]; if (start.cp == -1) { start.cp = 1; } // grahic images '\240' and kanji code (>0x80) // are doubly counted so make adjustment for (ptr = buffer; start.cp > 0; ++ptr, --start.cp) { if (*ptr & 0x80 || *ptr == '\240') { ++ptr; } } stringLen = strlen(string); if (dirFlag == YES) { // forward for (++ptr; ptr < bufferEnd; ++ptr) { if (*ptr == string[0] && strncmp(ptr, string, stringLen) == 0) { found = YES; break; } } } else { // backward for (--ptr; ptr >= buffer; --ptr) { if (*ptr == string[0] && strncmp(ptr, string, stringLen) == 0) { found = YES; break; } } } if (found == YES) { // grahic images are doubly counted so make adjustment graphicImageCount = 0; kanjiCount = 0; for (ptr1 = buffer; ptr1 < ptr; ++ptr1) { if (*ptr1 & 0x80) { if (*ptr1 == '\240') { ++graphicImageCount; } else { ++kanjiCount; } } } if (*ptr & 0x80) { // kanji code is 2bytes stringLen = (int)floor(strlen(string) / 2); } else { stringLen = strlen(string); } startPtr = ptr - buffer - graphicImageCount - (int)floor(kanjiCount / 2); [self setSel:startPtr :startPtr + stringLen]; [self scrollSelToVisible]; } NXCloseMemory(stream, NX_FREEBUFFER); return(self); } - (const char *)selection { NXSelPt start, end; char buffer[128]; int len; [self getSel:&start :&end]; len = end.cp - start.cp; if (len > sizeof(buffer)) { len = sizeof(buffer) - 1; } len = [self getSubstring:buffer start:start.cp length:len]; buffer[len] = '\0'; return(buffer); } - free { [embeddedViewControllers free]; return([super free]); } #undef __OBJC__ #include "errdebug.h" // textFilter() is also used to trigger [INewsBaseText displaySize:] which // dynamically updates the size char *textFilter(id sender, unsigned char *insertText, int *insertLength, int position) { // count charactor and display the size of document [sender perform:@selector(displaySize:) with:nil afterDelay:1 cancelPrevious:YES]; [sender setNeedsSaving:YES]; return ((char *)insertText); } #define __OBJC__ #include "errdebug.h" @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.