/* (c) 1992 Dirk Schwarzhans, Matthias Braun
   Use under the terms of the GNU General Public License */
   
#import "VideoTextView.h"

#import <appkit/color.h>
#import <appkit/nextstd.h>
#import <appkit/publicWraps.h>
#import <ctype.h>
#import <dpsclient/dpsNeXT.h>
#import <math.h>
#import <string.h>
#import "VTFontInit.h"
#import "VTFontProcs.h"

#import <appkit/Application.h>

static void flashHandler(DPSTimedEntry identification, double timeNow, void *data);
static int isVTalnum(int character, VTCharSet language);

@implementation VideoTextView


// **********************************************************
// Methoden die nicht von der Eltern-Klasse öbernommen werden
// **********************************************************
- initFrame:(const NXRect *)frameRect
{
	int i;
	
	[super initFrame:frameRect];

	background = [[NXImage alloc] init];
	foreground = [[NXImage alloc] init];
	hiddenList = [[Storage alloc] initCount:10 
							elementSize:sizeof(CharSeq) description:@encode(CharSeq)];
	flashingList = [[Storage alloc] initCount:10 
							elementSize:sizeof(CharSeq) description:@encode(CharSeq)];
		
	for (i = 0; i < 128; i++)
	{
		convtab[i] = i;
	}
	convtab['@'] = '§'; convtab['['] = '…'; convtab['\\'] = '–'; convtab[']'] = 'š';
	convtab['`'] = 'Ê'; convtab['{'] = 'Ù'; convtab['|'] = 'ð'; convtab['}'] = 'ö';
	convtab['~'] = 'û'; convtab[0x7f] = '*';
	
	dataValid = NO;
	showHiddenText = NO;
	allowFlashing = YES;
	flashTimedEntry = (DPSTimedEntry)-1;
	flashDelay = 0.75;
	delegate = self;
	selection.empty = YES;
	searchOn2Click = YES;
	pageRequested = NO;

	[self setFontSize:24];
	[self resetColorTab];

	return self;
}

- free
{
	if (flashTimedEntry != (DPSTimedEntry)-1)
		DPSRemoveTimedEntry(flashTimedEntry);

	[background free];
	[foreground free];
	[hiddenList free];
	[flashingList free];

	return [super free];
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
	NXPoint point;
	
	if (!dataValid)
	{
		PSsetgray(NX_WHITE);
		NXRectFill(rects);
		
		return self;
	}

	if (NXDrawingStatus != NX_DRAWING)
	{
		PSWVTFont1Init();		// der Drucker hat einen eigenen DSP-Kontext
		PSWVTFont2Init();
		PSWmakeVTFonts(fontSize);
		[self generateImages:self :self hasAlpha:NO];
	}
	else
	{
		point.x = point.y = 0.0;
		[background composite:NX_COPY toPoint:&point];
		[foreground composite:NX_SOVER toPoint:&point];
	
		[self drawSelection:&selection mode:YES];
	
		if (!showHiddenText)
			[self makeTextVisible:NO useList:hiddenList];
	
		PSflushgraphics();
	}

	return self;
}

// ************************************************************
// Methoden zum Setzen und Abfragen der VideoTextView-Attribute
// ************************************************************
- (BOOL)acceptsFirstResponder
{
	return YES;
}

- resetColorTab
{	
	colorTab[VT_BLACK] = NX_COLORBLACK;
	colorTab[VT_RED] = NX_COLORRED;
	colorTab[VT_GREEN] = NX_COLORGREEN;
	colorTab[VT_YELLOW] = NX_COLORYELLOW;
	colorTab[VT_BLUE] = NX_COLORBLUE;
	colorTab[VT_MAGENTA] = NX_COLORMAGENTA;
	colorTab[VT_CYAN] = NX_COLORCYAN;
	colorTab[VT_WHITE] = NX_COLORWHITE;
	[self generateImages:foreground :background hasAlpha:YES];	
	[self display];
	
	return self;
}

