/*	Copyright  (c)	Günter Woigk 2012 - 2017
					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.
*/

#include "MemoryHexInspector.h"
#include "MemoryInspector.h"
#include "SimpleTerminal.h"
#include <QScrollBar>
#include <QTimer>
#include <QBoxLayout>
#include <QTextEdit>
#include <QComboBox>
#include <QPushButton>
#include "MyLineEdit.h"
#include "util.h"
#include <QCheckBox>
#include <QRect>
#include <QToolTip>
#include <QToolButton>
#include <QMouseEvent>
#include <QMenu>
#include <QSettings>
#include <QApplication>
#include "unix/os_utilities.h"



/*	The "MemoryBytesInspector"

	This widget displays 3 TextEdit fields (SimpleTerminal fields)
	for	Address
		Hex Bytes
		Ascii Characters
	and a vertical ScrollBar.

	The display data can come from the Machine's Rom pages, Ram pages or from the CPU 'as seen'.
	'data_start' and 'data_size' define the displayable range of data for this widget.
	Within this range the display can be scrolled using the scrollbar.
	The current scroll position is reflected in 'display_base_address', which can be negative up to -(bytes_per_row-1).
	The currently displayed data is backed in 'current_data',
	which is valid between rows 'first_valid_row' and 'last_valid_row' (excl.).

	By resizing the widget the amount of displayed rows and bytes_per_row can be varied.
	While resizing the widget the 'display_base_address' is kept const.
*/


static const int MIN_BYTES_PER_ROW = 8;		// min. bytes per row;  must be even
//static const int MAX_BYTES_PER_ROW = 64;
static const int MIN_ROWS = 2;
//static const int MAX_ROWS = 64;


// offset mouse pointer hotspot -> 'feeled' hotspot
#define mouse_x_offset	-2
#define mouse_y_offset	-2

#define data_size 0x10000u




/* -------------------------------------------------------------------
	CONSTRUCTOR
*/
MemoryHexInspector::MemoryHexInspector(QWidget* parent, Cpu* cpu)
: MemoryInspector(parent,cpu,Bytes,"ram_hexview")
{
	xlogIn("new MemoryHexInspector");

	first_valid_row = 0;
	last_valid_row  = 0;
	edit_mode = 0;
	breakpoint_mask = 0;
//	is_edit_mode = no;
//	breakpoint_mode = 0;
//	is_editing_hex = no;


// create & add Widgets:
// note: sie erscheinen in der Kopfzeile in der Reihenfolge, in der sie zum head_widget hinzugefügt werden.

	#define W	20
	#define H	18

	widget_edit_mode = new QWidget(head_widget);
	widget_edit_mode->setFixedSize(3*W,2*H);

	button_edit_mode = new QPushButton(widget_edit_mode);
	connect(button_edit_mode,SIGNAL(toggled(bool)),this,SLOT(setEditMode(bool)));
	button_edit_mode->setFixedSize(3*W,H);
	button_edit_mode->setCheckable(1);
	button_edit_mode->setText("Edit");

	button_breakpoint_r = new QPushButton(widget_edit_mode);
	connect(button_breakpoint_r,SIGNAL(toggled(bool)),this,SLOT(setBreakpointR(bool)));
	button_breakpoint_r->move(0,H);
	button_breakpoint_r->setFixedSize(W,H);
	button_breakpoint_r->setCheckable(1);
	button_breakpoint_r->setText("R");
	button_breakpoint_r->setStyleSheet("color: rgb(0,0,255)");	// blue

	button_breakpoint_w = new QPushButton(widget_edit_mode);
	connect(button_breakpoint_w,SIGNAL(toggled(bool)),this,SLOT(setBreakpointW(bool)));
	button_breakpoint_w->move(W,H);
	button_breakpoint_w->setFixedSize(W,H);
	button_breakpoint_w->setCheckable(1);
	button_breakpoint_w->setText("W");
	button_breakpoint_w->setStyleSheet("color: rgb(0,180,0)");	// green

	button_breakpoint_x = new QPushButton(widget_edit_mode);
	connect(button_breakpoint_x,SIGNAL(toggled(bool)),this,SLOT(setBreakpointX(bool)));
	button_breakpoint_x->move(2*W,H);
	button_breakpoint_x->setFixedSize(W,H);
	button_breakpoint_x->setCheckable(1);
	button_breakpoint_x->setText("X");
	button_breakpoint_x->setStyleSheet("color: rgb(200,0,0)");	// red

	#undef W
	#undef H


	combobox_register->setParent(head_widget);


	set16			 = new QPushButton(head_widget);
	set16->setFixedSize(50,BUTTON_HEIGHT);		// less than 50 => non-native button
	set16->setText("16");
	connect(set16,SIGNAL(clicked()),this,SLOT(set16BytesPerRow()));

	set32			 = new QPushButton(head_widget);
	set32->setFixedSize(50,BUTTON_HEIGHT);
	set32->setText("32");
	connect(set32,SIGNAL(clicked()),this,SLOT(set32BytesPerRow()));

	address_view = new SimpleTerminal(data_widget);
	hex_view	 = new SimpleTerminal(data_widget);
	ascii_view	 = new SimpleTerminal(data_widget);

	connect(hex_view,SIGNAL(focusChanged(bool)),this,SLOT(slotFocusChanged(bool)));
	connect(ascii_view,SIGNAL(focusChanged(bool)),this,SLOT(slotFocusChanged(bool)));

	cw = hex_view->char_width;				// Character width
	rh = hex_view->line_height;				// Row height

	min_w = inspector_width_for_bytes(MIN_BYTES_PER_ROW);
	min_h = head_widget->height() + MIN_ROWS*rh + V_MARGINS;
	dw    = cw*6;
	dh    = rh;
	setMinimumSize(min_w,min_h);
}


