/*	Copyright  (c)	Günter Woigk 2012 - 2012
					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 2
#include "MemoryDisassInspector.h"
#include "MemoryInspector.h"
#include <QBoxLayout>
#include <QComboBox>
#include "MyLineEdit.h"
#include <QTimer>
#include "Uni/util.h"
#include "Z80/Z80_Disassembler.h"
#include "Machine.h"
#include "Application.h"
#include <QMouseEvent>
#include "Uni/Asm/AssZ80.h"
#include "Z80/Z80opcodes.h"
#include <QPushButton>


// Z80/Z80disass.cpp
extern int Z80OpcodeLength ( uint8 op1, uint8 op2 );

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


QColor color_disass_ok(0,180,0);
QColor color_disass_not_ok(200,0,0);



// ==================================================================================
// ======================   Disassembler Helper Classes   ===========================
// ==================================================================================


class CoreByteDisassembler : public Z80_Disassembler
{
public:
    virtual CoreByte  peek_cb (uint ip) const = 0;
    virtual CoreByte* pointer (uint ip) = 0;
};


class AsSeenByCpuDisass : public CoreByteDisassembler
{
	Z80*	cpu;
public:
	AsSeenByCpuDisass(Z80*cpu)						:cpu(cpu){}
    virtual uint8		peek	(uint ip) const     { return cpu->peek(ip); }
    virtual CoreByte	peek_cb	(uint ip) const     { return *cpu->rdPtr(ip); }
    virtual CoreByte*	pointer (uint ip)			{ return cpu->rdPtr(ip); }
};


class CoreDisass : public CoreByteDisassembler
{
public:
	CoreByte*	core;
	uint		size;
public:
	CoreDisass(CoreByte* core, uint size)			:core(core),size(size){}
    virtual uint8		peek	(uint ip) const     { return core[ip%size]; }
    virtual CoreByte	peek_cb	(uint ip) const     { return core[ip%size]; }
    virtual CoreByte*	pointer	(uint ip)			{ return core+(ip%size); }
};



// ==================================================================================
// ============================   Memory Disassebler   ==============================
// ==================================================================================


MemoryDisassInspector::MemoryDisassInspector(QWidget*parent, IsaObject *item)
:	MemoryInspector(parent,item,Disass)
{
	LogIn("new MemoryDisassInspector");

	disass = NULL;
	bytes_per_row = 1;
	pc=bc=de=hl=ix=iy=sp = ~0;
	edit_mode = 0;
	old_disass_edit_address = -1;
	old_pc = -1;
	disass_edit_string_valid = 0;
	disass_edit_address = 0;
	disass_edit_col = 0;
	hex_edit_address = 0;
	hex_edit_col = 0;

	switch(data_source)
	{
	default:			IERR();
	case AsSeenByCpu:	disass = new AsSeenByCpuDisass(machine->cpu); break;
	case AllRom:
	case RomPages:		disass = new CoreDisass(machine->rom.Data()+data_start,data_size); break;
	case AllRam:
	case RamPages:		disass = new CoreDisass(machine->ram.Data()+data_start,data_size); break;
	}

// create Widgets:

	address_view = new SimpleTerminal(data_widget);
	hex_view	 = new SimpleTerminal(data_widget);	connect(hex_view,SIGNAL(focusChanged(bool)),this,SLOT(slotFocusChanged(bool)));
	disass_view  = new SimpleTerminal(data_widget);	connect(disass_view,SIGNAL(focusChanged(bool)),this,SLOT(slotFocusChanged(bool)));
	cw = hex_view->char_width;
	rh = hex_view->line_height;

// 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_editmode= new QComboBox(head_widget);
//	combobox_editmode->addItems(QStringList()<<"View"<<"Edit"<<"x--"<<"-w-"<<"xw-"<<"--r"<<"x-r"<<"xw-"<<"xwr");
//	combobox_editmode->setFixedSize(60,COMBOBOX_HEIGHT);
//	combobox_editmode->setFocusPolicy(Qt::NoFocus);
//	connect(combobox_editmode,SIGNAL(currentIndexChanged(int)),this,SLOT(setEditMode(int)));

	combobox_register->setParent(head_widget);

	setMinimumWidth(L_MARGIN+R_MARGIN+2*H_SPACE+scrollbar_width + (5+8+16)*cw);
	setMaximumWidth(L_MARGIN+R_MARGIN+2*H_SPACE+scrollbar_width + (5+8+40)*cw);
	setMinimumHeight(head_widget->height() + 2*rh + V_MARGINS);
	setMaximumHeight(head_widget->height() + 100*rh + V_MARGINS);
	setBaseSize(minimumSize());
	setSizeIncrement(cw,rh);
}


MemoryDisassInspector::~MemoryDisassInspector()
{
	delete disass;
}


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

inline
bool MemoryDisassInspector::editing_in_disass()
{
	return disass_view->hasFocus();
}


// virtual protected: Qt Callback
void MemoryDisassInspector::resizeEvent( QResizeEvent* e )
{
	MemoryInspector::resizeEvent(e);

	rows  = (data_widget->height()+rh-1)/rh;	// new number of rows
	int h = rows*rh;							// new view height

	int w_address = 5*cw;						// address feldbreite
	int w_hex     = 8*cw;						// hexbyte feldbreite
	int w_disass  = max(12, (data_widget->width() - w_address - w_hex -2*H_SPACE) / cw) * cw;	// disass feldbreite

	// set widget sizes:
	address_view->setGeometry(0,0,w_address,h);
	hex_view->setGeometry(w_address+H_SPACE,0,w_hex,h);
	disass_view->setGeometry(w_address+w_hex+2*H_SPACE,0,w_disass,h);

	update_all = yes;
	updateScrollbar();
//	updateDisplayedData();	--> schreibt in terminal widget bevor dieses sein resize-event bekommen hat
}




void MemoryDisassInspector::setDataSource(int newdatasource)
{
	if(newdatasource==data_source) return;
	MemoryInspector::setDataSource(newdatasource);
	delete disass; disass=0;
	switch(data_source)
	{
	case AsSeenByCpu:	disass = new AsSeenByCpuDisass(machine->cpu); break;
	case AllRom:
	case RomPages:		disass = new CoreDisass(machine->rom.Data()+data_start,data_size); break;
	case AllRam:
	case RamPages:		disass = new CoreDisass(machine->ram.Data()+data_start,data_size); break;
	}

	hex_view->clearFocus();
	disass_view->clearFocus();
	update_all = yes;
}


void MemoryDisassInspector::setMemoryPage(int new_page)
{
	XLogIn("MemoryDisassInspector.setMemoryPage()");

	XXXASSERT(data_source==RamPages || data_source==RomPages);

	MemoryInspector::setMemoryPage(new_page);

	Mem& mem = data_source==RomPages ? machine->rom : machine->ram;
	CoreDisass* d = (CoreDisass*)disass;
	d->core = mem.Data()+data_start;
	d->size = data_size;

	hex_view->clearFocus();
	disass_view->clearFocus();
	update_all = yes;
}


/*
//slot for combobox_editmode:
void MemoryDisassInspector::setEditMode(int i)
{
	XLogIn("MemoryDisassInspector.setEditMode()");

	edit_mode = min(i,2);

	disass_view->setFocusPolicy(i==1 ? Qt::StrongFocus:Qt::NoFocus);
	hex_view->setFocusPolicy(i==1 ? Qt::StrongFocus:Qt::NoFocus);

	if(i==0)	// "View"
	{
		disass_view->setCursor(Qt::ArrowCursor);
		hex_view->setCursor(Qt::ArrowCursor);
	}
	else if(i==1)	// "Edit"
	{
		disass_view->setCursor(Qt::IBeamCursor);
		hex_view->setCursor(Qt::IBeamCursor);
	}
	else		// Breakpoints
	{
		XXXASSERT(cpu_break_x*2==cpu_break_w);
		XXXASSERT(cpu_break_x*4==cpu_break_r);
		breakpoint_mask = (i-1)*cpu_break_x;
		disass_view->setCursor(Qt::PointingHandCursor);
		hex_view->setCursor(Qt::PointingHandCursor);
	}
}
*/



