/*	Copyright  (c)	Günter Woigk 2016 - 2016
					mailto:kio@little-bat.de

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

	Permission to use, copy, modify, distribute, and sell this software and
	its documentation for any purpose is hereby granted without fee, provided
	that the above copyright notice appear in all copies and that both that
	copyright notice and this permission notice appear in supporting
	documentation, and that the name of the copyright holder not be used
	in advertising or publicity pertaining to distribution of the software
	without specific, written prior permission.  The copyright holder makes no
	representations about the suitability of this software for any purpose.
	It is provided "as is" without express or implied warranty.

	THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
	INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
	EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
	CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
	DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
	TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
	PERFORMANCE OF THIS SOFTWARE.
*/

#define SAFE 3
#define LOG 1
#include "kio/kio.h"
#include "MainWindow.h"
#include <QOpenGLWindow>
#include <QPainter>
#include "unix/pthreads.h"
#include <QThread>
#include <QMutex>
#include <QSemaphore>
#include "unix/os_utilities.h"
#include <QKeyEvent>
#include <QMouseEvent>
#include <QTimer>
#include <QMenuBar>
#include <QSerialPortInfo>
#include "IOPort.h"
#include "Pixmap.h"
#include "TTYObject.h"


// settings:

//#define WIDTH  	800				// Default Terminal Window size
//#define HEIGHT	600
//#define MAX_STREAMS 240			// max. number of stream	max. 240 wg. error correction protocol
QColor background_color(Qt::black);	// 0xAARRGGBB


// Settings reported by Identify:
bool enable_fs  = no;				// expose file system?
bool enable_kbd = yes;				// expose keyboard?	 default = yes
bool enable_joy = no;				// expose joysticks?
bool enable_ptr = no;				// expose pointer device (mouse)?
uint32 max_mem  = MAX_MEM;			// ram usage limit
//uint32 max_streams = MAX_STREAMS;	// max. logical streams


//// I/O Port Settings:
//bool server_selected = no;			// ip socket server in use?
//uint socket_port = 20042;			// if server_selected: port in use
//bool pipe_selected = no;			// named pipes in use?
//cstr pipe_path = "/tmp/GTermPipe";	// if pipe_selected: file system path
//bool stdio_selected = no;			// stdin/stdout in use?
//int  selected_sio = -1;				// serial port in use: idx in sio_infos, -1 = none
//uint sio_baudrate = 19200;			// if selected_sio: baudrate in use. always 8n1



// state:
MainWindow* main_window = NULL;		// GTerm Window
Stream		iostream;
//IOPort*  	ioport = NULL;
//uint		current_ostream = 0;
//QMutex		iowrite_mutex;

QList<QSerialPortInfo> sio_infos;	// List of serial devices

bool update_flag = true;			// screen update flag
QSemaphore ffb_sema;

RCPtr<Object> offscreenobjects;		// objects[0]
RCPtr<Object> framebufferobject;	// objects[1]


//// Transmission mode set with Escape:
////bool plain_transmission = true;		// plain_transmission = !escaped_transmission		default after reset
//bool escaped_transmission = false;		// CTL escaping and control codes, logical streams
//bool ec_transmission	  = false;		// Error detecting and correcting transmission
//bool enable_xonxoff		  = false;		// implies escaped transmission
//uint max_streams		  = MAX_STREAMS;// max. logical streams
//uint ec_windowsize		  = 4;			// 2 or 4 (default)
//uint ec_maxblocksize	  = 256;		// 64, 128 or 256 (default)


// Events enabled with CONFIGURE:
// Events are sent after frame painting
uint enabled_events = 0;
enum
{
	ptr_events_off  = 0,
	ptr_events_btn  = 1,
	ptr_events_drag = 2,
	ptr_events_move = 3,
	ptr_event_mask  = 3,
	ptr_moved_mask  = 2,

	joy_event_mask	  = 4,
	ffb_event_mask	  = 8,
	resize_event_mask = 0x10,
	kbd_event_mask    = 0x20,

	wait_ffb_pending  = 0x80		// CTL WAIT_FFB
};

// Event data:
uint mouse_current_buttons;
bool mouse_button_toggled;
int  mouse_x;
int  mouse_y;
bool mouse_moved;
bool window_resized;
uint kbd_char;
uint kbd_char2;




// ==============================================================
//						Workers:
// ==============================================================


// 'recent object' stacks for all streams:
struct OStack
{
	static const uint size = 1<<3;
	static const uint mask = size -1;
	RCPtr<Object> objects[size];
	uint idx = 0;

	void push(Object* o)	{ objects[++idx & mask] = o; }
	Object* pop()			{ return objects[idx-- & mask]; }
	Object* last()			{ return objects[idx   & mask]; }
	void clear()			{ idx=size; while(idx) objects[--idx] = offscreenobjects; }
}
ostack[MAX_STREAMS];

static
void clear_ostacks()
{
	for(uint i=MAX_STREAMS;i;) ostack[--i].clear();
}



class Worker : public QThread
{
public:
	uint	stream;			// stream

public:
			Worker()			{ XLogLine("new Worker"); }
	void	run()				override;

private:
	uint	nextByte()			throw(uint)		{ return iostream.readByte(); }
	uint	nextUWord()			throw(uint)		{ return iostream.readUWord(); }
	uint	nextSWord()			throw(uint)		{ return iostream.readSWord(); }
	uint	nextLong()			throw(uint)		{ return iostream.readLong(); }
	void	nextBytes(uchar* p,uint n) throw(uint)		{ iostream.readBytes(p,n); }
	Object* next_object(Object* o) throw(uint);
//	void	handle_esc(uint n);
};

Worker worker;