/* ------------------------------------------
	DESTRUCTOR
*/
MemoryHexInspector::~MemoryHexInspector()
{
}


// ==============================================================================


inline
bool MemoryHexInspector::editing_in_hex()
{
	return hex_view->hasFocus();
}

inline
bool MemoryHexInspector::editing_in_ascii()
{
	return ascii_view->hasFocus();
}

//Helper:
inline uint MemoryHexInspector::data_widget_width_for_bytes(uint n)
{
	return 2*H_SPACE + (5+6*n-1)*cw;
}


//Helper:
uint MemoryHexInspector::inspector_width_for_bytes(uint n)
{
	return L_MARGIN+R_MARGIN + scrollbar_width + data_widget_width_for_bytes(n);
}


//Helper:
void MemoryHexInspector::set_bytes_per_row(int bytes_per_row)
{
	int delta_width  = data_widget_width_for_bytes(bytes_per_row) -data_widget->width();
	int delta_height = rows*rh									  -data_widget->height();

	if( !delta_width && !delta_height ) return;

	QRect r = window()->geometry();
	r.setWidth ( r.width()  + delta_width  );
	r.setHeight( r.height() + delta_height );
	window()->setGeometry(r);
}


// virtual protected: Qt Callback
void MemoryHexInspector::resizeEvent( QResizeEvent* e )
{
	xlogIn("MemoryHexInspector::resizeEvent");
//	logline("h: %i,%i,%i,%i",this->baseSize().height(),this->minimumHeight(),this->height(),this->maximumHeight());
//	logline("w: %i,%i,%i,%i",this->baseSize().width(),this->minimumWidth(),this->width(),this->maximumWidth());


	MemoryInspector::resizeEvent(e);

	uint d  = data_widget->width() - (5-1)*cw - 2*H_SPACE;	// verteilbarer X-Raum
		 d  = d/cw/6;								// Bytes pro Zeile [Bytes]
	uint w1 = 5*cw;									// Width of address field --> "$1234"
	uint w2 = (d*5-1)*cw;							// Width of Hex field
	uint w3 = d*cw;									// Width of Ascii field

	if(bytes_per_row != d) updateAll();				// set bytes_per_row
	bytes_per_row = d;

	rows = (data_widget->height()+rh-1)/rh;			// new number of rows
	limit(0u,first_valid_row,rows);
	limit(0u,last_valid_row,rows);

	// set widget sizes:
	int h = rows*rh;								// new view height
	address_view->setGeometry(0,0,w1,h);
	hex_view->setGeometry(w1+H_SPACE,0,w2,h);
	ascii_view->setGeometry(w1+w2+H_SPACE*2,0,w3,h);

//	set_bytes_per_row(d);	hor works, but vert is jerky

//	update();	--> schreibt in terminal widget bevor dieses sein resize-event bekommen hat
	updateScrollbar();
}