//slot
void MemoryDisassInspector::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);
	disass_view->setFocusPolicy(f?Qt::StrongFocus:Qt::NoFocus);
	hex_view->setCursor(f?Qt::IBeamCursor:Qt::ArrowCursor);
	disass_view->setCursor(f?Qt::IBeamCursor:Qt::ArrowCursor);
}


// Helper for Slots: setBreakpoint…()
void MemoryDisassInspector::set_breakpoint(CoreByte 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);
	disass_view->setCursor(edit_mode?Qt::PointingHandCursor:Qt::ArrowCursor);
}





#define max_base_address()	(data_size-8)


//virtual: set display base address
//note: may be called for data source change => don't fast quit if new addr == old addr
//reimplemented to adjusted limit test
void MemoryDisassInspector::setBaseAddress(int32 new_base_address)
{
	XXLogIn("MemoryDisassInspector.setBaseAddress(%i)",new_base_address);

	display_base_address = minmax(0,new_base_address,max_base_address());
	updateScrollbar();
}


//virtual slot for scrollbar:
//reimplemented to lock to opcode boundaries
void MemoryDisassInspector::setScrollPosition(int new_base_address)
{
	XXLogIn("MemoryDisassInspector.setScrollPosition(%i)",new_base_address);

	display_base_address = start_of_opcode(minmax(0,new_base_address,max_base_address()));
	XXLogLine("--> display_base_address = %i",(int)display_base_address);
	updateScrollbar();
}