- setColorTab:(NXColor *)newColors
{
	int i;
	
	for (i = 0; i < VT_COLORS; i++)
		colorTab[i] = newColors[i];

	[self generateImages:foreground :background hasAlpha:YES];	
	[self display];
	
	return self;
}
	
- (const NXColor *)colorTab
{
	return colorTab;
}

- setSearchOn2Click:(BOOL)mode
{
	searchOn2Click = mode;
	
	return self;
}

- (BOOL)searchOn2Click
{
	return searchOn2Click;
}
	
- setFontSize:(float)points
{
	NXRect rect;
	
	fontSize = points;
	// 0.9 = 900/1000 ist das VerhÙltnis der Grafikzeichenhðhe zu der in
	// /FontMatrix angegebenen Einheit
	lineHeight = fontSize * 0.9;
	charWidth = fontSize * 0.6;

	rect.origin.x = frame.origin.x;
	rect.origin.y = frame.origin.y;
	rect.size.width = floor(charWidth * COLUMNS);
	rect.size.height = floor(lineHeight * ROWS);

	[self setFrame:&rect];
	[window sizeWindow:frame.size.width :frame.size.height];

	// 0.15 = 150/1000 beginnen die Textzeichen öber der Basislinie der Zeile
	line0_y = bounds.size.height - lineHeight + 0.15 * fontSize;

	[background setSize:&(bounds.size)];
	[foreground setSize:&(bounds.size)];
	[self generateImages:foreground :background hasAlpha:YES];
	
	return self;
}

- (float)fontSize
{
	return fontSize;
}
	
- showHiddenText:(BOOL)mode
{
	showHiddenText = mode;
	
	[self lockFocus];
	[self makeTextVisible:mode useList:hiddenList];
	PSflushgraphics();
	[self unlockFocus];
	
	return self;
}

- (BOOL)textHidden
{
	return showHiddenText;
}
	
- allowFlashing:(BOOL)mode
{
	allowFlashing = mode;
	
	if (mode == YES)
		[self startFlashing];
	else
		[self stopFlashing];
		
	return self;
}

- (BOOL)isFlashing
{
	return allowFlashing;
}

- setDelegate:object
{
	delegate = object;
	
	return self;
}
	
- delegate
{	
	return delegate;
}

- (BOOL)hasSelection
{
	return !selection.empty;
}