void MemoryHexInspector::set16BytesPerRow()
{
	xlogIn("MemoryHexInspector::set16BytesPerRow");

	set_bytes_per_row(16);
	setBaseAddress(display_base_address&~15);
}


void MemoryHexInspector::set32BytesPerRow()
{
	xlogIn("MemoryHexInspector::set32BytesPerRow");

	set_bytes_per_row(32);
	setBaseAddress(display_base_address&~31);
}




//slot
//note: may be called for data source change => don't fast quit if new addr == old addr
void MemoryHexInspector::setBaseAddress(uint16 new_address)
{
	new_address = min( new_address, (uint16)(data_size-2*bytes_per_row) );

	if(new_address != display_base_address && !update_all)
	{
		if((new_address-display_base_address)%bytes_per_row)
		{
			updateAll();	// horizontaler Versatz => update all
		}
		else if(!update_all)	// scroll up or down:
		{
			int d = (new_address - display_base_address) / bytes_per_row;	// d>0 --> scroll up

			first_valid_row = minmax(0u,first_valid_row-d,rows);
			last_valid_row  = minmax(0u,last_valid_row-d ,rows);

			if(first_valid_row<last_valid_row)
			{
				address_view->scrollScreen(d);		// TODO: copyWindow
				hex_view->scrollScreen(d);
				ascii_view->scrollScreen(d);

				uint cnt   = bytes_per_row * (last_valid_row-first_valid_row);
				uint z_off = bytes_per_row *  first_valid_row;
				uint q_off = bytes_per_row * (first_valid_row+d);

				memmove(&displayed_data[z_off], &displayed_data[q_off], cnt * sizeof(uint16));
			}
		}
	}

	display_base_address = new_address;
	updateScrollbar();
}


//virtual: scroll_bar
void MemoryHexInspector::setScrollPosition(int r)
{
	MemoryInspector::setScrollPosition(r);
	update();
}


inline QColor pen_color_for_corebyte(uint16 bits)
{
	return ~bits&cpu_break_rwx				// not all bits set?
			? QColor( bits&cpu_break_x?180:0, bits&cpu_break_w?160:0, bits&cpu_break_r?220:0 )
			: QColor(Qt::lightGray);		// all bits set: light grey, not white for readability
}


/*	Print a single byte at row y, col x
	Used to update data in the memory view
*/
void MemoryHexInspector::print_byte(int row, int col, Byte cb)
{
	if(cb.bits&cpu_break_rwx) { hex_view->setPenColor(pen_color_for_corebyte(cb.bits)); hex_view->setAttributes(BOLD); }
	hex_view->printAt(row, col*5,hexstr(cb.data,4));
	if(cb.bits&cpu_break_rwx) { hex_view->setPenColor(Qt::black); hex_view->clearAttributes(BOLD); }


	if(cb.data&0xFF80) ascii_view->setAttributes(INVERTED);
	ascii_view->printAt(row,col,printablechar(cb.data&0x7F));
	if(cb.data&0xFF80) ascii_view->clearAttributes(INVERTED);
}