//virtual helper:
//reimplemented due to varying page size
void MemoryDisassInspector::updateScrollbar()
{
	XXLogIn("MemoryDisassInspector.updateScrollbar");

	int page_step = max(rows, displayed_data[rows-1].address - displayed_data[0].address);
	XXLogLine("--> page_step = %i",page_step);

	scrollbar->blockSignals(true);
		scrollbar->setMinimum(0);
		scrollbar->setMaximum(max_base_address());
		scrollbar->setValue(display_base_address);
		scrollbar->setPageStep(page_step);
		scrollbar->setSingleStep(max(1,page_step/16));
	scrollbar->blockSignals(false);
}



// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%



void MemoryDisassInspector::paintEvent(QPaintEvent*e)
{
	MemoryInspector::paintEvent(e);
}


// helper:
// assembles opcode into buffer[4]
// returns size of opcode (1..4) 0r 0 if error
int assemble( uint16 address, QString source_str, char buffer[] )
{
	AssZ80 ass;
	return ass.assembleSingleLine(address,catstr(" ",source_str.toUtf8().data()),buffer);
}

bool assembles_ok( uint16 address, QString source_str )
{
	char bu[4];
	return assemble(address,source_str,bu)!=0;
}

void MemoryDisassInspector::assemble_and_store_opcode()
{
	char opcode_buffer[4];

	int opcode_size = assemble(disass_edit_address,disass_edit_string,opcode_buffer);
	opcode_size = min(opcode_size,data_size-disass_edit_address);

	int old_opcode_size = 0;
	while(old_opcode_size<opcode_size) old_opcode_size += disass->opcodeLength(disass_edit_address+old_opcode_size);

	for(int i=0;i<opcode_size;i++)
	{
		((fourbytes*)disass->pointer(disass_edit_address+i))->data = opcode_buffer[i];
	}

	for(int i=opcode_size;i<old_opcode_size;i++)
	{
		((fourbytes*)disass->pointer(disass_edit_address+i))->data = NOP;
	}
}


//helper
inline QColor pen_color_for_corebyte(CoreByte cb)
{
	return (cb & cpu_break_rwx)==0 ? color_black				// no breakpoint
		:  (~cb & cpu_break_rwx)==0 ? color_light_grey		// all rwx breakpoints: not white for readability
		:  QColor( cb&cpu_break_x?180:0, cb&cpu_break_w?160:0, cb&cpu_break_r?220:0 );	// some breakpoints set
}