- setPageData:(const unsigned char *)newPage
{
	int					row, column;
	const unsigned char	*info;
	
	VTChar				current;
	BOOL				graphics;
	BOOL				holdGraphics;
	BOOL				separated;
	unsigned char		currentChar, lastChar;
	
	pageRequested = NO;
	selection.empty = YES;

	[self stopFlashing];
	[hiddenList empty];
	[flashingList empty];
	
	info = newPage + INFO_OFFSET;

	for (row = 0; row < ROWS; row++)
	{
		// Werte för den Zeilenanfang		
		current.charColor = VT_WHITE;
		current.bgndColor = VT_BLACK;
		current.flashing = NO;
		current.hidden = NO;
		current.fontSize = NORMALSIZE;
		current.font = TEXTFONT;
		lastChar = ' ';
		graphics = NO;
		separated = NO;
		holdGraphics = NO;

		for (column = 0; column < COLUMNS; column++)
		{
			currentChar = VALIDBITS(newPage[row * COLUMNS + column]);
			if (!ISCTRL(currentChar))
			{
				// Text oder Grafikzeichen
				if (graphics && ISGRAPHICCHAR(currentChar))
				{
					current.charCode = currentChar;
					if (separated)
						current.font = SEPGRAPHFONT;
					else
						current.font = CONGRAPHFONT;
				}
				else
				{
					current.charCode = convtab[currentChar];
					current.font = TEXTFONT;
				}
			}
			else
			{
				// sofort wirkende Steuerzeichen
				switch((VTCtrl)currentChar)
				{
				case BLACKBGND:
					current.bgndColor = VT_BLACK;
					break;
				case NEWBGND:
					current.bgndColor = current.charColor;
					break;
				case HOLDGRAPH:
					holdGraphics = YES;
					break;
				case STEADY:
					current.flashing = NO;
					break;
				case NORMAL:
					current.fontSize = NORMALSIZE;
					break;
				case HIDE:
					current.hidden = YES;
					break;
				default:
					break;
				}
	
				if (holdGraphics && graphics && ISGRAPHICCHAR(lastChar))
					current.charCode = lastChar;
				else
					current.charCode = ' ';			
			}
			
			// Zuweisung der aktuellen Zeichendaten
			decodedPage[row][column] = current;

			// Auswertung von verzðgert wirkenden Steuerzeichen
			if (ISCTRL(currentChar))
			{
				switch((VTCtrl)currentChar)
				{
				case ALPHABLACK: 
				case ALPHARED:
				case ALPHAGREEN: 
				case ALPHAYELLOW:
				case ALPHABLUE: 
				case ALPHAMAGENTA: 
				case ALPHACYAN: 
				case ALPHAWHITE:
					current.hidden = NO;
					current.charColor = CTRL2COLOR(currentChar);
					graphics = NO;
					break;
				case GRAPHBLACK: 
				case GRAPHRED: 
				case GRAPHGREEN: 
				case GRAPHYELLOW: 
				case GRAPHBLUE: 
				case GRAPHMAGENTA: 
				case GRAPHCYAN: 
				case GRAPHWHITE:
					graphics = YES;
					current.hidden = NO;
					current.charColor = CTRL2COLOR(currentChar);
					break;
				case FLASH:
					current.flashing = YES;
					break;
				case DOUBLE:
					if (row != 23)
						current.fontSize = DOUBLESIZE;
					break;
				case CONGRAPH:
					separated = NO;
					break;
				case SEPGRAPH:
					separated = YES;
					break;
				case RELEASEGRAPH:
					holdGraphics = NO;
					break;
				default:
					break;
				}
			}

			lastChar = current.charCode;
		} // endfor: column
	} // endfor: row
		
	// Bereinigung der Nachfolgezeile einer doppelt hohen Zeile
	// d.h. Text ist immer ' '
	//      Hintergrundfarbe an jeder Position, wie eine Zeile hðher
	for (row = 1; row < ROWS; row++)
	{
		doubleTab[row] = doubleTab[row - 1] = 0;
		for (column = 0; column < COLUMNS; column++)
			if (decodedPage[row - 1][column].fontSize == DOUBLESIZE)
			{
				doubleTab[row] = 2;
				doubleTab[row - 1] = 1;
				break;
			}
		if (doubleTab[row])
		{
			for (column = 0; column < COLUMNS; column++)
			{
				decodedPage[row][column].bgndColor = 
					decodedPage[row - 1][column].bgndColor;
				decodedPage[row][column].charCode = ' ';				
			}
			row++;
		}
	}

	// Einfögen der Text-Break-Informationen, die angeben, daû sich best. Modi
	// im Vergleich zum vorhergehenden Zeichen verÙndert haben
	// und Anlegen von Listen mit versteckten und blinkenden Text-Bereichen
	{
		BOOL lastFlash, lastHide;
		CharSeq hideSeq, flashSeq;
			
		for (row = 0; row < ROWS; row++)
		{
			decodedPage[row][0].textBreak = NO;		
			// Text-Break-Informationen erzeugen				
			for (column = 1; column < COLUMNS; column++)
			{
				if (decodedPage[row][column].charColor !=
					decodedPage[row][column - 1].charColor ||
					decodedPage[row][column].font !=
					decodedPage[row][column - 1].font ||
					decodedPage[row][column].fontSize !=
					decodedPage[row][column - 1].fontSize)
	
					decodedPage[row][column].textBreak = YES;				
				else
					decodedPage[row][column].textBreak = NO;						
			}

			// Flash- und Hidden-Listen anlegen	
			lastFlash = lastHide = NO;
			for (column = 0; column < COLUMNS; column++)
			{
				if (lastFlash != decodedPage[row][column].flashing ||
					column == COLUMNS - 1)
				{
					if (lastFlash)
					{
						flashSeq.empty = NO;
						flashSeq.end.row = row;
						flashSeq.end.column = 
							((column == COLUMNS - 1) ? COLUMNS : column);
						[flashingList addElement:&flashSeq];
					}
					else
					{	
						flashSeq.start.row = row;
						flashSeq.start.column = column;
					}
				}

				if (lastHide != decodedPage[row][column].hidden ||
					column == COLUMNS - 1)
				{
					if (lastHide)
					{
						hideSeq.empty = NO;
						hideSeq.end.row = row;
						hideSeq.end.column = 
							((column == COLUMNS - 1) ? COLUMNS : column);
						[hiddenList addElement:&hideSeq];
					}
					else
					{
 						hideSeq.start.row = row;
						hideSeq.start.column = column;
					}
				}
				lastFlash = decodedPage[row][column].flashing;
				lastHide = decodedPage[row][column].hidden;
			} // endfor column		
					
		} // endfor row
	} // Ende lokale Variablen
	
	// Auswerten der Status-Informationen der Seite
	language = (info[7] & 0xE) >> 1;

	dataValid = YES;
	[self generateImages:foreground :background hasAlpha:YES];	
	[self display];
	[self startFlashing];
		
	return self;
}