/*	Print rows [a..[e to textedit widgets.
	Used to print data after clear or scroll screen.
	Prints address into textedit_address,
		hex codes into textedit_hex and
		ascii characters into textedit_ascii.
//	Updates displayed_data[]

	row_a	first row
	row_e	last row +1
	address	address to print for row_a
	bytes	pointer to first data byte
	offs	start offset in row_a	idR: 0
	cnt		total bytes in buffer	idR: bytes_per_row * (row_e-row_a)

	if offs>0 then
			address is still the address for the first line
			bytes* points to the first actual data byte for address[+offs]
			cnt does not include the skipped offs bytes
			--> the first line starts with some spaces
	if cnt < bytes_per_row * (row_e-row_a) - offs
			--> the last line ends with some spaces

	TODO: Kolorierung entsprechend High Byte
*/
void MemoryHexInspector::print_rows(uint row_a, uint row_e, uint address, Byte *bytes, uint cnt)
{
	assert(row_a<=row_e);
	assert(row_a>=0);
	assert(row_e<=rows);
	assert(displayed_data.count()>=bytes_per_row*rows);

	str as = spacestr(bytes_per_row);		// ascii string
	int hs_len = bytes_per_row*5-1;
	str hs = spacestr(hs_len);				// hex string
	char ad[] = "$0000";
	uint n = bytes_per_row;					// bytes to print per row

	while(row_a<row_e && cnt>0)
	{
		if(n>cnt) { memset(as,' ',n); memset(hs,' ',hs_len); n=cnt; }

	// print address:
		ad[1] = hexchar(address>>12);
		ad[2] = hexchar(address>>8);
		ad[3] = hexchar(address>>4);
		ad[4] = hexchar(address);
		address_view->printAt(row_a,0,ad);

	// print hex:
		for(uint i=0;i<n;i++)
		{
			int x   = i*5;
			hs[x++] = hexchar(bytes[i].data>>12);
			hs[x++] = hexchar(bytes[i].data>>8);
			hs[x++] = hexchar(bytes[i].data>>4);
			hs[x]   = hexchar(bytes[i].data>>0);
		}
		hex_view->printAt(row_a,0,hs);															// print the black bytes
		for(uint i=0;i<n;i++) if(bytes[i].bits&cpu_break_rwx) print_byte(row_a, i, bytes[i]);	// print the colored bytes

	// print ascii:
		for(uint i=0;i<n;i++) as[i] = printablechar( bytes[i].data );
		ascii_view->printAt(row_a,0,QLatin1String(as));
		ascii_view->setAttributes(INVERTED);
		for(uint i=0;i<n;i++) if(bytes[i].data&0xFF80) { ascii_view->printAt(row_a,i,printablechar(bytes[i].data&0x7F)); }
		ascii_view->clearAttributes(INVERTED);

	// increment:
		bytes += n;
		cnt -= n;
		address += bytes_per_row;
		row_a++;
	}
}




//helper
void MemoryHexInspector::print_byte_seen_by_cpu(uint16 addr)
{
	Byte p = cpu->ram[addr];

	uint row = (addr-display_base_address) / bytes_per_row;
	uint col = (addr-display_base_address) % bytes_per_row;

	if(row<rows) print_byte(row,col,p);
}