void handleEscape(uint n)
{
	if((n&0xC0)==0xC0)
	{
		//	set transmission mode:
		//		if both bits 0xC0 are set.

		//	$C0 disable all special transmission modes (default).
		//	$Ex enable special transmission mode.
		//		enables at least escaped reset, if no other bits set.
		//		note: any occurance of CTL must be escaped.
		//		bit fields in the low bits specify the transmission mode.

		//	$10	enable logical streams

		//	$08 enable enable SW handshake
		//		note: XON and XOFF must be escaped

		//	$04	window size in error correction mode
		//		0: 2 blocks (tiny)
		//		1: 4 blocks (default)

		//	$03	enable error correction
		//		$03: max. block size = 256 (default)
		//		$02: max. block size = 128
		//		$01: max. block size = 64  (tiny)

		if(n==0xC0)
		{
			iostream.setPlainTransmission();
		}
		else if(n&3)
		{
			iostream.setECTransmission(
				n&8,							/*sw_hsk*/
				n&0x10 ? MAX_STREAMS : 1,		/*maxstreams*/
				n&4 ? 4 : 2,					/*ec_windowsize*/
				32 << (n&3));					/*ec_max_blocksize*/
		}
		else
		{
			iostream.setEscapedTransmission(
				n&8,							/*sw_hsk*/
				n&0x10 ? MAX_STREAMS : 1);		/*maxstreams*/
		}

		iostream.flushSio();	// flush input for sios only
		return;
	}

	switch(n)
	{
	case 0:
		//	write settings to EEPROM
		//	- sio_baudrate
		//	- transmission mode

		LogLine("ESC 0: write settings to EEPROM: TODO");
		return;

	case 1:	//	1 	display lighter
	case 2:	//	2 	display darker
		LogLine("ESC 1|2: display lighter|darker: ignored");		// could be handled by TTY
		return;

//	case 4:	//	set UDG: already handled by TTY widget
//		return;
	}

	if(n <= 19)
	{
		//	6…19: set sio baudrate to 150 << (n/2) * (2+n&1)
		//    	  applied after write_to_eeprom + reset				APPLIED AFTER RESET!
		//	6  	150<<3 * 2 = 2400									DENK: how to detect $00 RESET sent to TTY?
		//	7  	150<<3 * 3 = 3600
		//	8  	150<<4 * 2 = 4800
		//	9  	150<<4 * 3 = 7200
		//	10 	150<<5 * 2 = 9600    (default)
		//	11 	150<<5 * 3 = 14k4
		//	12 	150<<6 * 2 = 19k2
		//	13 	150<<6 * 3 = 28k8
		//	14 	150<<7 * 2 = 38k4
		//	15 	150<<7 * 3 = 57k6
		//	16 	150<<8 * 2 = 76k8
		//	17 	150<<8 * 3 = 115k2
		//	18 	150<<9 * 2 = 153k6	new, ignored (impossible value, only for systematical reason)
		//	19 	150<<9 * 3 = 230k4	new
		//	20						reserved
		//	21						reserved
		//	22						reserved
		//	23						reserved

		LogLine("ESC 6…19: set sio baudrate: TODO");
		return;
	}

	LogLine("ESC: unknown argument");
}


/*	Get Next Object in Escaped Transmission Mode:
	get object from next 1- or 2-byte object id or throw CTL code
*/
Object* Worker::next_object(Object* object) throw(uint)
{
	uint c = nextByte();
	switch(c)
	{
	case OFFSCREENOBJECTS:	return offscreenobjects.ptr();
	case FRAMEBUFFEROBJECT:	return framebufferobject.ptr();
	case PARENT:			return object->parent;
	case CURRENT:			return object;
	case PREVIOUS:			return ostack[stream].last();
	case FIRSTCHILD:		return object->firstChild();
	case LASTCHILD:			return object->lastChild();
	case NOTHING:			return nullptr;
	case NEXTSIBLING:		return object->nextSibling();		// expensive!
	case PREVSIBLING:		return object->prevSibling();		// expensive!
	case CHILDAT:			return object->childAt(nextSWord());
	}
	return getObject( (c<<8) + nextByte() );
}