// *****************************************************
// interne Methoden zum Zeichnen und VerÙndern des Views
// *****************************************************
- generateImages:foregroundFocus :backgroundFocus hasAlpha:(BOOL)alphaMode
{
	int			row, column;			// momentane Zeile, Spalte
	VTChar		*startChar;				//    mit gleichartigen Zeichen
	char		buffer[COLUMNS + 1];	// Speicher för max. eine Textzeile
	int			index;					// Laufindex för 'buffer'

	if (!dataValid)
		return self;

	PSWmakeVTFonts(fontSize);
	
	// Hintergrund erzeugen
	[backgroundFocus lockFocus];
	PSsetgray(NX_WHITE);
	PSsetalpha(1.0);
	NXRectFill(&bounds);
	
	for (row = 0; row < ROWS; row++)
	{
		PSWsetVTFont(CONGRAPHFONT);
		PSmoveto(0.0, line0_y - row * lineHeight);
		
		column = 0;
		index = 0;
		startChar = &(decodedPage[row][column]);
		while (++column <= COLUMNS)
		{
			if (decodedPage[row][column].bgndColor != startChar->bgndColor
				|| column == COLUMNS)
			{
				buffer[0] = 0x7f;;
				buffer[++index] = 0;

				NXSetColor(colorTab[startChar->bgndColor]);
				PSshow(buffer);		
				
				if (column != COLUMNS)
				{
					index = 0;
					startChar = &(decodedPage[row][column]);
				}
			}
			else
				buffer[++index] = 0x7f;		
		}
	}
	[backgroundFocus unlockFocus];
	
	// Text und Grafik ausgeben
	[foregroundFocus lockFocus];
	if (alphaMode)
	{
		PSsetgray(NX_WHITE);
		PSsetalpha(0.0);
		NXRectFill(&bounds);
		PSsetalpha(1.0);
	}
		
	for (row = 0; row < ROWS; row++)
	{
		PSmoveto(0.0, line0_y - row * lineHeight);
		
		column = 0;
		index = 0;
		startChar = &(decodedPage[row][column]);
		while (++column <= COLUMNS)
		{
			if (decodedPage[row][column].textBreak || column == COLUMNS)
			{
				buffer[0] = startChar->charCode;
				buffer[++index] = 0;

				NXSetColor(colorTab[startChar->charColor]);
				if (startChar->fontSize == DOUBLESIZE)
				{
					PSrmoveto(0.0, -(lineHeight- 0.15 * fontSize));
					PSWsetVTFontDouble(startChar->font);
					PSshow(buffer);		
					PSrmoveto(0.0, (lineHeight - 0.15 * fontSize));
				}
				else
				{
					PSWsetVTFont(startChar->font);					
					PSshow(buffer);	
				}

				if (column != COLUMNS)
				{
					index = 0;
					startChar = &(decodedPage[row][column]);
				}
			}
			else
				buffer[++index] = decodedPage[row][column].charCode;		
		}
	}
	[foregroundFocus unlockFocus];
		
	return self;
}