//helper
//	returns -1 if row is out of screen
//	returned row is unprecise if the machine is running and modifies the displayed data
int MemoryDisassInspector::row_for_address(int32 address)
{
	if(address<displayed_data[0].address) return -1;

	int row_a = 0;
	int row_e = rows-1;

	int32 addr_e = displayed_data[row_e].address;
	if(address>=addr_e)
	{
		if(address >= addr_e + 4) return -1;
		if(address >= addr_e + disass->opcodeLength(addr_e)) return -1;
		return row_e;
	}

	while(row_a<row_e)
	{
		int row_m = (row_e+row_a+1)/2;
		if(address<displayed_data[row_m].address) row_e = row_m-1;
		else									  row_a = row_m;
	}

	// tests can fail if the machine is running and modifies the displayed data:
	//	XXXASSERT(address >= displayed_data[row_e].address);
	//	XXXASSERT(address < displayed_data[row_e].address + disass->opcodeLength(displayed_data[row_e].address));
	return row_e;
}


//helper
//note: modifies Pen Color!
void MemoryDisassInspector::print_byte_at_address(int32 addr)
{
	int row = row_for_address(addr); if(row==-1) return;
	int col = addr-displayed_data[row].address;
	CoreByte byte = displayed_data[row].data[col];

	hex_view->setPenColor(pen_color_for_corebyte(byte));
	if(byte&cpu_break_rwx) hex_view->setAttributes(BOLD);
	hex_view->printAt(row,col*2,hexstr(byte,2));
	if(byte&cpu_break_rwx) hex_view->clearAttributes(BOLD);
}


//helper
void MemoryDisassInspector::show_hex_cursor(bool show)
{
	int row = row_for_address(hex_edit_address); if(row==-1) return;
	int col = hex_edit_address-displayed_data[row].address;
	CoreByte cb = displayed_data[row].data[col];

	hex_view->setPenColor(pen_color_for_corebyte(cb));
	hex_view->setAttributesTo(show?INVERTED:0);
	if(cb&cpu_break_rwx) hex_view->setAttributes(BOLD);
	hex_view->printAt(row,col*2+hex_edit_col,hexchar(hex_edit_col?cb:cb>>4));
	if(cb&cpu_break_rwx) hex_view->setPenColor(color_black);
	hex_view->clearAllAttributes();
}


//helper
void MemoryDisassInspector::show_disass_cursor(bool show)
{
	int row = row_for_address(disass_edit_address); if(row==-1 || disass_edit_address!=displayed_data[row].address) return;
	int col = disass_edit_col; if(col>=disass_view->cols) return;
	QChar c = col<disass_edit_string.length() ? disass_edit_string[col] : QChar(' ');

	QColor const& color =
		!show && editing_in_disass() && displayed_data[row].address==old_disass_edit_address
		? disass_edit_string_valid ? color_disass_ok : color_disass_not_ok
		: color_black;

	disass_view->setPenColor(color);
	disass_view->setAttributesTo(show?INVERTED:0);
	disass_view->printAt(row, col, c);
	disass_view->clearAllAttributes();
	disass_view->setPenColor(color_black);
}


//remove cursor blob
//resets pen color and attributes in affected view
//helper
void MemoryDisassInspector::hide_cursor()
{
	if(edit_flashphase)
	{
		if(editing_in_hex()) show_hex_cursor(0); else
		if(editing_in_disass()) show_disass_cursor(0);
	}
	edit_flashphase = 1;
	edit_flashtime  = system_time;
}