void Worker::run()
{
	XLogLine("worker running");

	XXASSERT(iostream.ioport);

	RCPtr<Object> object(framebufferobject);
	QPainter painter;

	iostream.reset();
	iostream.flushSio();
	clear_ostacks();
	object = framebufferobject;
	stream = 0;
	XXXASSERT(iostream.istream==0);

	int  n;
	uint c=0;

a:	try
	{
		object->paintjob(iostream,painter);
	}
	catch(uint e)
	{
		c = e;
	}

	// handle CTL code:

b:	try				// catch premature CTL codes
	{
		switch(c&0xff)
		{
		case RESET:		// CTL, RESET, 0,0,0,0
		{
			switch(nextLong())
			{
			case 0x00000000:	// RESET
			{
				throw("RESET: TODO");
				update_flag = true;
			}
			case 0x09090909:	// BREAK: EC transmission
			{
				throw("BREAK: EC transmission not in use");
			}
			default:
				throw("RESET: wrong magic");
			}
			break;
		}
		case CREATE:	// CTL, CREATE, TYPE, WW, HH, ARGB		create & select
		{
			//	// CREATE.TYPE:
			//	TTY		= 1,
			//	CRT		= 2,
			//	GFX		= 3,
			//	UNI		= 4,
			//	AUDIO	= 5,			// AY sound chip TODO
			//	ALIAS	= 6,			// same type as current object, sharing the same pixmap. TTY and GFX only.
			//	AND_APPENTO	= 0x10,		// --> NN objID follows, else it is appended to offscreenobjects
			//	AND_MOVETO  = 0x40,		// --> XX,YY follow
			//	AND_SELECT	= 0x80,		// implied if !AND_APPENDTO

			uint type = nextByte();
			Object* o;

			if((type & 0x0f) == ALIAS)	// w,h,c nicht nötig!
			{
				if(object->isa_id==IsaGFXObject)
					o = new GFXObject(static_cast<GFXObject*>(object.ptr())->pixmap,0,0);
				else if(object->isa_id==IsaTTYObject)
					o = new TTYObject(static_cast<TTYObject*>(object.ptr())->pixmap,0,0);
				else
					throw("CREATE ALIAS: current object must be TTY or GFX");
			}
			else if((type & 0x0f) == AUDIO)			// w,h,c nicht nötig!
			{
				throw("CREATE AUDIO: TODO");		// TODO
			}
			else
			{
				uint w = nextUWord();
				uint h = nextUWord();
				uint c = nextLong();

				switch(type&0x0F)
				{
				default: throw("CREATE: illegal type");
				case TTY:	o = new TTYObject(w,h,c); break;
				case GFX:	o = new GFXObject(w,h,c); break;
				case CRT:	o = new CRTObject(w,h,c); break;
				case UNI:	o = new RectObject(w,h,c); break;
				}
			}

			if(type&AND_APPENTO)
			{
				Object* p = next_object(object);
				if(!p) throw "CREATE AND_APPEND: object does not exist";
				p->appendChild(o);
				update_flag = true;
			}
			else offscreenobjects->appendChild(o);

			if(type&AND_MOVETO)
			{
				o->zbox.setLeft(nextSWord());
				o->zbox.setTop(nextSWord());
			}
			if(type&AND_SELECT)
			{
				ostack[stream].push(object);
				object = o;
			}
			break;
		}
		case SELECT:	// CTL, SELECT, NN						and push old selection
		{
			c = ostack[stream].idx;
			Object* o = next_object(object);
			if(!o) throw("SELECT: Object does not exist");
			if(ostack[stream].idx != c) ostack[stream].push(object);  // wenn nicht PREVIOUS
			object = o;
			break;
		}
		case DISPOSE:	// CTL, DISPOSE, NN
		{
			Object* o = next_object(object);
			if(!o) throw("DISPOSE: Object does not exist");
			if(!o->parent) throw "DISPOSE: can't dispose root object";
			if(!update_flag && o->isDescendantOf(framebufferobject)) update_flag = true;
			o->dispose();
			break;
		}
		case QUERY:		// CTL, QUERY, NN, WHAT		--> reply: CTL, QUERY, NN, ..
		{
			//	// QUERY.WHAT:
			//	GET_OBJECT_ID	= 0,	// --> NN		always sent: 2-byte obj_id or 1-byte NOTHING => message ends here
			//	GET_TYPE		= 1,	// --> TYPE
			//	GET_INDEX		= 2,	// --> NN		index in parent's child list,  -1 = no parent
			//	GET_CHILDCOUNT	= 4,	// --> NN
			//	GET_ZPOS		= 8,	// --> XX, YY	relative to parent
			//	GET_ZSIZE		= 16,	// --> WW, HH
			//	GET_QSIZE		= 32,	// --> WW, HH

			Object* o = next_object(object);
			uint what = nextByte();
			if(!o)
			{
				static uchar qmsg[] = { CTL, QUERY, NOTHING };
				iostream.write(qmsg,sizeof(qmsg),stream);
				break;
			}
			uchar rbu[20] = { CTL, QUERY };
			n = 2;
			if(what & GET_TYPE)  { rbu[n++] = o->isa_id; }
			if(what & GET_INDEX) { poke2X(rbu+n, o->index()); n+=2; }
			if(what & GET_CHILDCOUNT) { poke2X(rbu+n, o->children.count()); n+=2; }
			if(what & GET_ZPOS)  { poke2X(rbu+n, o->zbox.left()); poke2X(rbu+n+2, o->zbox.top()); n+=4; }
			if(what & GET_ZSIZE) { poke2X(rbu+n, o->zbox.width()); poke2X(rbu+n+2, o->zbox.height()); n+=4; }
			if(what & GET_QSIZE) { poke2X(rbu+n, o->zwidth()); poke2X(rbu+n+2, o->zheight()); n+=4; }
			iostream.write(rbu,n,stream);
			break;
		}
		case REORDER:	// CTL, REORDER, NN, HOW, ..
		{
			//	REORDER.HOW:

			// move object inside siblings list:
			// works for all objects except for framebuffer and offscreen, which have no parent:
			//	HIGHER		= 1,		// move object in it's parent's child list --> index -= 1
			//	LOWER		= 2,		// move object in it's parent's child list --> index += 1
			//	TOSTART		= 3,		// index = 0
			//	TOEND		= 4,		// index = last
			//	TOINDEX		= 5,		// index NN follows, inserts

			// move object to other parent:
			// Q and Z must not be FFB|OFF:
			//	BEHIND		= 6,		// object_id NN² follows
			//	BEFORE		= 7,		// object_id NN² follows
			//	EXCHANGE	= 8,		// object_id NN² follows
			//	REPLACE		= 9,		// object_id NN² follows

			// move object to other parent:
			// Q must not be FFB|OFF:
			//	ATSTART		= 10,		// object_id of new parent NN² follows
			//	ATEND		= 11,		// object_id of new parent NN² follows
			//	ATINDEX		= 12,		// object_id of new parent NN²  and index NN follow, inserts

			// exchange position and personalities:
			//  TRANSPOSE	= 13,		// object_id NN² follows

			Object* o  = next_object(object);
					if(!o) throw "REORDER: object not found";
					if(!update_flag && o->isDescendantOf(framebufferobject)) update_flag = true;

			uint how   = nextByte();
			if(!o->parent && how < TRANSPOSE) throw "REORDER: can't reorder root objects";

			Object* o2 = nullptr;
			if(how >= BEHIND && how <= TRANSPOSE)
			{
				o2 = next_object(object); if(!o2) throw "REORDER: object#2 not found";
				if(!o2->parent && /* how>=BEHIND && */ how<=REPLACE) throw "REORDER: can't reorder root objects";
				if(!update_flag && o2->isDescendantOf(framebufferobject)) update_flag = true;
			}

			switch(how)
			{
			case HIGHER:
				XXXASSERT(o->parent);
				o2 = o->nextSibling();
				if(o2) o->exchange(o2);	// if not already at end
				break;
			case LOWER:
				XXXASSERT(o->parent);
				o2 = o->prevSibling();
				if(o2) o->exchange(o2);	// if not already at start
				break;
			case TOSTART:
				XXXASSERT(o->parent);
				n = o->index();
				XXXASSERT(n>=0);
				o->parent->children.ror(0,n+1);
				break;
			case TOEND:
				XXXASSERT(o->parent);
				n = o->index();
				XXXASSERT(n>=0);
				o->parent->children.rol(n,o->parent->children.count());
				break;
			case TOINDEX:
				XXXASSERT(o->parent);
				o->moveToIndex(nextSWord());
				break;

			case BEHIND:
			case BEFORE:
				XXXASSERT(o->parent);
				XXXASSERT(o2->parent);

				if(o==o2) break;

				n = o2->index();
				XXXASSERT(n>=0);
				n += how==BEHIND;

				if(o->parent != o2->parent)
				{
					if(o2->isDescendantOf(o)) throw "REORDER BEFORE|BEHIND: object#2 is descendant of object#1";
					o->moveToParent(o2);
				}
				o->moveToIndex(n);
				break;

			case ATSTART:
				XXXASSERT(o->parent);
				if(o2->isDescendantOf(o)) throw "REORDER ATSTART: object#2 is descendant of object#1";
				o->moveToParent(o2);
				o->moveToIndex(0);
				break;

			case ATEND:
				XXXASSERT(o->parent);
				if(o2->isDescendantOf(o)) throw "REORDER ATEND: object#2 is descendant of object#1";
				if(o->parent==o2)
					o->moveToIndex(0x7FFE);
				else
					o->moveToParent(o2);
				break;

			case ATINDEX:
				XXXASSERT(o->parent);
				if(o2->isDescendantOf(o)) throw "REORDER ATINDEX: object#2 is descendant of object#1";
				n = nextSWord();
				o->moveToParent(o2);
				o->moveToIndex(n);
				break;

			case REPLACE:		// o replaces o2
				XXXASSERT(o->parent);
				XXXASSERT(o2->parent);

				if(o==o2) break;

				if(o2->isDescendantOf(o)) throw "REORDER REPLACE: object#2 is descendant of object#1";
				o->replace(o2);
				break;

			case EXCHANGE:
				XXXASSERT(o->parent);
				XXXASSERT(o2->parent);

				if(o==o2) break;

				if(o->parent != o2->parent)
				{
					if(o->isDescendantOf(o2)) throw "REORDER EXCHANGE: object#1 is descendant of object#2";
					if(o2->isDescendantOf(o)) throw "REORDER EXCHANGE: object#2 is descendant of object#1";
				}
				o->exchange(o2);
				break;

			case TRANSPOSE:
				if(o==o2) break;

				if(o->parent != o2->parent)
				{
					if(o->isDescendantOf(o2)) throw "REORDER TRANSPOSE: object#1 is descendant of object#2";
					if(o2->isDescendantOf(o)) throw "REORDER TRANSPOSE: object#2 is descendant of object#1";
				}
				o->swap_all(o2);
				break;

			default:		throw "REODER: unknown 'how'";
			}
			break;
		}
		case GEOMETRY:	// CTL, GEOMETRY, NN, WHAT, ..
		{
			//	GEOMETRY.FLAGS:
			//	RESET_GEOMETRY	= 0x00,	// ZSIZE=QSIZE, QPOS=0,0, ROT=0
			//	SET_ZPOS		= 1,	// --> XX, YY follow
			//	SET_ZSIZE		= 2,	// --> WW, HH follow
			//	SET_QPOS		= 4,	// --> XX, YY follow
			//	SET_QSIZE		= 8,	// --> WW, HH follow
			//	//SET_SCALE
			//	//SET_ROTATION

			Object* o = next_object(object); if(!o) throw "GEOMETRY: object not found";
					if(!update_flag && o->isDescendantOf(framebufferobject)) update_flag = true;
			uint what = nextByte();

			if(what==RESET_GEOMETRY)	// reset geometry: ZSIZE=QSIZE, QPOS=0,0, ROT=0
			{
				o->resetGeometry();
				break;
			}

			if(what&SET_ZPOS)
			{
				o->zbox.moveLeft(nextSWord());
				o->zbox.moveTop(nextSWord());
			}
			if(what&SET_ZSIZE)
			{
				o->zbox.setWidth(nextUWord());
				o->zbox.setHeight(nextUWord());
			}
			if(what&SET_QPOS)
			{
				int x = nextSWord();
				int y = nextSWord();
				o->setQPos(x,y);
			}
			if(what&SET_QSIZE)
			{
				uint w = nextUWord();
				uint h = nextUWord();
				o->setQSize(w,h);
			}
			break;
		}
		case CONFIGURE:	// CTL, CONFIGURE, WHAT
		{
			//	CONFIGURE.WHAT:
			//	PTR_EVENTS_OFF	= 0,	// %00=off, %01=btns, %10=drag, %11=move
			//	PTR_EVENTS_BTN	= 1,
			//	PTR_EVENTS_DRAG	= 2,
			//	PTR_EVENTS_MOVE	= 3,
			//	RESIZE_EVENTS	= 4,	// bit0: on/off
			//	JOY_EVENTS		= 6,	// bit0: on/off
			//	KBD_EVENTS		= 8,	// bit0: on/off, default = ON: send control code and printable characters "as is"
			//	FFB_EVENTS		= 10,	// bit0: on/off, will be sent on PTR or JOY events too

			uint flags = nextByte();

			switch(flags)
			{
			case 0: case 1:
			case 2: case 3:			enabled_events &= ~3; enabled_events |= flags; break;
			case RESIZE_EVENTS:		enabled_events &= ~resize_event_mask;	break;
			case RESIZE_EVENTS+1:	enabled_events |=  resize_event_mask;	break;
			case JOY_EVENTS:		enabled_events &= ~joy_event_mask;		break;
			case JOY_EVENTS+1:		enabled_events |=  joy_event_mask;		break;
			case KBD_EVENTS:		enabled_events &= ~kbd_event_mask;		break;
			case KBD_EVENTS+1:		enabled_events |=  kbd_event_mask;		break;
			case FFB_EVENTS:		enabled_events &= ~ffb_event_mask;		break;
			case FFB_EVENTS+1:		enabled_events |=  ffb_event_mask;		break;
			default:				throw "CONFIGURE: illegal flag";
			}
			break;
		}
		case WAIT_FFB:	// CTL, WAIT_FFB				--> CTL, WAIT_FFB, FLAGS, ..
		{
			//	// WAIT_FFB.FLAGS:
			//	// event infos are attached to the FFB reply only if changed:
			//	PTR_EVENT	 = 1,		// --> 1 byte button mask new state %00000mrl, XX, YY
			//	RESIZE_EVENT = 8,		// --> WW, HH
			//	JOY_EVENT	 = 0x10,	// --> 2 bytes button mask new state: %xxxfudlr 1=active
			//	JOY2_EVENT	 = 0x20,	//
			//	JOY3_EVENT	 = 0x40,	//
			//	JOY4_EVENT	 = 0x80,	//
			enabled_events |= wait_ffb_pending;
			ffb_sema.acquire(ffb_sema.available()+1);
			enabled_events &= ~wait_ffb_pending;
			break;
		}
		case 0xff:
		default:		// SWITCH STREAM
			if(c>0xff)
			{
				if((c&0xff) >= iostream.max_streams) throw("SELECT STREAM: stream number out of range");
				iostream.istream = stream = (c&0xff);
				break;
			}
			else
			{
				// test for ESC and RESET
				TODO();
				break;
			}
		}//switch
		goto a;
	}
	catch(uint e)		// catch a thrown CTL code:
	{
		LogLine("Command 0x%02X truncated",c);
		XXXASSERT(e>0xff);
		c = e;
		goto b;
	}
	catch(cstr s)		// catch an error:
	{
		LogLine("%s", s);
		try
		{
			uchar bu[100];
			for(;;) iostream.readBytes(bu,100);	// flush up to next CTL
		}
		catch(uint e)
		{
			c = e;
			goto b;
		}
	}

	IERR(); // never reached
}