//slot
void MemoryHexInspector::update()
{
	assert(first_valid_row>=0 || update_all);
	assert(last_valid_row<=rows || update_all);
	assert(display_base_address>=0);

// Feststellen, ob das Toolwindow sichtbar ist:
	if(!this->isVisible()) return;

	MemoryInspector::update();

	if(first_valid_row>last_valid_row && !update_all) logline("WARNING: MemoryByteInspector: first_valid_row>last_valid_row");

	if(rows*bytes_per_row>displayed_data.count()) displayed_data.grow(rows*bytes_per_row);

	if(update_all || first_valid_row>=last_valid_row)
	{
		address_view->clearScreen();
		hex_view->clearScreen();
		ascii_view->clearScreen();
		update_all = false;
		first_valid_row=last_valid_row=0;
		ip = 0;
	}

	uint cnt  = min(rows*bytes_per_row, data_size-display_base_address);	// real count; excl. tail

	Byte* newdata = &cpu->ram[display_base_address];

// zeilen vor first_valid_line schreiben:
	Byte* np = newdata;
	Byte* dp = displayed_data.getData();
	uint r = first_valid_row;
	uint n;
	if(r)
	{
		n = min(cnt,r*bytes_per_row);
		print_rows(0, r, display_base_address, np, n);
		memcpy(dp, np, n * sizeof(Byte));
		np  += n;
		dp  += n;
		cnt -= n;
	}

// Zeilen im validen Mittelbereich aktualisieren:
	for(;r<last_valid_row;r++)
	{
		n = min(cnt,bytes_per_row);
		for(uint i=0;i<n;i++)
		{
			if( (dp[i].corebyte^np[i].corebyte) & (0xffff|(cpu_break_rwx<<16)) )  print_byte(r,i,dp[i]=np[i]);
		}
		np  += n;
		dp  += n;
		cnt -= n;
	}

// Zeilen nach last_valid_row schreiben:
	if(r<rows)
	{
		n = cnt;
		print_rows(r, rows, display_base_address+r*bytes_per_row, np, n);
		memcpy(dp, np, n * sizeof(Byte));
	}

	first_valid_row = 0;
	last_valid_row  = rows;

// register-highlight aktualisieren:
	// remove old:
	print_byte_seen_by_cpu(ip); ip = cpu->areg_ip;
	print_byte_seen_by_cpu(sp); sp = cpu->areg_sp;
	print_byte_seen_by_cpu(hp); hp = cpu->areg_hp;
	print_byte_seen_by_cpu(a0); a0 = cpu->areg_a0;
	print_byte_seen_by_cpu(a1); a1 = cpu->areg_a1;
	print_byte_seen_by_cpu(atmp); atmp = cpu->areg_atmp;
	print_byte_seen_by_cpu(d2ar); d2ar = cpu->reg_d2ar;

	// print new:
	hex_view->setPaperColor(paper_color_reg);	//gelb
	ascii_view->setPaperColor(paper_color_reg);
	print_byte_seen_by_cpu(a0);
	print_byte_seen_by_cpu(a1);
	print_byte_seen_by_cpu(atmp);
	print_byte_seen_by_cpu(d2ar);

	hex_view->setPaperColor(paper_color_sp);	//grün
	ascii_view->setPaperColor(paper_color_sp);
	print_byte_seen_by_cpu(sp);

	hex_view->setPaperColor(paper_color_hl);	//cyan
	ascii_view->setPaperColor(paper_color_hl);
	print_byte_seen_by_cpu(hp);

	hex_view->setPaperColor(paper_color_pc);	//rot
	ascii_view->setPaperColor(paper_color_pc);
	print_byte_seen_by_cpu(ip);
	hex_view->setPaperColor(Qt::white);
	ascii_view->setPaperColor(Qt::white);

// edit position aktualisieren:
	if(now() >= edit_flashtime+0.35) { edit_flashtime+=0.35; edit_flashphase ^= 1; }
	if(editing_in_hex()) show_hex_cursor(edit_flashphase); else
	if(editing_in_ascii()) show_ascii_cursor(edit_flashphase);

// tooltip:
	update_tooltip();
}