/*	Print one row:
	- Address
	- Hex
	- Disassemble
	Update displayed_data[]
	Colorizes line with new_pc
	Decolorizes line with old pc

	Returns opcode size: 1 .. 4
*/
int MemoryDisassInspector::print_row(int row, int32 address)
{
	DisassData& data = displayed_data[row];

	if(editing_in_disass() && address==disass_edit_address && old_disass_edit_address!=disass_edit_address && old_disass_edit_address!=-1)
	{
		print_row(old_disass_edit_address);
	}

	bool paper_color_for_pc = address==pc;
	bool paper_color_change = (address==old_pc) != paper_color_for_pc;

	if(paper_color_for_pc)
	{
		address_view->setPaperColor(paper_color_pc);
		disass_view->setPaperColor(paper_color_pc);
		disass_view->clear_color = paper_color_pc;
		hex_view->setPaperColor(paper_color_pc);
		hex_view->clear_color = paper_color_pc;
		old_pc = pc;
	}

	bool pen_color_for_disass_edit_address = editing_in_disass() && (address==disass_edit_address);
	bool pen_color_change = (/*editing_in_disass() &&*/ (address==old_disass_edit_address)) != pen_color_for_disass_edit_address;

	if(pen_color_change)
	{
		if(address==old_disass_edit_address) old_disass_edit_address = -1;
		if(address==disass_edit_address && editing_in_disass())     old_disass_edit_address = disass_edit_address;
	}

	if(pen_color_for_disass_edit_address)
	{
		disass_view->setPenColor(disass_edit_string_valid ? color_disass_ok : color_disass_not_ok);
	}

	if(data.address!=address || paper_color_change || update_all)
	{
		char s[] = "$0000";
		s[1] = hexchar(address>>12);
		s[2] = hexchar(address>>8);
		s[3] = hexchar(address>>4);
		s[4] = hexchar(address);
		address_view->printAt(row,0,s);
		data.address = address;
	}

	int n = disass->opcodeLength(address);		XXXASSERT(n>=1&&n<=4);

	bool u = update_all || paper_color_change || pen_color_change;
	for(int i=0; !u && i<n; i++) u = data.data[i] != (disass->peek_cb(address+i) & (255 | cpu_break_rwx));

	if(u)
	{
		CoreByte corebyte;
		CoreByte flags = 0;

		for(int i=0;i<n;i++)
		{
			data.data[i] = corebyte = disass->peek_cb(address+i) & (255 | cpu_break_rwx);

			if(flags!=(corebyte&cpu_break_rwx))
			{
				flags = corebyte&cpu_break_rwx;
				if(flags) { hex_view->setAttributes(BOLD);   hex_view->setPenColor(pen_color_for_corebyte(corebyte)); }
				else      { hex_view->clearAttributes(BOLD); hex_view->setPenColor(color_black); }
			}

			hex_view->printAt(row,i*2,hexstr(corebyte,2));
			hex_view->clearToEndOfLine();
		}

		if(flags) { hex_view->clearAttributes(BOLD); hex_view->setPenColor(color_black); }

		disass_view->printAt(row,0,pen_color_for_disass_edit_address ? disass_edit_string.left(disass_view->cols) : disass->disassemble(address));
		disass_view->clearToEndOfLine();
	}

	if(pen_color_for_disass_edit_address)
	{
		disass_view->setPenColor(color_black);
	}

	if(paper_color_for_pc)
	{
		address_view->setPaperColor(color_white);
		disass_view->setPaperColor(color_white);
		disass_view->clear_color = color_white;
		hex_view->setPaperColor(color_white);
		hex_view->clear_color = color_white;
	}

	return n;
}


void MemoryDisassInspector::print_row(int32 address)
{
	int row = row_for_address(address);
	if(row!=-1) print_row(row,address);
}


/*	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
		disassembly into textedit_disass.
	Updates displayed_data[]

	row_a	first row
	row_e	last row +1
	address	address to print for row_a
*/
void MemoryDisassInspector::print_rows(int row_a, int row_e, int32 address)
{
	for(int row=row_a; row<row_e; row++)
	{
		address += print_row(row,address);
	}
}