// =================================================================
//					The MainWindow
// =================================================================



MainWindow::MainWindow(QWindow* parent)
:
	QOpenGLWindow(QOpenGLWindow::NoPartialUpdate, parent)
{
	XLogIn("new MainWindow");

	XXASSERT(main_window == NULL);

	main_window = this;
	this->setMinimumSize(QSize(200,160));
	this->setWidth(WIDTH);
	this->setHeight(HEIGHT);

// find serial ports:
	sio_infos = QSerialPortInfo::availablePorts();
	//if(selected_sio >= sio_infos.count()) selected_sio = -1;
	for(int i=0;i<sio_infos.count(); i++)
	{
		QSerialPortInfo& info = sio_infos[i];
		XLogIn("%s",info.portName().toUtf8().data());
		XLogLine("description: %s",info.description().toUtf8().data());
		XLogLine("manufacturer: %s",info.manufacturer().toUtf8().data());
		XLogLine("serial number: %s",info.serialNumber().toUtf8().data());
		XLogLine("system location: %s",info.systemLocation().toUtf8().data());
	}

	offscreenobjects = new RectObject(0,0,0);
	framebufferobject = new TTYObject(new TTYPixmap(800,600),0,0);

	iostream.ioport = new TestPort();
	worker.start();

	if(1)	// Add test images:
	{
//		Image* image = new Image(40,40,RGB);
		GFXPixmap* pixmap = new GFXPixmap(40,40,0xFFcc0000);	// 0xAARRGGBB
		framebufferobject->appendChild(new GFXObject(pixmap,100,10));

		pixmap = new GFXPixmap(80,80,0xFFcccc00);		// 0xAARRGGBB
		framebufferobject->appendChild(new GFXObject(pixmap,760,560));

		pixmap = new GFXPixmap(20,20,0xFF00cccc);		// 0xAARRGGBB
		framebufferobject->lastChild()->appendChild(new GFXObject(pixmap,30,-10));

//		pixmap = new Pixmap(800,600);
//		QPainter p5(pixmap);
//		p5.setBrush(QBrush(0xFF66cccc));	// 0xAARRGGBB
//		p5.setPen(Qt::NoPen);
//		p5.drawRect(pixmap->rect());
//		framebufferobject->appendChild(new PixmapObject(pixmap,0,0));
//		framebufferobject->appendChild(new PixmapObject(pixmap,0,0));
//		framebufferobject->appendChild(new PixmapObject(pixmap,0,0));
	}


// Menu bar and main menus:
	QMenuBar* menubar = new QMenuBar();
	QMenu*	  m_connect = menubar->addMenu("Connect");
	QMenu*	  m_settings = menubar->addMenu("Settings");
	QMenu*	  m_actions = menubar->addMenu("Actions");
	QMenu*	  m_help = menubar->addMenu("Help");		// invisible: Qt moves all items to the appl menu


// Mutual exclusive groups:
	QActionGroup* g_connection = new QActionGroup(this);
	QActionGroup* g_speed      = new QActionGroup(this);


// "Connect" Menu:
	QAction* a_stdio = g_connection->addAction(m_connect->addAction("Stdin/stdout"));
	a_stdio->setCheckable(true); a_stdio->setChecked(iostream.stdio_selected);

	m_connect->addSeparator();
	for(int i=0; i<sio_infos.count(); i++)
	{
		QString text = sio_infos.at(i).portName(); if(text.startsWith("cu.")) text = text.mid(3);
		if(sio_infos.at(i).manufacturer().count()) text += QString(" (") + sio_infos.at(i).manufacturer() + ")";
		QAction* a = m_connect->addAction(text);
		a->setCheckable(true);
		a->setChecked(iostream.selected_sio == i);
		g_connection->addAction(a);
	}
	QMenu* m_sio_speed = m_connect->addMenu("Speed");
		   m_sio_speed->setEnabled(iostream.selected_sio>=0);

	m_connect->addSeparator();
	QAction* a_named_pipe = g_connection->addAction(m_connect->addAction("Named Pipe"));
			 a_named_pipe->setCheckable(true); a_named_pipe->setChecked(iostream.pipe_selected);
	QAction* a_pipe_name  = m_connect->addAction(catstr("Path: ",iostream.pipe_path));
			 a_pipe_name->setEnabled(iostream.pipe_selected);

	m_connect->addSeparator();
	QAction* a_webserver = g_connection->addAction(m_connect->addAction("IP Socket Server"));
			 a_webserver->setCheckable(true); a_webserver->setChecked(iostream.server_selected);
	QAction* a_port      = m_connect->addAction(usingstr("Port: %u",iostream.socket_port));
			 a_port->setEnabled(iostream.server_selected);

// "Settings" Menu:
	QAction* a_str = m_settings->addAction(usingstr("Max. Streams: %u",iostream.max_streams)); a_str->setEnabled(no);
	cstr s = max_mem >= 1 GB ? usingstr("%.1f GB",max_mem/(1.0 GB)) : usingstr("%u MB",max_mem/(1 MB));
	QAction* a_mem = m_settings->addAction(usingstr("Max. Memory: %s",s)); a_mem->setEnabled(no);
	QAction* a_fs  = m_settings->addAction("Expose File System");
			 a_fs->setCheckable(true);	a_fs->setChecked(enable_fs);
//	QAction* a_gfx = m_settings->addAction("Graphics Terminal");
//			 a_gfx->setCheckable(true);	a_gfx->setChecked(enable_gfx);
	QAction* a_kbd = m_settings->addAction("Enable Keyboard");
			 a_kbd->setCheckable(true);	a_kbd->setChecked(enable_kbd);
	QAction* a_ptr = m_settings->addAction("Enable Mouse");
			 a_ptr->setCheckable(true);	a_ptr->setChecked(enable_ptr);
	QAction* a_joy = m_settings->addAction("Enable Joysticks");
			 a_joy->setCheckable(true);	a_joy->setChecked(enable_joy);
	m_settings->addSeparator();
	QAction* a_reset = m_actions->addAction("Reset Terminal");


// "Application" Menu:
	QAction* a_about = m_help->addAction("About…");			// will be moved to appl menu
	QAction* a_prefs = m_help->addAction("Preferences…");	// will be moved to appl menu


// "Sio Speed" Menu:
	QAction* a_speed[] =
	{
		m_sio_speed->addAction("2400"),
		m_sio_speed->addAction("4800"),
		m_sio_speed->addAction("9600"),
		m_sio_speed->addAction("19200"),
		m_sio_speed->addAction("38400"),
		m_sio_speed->addAction("76800"),
		m_sio_speed->addSeparator(),
		m_sio_speed->addAction("3600"),
		m_sio_speed->addAction("7200"),
		m_sio_speed->addAction("14400"),
		m_sio_speed->addAction("28800"),
		m_sio_speed->addAction("57600"),
		m_sio_speed->addAction("115200")
	};

	s = numstr(iostream.sio_baudrate);
	for(uint i=0;i<NELEM(a_speed);i++)
	{
		g_speed->addAction(a_speed[i])->setCheckable(true);
		a_speed[i]->setChecked(a_speed[i]->text()==s);
	}

	bool f = 1;
	f = connect(a_about,&QAction::triggered,this,&MainWindow::slotShowAbout) && f;
	f = connect(a_prefs,&QAction::triggered,this,&MainWindow::slotShowPreferences) && f;
	f = connect(a_port,&QAction::triggered,this,&MainWindow::slotSetTCPPort) && f;
	f = connect(a_pipe_name,&QAction::triggered,this,&MainWindow::slotSetPipeName) && f;
	f = connect(a_reset,&QAction::triggered,this,&MainWindow::slotResetTerminal) && f;
	XXASSERT(f);
}


