This is ObjCText.m in view mode; [Download] [Up]
#import "ObjCText.h"
#import <stdlib.h>
#import <strings.h>
#import <appkit/publicWraps.h>
#import <appkit/Scroller.h>
#import <appkit/ScrollView.h>
#import <appkit/Application.h>
#define ESC 27
#define CTRL_A (1)
#define CTRL_B (2)
#define CTRL_C (3)
#define CTRL_D (4)
#define CTRL_E (5)
#define CTRL_F (6)
#define CTRL_K (11)
#define CTRL_N (14)
#define CTRL_P (16)
#define CTRL_V (22)
//
// following procedures were "stolen" from
// Lee boynton's LispExample
/*
** NXBGetc - a text stream macro to get the previous character.
*/
typedef struct {
id text;
NXTextBlock *block;
} textInfo;
static char getPrevious(NXStream *s)
{
textInfo *info = (textInfo *) s->info;
NXTextBlock *block = info->block->prior;
if (!block) {
return EOF;
}
s->buf_base = block->text;
s->buf_ptr = s->buf_base + block->chars;
s->offset -= block->chars;
info->block = block;
return *(--s->buf_ptr);
}
#define NXBGetc(s) \
(((s)->buf_base == (s)->buf_ptr) ? getPrevious(s) : \
*(--((s)->buf_ptr)) )
int
findMatchingOpenParen(id text, char thisChar, char thatChar, int curPos)
{ int count = 1;
char ch;
NXStream *s = [text stream];
NXSeek(s, curPos, NX_FROMSTART);
while ((ch = NXBGetc(s)) != EOF)
{ if(ch == thatChar && !(--count))
return NXTell(s);
else if (ch == thisChar)
count++;
}
return -1;
}
int
findMatchingCloseParen(id text, char thisChar, char thatChar, int curPos)
{
int count = 1;
char ch;
NXStream *s = [text stream];
NXSeek(s, curPos, NX_FROMSTART);
while ((ch = NXGetc(s)) != EOF) {
if (ch == thatChar && !(--count))
return NXTell(s);
else if (ch == thisChar)
count++;
}
return -1;
}
void highlightChar(id text, int charPosition)
{
NXSelPt selStart, selEnd;
[text getSel:&selStart :&selEnd];
[text setSel:charPosition :charPosition+1];
[[text window] flushWindow];
NXPing();
[[text window] disableFlushWindow];
[text setSel:selStart.cp :selEnd.cp];
[[text window] reenableFlushWindow];
}
@implementation ObjCText:Text
- initFrame:(NXRect *) aRect ;
{ self = [super initFrame: aRect] ;
return self ;
}
- keyDown:(NXEvent *)theEvent
{ // provide some emacs-style key bindings
int i;
static unsigned short val = '\0', lastChar = '\0' ;
NXSelPt selStart, selEnd;
[self getSel:&selStart :&selEnd];
lastChar = val ; // val was set in "previous" call of keyDown..
val = theEvent->data.key.charCode + (theEvent->data.key.charSet << 8);
switch (val) {
case ESC: // don't want ESC to cause a beep
return self ;
case '<':
if(lastChar == ESC)
{ [self setSel: 0 :0];
[self scrollSelToVisible];
return self;
}
break;
case '>':
if(lastChar == ESC)
{ i = [self textLength];
[self setSel: i :i];
[self scrollSelToVisible];
return self;
}
break;
case ')':
i = findMatchingOpenParen(self,')','(',selEnd.cp);
[super keyDown:theEvent];
if (i >= 0)
highlightChar(self,i);
return self;
case '}':
i = findMatchingOpenParen(self,'}','{',selEnd.cp);
[super keyDown:theEvent];
if (i >= 0)
highlightChar(self,i);
return self;
case ']':
i = findMatchingOpenParen(self,']','[',selEnd.cp);
[super keyDown:theEvent];
if (i >= 0)
highlightChar(self,i);
return self;
case CTRL_A: // beginning of line
i = [self lineFromPosition:selStart.cp];
i = [self positionFromLine: i];
[self setSel:i :i];
return self;
case CTRL_E: // end of line
i = [self lineFromPosition: selStart.cp] ;
i = [self positionFromLine: i + 1] ; // beginning of next line
if (i == -1) // is there no next line in the text?
{
i = [self textLength];
[self setSel: i :i];
}
else // there is a next line in the text
{
[self setSel:i-1 :i-1];
}
return self;
case CTRL_B:// backwards one char
if (selStart.cp > 1)
[self setSel:selStart.cp - 1 :selStart.cp -1];
else
NXBeep();
return self;
case CTRL_F: // forwards one char
if (selEnd.cp < [self textLength])
[self setSel:selEnd.cp+1 :selEnd.cp+1];
else
NXBeep();
return self;
case CTRL_D: // delete char under cursor
[self setSel:selEnd.cp :selEnd.cp+1];
[self replaceSel:""];
[self setSel:selStart.cp :selStart.cp];
[[NXApp mainWindow] setDocEdited:YES];
return self;
case CTRL_K: // clear to end of line
i = [self lineFromPosition: selStart.cp] ;
i = [self positionFromLine: i + 1] ; // beginning of next line
if (i == -1) // is there no next line in the text?
{
i = [self textLength];
[self setSel: selStart.cp :i];
}
else
{
[self setSel:selStart.cp :--i];
}
[self replaceSel:""];
[self setSel:selStart.cp :selStart.cp];
[[NXApp mainWindow] setDocEdited:YES];
return self;
case CTRL_P: // move directly up one line...fake down arrow
theEvent->data.key.charCode = 173 ;
theEvent->data.key.charSet = 1 ;
theEvent->flags = NX_NUMERICPADMASK ;
break ;
case CTRL_N: // move directly down one line...fake up arrow
theEvent->data.key.charCode = 175 ;
theEvent->data.key.charSet = 1 ;
theEvent->flags = NX_NUMERICPADMASK ;
break ;
case 'v': // move up one screenful
if(lastChar != ESC)
break ;
case CTRL_V: // move down one screenful
{ id scroller ;
NXRect aRect ;
NXEvent fakeEvent ;
fakeEvent = *theEvent ;
fakeEvent.type = NX_MOUSEDOWN ;
fakeEvent.data.mouse.reserved = 18 ;
fakeEvent.data.mouse.eventNum = 9999 ;
fakeEvent.data.mouse.click = 1 ;
/*fakeEvent.data.mouse.unused = 0 ;*/
fakeEvent.flags = NX_ALTERNATEMASK ;
scroller = [[superview superview] vertScroller] ;
[scroller calcRect:&aRect forPart:
val == 'v'? NX_DECLINE :NX_INCLINE ] ;
[scroller convertPoint: &aRect.origin toView: nil] ;
fakeEvent.location = aRect.origin ;
DPSPostEvent(&fakeEvent,1) ;
fakeEvent.type = NX_MOUSEUP ;
fakeEvent.time += 10 ; // 10 /68ths of a second
DPSPostEvent(&fakeEvent,0) ;
return self ;
}
default:
break;
}
return [super keyDown:theEvent];
}
- mouseDown: (NXEvent *) anEvent ;
{ // redefine double-click behavior to
// check for bracketed structures and quoted
// items
NXSelPt start, end ;
char openers[] = "{([<\"'/`" ;
char closers[] = "})]>\"'/`" ;
char c,d, *place ;
int otherPos ;
NXStream *aStream ;
if(anEvent->data.mouse.click == 2)
{ aStream = [self stream] ;
[self getSel: &start :&end] ;
if(start.cp > 0)
{ NXSeek(aStream,start.cp-1,NX_FROMSTART) ;
c = NXGetc(aStream) ;
if(place = index(closers,c)) // we have a structure
{ d = openers[(int) place - (int) closers] ;// grab matching char
if( (otherPos = findMatchingOpenParen(self, c, d, start.cp -1))
!= -1)
{ if(c == '`') // put selection "inside" ` ... ` pair
[self setSel: otherPos + 1 :start.cp - 1] ;
else
[self setSel: otherPos :start.cp] ;
return self ;
}
}
else
{ NXSeek(aStream,start.cp,NX_FROMSTART) ;
c = NXGetc(aStream) ;
if(place = index(openers,c)) // we have a structure
{ d = closers[(int) place - (int) openers] ;
if( (otherPos = findMatchingCloseParen(self, c, d, start.cp +1))
!= -1)
{ if(c == '`') // put selection "inside" ` ... ` pair
[self setSel: start.cp +1 :otherPos-1] ;
else
[self setSel: start.cp :otherPos] ;
return self ;
}
}
}
}
}
else if(anEvent->data.mouse.click == 4)
{ // triple click == search back for backquote (or beginning of text)
// forward for backquote (or end ot text), select
int from, to ;
aStream = [self stream] ;
[self getSel: &start :&end] ;
if(start.cp > 0)
{ from = start.cp - 1 ;
NXSeek(aStream,from,NX_FROMSTART) ;
while(from >= 1 && ((c = NXBGetc(aStream)) != '`'))
from-- ;
to = start.cp ;
NXSeek(aStream,to,NX_FROMSTART) ;
while(!NXAtEOS(aStream) && ((c = NXGetc(aStream)) != '`'))
to++ ;
[self setSel: from :to] ;
return self ;
}
}
return [super mouseDown: anEvent] ;
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.