void MemoryDisassInspector::updateDisplayedData()
{
// Feststellen, ob das Toolwindow sichtbar ist:
// das ist gar nicht so einfach...
	if(!appl->isActiveApplication()) return;				 // sonst raus
	if(machine->controller!=front_machine_controller)return; // sonst raus

// update head and general layout:
	MemoryInspector::updateDisplayedData();

// update display:
	Z80Regs& regs = machine->cpu->getRegisters();
	pc = pageAddressForCpuAddress(regs.pc);
	print_rows(0,rows,display_base_address);
	update_all = false;

// register-highlight aktualisieren:
	// remove old:	note: modifies pen color
	print_byte_at_address(bc); bc = pageAddressForCpuAddress(regs.bc);
	print_byte_at_address(de); de = pageAddressForCpuAddress(regs.de);
	print_byte_at_address(hl); hl = pageAddressForCpuAddress(regs.hl);
	print_byte_at_address(ix); ix = pageAddressForCpuAddress(regs.ix);
	print_byte_at_address(iy); iy = pageAddressForCpuAddress(regs.iy);
	print_byte_at_address(sp); sp = pageAddressForCpuAddress(regs.sp);

	// print new:
	hex_view->setPaperColor(paper_color_reg);	//gelb
	print_byte_at_address(bc);
	print_byte_at_address(de);
	print_byte_at_address(ix);
	print_byte_at_address(iy);
	hex_view->setPaperColor(paper_color_sp);	//grün
	print_byte_at_address(sp);
	hex_view->setPaperColor(paper_color_hl);	//cyan
	print_byte_at_address(hl);
	hex_view->setPaperColor(color_white);
	hex_view->setPenColor(color_black);

// edit position aktualisieren:
	if(system_time >= edit_flashtime+0.35) { edit_flashtime=max(system_time,edit_flashtime+0.35); edit_flashphase ^= 1; }
	if(editing_in_hex()) show_hex_cursor(edit_flashphase); else
	if(editing_in_disass()) show_disass_cursor(edit_flashphase);

// tooltip:
//	update_tooltip();
}


inline//helper
void MemoryDisassInspector::step_left_in_hex()
{
	if(hex_edit_address || hex_edit_col)
	{
		hex_edit_col = !hex_edit_col;
		hex_edit_address -= hex_edit_col;
	}
}


inline //helper
void MemoryDisassInspector::step_right_in_hex()
{
	if(hex_edit_col==0 || hex_edit_address < data_size-1)
	{
		hex_edit_address += hex_edit_col;
		hex_edit_col = !hex_edit_col;
	}
}


//helper
//	Note: Stepping back is ambiguous!
//	Note: Unprecise if running machine modifies code!
int32 MemoryDisassInspector::start_of_opcode(int32 address)
{
	for(int32 a = max(address-16,0);;)
	{
		int n = disass->opcodeLength(a);
		if((a+=n)>address) return a-n;
	}
}


//helper
// step back one opcode
// note: stepping back is ambiguous!
int32 MemoryDisassInspector::prev_opcode(int32 address)
{
	if(address>display_base_address && address<=displayed_data[rows-1].address)
	{
		uint row = row_for_address(address);
		return displayed_data[address>displayed_data[row].address ? row : row-1].address;
	}
	else
	{
		int32 addr0 = 0;
		int32 addr1 = max(address-16,0);
		while(addr1<address)
		{
			addr0 = addr1;
			addr1 += disass->opcodeLength(addr1);
		}
		return addr0;
	}
}


//helper
// • Step back n Opcodes
// • Wenn 'address' nicht auf den Anfang eines Opcodes zeigt,
//		count_partial_opcode==0: wird der angebrochene Opcode nicht mitgezählt.
//		count_partial_opcode==1: wird der angebrochene Opcode mitgezählt.
// • Stops at address 0.
// • Note: Stepping back is ambiguous!
int32 MemoryDisassInspector::prev_opcode(int32 address, int n, bool count_partial_opcode)
{
	XXXASSERT(n>=0);
//	if(n==0) return address;
	if(n>=address) return 0;

	int   sz = min(address,n*4+12);
	int32 addr1 = max(address-sz,0);
	int32 addr[sz];
	int   i = 0;

	while(addr1<address)
	{
		addr[i++] = addr1;
		addr1 += disass->opcodeLength(addr1);
	}

	if(addr1>address && !count_partial_opcode) n += 1;

	return i>n ? addr[i-1-n] : 0;
}



//helper
// step fore one opcode
inline
int32 MemoryDisassInspector::next_opcode(int32 address)
{
	address = prev_opcode(address+1);
	return address + disass->opcodeLength(address);
}