MainWindow::~MainWindow()
{}


/*	This virtual function is called once before the first call to paintGL() or resizeGL().
	Reimplement it in a subclass.
	This function should set up any required OpenGL resources and state.
	There is no need to call makeCurrent() because this has already been done when this function is called.
	Note however that the framebuffer, in case partial update mode is used, is not yet available at this stage,
	so avoid issuing draw calls from here. Defer such calls to paintGL() instead.
*/
void MainWindow::initializeGL()
{
	XLogIn("MainWindow::initializeGL");
}


/*	This virtual function is called whenever the window contents needs to be painted.
	Reimplement it in a subclass.
	There is no need to call makeCurrent() because this has already been done when this function is called.
	Before invoking this function, the context and the framebuffer, if there is one, are bound,
	and the viewport is set up by a call to glViewport().
	No other state is set and no clearing or drawing is performed by the framework.
	Note: When using a partial update behavior, like PartialUpdateBlend, the output of the previous paintGL() call
	is preserved and, after the additional drawing perfomed in the current invocation of the function,
	the content is blitted or blended over the content drawn directly to the window in paintUnderGL().
*/
void MainWindow::paintGL()
{
	XXLogIn("MainWindow::paintGL");

	// due to double buffering we need to paint everything twice:
	static bool u2 = false;

	if(u2 || update_flag)				// else nothing was drawn
	{
		XXLogLine("painting ...");

		QPainter p(this);
		QRect r(0,0,width(),height());	// 0/0 = top/left

		u2 = update_flag;
		update_flag = false;

		// wenn das erste Image nicht den gesamten Hintergrund abdeckt, dann den Hintergrund schwarz malen:
		Object* o = framebufferobject.ptr();
		if(!o->isOpaque() || (o->zbox & r) != r)
			p.fillRect(r,background_color);

		o->paint_out(p,0,0);
	}
	else
	{
		XXLogLine("no painting required");
	}

	// if worker is waiting:
	ffb_sema.release();

	// send registered events:
	// mouse, joysticks, ffb
	if(enabled_events)
	{
		uchar msg[20] = { CTL, WAIT_FFB, 0x00/*flags*/ };
		uint  sz = 3;

		if(mouse_button_toggled)
		{
			mouse_button_toggled = false;
			if(enabled_events & ptr_event_mask)
			{
				msg[2] |= MSG_PTR_EVENT;
				msg[sz++] = mouse_current_buttons;
			}
		}

		if(mouse_moved)
		{
			mouse_moved = false;
			if(enabled_events & ptr_moved_mask)
			{
				msg[2] |= MSG_PTR_MOVED;
				poke2X(msg+sz, mouse_x); sz+=2;
				poke2X(msg+sz, mouse_y); sz+=2;
			}
		}

		if(kbd_char)
		{
			if(enabled_events & kbd_event_mask)
			{
				msg[2] |= MSG_INPUT_CHAR;
				msg[sz++] = kbd_char;
			}
			kbd_char = kbd_char2;
			kbd_char2 = 0;
		}

		if(window_resized)
		{
			window_resized = false;
			if(enabled_events & resize_event_mask)
			{
				msg[2] |= MSG_RESIZED;
				poke2X(msg+sz, width()); sz+=2;
				poke2X(msg+sz, height()); sz+=2;
			}
		}

		if((enabled_events & joy_event_mask) && false)
			TODO();

		iostream.write(msg,sz,0/*stream*/);
	}

	update();	// trigger next paint event
}