- makeTextVisible:(BOOL)mode useList:(Storage *)list
{
	int i, count;
	CharSeq *work;
	NXRect rect;
	
	count = [list count];
	for (i = 0; i < count; i++)
	{
		work = [list elementAt:i];

		rect.origin.x = work->start.column * charWidth;
		rect.origin.y = [self bottomOfLine:work->start.row];
		rect.size.width = (work->end.column - work->start.column) * charWidth;
		rect.size.height = [self heightOfLine:work->start.row];
		if (mode)
			[foreground composite:NX_SOVER fromRect:&rect toPoint:&(rect.origin)];
		else
			[background composite:NX_COPY fromRect:&rect toPoint:&(rect.origin)];
		
	}

	return self;
}
	
- flashText
{
	[self lockFocus];

	switch(nextAction)
	{
	case FLASH_HIDE:
		[self makeTextVisible:NO useList:flashingList];
		nextAction = FLASH_SHOW;
		break;
	case FLASH_SHOW:
		[self makeTextVisible:YES useList:flashingList];
		nextAction = FLASH_HIDE;
		break;
	}	
	
	PSflushgraphics();
	[self unlockFocus];

	return self;
}

- startFlashing
{
	if (flashTimedEntry == (DPSTimedEntry)-1 && [flashingList count] != 0 &&
		allowFlashing == YES)
		flashTimedEntry = DPSAddTimedEntry(flashDelay, flashHandler, self,
										   NX_BASETHRESHOLD);
	nextAction = FLASH_HIDE;
						   
	return self;
}

- stopFlashing
{
	if (flashTimedEntry != (DPSTimedEntry)-1)
	{
		DPSRemoveTimedEntry(flashTimedEntry);
		flashTimedEntry = (DPSTimedEntry)-1;
		if (nextAction == FLASH_SHOW)
			[self flashText];
	}
	
	return self;
}

- drawSelection:(CharSeq *)sel mode:(BOOL)show
{
	NXRect	rects[3];
	int		rectCount;
	int		sCol, eCol;
		
	if (sel->empty)
		return self;

	rectCount = 1;
	if (sel->start.column < sel->end.column)
	{	
		sCol = sel->start.column;
		eCol = sel->end.column;
		rects[0].origin.y = [self bottomOfLine:sel->end.row];
		rects[0].size.height = [self topOfLine:sel->start.row] - 
								[self bottomOfLine:sel->end.row];
	}
	else
	{
		eCol = sel->start.column;
		sCol = sel->end.column;
		rects[0].origin.y = [self topOfLine:sel->end.row];
		rects[0].size.height = [self bottomOfLine:sel->start.row] - 
								[self topOfLine:sel->end.row];
	}
	rects[0].origin.x = sCol * charWidth;
	rects[0].size.width = (eCol - sCol) * charWidth;

	// Start und Ende liegen in unterschiedlichen VideoText-Zeilen
	if ([self topOfLine:sel->start.row] != [self topOfLine:sel->end.row])
	{
		rectCount = 3;
		rects[1].origin.x = 0;
		rects[1].origin.y = [self bottomOfLine:sel->end.row];
		rects[1].size.width = sCol * charWidth;
		rects[1].size.height = [self bottomOfLine:sel->start.row] -
							   [self bottomOfLine:sel->end.row];

		rects[2].origin.x = eCol * charWidth;
		rects[2].origin.y = [self topOfLine:sel->end.row];
		rects[2].size.width = (COLUMNS - eCol) * charWidth;
		rects[2].size.height = [self topOfLine:sel->start.row] -
							   [self topOfLine:sel->end.row];;		
	}	

	// ausgewÙhlten Bereich markieren bzw. Markierung lðschen
	PSsetgray(NX_LTGRAY);
	while(rectCount--)
	{
		if (show)
			NXRectFill(&rects[rectCount]);
		else
			[background composite:NX_COPY fromRect:&(rects[rectCount])
					toPoint:&(rects[rectCount].origin)];			
		[foreground composite:NX_SOVER fromRect:&(rects[rectCount])
					toPoint:&(rects[rectCount].origin)];
	}
	
	return self;
}
	
