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.