/*	This virtual function is called whenever the widget has been resized. Reimplement it in a subclass.
	The new size is passed in w and h.
	Note: This is merely a convenience function in order to provide an API that is compatible with QOpenGLWidget.
	Unlike with QOpenGLWidget, derived classes are free to choose to override resizeEvent() instead of this function.

	Note: Avoid issuing OpenGL commands from this function as there may not be a context current when it is invoked.
	If it cannot be avoided, call makeCurrent().

	Note: Scheduling updates from here is not necessary. The windowing systems will send expose events
	that trigger an update automatically.
*/
void MainWindow::resizeGL(int w, int h)
{
	XLogIn("MainWindow::resizeGL");

	QRect& zbox = objects[1]->zbox;

	zbox.moveLeft(zbox.width()>w ? 0 : (w-zbox.width())/2);
	zbox.moveTop(zbox.height()>h ? 0 : (h-zbox.height())/2);

	update_flag = true;
}




#if 0
/*	This virtual function is called after each invocation of paintGL().
	When the update mode is set to NoPartialUpdate, there is no difference between this function and paintGL(),
	performing rendering in either of them leads to the same result.
	Like paintUnderGL(), rendering in this function targets the default framebuffer of the window,
	regardless of the update behavior. It gets called after paintGL() has returned
	and the blit (PartialUpdateBlit) or quad drawing (PartialUpdateBlend) has been done.
*/
void MainWindow::paintOverGL()
{
	printf("MainWindow::paintOverGL\n");
}

/*	The virtual function is called before each invocation of paintGL().
	When the update mode is set to NoPartialUpdate, there is no difference between this function and paintGL(),
	performing rendering in either of them leads to the same result.
	The difference becomes significant when using PartialUpdateBlend, where an extra framebuffer object is used.
	There, paintGL() targets this additional framebuffer object, which preserves its contents,
	while paintUnderGL() and paintOverGL() target the default framebuffer, i.e. directly the window surface,
	the contents of which is lost after each displayed frame.
	Note: Avoid relying on this function when the update behavior is PartialUpdateBlit.
	This mode involves blitting the extra framebuffer used by paintGL() onto the default framebuffer
	after each invocation of paintGL(), thus overwriting all drawing generated in this function.
*/
void MainWindow::paintUnderGL()
{
	printf("MainWindow::paintUnderGL\n");
}

/*	Reimplemented from QPaintDeviceWindow::paintEvent().
	Paint event handler. Calls paintGL().
*/
void MainWindow::paintEvent(QPaintEvent* e)
{
	static uint n=0; printf("MainWindow::paintEvent %u\n",++n);
	QOpenGLWindow::paintEvent(e);
}

/*	Reimplemented from QWindow::resizeEvent().
	Resize event handler. Calls resizeGL().
*/
void MainWindow::resizeEvent(QResizeEvent* e)
{
	static uint n=0; printf("MainWindow::resizeEvent %u\n",++n);
	QOpenGLWindow::resizeEvent(e);
}