// *********************************************
// Methoden zum Verarbeiten von Maus-Ereignissen
// *********************************************		
- mouseDown:(NXEvent *)event
{
	Position currentPos;
	int start, end, value;
	int shift, alternate, control;
	BOOL number;

	[self stopFlashing];
	currentPos.column = (int)floor((event->location.x) / charWidth);
	currentPos.row = (int)floor(ROWS - (event->location.y) / lineHeight);				
	shift = (event->flags) & NX_SHIFTMASK;
	alternate = (event->flags) & NX_ALTERNATEMASK;
	control = (event->flags) & NX_CONTROLMASK;

	[window addToEventMask:NX_LMOUSEDRAGGEDMASK];
	[self lockFocus];	
		
	switch(clickCount = event->data.mouse.click)
	{
	case 1:
		if (!alternate)
		{
			if (selection.empty == NO)
			{
				[self drawSelection:&selection mode:NO];
				PSflushgraphics();
			}
			selection.empty = YES;
		}
		break;
	case 2:
		if (doubleTab[currentPos.row] == 2)
			currentPos.row--;
		if (decodedPage[currentPos.row][currentPos.column].font != TEXTFONT)
			break;
		if (isVTalnum(decodedPage[currentPos.row][currentPos.column].charCode, language))
		{
			start = currentPos.column;
			while (start > 0 &&
				  isVTalnum(decodedPage[currentPos.row][start - 1].charCode, language) &&
				  decodedPage[currentPos.row][start - 1].font == TEXTFONT)
				start--;
			end = currentPos.column;
			while (end < COLUMNS &&
					isVTalnum(decodedPage[currentPos.row][end + 1].charCode, language) &&
					decodedPage[currentPos.row][end + 1].font == TEXTFONT)
				end++;
		}
		else
			start = end = currentPos.column;

		selection.empty = NO;
		selection.appendCR = NO;
		selection.start.row = selection.end.row = currentPos.row;
		selection.start.column = start;
		selection.end.column = end + 1;
		[self drawSelection:&selection mode:YES];
		PSflushgraphics();
		
		// ggf. neue Seite suchen lassen
		number = YES;
		if ((shift && !searchOn2Click) || (!shift && searchOn2Click))
		{
			value = 0;
			while (start <= end)
			{
				if (!isdigit(decodedPage[currentPos.row][start].charCode))
				{
					number = NO;
					break;
				}
				value *= 10;
				value += decodedPage[currentPos.row][start++].charCode - '0';
			}
				
			if (value >= 100 && value <= 899 && number)
				{
				if (control)
					[delegate requestPage:value subpage:0 forWindow:nil];
				else
					[delegate requestPage:value subpage:0 forWindow:window];
				pageRequested = YES;
				}
				
		}
				
		break;
	case 3:
		selection.empty = NO;
		selection.appendCR =YES;
		selection.start.row = selection.end.row = currentPos.row;
		selection.start.column = 0;
		selection.end.column = COLUMNS;
		[self drawSelection:&selection mode:YES];
		PSflushgraphics();
		break;
	default:
		return self;
		break;
	}
	
	draggPos.row = currentPos.row;
	draggPos.column = currentPos.column;
	draggStart = selection.start;
	
	[self unlockFocus];	

	return self;
}