/*	Tooltip aktualisieren:
	aber nur, wenn die Maus über dem MemoryView hovert
	da QToolTip::showText(…) die App zwangsweise in den Vordergrund (zurück-) bringt.
*/
void MemoryHexInspector::update_tooltip()
{
	xlogIn("updateTooltip");

	QPoint gpos       = QCursor::pos();
	if(QApplication::topLevelAt(gpos)!=window()) return;
	QPoint lpos_hex   = hex_view->mapFromGlobal(gpos);
	QPoint lpos_ascii = ascii_view->mapFromGlobal(gpos);

	uint32 addr;
	int    x,y;
	bool   in_hex;

	if(hex_view->rect().contains(lpos_hex,true))
	{
		x = (lpos_hex.x()+mouse_x_offset) / cw;
		y = (lpos_hex.y()+mouse_y_offset) / rh;

		if(x%5==4) goto X; else x = x/5;
		in_hex = yes;
	}
	else if(ascii_view->rect().contains(lpos_ascii,true))
	{
		x = (lpos_ascii.x()+mouse_x_offset) / cw;
		y = (lpos_ascii.y()+mouse_y_offset) / rh;
		in_hex = no;
	}
	else
	{
X:		QToolTip::showText(gpos, NULL, ascii_view, QRect());
		return;
	}

	addr = display_base_address + x + y*bytes_per_row;

	if(addr<(uint32)data_size)
	{
		Byte byte = cpu->ram[addr];

		cstr fmt = "$%04X";
		if(ip==addr) fmt = "ip -> $%04X"; else
		if(sp==addr) fmt = "sp -> $%04X"; else
		if(hp==addr) fmt = "hp -> $%04X"; else
		if(a0==addr) fmt = "a0 -> $%04X"; else
		if(a1==addr) fmt = "a1 -> $%04X"; else
		if(atmp==addr) fmt = "atmp -> $%04X"; else
		if(d2ar==addr) fmt = "d2ar -> $%04X";

		if(in_hex)
		{
			assert(cpu_break_x*2==cpu_break_w);
			assert(cpu_break_x*4==cpu_break_r);

			if(byte.bits&cpu_break_rwx) fmt = catstr(fmt,": breakpoints: ",binstr(byte.bits&cpu_break_rwx,"---","rwx"));

			QToolTip::showText(gpos, usingstr(fmt,(uint)addr), hex_view, QRect());
		}
		else
		{
			fmt = catstr(fmt,": $%02X");
			QToolTip::showText(gpos, usingstr(fmt,(uint)addr,(uint)byte.data), ascii_view, QRect());
		}
	}
}


//slot
void MemoryHexInspector::setEditMode(bool f)
{
	if(f)
	{
		button_breakpoint_r->setChecked(0);
		button_breakpoint_w->setChecked(0);
		button_breakpoint_x->setChecked(0);
	}

	edit_mode = f ? 1 : 0;
	hex_view->setFocusPolicy(f?Qt::StrongFocus:Qt::NoFocus);
	ascii_view->setFocusPolicy(f?Qt::StrongFocus:Qt::NoFocus);
	hex_view->setCursor(f?Qt::IBeamCursor:Qt::ArrowCursor);
	ascii_view->setCursor(f?Qt::IBeamCursor:Qt::ArrowCursor);
}


// Helper for Slots: setBreakpoint…()
void MemoryHexInspector::set_breakpoint(uint16 mask, bool f)
{
	breakpoint_mask &= ~mask;
	if(f)
	{
		breakpoint_mask |= mask;
		button_edit_mode->setChecked(0);
	}
	edit_mode = breakpoint_mask ? 2 : 0;
	hex_view->setCursor(edit_mode?Qt::PointingHandCursor:Qt::ArrowCursor);
	ascii_view->setCursor(edit_mode?Qt::PointingHandCursor:Qt::ArrowCursor);
}


// Helper:
void MemoryHexInspector::step_left_in_hex()
{
	if(hex_edit_nibble==3)
	{
		if(hex_edit_address) { hex_edit_address -= 1; hex_edit_nibble = 0; }
	}
	else hex_edit_nibble++;
}


// Helper:
void MemoryHexInspector::step_right_in_hex()
{
	if(hex_edit_nibble==0)
	{
		if(hex_edit_address<0xFFFF) { hex_edit_address += 1; hex_edit_nibble = 3; }
	}
	else hex_edit_nibble--;
}