//helper
//identification of a physical key:
uint keyforkey(uint qtkey)
{
	uint16 key = qtkey;
	switch(qtkey >> 16)
	{
	case 0x0000:			// 0x20 .. 0xFF = Latin-1 code
		return key;

	case 0x0100:
		// 0x01000000 - 26	esc, tab, backspace, enter, insert, home, cursors, modifiers capslock o.Ä.
		// 0x01000030 - 60	function keys o.Ä.
		// 0x01000061 - 124	multimedia/internet keys - ignored by default - see QKeyEvent c'tor
		//					-> 0xFCxx .. 0xFDxx
		// 0x01001100 - 40	International & multi-key character composition
		//					-> FE00 .. FE50
		// 0x01001250 - 62	dead keys (X keycode - 0xED00 to avoid the conflict)
		//					-> FE50 .. FE70

		if(key < 0x0200)	return 0xFC00 + key;				// FCxx, FDxx
		if(key < 0x1100)	break;
		if(key < 0x1150)	return 0xFE00 + key - 0x1100;		// 0xFE00..50
		if(key < 0x1250)	break;
		if(key < 0x1270)	return 0xFE50 + key - 0x1250;		// 0xFE50..70
		break;

	case 0x0102:
		// 0x01020001 - 0A	Newer misc keys
		if(key<0x10) return 0xFE80 + key;						// 0xFE80..90
		break;

	case 0x0110:
		// 0x01100000 - 21	(mobile) Device keys
		if(key<0x20) return 0xFEA0 + key;						// 0xFEA0..C0
		break;

	case 0x0101:
		// 0x01010000 - 03	Keypad navigation keys
		if(key<0x10) return 0xFED0 + key;						// 0xFED0..E0
		break;
	}
	return 0xffff;			// don't know
}


/*	Qt callback: key down:

	e.key()                 Großbuchstabe der Taste, wenn kein Modifierkey gedrückt wäre
	e.modifiers()           Maske aller gedrückter Modifier
								Qt::SHIFT   = Shift-Taste
								Qt::META    = Control-Taste
								Qt::CTRL    = Cmd-Taste
								Qt::ALT     = Alt-Taste
	e.nativeModifiers()     OSX-Modifiermaske, außer wenn Modifiertaste alleine gedrückt ist, dann 0
	e.nativeScanCode()      nutzlos: 0 oder 1
	e.nativeVirtualKey()    OSX-Keycode, außer wenn Modifiertaste alleine gedrückt ist, dann 0
							ACHTUNG: Der Tastencode für 'A' ist auch 0!
	e.text()                Resultierendes druckbares Zeichen, außer bei Controlcodes, CTRL+Taste und CMD+Taste: leer
*/
void MainWindow::keyPressEvent(QKeyEvent* e)
{
	if(~registered_events & KeyDownEventMask)
		return QOpenGLWindow::keyPressEvent(e);

	XXLogLine("key down:   0x%08x (%i)",(int)e->key(),(int)e->key());
	XXLogLine("modifiers:  0x%08x", (int)e->modifiers());
	XXLogLine("native key: 0x%08x", (int)e->nativeVirtualKey());
	XXLogLine("text:       %s",     e->text().toUtf8().data());

	uint32 qtmodifiers = e->modifiers();           // Modifier-Maske
	uint8 modifiers = e->isAutoRepeat() ? RepeatedKeyMask : 0x00;
	if(qtmodifiers)
	{
		if(qtmodifiers & Qt::ShiftModifier)   modifiers |= ShiftKeyMask;
		if(qtmodifiers & Qt::MetaModifier)    modifiers |= CtrlKeyMask;
		if(qtmodifiers & Qt::AltModifier)	  modifiers |= AltKeyMask;
		if(qtmodifiers & Qt::ControlModifier) modifiers |= CmdKeyMask;
	}

	uint32 qtkey = e->key();		// Taste ohne Modifier, Uppercase
	if(qtkey&0x01000000)
	{
		if(qtkey==Qt::Key_Shift)	modifiers |= ShiftKeyMask; else
		if(qtkey==Qt::Key_Meta)		modifiers |= CtrlKeyMask;  else
		if(qtkey==Qt::Key_Alt )		modifiers |= AltKeyMask;   else
		if(qtkey==Qt::Key_Control)	modifiers |= CmdKeyMask;
	}

	// Resulting printable character:
	// Resultierendes druckbares Zeichen, außer bei Controlcodes, CTRL+Taste und CMD+Taste: leer
	QString	qttext	= e->text();
	QChar	qtchar	= qttext.count()==0 ? 0 : qttext.at(0);
	uint16  unicode	= qtchar.unicode();

	#define HI(N) (uint8)((N)>>8)
	#define LO(N) (uint8)((N)&0xFF)

	if(registered_events & KeyUpEventMask)
	{
		// identification of a physical key:
		uint16 key = qtkey <= 0xFF ? qtkey : keyforkey(qtkey);

		uint8 msg[] =
		{
			KeyDownEvent,
			(uint8)(modifiers|KeyCodeMask), // modifier keys, incl. RepeatedKeyMask and KeyCodeMask=1
			HI(unicode), LO(unicode),		// printable character or 0
			HI(key), LO(key)				// physical key identifier, same as uppercase char for normal keys
		};
		streams[stream_kbd_events].storeReply(msg,sizeof(msg));
	}
	else
	{
		uint8 msg[] =
		{
			KeyDownEvent,
			modifiers,						// modifier keys, incl. RepeatedKeyMask and KeyCodeMask=0
			HI(unicode), LO(unicode)		// printable character or 0
		};
		streams[stream_kbd_events].storeReply(msg,sizeof(msg));		// TODO: mutex locken...
	}
}

void MainWindow::keyReleaseEvent(QKeyEvent* e)
{
	if(~registered_events & KeyUpEventMask)
		return QOpenGLWindow::keyReleaseEvent(e);

	XXLogLine("key up:     0x%08x (%i)",(int)e->key(),(int)e->key());
	XXLogLine("modifiers:  0x%08x", (int)e->modifiers());
	XXLogLine("native key: 0x%08x", (int)e->nativeVirtualKey());
	XXLogLine("text:       %s",     e->text().toUtf8().data());

	uint32 qtmodifiers = e->modifiers();           // Modifier-Maske
	uint8 modifiers = 0x00;
	if(qtmodifiers)
	{
		if(qtmodifiers & Qt::ShiftModifier)   modifiers |= ShiftKeyMask;
		if(qtmodifiers & Qt::MetaModifier)    modifiers |= CtrlKeyMask;
		if(qtmodifiers & Qt::AltModifier)	  modifiers |= AltKeyMask;
		if(qtmodifiers & Qt::ControlModifier) modifiers |= CmdKeyMask;
	}

	uint32 qtkey = e->key();		// Taste ohne Modifier, Uppercase
	if(qtkey&0x01000000)
	{
		if(qtkey==Qt::Key_Shift)	modifiers &= ~ShiftKeyMask; else
		if(qtkey==Qt::Key_Meta)		modifiers &= ~CtrlKeyMask;  else
		if(qtkey==Qt::Key_Alt )		modifiers &= ~AltKeyMask;   else
		if(qtkey==Qt::Key_Control)	modifiers &= ~CmdKeyMask;
	}

	// identification of a physical key:
	uint16 key = qtkey <= 0xFF ? qtkey : keyforkey(qtkey);

	uint8 msg[] =
	{
		KeyUpEvent,
		(uint8)(modifiers|KeyCodeMask), 	// modifier keys, incl. RepeatedKeyMask=0 and KeyCodeMask=1
		HI(key), LO(key)					// physical key identifier, same as uppercase char for normal keys
	};
	streams[stream_kbd_events].storeReply(msg,sizeof(msg));		// TODO: mutex locken...
}