- mouseUp:(NXEvent *)event;
{
	[window removeFromEventMask:NX_LMOUSEDRAGGEDMASK];

	if (selection.empty == NO && pageRequested == NO)
		[delegate dontDisturb:YES];
	else
		[delegate dontDisturb:NO];

	if (selection.empty == YES)
		[self startFlashing];
		
	return self;
}

- mouseDragged:(NXEvent *)event
{
	Position		currentPos;
	CharSeq			oldSelection;
	BOOL			flag;

	if (clickCount != 1)		// noch kein zeilen- bzw. wortweises Draggen mðglich
		return self;

	[delegate dontDisturb:YES];
	[self convertPoint:&(event->location) fromView:nil];

	currentPos.column = (int)floor(((event->location.x) / charWidth));
	currentPos.row = (int)floor((ROWS - (event->location.y) / lineHeight));

	if (currentPos.row == draggPos.row && currentPos.column == draggPos.column &&
		!selection.empty)
		return self;
	draggPos = currentPos;

	selection.appendCR = NO;
	oldSelection = selection;

	if (selection.empty)
	{
		selection.empty = NO;
		draggStart = currentPos;
	}

	if (currentPos.column < 0)
	{
		currentPos.column = 0;
		flag = YES;
	}
	else
		flag = NO;

	if (currentPos.column >= COLUMNS)
	{
		currentPos.column = COLUMNS; 
		selection.appendCR = YES;
	}

	if ([self rowCol2Offset:&currentPos] < [self rowCol2Offset:&draggStart])	
	{
		selection.start = currentPos;
		selection.end = draggStart;
	}
	else
	{
		selection.start = draggStart;
		selection.end = currentPos;
	}
	
	if (currentPos.row < 0)
	{
		selection.start.row = 0;
		selection.start.column = 0;
	}

	if (currentPos.row >= ROWS)
	{
		selection.end.row = ROWS - 1;
		selection.end.column = COLUMNS;
		selection.appendCR = YES;
	}

	if (selection.end.column < COLUMNS && selection.start.row == draggStart.row &&
		selection.start.column == draggStart.column && !flag)
		selection.end.column++;

	if (doubleTab[selection.start.row] == 2)
		selection.start.row--;
	if (doubleTab[selection.end.row] == 2)
		selection.end.row--;		
	
	[self lockFocus];	
	[self drawSelection:&oldSelection mode:NO];
	[self drawSelection:&selection mode:YES];
	PSflushgraphics();
	[self unlockFocus];	
	
	return self;
}

// *****************************************************
// weitere Methoden, die wir als FirstResponder anbieten 
// *****************************************************
- copy:sender
{
	const char *types[] = {NXAsciiPboardType, NULL};

	if (selection.empty)
		return nil;

	[self writeSelectionToPasteboard:[Pasteboard new] types:types];
	
	return self;
}

- cut:sender
{
	[self copy:sender];
	NXBeep();
	
	return self;
}

- paste:sender
{
	NXBeep();
	
	return self;
}

- validRequestorForSendType:(NXAtom)sendType andReturnType:(NXAtom)returnType
{
	if (returnType == NULL && sendType == NXPostScriptPboardType)
		return self;
		
	if (returnType == NULL && sendType == NXAsciiPboardType && !selection.empty)
		return self;
		
	return nil;
}