bool MemoryHexInspector::event(QEvent*e)
{
	xlogIn("MemoryHexInspector::event: %s",QEventTypeStr(e->type()));
	return MemoryInspector::event(e);
}


void MemoryHexInspector::mousePressEvent(QMouseEvent*e)
{
	if(e->button()!=Qt::LeftButton) { MemoryInspector::mousePressEvent(e); return; }
	if(edit_mode==0) return;

	QPoint gpos       = e->globalPos();
	QPoint lpos_hex   = hex_view->mapFromGlobal(gpos);
	QPoint lpos_ascii = ascii_view->mapFromGlobal(gpos);

	bool in_hex   = hex_view->rect().contains(lpos_hex,true);
	bool in_ascii = ascii_view->rect().contains(lpos_ascii,true);
	if(!in_hex && !in_ascii) return;

	int x,y;

	if(in_hex)
	{
		x = (lpos_hex.x()+mouse_x_offset) / cw;
		y = (lpos_hex.y()+mouse_y_offset) / rh;

		if(x%5==4) return; else x = x/5;
	}
	else //if(in_ascii)
	{
		x = (lpos_ascii.x()+mouse_x_offset) / cw;
		y = (lpos_ascii.y()+mouse_y_offset) / rh;
	}

	uint32 addr = display_base_address + x + y*bytes_per_row;
	if(addr>=(uint32)data_size) return;

	Byte& byte1 = cpu->ram[addr];

	if(edit_mode==2) // BREAKPOINTS
	{
		if((byte1.bits&breakpoint_mask)==breakpoint_mask)
			byte1.bits &= ~breakpoint_mask;
		else
			byte1.bits |= breakpoint_mask;
	}
	else // edit_mode==1: EDIT
	{
		hide_cursor();
		if(editing_in_hex()) { hex_edit_address = addr; hex_edit_nibble = 0; }
		else				 { ascii_edit_address = addr; }
		show_cursor();
	}
}


//virtual
void MemoryHexInspector::keyPressEvent(QKeyEvent* e)
{
	if(editing_in_hex())
	{
		hide_cursor();

		switch(e->key())
		{
	//	case Qt::Key_Backtab:
	//	case Qt::Key_Return:
	//	case Qt::Key_Enter:
		case Qt::Key_Tab:
		case Qt::Key_Escape:
			hex_view->clearFocus();
			break;
		case Qt::Key_Up:
			if(hex_edit_address>=bytes_per_row) hex_edit_address -= bytes_per_row; else hex_edit_address = 0;
			goto X;//break;
		case Qt::Key_Left:
			step_left_in_hex();
			goto X;//break;
		case Qt::Key_Down:
			hex_edit_address += bytes_per_row; if(hex_edit_address<bytes_per_row) hex_edit_address=0xFFFF;
			goto X;//break;
		case Qt::Key_Right:
			step_right_in_hex();
			goto X;//break;
		default:
			if(is_hex_digit(e->key()))
			{
				uint16& byte = cpu->ram[hex_edit_address].data;
				switch(hex_edit_nibble)
				{
				case 0:	byte = (byte & 0xFFF0) + (digit_value(e->key())<<0); break;
				case 1:	byte = (byte & 0xFF0F) + (digit_value(e->key())<<4); break;
				case 2:	byte = (byte & 0xF0FF) + (digit_value(e->key())<<8); break;
				case 3:	byte = (byte & 0x0FFF) + (digit_value(e->key())<<12); break;
				}
				step_right_in_hex();
			}
	X:		while(hex_edit_address<display_base_address) { setBaseAddress(display_base_address-bytes_per_row); update(); }
			while(hex_edit_address>=display_base_address+rows*bytes_per_row) { setBaseAddress(display_base_address+bytes_per_row); update(); }
			show_cursor();
			break;
		}
	}
	else if(editing_in_ascii())
	{
		hide_cursor();

		switch(e->key())
		{
	//	case Qt::Key_Backtab:
	//	case Qt::Key_Return:
	//	case Qt::Key_Enter:
		case Qt::Key_Tab:
		case Qt::Key_Escape:
			ascii_view->clearFocus();
			break;
		case Qt::Key_Up:
			if(ascii_edit_address>=bytes_per_row) ascii_edit_address -= bytes_per_row; else ascii_edit_address = 0;
			goto Y;//break;
		case Qt::Key_Left:
			if(ascii_edit_address>0) ascii_edit_address -= 1;
			goto Y;//break;
		case Qt::Key_Down:
			ascii_edit_address += bytes_per_row; if(ascii_edit_address<bytes_per_row) ascii_edit_address=0xffff;
			goto Y;//break;
		case Qt::Key_Right:
			if(ascii_edit_address<0xffff) ascii_edit_address += 1;
			goto Y;//break;
		default:
			if(e->key()>=0x20 && e->key()<0x7F)
			{
				int c = e->key();
				if(~e->modifiers()&Qt::ShiftModifier) c = tolower(c);

				uint16& byte = cpu->ram[ascii_edit_address].data;
				byte = c;
				if(ascii_edit_address<0xffff) ascii_edit_address += 1;
			}
	Y:		while(ascii_edit_address<display_base_address) { setBaseAddress(display_base_address-bytes_per_row); update(); }
			while(ascii_edit_address>=display_base_address+rows*bytes_per_row) { setBaseAddress(display_base_address+bytes_per_row); update(); }
			show_cursor();
			break;
		}
	}
	else MemoryInspector::keyPressEvent(e);
}