// mouse move timer:
// mouse move events are collected and transmitted once per 1/100 sec.
static QTimer m_timer;
static bool   m_timer_valid = no;
static int32  m_ts_down = 0;
static int32  m_ts_up   = 0;

void MainWindow::mouseMoveEvent(QMouseEvent* e)
{
	if(~registered_events & MouseMoveEventMask)
		return QOpenGLWindow::mouseMoveEvent(e);

	if(m_timer.isActive()) return;

	static int buttons,x,y;
	buttons = e->buttons();
	x = e->x();
	y = e->y();

	if(!m_timer_valid)
	{
		m_timer_valid = yes;
		m_timer.setSingleShot(true);
		connect(&m_timer,&QTimer::timeout,[]()
		{
			uint8 msg[] =
			{
				MouseMoveEvent,
				(uint8)buttons,		// state of mouse buttons
				HI(x),LO(x),		// latest position
				HI(y),LO(y)
			};
			streams[stream_mouse_events].storeReply(msg,sizeof(msg));		// TODO: mutex locken...
		});
	}
	m_timer.start(10);
}

void MainWindow::mousePressEvent(QMouseEvent* e)
{
	if(~registered_events & MouseDownEventMask)
		return QOpenGLWindow::mousePressEvent(e);

	m_timer.stop();
	int x = e->x();
	int y = e->y();
	m_ts_down = e->timestamp() / 10;	// hopefully ms -> 1/100s
	uint8 delay = min(m_ts_down-m_ts_up,255);

	uint8 msg[] =
	{
		MouseDownEvent,
		(uint8)e->button(),			// the button which toggled
		(uint8)e->buttons(),		// new state of mouse buttons
		HI(x),LO(x),				// position of event
		HI(y),LO(y),
		delay						// delay [1/100s] since last mouse up
	};
	streams[stream_mouse_events].storeReply(msg,sizeof(msg));		// TODO: mutex locken...
}

void MainWindow::mouseReleaseEvent(QMouseEvent* e)
{
	if(~registered_events & MouseUpEventMask)
		return QOpenGLWindow::mouseReleaseEvent(e);

	m_timer.stop();
	int x = e->x();
	int y = e->y();
	m_ts_up = e->timestamp() / 10;	// hopefully ms -> 1/100s
	uint8 delay = min(m_ts_up-m_ts_down,255);

	uint8 msg[] =
	{
		MouseDownEvent,
		(uint8)e->button(),
		(uint8)e->buttons(),
		HI(x),LO(x),
		HI(y),LO(y),
		delay						// delay [1/100s] since last mouse down
	};
	streams[stream_mouse_events].storeReply(msg,sizeof(msg));		// TODO: mutex locken...
}
#endif


void MainWindow::keyPressEvent(QKeyEvent* e)
{
	XLogIn("MainWindow::keyPressEvent");

	/*
		falls wir mal einen kbd joystick anbieten,
		müssen wir hier den Tastencode auswerten.
	*/

	QString	text = e->text();
	if(text.count()==0) return;
	uint16 unicode = text.at(0).unicode();
	if(unicode>0xff) LogLine("Character code of key pressed 0x%04X > 0xFF",unicode);
	(kbd_char ? kbd_char2 : kbd_char) = unicode;
}


void MainWindow::keyReleaseEvent(QKeyEvent*)
{
	XLogIn("MainWindow::keyReleaseEvent");

	/*
		falls wir mal einen kbd joystick anbieten,
		müssen wir hier den Tastencode auswerten.
	*/
}


void MainWindow::mouseMoveEvent(QMouseEvent* e)
{
	XXLogIn("MainWindow::mouseMoveEvent");

	// es sieht so aus, also ob wir das Event immer bekommen,
	// auch wenn kein MouseButton gedrückt ist.
	// setMouseTracking(bool) gibt es für QOpenGLWindow nicht.

	if(enabled_events & ptr_moved_mask)			// mouse_drag or mouse_move enabled?
	{
		if(mouse_button_toggled) return;		// click pending => do not change the click position!

		if((enabled_events & 1) || e->buttons())	// user is dragging or mouse_move enabled?
		{
			mouse_x = e->x();						// rel. to widget == rel. to window
			mouse_y = e->y();						// rel. to widget == rel. to window
			mouse_moved = true;
		}
	}
}


void MainWindow::mousePressEvent(QMouseEvent* e)
{
	XLogIn("MainWindow::mousePressEvent");

	if(enabled_events & ptr_event_mask)
	{
		mouse_x = e->x();						// rel. to widget == rel. to window
		mouse_y = e->y();						// rel. to widget == rel. to window
		mouse_moved = true;
		mouse_current_buttons = e->buttons();	// %00000mrl incl. the pressed button
		mouse_button_toggled |= e->button();
	}
}


void MainWindow::mouseReleaseEvent(QMouseEvent* e)
{
	XLogIn("MainWindow::mouseReleaseEvent");

	if(enabled_events & ptr_event_mask)
	{
		mouse_x = e->x();						// rel. to widget == rel. to window
		mouse_y = e->y();						// rel. to widget == rel. to window
		mouse_current_buttons = e->buttons();	// %00000mrl excl. the released button
		mouse_button_toggled |= e->button();
	}
}





void MainWindow::slotSetTCPPort(){LogIn("slotSetIPAddress TODO");}
void MainWindow::slotSetPipeName(){LogIn("slotSetQueueNames TODO");}
void MainWindow::slotShowAbout(){LogIn("slotShowAbout TODO");}
void MainWindow::slotShowPreferences(){LogIn("slotShowPreferences TODO");}
void MainWindow::slotResetTerminal(){LogIn("slotResetTerminal TODO");}