//virtual Qt:
// mouse button pressed
// if edit=mode == EDIT start editing in hex_view or disass_view
// if edit_mode == BREAKPOINTS set / clear breakpoint
void MemoryDisassInspector::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_disass	= disass_view->mapFromGlobal(gpos);

	bool in_hex		= hex_view->rect().contains(lpos_hex,true);
	bool in_disass	= disass_view->rect().contains(lpos_disass,true);
	if(!in_hex && !in_disass) return;

	hide_cursor();

	int row,col;

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

	if(in_disass)
	{
		col = (lpos_disass.x()+mouse_x_offset) / cw;
		row = (lpos_disass.y()+mouse_y_offset) / rh;
	}

	if(edit_mode==1) // Edit
	{
		if(in_hex)
		{
			hex_edit_address		 = displayed_data[row].address;
			int opcodelen  = disass->opcodeLength(hex_edit_address);
			if(col>=opcodelen*2) col = opcodelen*2-1;
			hex_edit_col		 	 = col&1;
			hex_edit_address		+= col/2;
			if(hex_edit_address>=data_size) { hex_edit_address = data_size-1; hex_edit_col = 1; }
		}
		else
		{
			disass_edit_address		 = displayed_data[row].address;
			disass_edit_string		 = disass->disassemble(disass_edit_address);
			disass_edit_col			 = min(col,disass_edit_string.length());
			disass_edit_string_valid = assembles_ok(disass_edit_address,disass_edit_string);//yes;
			print_row(old_disass_edit_address);
		}
	}
	else	// Breakpoint
	{
		int32 address = displayed_data[row].address;
		int opcodelen = disass->opcodeLength(address);

		if(in_hex)
		{
			if(col<opcodelen*2)
			{
				CoreByte* cb = disass->pointer(address+col/2);
				if((*cb&breakpoint_mask)==breakpoint_mask) { *cb &= ~breakpoint_mask; }
				else									   { *cb |=  breakpoint_mask; }
			}
		}
		else // in disass
		{
			CoreByte sum = disass->peek_cb(address);
			for(int i=1;i<opcodelen;i++) sum &= disass->peek_cb(address+i);

			if((sum&breakpoint_mask)==breakpoint_mask)
				 for(int i=0;i<opcodelen;i++) { *disass->pointer(address+i) &= ~breakpoint_mask; }
			else for(int i=0;i<opcodelen;i++) { *disass->pointer(address+i) |=  breakpoint_mask; }
		}
	}
}


void MemoryDisassInspector::scroll_to_show_address(int32 address)
{
	if(address<display_base_address)	// scroll down:
	{
		display_base_address = prev_opcode(address+1);
		return;
	}

	int32 display_end_address = next_opcode(displayed_data[rows-1].address);
	if(address<display_end_address) return;		// ok: don't scroll

	// scroll up:

	if(display_base_address+rows*4<address)
	{
		display_end_address = display_base_address = prev_opcode(address - rows*4);
		for(int i=0;i<rows;i++) display_end_address = next_opcode(display_end_address);
	}

	while(address>=display_end_address)
	{
		display_base_address = next_opcode(display_base_address);
		display_end_address = next_opcode(display_end_address);
	}
}