- (BOOL)writeSelectionToPasteboard:pasteboard types:(NXAtom *)types
{
    NXStream 	*stream;
    char		*data;
    int			maxLength, length, row, column;
	BOOL		success;
		
	success = NO;
	do
	{
		if (*types == NXAsciiPboardType && !selection.empty)
		{
			[pasteboard declareTypes:&NXAsciiPboardType num:1 owner:self];
		
			stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
		
			row = selection.start.row;
			column = selection.start.column;
		
			do 
			{
				if (decodedPage[row][column].font == TEXTFONT)
					NXPutc(stream, decodedPage[row][column].charCode);
				else
					NXPutc(stream,' ');
				
				if (++column >= COLUMNS)
				{
					if (row != selection.end.row || selection.appendCR)
						NXPutc(stream, '\n');		 
					row++;
					column = 0;
				}
			}while (row < selection.end.row || 
					(column < selection.end.column && row == selection.end.row));
		
			NXGetMemoryBuffer(stream, &data, &length, &maxLength);
			[pasteboard writeType:NXAsciiPboardType data:data length:length];
			NXCloseMemory(stream, NX_FREEBUFFER);
			success = YES;
		}

		if (*types == NXPostScriptPboardType)
		{
			[pasteboard declareTypes:&NXPostScriptPboard num:1 owner:self];
		
			stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
			[self copyPSCodeInside:&frame to:stream];
			NXGetMemoryBuffer(stream, &data, &length, &maxLength);
			[pasteboard writeType:NXPostScriptPboard data:data length:length];
			NXCloseMemory(stream, NX_FREEBUFFER);
			success = YES;
		}
	}while(*(++types) != NULL && !success);
	
	return success;
}

- selectAll:sender
{
	selection.empty = NO;
	selection.appendCR = YES;
	selection.start.row = selection.start.column = 0;
	selection.end.row = ROWS - 1;
	selection.end.column = COLUMNS;

	[self lockFocus];	
	[self drawSelection:&selection mode:YES];
	PSflushgraphics();
	[self unlockFocus];	
	
	return self;
}

- clearSelection
{
	if (selection.empty == YES)
		return self;
		
	[self lockFocus];	
	[self drawSelection:&selection mode:NO];
	PSflushgraphics();
	[self unlockFocus];	
	selection.empty = YES;

	return self;
}

// ****************************************************
// interne Methoden zum berechnen von Zeichenpositionen
// ****************************************************
- (float)bottomOfLine:(int)row
{
	if (doubleTab[row] == 1)
		row++;
		
	return bounds.size.height - lineHeight * (row + 1);
}

- (float)topOfLine:(int)row
{
	if (doubleTab[row] == 2)
		row--;
		
	return bounds.size.height - lineHeight * row;
}

- (float)heightOfLine:(int)row
{
	return [self topOfLine:row] - [self bottomOfLine:row];
}

- (int)rowCol2Offset:(Position *)pos
{
	if (doubleTab[pos->row] == 2)
		return pos->column + (pos->row - 1)* COLUMNS;
	else
		return pos->column + pos->row * COLUMNS;
}

// **********************************************************
// Dummy-Methoden för den Fall, daû kein delegate bekannt ist
// **********************************************************
- requestPage:(int)page subpage:(int)subpage forWindow:(Window *)window
{	return self;	}

- dontDisturb:(BOOL)mode
{	return self;	}

@end

// Wird durch einen 'timed entry' aufgerufen und erzeugt einen Objective-C Aufruf
static void flashHandler(DPSTimedEntry identification, double timeNow, void *data)
{
	[(VideoTextView *)data flashText];
}

// erweiterte Routine, die auch Umlaute in Video-Text-Kodierung erkennt
// vorlÙufig nur för den deutschen Zeichensatz
static int isVTalnum(int character, VTCharSet language)
{
	if (isalnum(character))
		return YES;
	if (language != GERMAN)
		return NO;
	switch(character)
	{
	case 0xD9:	// Ù
	case 0xF0:	// ð
	case 0xF6:	// ö
	case 0x85:	// …
	case 0x96:	// –
	case 0x9A:	// š
	case 0xFB:	// û
		return YES;
	default:
		return NO;
	}
}