void MemoryHexInspector::show_hex_cursor(bool show)
{
	uint rel_addr = hex_edit_address-display_base_address;
	if(rel_addr >= uint(rows*bytes_per_row)) return;

	uint row = rel_addr/bytes_per_row;
	uint col = rel_addr%bytes_per_row;
		 col = col*5;

	Byte cb = displayed_data[rel_addr];

	if(show) hex_view->setAttributes(INVERTED);
	if(cb.bits&cpu_break_rwx) { hex_view->setPenColor(pen_color_for_corebyte(cb.bits)); hex_view->setAttributes(BOLD); }
	hex_view->printAt(row, col+3-hex_edit_nibble, hexchar(cb.data>>(hex_edit_nibble*4)));
	if(cb.bits&cpu_break_rwx) { hex_view->setPenColor(Qt::black); }
	hex_view->clearAttributes(INVERTED|BOLD);
}


void MemoryHexInspector::show_ascii_cursor(bool show)
{
	uint rel_addr = ascii_edit_address-display_base_address;
	if(rel_addr >= uint(rows*bytes_per_row)) return;

	uint row = rel_addr/bytes_per_row;
	uint col = rel_addr%bytes_per_row;
	Byte cb = displayed_data[rel_addr];

	if(cb.data&0xFF80) show = !show;
	ascii_view->setAttributes(show?INVERTED:0);
	ascii_view->printAt(row, col, printablechar(cb.data&0x7f));
	ascii_view->clearAttributes(INVERTED);
}


//remove cursor blob
//resets pen color and attributes in affected view
//helper
void MemoryHexInspector::hide_cursor()
{
	if(edit_flashphase)
	{
		if(editing_in_hex()) show_hex_cursor(0); else
		if(editing_in_ascii()) show_ascii_cursor(0);
		edit_flashphase = 0;
	}
}


//helper
void MemoryHexInspector::show_cursor()
{
	edit_flashphase = 1;
	edit_flashtime  = now();
	if(editing_in_hex()) show_hex_cursor(1); else
	if(editing_in_ascii()) show_ascii_cursor(1);
}


void MemoryHexInspector::slotFocusChanged(bool f)
{
	QObject* sender = (QObject::sender());

	if(edit_flashphase)
	{
		if(sender==hex_view) show_hex_cursor(f); else
		if(sender==ascii_view) show_ascii_cursor(f);
	}
}