//virtual
void MemoryDisassInspector::keyPressEvent(QKeyEvent* e)
{
	if(!editing_in_hex() && !editing_in_disass()) { MemoryInspector::keyPressEvent(e); return; }

	hide_cursor();

	if(editing_in_hex())
	{
		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:
			hex_edit_address = prev_opcode(hex_edit_address);
			hex_edit_col     = 0;
			goto X;
		case Qt::Key_Left:
			step_left_in_hex();
			if(hex_edit_col) goto X; else break;
		case Qt::Key_Down:
			hex_edit_address = next_opcode(hex_edit_address);
			hex_edit_col     = 0;
			if(hex_edit_address>=data_size) { hex_edit_address = data_size-1; hex_edit_col = 1; }
			goto X;
		case Qt::Key_Right:
			step_right_in_hex();
			if(hex_edit_col) break; else goto X;
		default:
			if(hex_edit_address<=data_size-1 && is_hex_digit(e->key()))
			{
				uint8& byte = ((fourbytes*)rdPtr(hex_edit_address))->data;
				if(hex_edit_col) byte = (byte & 0xF0) + (digit_value(e->key()));
				else			 byte = (byte & 0x0F) + (digit_value(e->key())<<4);
				step_right_in_hex();
				goto X;
			}
		// scroll window to show cursor pos:
	X:		scroll_to_show_address(hex_edit_address);
			show_hex_cursor(1);
			break;
		}
	}

	else	// editing_in_disass
	{
		switch(e->key())
		{
		case Qt::Key_Escape:	// abort & clear focus:
			disass_view->clearFocus();
			break;

		case Qt::Key_Return:	// enter opcode (if valid) and goto next line:
		case Qt::Key_Enter:
			disass_edit_col = 0;
			assemble_and_store_opcode();
		case Qt::Key_Down:
			if(next_opcode(disass_edit_address)<data_size) disass_edit_address = next_opcode(disass_edit_address);
			disass_edit_string = disass->disassemble(disass_edit_address);
			disass_edit_string_valid = assembles_ok(disass_edit_address,disass_edit_string);//yes;
			goto Y;
		case Qt::Key_Up:		// enter opcode (if valid) and goto previous line:
			if(disass_edit_address) disass_edit_address = prev_opcode(disass_edit_address);
			disass_edit_string = disass->disassemble(disass_edit_address);
			disass_edit_string_valid = assembles_ok(disass_edit_address,disass_edit_string);//yes;
			goto Y;

		case Qt::Key_Left:		// move left, stays in line!
			disass_edit_col = minmax(0,disass_edit_col-1,disass_edit_string.length());
			goto Z;
		case Qt::Key_Right:		// move right, stays in line!
			disass_edit_col = minmax(0,disass_edit_col+1,disass_edit_string.length());
			goto Z;
		case Qt::Key_Backspace:	// delete char left to cursor
			if(disass_edit_col)
			{	disass_edit_string = disass_edit_string.left(disass_edit_col-1) + disass_edit_string.mid(disass_edit_col);
				disass_edit_string_valid = assembles_ok(disass_edit_address,disass_edit_string);
				disass_edit_col--;
			}	goto Z;
		default:				// input key, stay in line:
			if(e->text().length()==1 && e->text().at(0)>=0x20 && e->text().at(0)<0x80)
			{
				if(disass_edit_col>disass_edit_string.length()) disass_edit_string += spacestr(disass_edit_col-disass_edit_string.length());
				disass_edit_string = disass_edit_string.left(disass_edit_col) + e->text() + disass_edit_string.mid(disass_edit_col+1);
				disass_edit_string_valid = assembles_ok(disass_edit_address,disass_edit_string);
				disass_edit_col++;
			}
			else LogLine("MemoryDisassInspector::keyPressEvent: %i not handled",e->key());
		Z:	if(old_disass_edit_address==disass_edit_address) old_disass_edit_address = -1;
			print_row(disass_edit_address);
		Y:	scroll_to_show_address(disass_edit_address);
			show_disass_cursor(1);
			break;
		}
		// TODO
	}
}


// slot for hex_view and disass_view:
void MemoryDisassInspector::slotFocusChanged(bool f)
{
	QObject* sender = (QObject::sender());

	if(sender==disass_view)
	{
		if(f)	// disass view gains focus:
		{
			disass_edit_string_valid = (uint32)disass_edit_address < (uint32)data_size;
			if(disass_edit_string_valid)
			{
				disass_edit_string = disass->disassemble(disass_edit_address);
				disass_edit_col = min(disass_edit_col,disass_edit_string.length());
//				print_row(disass_edit_address);
			}
		}
		else	// disass view loses focus:
		{
//			print_row(disass_edit_address);
		}
	}

	if(edit_flashphase)
	{
		if(sender==hex_view) show_hex_cursor(f); else
		if(sender==disass_view) show_disass_cursor(f);
	}
}














