/*
Copyright (C) 2006 Evan Teran
                   eteran@alum.rit.edu

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

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.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "QHexView.h"

#include <QApplication>
#include <QPixmap>
#include <QPainter>
#include <QScrollBar>
#include <QMouseEvent>
#include <QtGlobal>
#include <QMenu>
#include <QFontDialog>
#include <QClipboard>

#include "ByteStream.h"

//------------------------------------------------------------------------------
// Name: QHexView(QWidget * parent)
// Desc: constructor
//------------------------------------------------------------------------------
QHexView::QHexView(QWidget * parent) : QAbstractScrollArea(parent),
		m_RowWidth(16), m_WordWidth(1), m_AddressColor(Qt::red), 
		m_LineColor(Qt::black), m_ShowHex(true), m_ShowAscii(true), 
		m_ShowAddress(true), m_ShowComments(true), m_Origin(0x00000000), m_AddressOffset(0), 
		m_SelectionStart(-1), m_SelectionEnd(-1), m_Data(0),
		m_Highlighting(Highlighting_None), m_EvenWord(Qt::blue), 
		m_OddWord(Qt::black), m_PrintableText(Qt::black), 
		m_NonPrintableText(Qt::red), m_MarkedText(Qt::white), 
		m_MarkedTextBackground(0x00678DB2), m_UnprintableChar('.'),
		m_ShowLine1(true), m_ShowLine2(true), m_ShowLine3(true), 
		m_ShowAddressSeparator(true) {
		
	
	// TODO: this assumes 32-bit address type	
	strncpy(m_AddressFormatString, "%04x:%04x", sizeof(m_AddressFormatString));
	m_AddressFormatString[sizeof(m_AddressFormatString) - 1] = '\0';
			
	setFont(QFont("Monospace", 10));
}

//------------------------------------------------------------------------------
// Name: ~QHexView()
// Desc: destructor
//------------------------------------------------------------------------------
QString QHexView::formatAddress(address_t address) {
	// TODO: this assumes 32-bit address type	
	return QString().sprintf(m_AddressFormatString, (address >> 16) & 0xffff, address & 0xffff);
}

//------------------------------------------------------------------------------
// Name: ~QHexView()
// Desc: destructor
//------------------------------------------------------------------------------
QHexView::~QHexView() {
	m_Data = 0;
}

//------------------------------------------------------------------------------
// Name: setComment(address_t address, const QString &comment)
// Desc: adds a comment for a given address
//------------------------------------------------------------------------------
void QHexView::setComment(address_t address, const QString &comment) {
	m_Comments[address] = comment;
}

//------------------------------------------------------------------------------
// Name: delComment(address_t address)
// Desc: removes a comment for a specific address
//------------------------------------------------------------------------------
void QHexView::delComment(address_t address) {
	m_Comments.remove(address);
}

//------------------------------------------------------------------------------
// Name: dataSize() const
// Desc: returns how much data we are viewing
//------------------------------------------------------------------------------
int QHexView::dataSize() const {
	return m_Data != 0 ? m_Data->size() : 0;
}

//------------------------------------------------------------------------------
// Name: setFont(const QFont &f)
// Desc: overloaded version of setFont, calculates font metrics for later
//------------------------------------------------------------------------------
void QHexView::setFont(const QFont &f) {

	// recalculate all of our metrics/offsets
	const QFontMetrics fm(f);
	m_FontWidth		= fm.maxWidth();
	m_FontHeight	= fm.height();
	
	updateScrollbars();
	
	// TODO: assert that we are using a fixed font
	// TODO: find out if we care?
	QAbstractScrollArea::setFont(f);
}

//------------------------------------------------------------------------------
// Name: addToggleActionToMenu(QMenu *menu, const QString &caption, bool checked, QObject *reciever, const char *slot)
// Desc: convinience function used to add a checkable menu item to the context menu
//------------------------------------------------------------------------------
bool QHexView::addToggleActionToMenu(QMenu *menu, const QString &caption, bool checked, QObject *reciever, const char *slot) {
	QAction *const action = new QAction(caption, menu);
    action->setCheckable(true);
    action->setChecked(checked);
	menu->addAction(action);
	return connect(action, SIGNAL(toggled(bool)), reciever, slot);
}


void QHexView::mnuSetRowWidth1() {
	setRowWidth(1);
}
void QHexView::mnuSetRowWidth2() {
	setRowWidth(2);
}
void QHexView::mnuSetRowWidth4() {
	setRowWidth(4);
}
void QHexView::mnuSetRowWidth8() {
	setRowWidth(8);
}
void QHexView::mnuSetRowWidth16() {
	setRowWidth(16);
}
void QHexView::mnuSetWordWidth1() {
	setWordWidth(1);
}
void QHexView::mnuSetWordWidth2() {
	setWordWidth(2);
}
void QHexView::mnuSetWordWidth4() {
	setWordWidth(4);
}
void QHexView::mnuSetWordWidth8() {
	setWordWidth(8);
}

//------------------------------------------------------------------------------
// Name: createStandardContextMenu()
// Desc: creates the "standard" context menu for the widget
//------------------------------------------------------------------------------
QMenu *QHexView::createStandardContextMenu() {

	QMenu *const menu = new QMenu(this);
	
	menu->addAction("Set &Font", this, SLOT(mnuSetFont()));	
	menu->addSeparator();
	addToggleActionToMenu(menu, "Show A&ddress", m_ShowAddress, this, SLOT(setShowAddress(bool)));
	addToggleActionToMenu(menu, "Show &Hex", m_ShowHex, this, SLOT(setShowHexDump(bool)));
	addToggleActionToMenu(menu, "Show &Ascii", m_ShowAscii, this, SLOT(setShowAsciiDump(bool)));
	addToggleActionToMenu(menu, "Show &Comments", m_ShowComments, this, SLOT(setShowComments(bool)));

	QMenu *const wordMenu = new QMenu("Set Word Width", this);
	addToggleActionToMenu(wordMenu, "1 Byte", m_WordWidth == 1, this, SLOT(mnuSetWordWidth1()));
	addToggleActionToMenu(wordMenu, "2 Bytes", m_WordWidth == 2, this, SLOT(mnuSetWordWidth2()));
	addToggleActionToMenu(wordMenu, "4 Bytes", m_WordWidth == 4, this, SLOT(mnuSetWordWidth4()));
	addToggleActionToMenu(wordMenu, "8 Bytes", m_WordWidth == 8, this, SLOT(mnuSetWordWidth8()));

	QMenu *const rowMenu = new QMenu("Set Row Width", this);
	addToggleActionToMenu(rowMenu, "1 Word", m_RowWidth == 1, this, SLOT(mnuSetRowWidth1()));
	addToggleActionToMenu(rowMenu, "2 Words", m_RowWidth == 2, this, SLOT(mnuSetRowWidth2()));
	addToggleActionToMenu(rowMenu, "4 Words", m_RowWidth == 4, this, SLOT(mnuSetRowWidth4()));
	addToggleActionToMenu(rowMenu, "8 Words", m_RowWidth == 8, this, SLOT(mnuSetRowWidth8()));
	addToggleActionToMenu(rowMenu, "16 Words", m_RowWidth == 16, this, SLOT(mnuSetRowWidth16()));

	menu->addSeparator();
	menu->addMenu(wordMenu);
	menu->addMenu(rowMenu);
	
	menu->addSeparator();
	menu->addAction("&Copy Selection To Clipboard", this, SLOT(mnuCopy()));	
	
	
	
	return menu;
}

//------------------------------------------------------------------------------
// Name: contextMenuEvent(QContextMenuEvent *event)
// Desc: default context menu event, simply shows standard menu
//------------------------------------------------------------------------------
void QHexView::contextMenuEvent(QContextMenuEvent *event) {
	QMenu *const menu = createStandardContextMenu();
	menu->exec(event->globalPos());
	delete menu;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void QHexView::mnuCopy() {
	if(hasSelectedText()) {
		QByteArray sel = selectedBytes();
		QApplication::clipboard()->setText(sel);
	}
}

//------------------------------------------------------------------------------
// Name: mnuSetFont()
// Desc: slot used to set the font of the widget based on dialog selector
//------------------------------------------------------------------------------
void QHexView::mnuSetFont() {
    setFont(QFontDialog::getFont(0, font(), this));
}

//------------------------------------------------------------------------------
// Name: clear()
// Desc: clears all data from the view
//------------------------------------------------------------------------------
void QHexView::clear() {
	if(m_Data != 0) {
		m_Data->clear();
	}
	
	m_Comments.clear();
	
	viewport()->repaint();
}

//------------------------------------------------------------------------------
// Name: hasSelectedText() const
// Desc: returns true if any text is selected
//------------------------------------------------------------------------------
bool QHexView::hasSelectedText() const {
	return !(m_SelectionStart == -1 || m_SelectionEnd == -1);
}

//------------------------------------------------------------------------------
// Name: isInViewableArea(int index) const
// Desc: returns true if the word at the given index is in the viewable area
//------------------------------------------------------------------------------
bool QHexView::isInViewableArea(int index) const {

	const int firstViewableWord	= verticalScrollBar()->value() * m_RowWidth;
	const int viewableLines		= viewport()->height() / m_FontHeight;
	const int viewableWords		= viewableLines * m_RowWidth;
	const int lastViewableWord	= firstViewableWord + viewableWords;
	
	return index >= firstViewableWord && index < lastViewableWord;
}

//------------------------------------------------------------------------------
// Name: keyPressEvent(QKeyEvent *event)
// Desc: 
//------------------------------------------------------------------------------
void QHexView::keyPressEvent(QKeyEvent *event) {
	if(event->modifiers() & Qt::ControlModifier) {
		switch(event->key()) {
		case Qt::Key_A:
			selectAll();
			viewport()->repaint();
			break;
		case Qt::Key_Home:
			scrollTo(0);
			break;
		case Qt::Key_End:
			// scroll to the end, since -1 interpreted as an unsigned
			// value is the bigest number we can create, we'll just
			// scroll to that and let the scrollbars stop us when we hit the
			// end, it's easier
			//scrollTo(static_cast<unsigned int>(-1));
			
			scrollTo(dataSize() - m_RowWidth * m_WordWidth);
			break;
		}
	}
	
	QAbstractScrollArea::keyPressEvent(event);
}

//------------------------------------------------------------------------------
// Name: isPrintable(unsigned int ch)
// Desc: determines if a character has a printable ascii symbol
//------------------------------------------------------------------------------
bool QHexView::isPrintable(unsigned int ch) {

	// if it's standard ascii use isprint, otherwise go with our observations
	if(ch < 128) {
		return std::isprint(ch);
	} else {
		return (ch & 0xff) >= 0xa0;
	}
}

//------------------------------------------------------------------------------
// Name: line3() const
// Desc: returns the x coordinate of the 3rd line
//------------------------------------------------------------------------------
unsigned int QHexView::line3() const {
	if(m_ShowAscii) {
		const unsigned int elements = (m_RowWidth * m_WordWidth);
		return asciiDumpLeft() + (elements * m_FontWidth) + (m_FontWidth / 2);
	} else {
		return line2();
	}
}

//------------------------------------------------------------------------------
// Name: line2() const
// Desc: returns the x coordinate of the 2nd line
//------------------------------------------------------------------------------
unsigned int QHexView::line2() const {
	if(m_ShowHex) {
		const unsigned int elements = m_RowWidth * (charsPerWord() + 1) - 1;
		return hexDumpLeft() + (elements * m_FontWidth) + (m_FontWidth / 2);
	} else {
		return line1();
	}
}

//------------------------------------------------------------------------------
// Name: line1() const
// Desc: returns the x coordinate of the 1st line
//------------------------------------------------------------------------------
unsigned int QHexView::line1() const {
	if(m_ShowAddress) {
		const unsigned int elements = addressLen();
		return (elements * m_FontWidth) + (m_FontWidth / 2);
	} else {
		return 0;
	}
}

//------------------------------------------------------------------------------
// Name: hexDumpLeft() const
// Desc: returns the x coordinate of the hex-dump field left edge
//------------------------------------------------------------------------------
unsigned int QHexView::hexDumpLeft() const {
	return line1() + (m_FontWidth / 2);
}

//------------------------------------------------------------------------------
// Name: asciiDumpLeft() const
// Desc: returns the x coordinate of the ascii-dump field left edge
//------------------------------------------------------------------------------
unsigned int QHexView::asciiDumpLeft() const {
	return line2() + (m_FontWidth / 2);
}

//------------------------------------------------------------------------------
// Name: commentLeft() const
// Desc: returns the x coordinate of the comment field left edge
//------------------------------------------------------------------------------
unsigned int QHexView::commentLeft() const {
	return line3() + (m_FontWidth / 2);
}

//------------------------------------------------------------------------------
// Name: charsPerWord() const
// Desc: returns how many characters each word takes up
//------------------------------------------------------------------------------
unsigned int QHexView::charsPerWord() const {
	return m_WordWidth * 2;
}

//------------------------------------------------------------------------------
// Name: addressLen() const
// Desc: returns the lenth in characters the address will take up
//------------------------------------------------------------------------------
unsigned int QHexView::addressLen() const {	
	const unsigned int addressLength = (sizeof(address_t) * CHAR_BIT) / 4;
	return addressLength + (m_ShowAddressSeparator ? 1 : 0);
}

//------------------------------------------------------------------------------
// Name: updateScrollbars()
// Desc: recalculates scrollbar maximum value base on lines total and lines viewable
//------------------------------------------------------------------------------
void QHexView::updateScrollbars() {
	const unsigned int totalLines		= dataSize() / (m_RowWidth * m_WordWidth);
	const unsigned int viewableLines	= viewport()->height() / m_FontHeight;

	unsigned int scrollMax = (totalLines > viewableLines) ? totalLines - 1 : 0;
	
	if(m_Origin != 0) {
		++scrollMax;
	}
	
	verticalScrollBar()->setMaximum(scrollMax);
}

//------------------------------------------------------------------------------
// Name: scrollTo(unsigned int offset)
// Desc: scrolls view to given byte offset
//------------------------------------------------------------------------------
void QHexView::scrollTo(unsigned int offset) {


	m_Origin = offset % (m_RowWidth * m_WordWidth);
	updateScrollbars();
	
	address_t address = offset / (m_RowWidth * m_WordWidth);
	
	if(m_Origin != 0) {
		++address;
	}

	verticalScrollBar()->setValue(address);
	viewport()->repaint();
}

//------------------------------------------------------------------------------
// Name: setShowAddress(bool show)
// Desc: sets if we are to display the address column
//------------------------------------------------------------------------------
void QHexView::setShowAddress(bool show) {
	m_ShowAddress = show;
	viewport()->repaint();
}

//------------------------------------------------------------------------------
// Name: setShowHexDump(bool show)
// Desc: sets if we are to display the hex-dump column
//------------------------------------------------------------------------------
void QHexView::setShowHexDump(bool show) {
	m_ShowHex = show;
	viewport()->repaint();
}

//------------------------------------------------------------------------------
// Name: setShowComments(bool show)
// Desc: sets if we are to display the comments column
//------------------------------------------------------------------------------
void QHexView::setShowComments(bool show) {
	m_ShowComments = show;
	viewport()->repaint();
}

//------------------------------------------------------------------------------
// Name: setShowAsciiDump(bool show)
// Desc: sets if we are to display the ascii-dump column
//------------------------------------------------------------------------------
void QHexView::setShowAsciiDump(bool show) {
	m_ShowAscii = show;
	viewport()->repaint();
}

//------------------------------------------------------------------------------
// Name: setRowWidth(int rowWidth)
// Desc: sets the row width (units is words)
//------------------------------------------------------------------------------
void QHexView::setRowWidth(int rowWidth) {
	m_RowWidth = rowWidth;
	
	updateScrollbars();
	viewport()->repaint();
}

//------------------------------------------------------------------------------
// Name: setWordWidth(int wordWidth)
// Desc: sets how many bytes represent a word
//------------------------------------------------------------------------------
void QHexView::setWordWidth(int wordWidth) {
	m_WordWidth = wordWidth;
	
	updateScrollbars();
	viewport()->repaint();
}

//------------------------------------------------------------------------------
// Name: 
//------------------------------------------------------------------------------
int QHexView::pixelToWord(unsigned int x, unsigned int y) const {
	int word = -1;
	
	if(m_Highlighting == Highlighting_Data) {
		if(x < line1()) {
			x = line1();
		}

		// the right edge of a box is kinda quiry, so we pretend there is one
		// extra character there
		if(x > line2()) {
			x = line2() + m_FontWidth;
		}
		
		// the selection is in the data view portion
		x -= line1();

		// scale x/y down to character from pixels
		x /= m_FontWidth;
		y /= m_FontHeight;

		// make x relative to rendering mode of the bytes
		x /= (charsPerWord() + 1);
		
	} else if(m_Highlighting == Highlighting_Ascii) {
		if(x < asciiDumpLeft()) {
			x = asciiDumpLeft();
		}
		
		if(x > line3()) {
			// we do the "- m_FontWidth" to compensate for the fact
			// that the line is at a location 1 character past the last one
			x = line3();
		}
		
		// the selection is in the ascii view portion
		x -= asciiDumpLeft();

		// scale x/y down to character from pixels
		x /= m_FontWidth;
		y /= m_FontHeight;

		// make x relative to rendering mode of the bytes
		x /= m_WordWidth;
	}
	
	
	// starting offset in bytes
	unsigned int startOffset = verticalScrollBar()->value() * (m_RowWidth * m_WordWidth);
	
	// take into account the origin
	if(m_Origin != 0) {
		if(startOffset > 0) {	
			startOffset += m_Origin;	
			startOffset -= (m_RowWidth * m_WordWidth);
		}
	}
	
	// convert byte offset to word offset, rounding up
	startOffset /= m_WordWidth;
	if((m_Origin % m_WordWidth) != 0) {
		startOffset += 1;
	}

	word = ((y * m_RowWidth) + x) + startOffset;
	
	return word;
}

//------------------------------------------------------------------------------
// Name: 
//------------------------------------------------------------------------------
void QHexView::mousePressEvent(QMouseEvent *event) {
	if(event->button() == Qt::LeftButton) {
		const unsigned int x = event->x();
		const unsigned int y = event->y();

		if(x < line2()) {
			m_Highlighting = Highlighting_Data;
		} else if(x >= line2()) {
			m_Highlighting = Highlighting_Ascii;
		}

		const int offset = pixelToWord(x, y);
		int byteOffset = (offset * m_WordWidth);
		if(m_Origin) {
			if(m_Origin % m_WordWidth) {
				byteOffset -= m_WordWidth - (m_Origin % m_WordWidth);
			}
		}

		if(offset < dataSize()) {
			m_SelectionStart = m_SelectionEnd = byteOffset;
		} else {
			m_SelectionStart = m_SelectionEnd = -1;
		}
		viewport()->repaint();
	}
}

//------------------------------------------------------------------------------
// Name: 
//------------------------------------------------------------------------------
void QHexView::mouseMoveEvent(QMouseEvent *event) {
	if(m_Highlighting != Highlighting_None) {
		const unsigned int x = event->x();
		const unsigned int y = event->y();

		const int offset = pixelToWord(x, y);

		if(m_SelectionStart != -1) {
			if(offset == -1) {
				m_SelectionEnd = (m_RowWidth - m_SelectionStart) + m_SelectionStart;
			} else {
			
				int byteOffset = (offset * m_WordWidth);
				if(m_Origin) {
					if(m_Origin % m_WordWidth) {
						byteOffset -= m_WordWidth - (m_Origin % m_WordWidth);
					}
					
				}
				m_SelectionEnd = byteOffset;
			}
			
			if(!isInViewableArea(m_SelectionEnd)) {
				// TODO: scroll to an appropriate location
			}
			
		}
		viewport()->repaint();
	}
}

//------------------------------------------------------------------------------
// Name: 
//------------------------------------------------------------------------------
void QHexView::mouseReleaseEvent(QMouseEvent *event) {
	if(event->button() == Qt::LeftButton) {
		m_Highlighting = Highlighting_None;
	}
}

//------------------------------------------------------------------------------
// Name: 
//------------------------------------------------------------------------------
void QHexView::setData(C *d) {

	m_Data = d;
	
	deselect();
	updateScrollbars();
	viewport()->repaint();
	
}

//------------------------------------------------------------------------------
// Name: 
//------------------------------------------------------------------------------
void QHexView::resizeEvent(QResizeEvent *) {
	updateScrollbars();
}

//------------------------------------------------------------------------------
// Name: setAddressOffset(address_t offset)
//------------------------------------------------------------------------------
void QHexView::setAddressOffset(address_t offset) {
	m_AddressOffset = offset;
}

//------------------------------------------------------------------------------
// Name: isSelected(int index) const
//------------------------------------------------------------------------------
bool QHexView::isSelected(int index) const {

	if(index < static_cast<int>(dataSize())) {
		if(m_SelectionStart != m_SelectionEnd) {
			if(m_SelectionStart <= m_SelectionEnd) {
				return (index >= m_SelectionStart && index < m_SelectionEnd);
			} else {
				return (index >= m_SelectionEnd && index < m_SelectionStart);
			}
		}
	}
	return false;
}

//------------------------------------------------------------------------------
// Name: 
//------------------------------------------------------------------------------
void QHexView::drawComments(QPainter &painter, unsigned int offset, unsigned int row) const {
	const address_t address = m_AddressOffset + offset;
	painter.setPen(QPen(Qt::black));
	
	const QString comment = m_Comments[address];

	painter.drawText(
		commentLeft(),
		row, 
		comment.length() * m_FontWidth,
		m_FontHeight,
		Qt::AlignTop,
		comment
		);
}

//------------------------------------------------------------------------------
// Name: 
//------------------------------------------------------------------------------
void QHexView::drawHexDump(QPainter &painter, unsigned int offset, unsigned int row, int &wordCount) const {

	// i is the word we are currently rendering
	for(int i = 0; i < m_RowWidth; ++i) {
		QString byteBuffer;

		union {
			quint64 q;
			quint32 d;
			quint16 w;
			quint8	b;
		} value = { 0 };

		// index of first byte of current "word"
		const int index = offset + (i * m_WordWidth);
		
		const C &dataRef(*m_Data);

		if(index + m_WordWidth > dataSize()) {
			break;
		}

		switch(m_WordWidth) {
		case 1:
			value.b |= dataRef[index + 0];
			byteBuffer.sprintf("%02x", value.b);
			break;
		case 2:
			value.w |= dataRef[index + 0];
			value.w |= dataRef[index + 1] << 8;
			byteBuffer.sprintf("%04x", value.w);
			break;
		case 4:
			value.d |= dataRef[index + 0];
			value.d |= dataRef[index + 1] << 8;
			value.d |= dataRef[index + 2] << 16;
			value.d |= dataRef[index + 3] << 24;
			byteBuffer.sprintf("%08x", value.d);
			break;
		case 8:
			value.q |= static_cast<quint64>(dataRef[index + 0]);
			value.q |= static_cast<quint64>(dataRef[index + 1]) << 8;
			value.q |= static_cast<quint64>(dataRef[index + 2]) << 16;
			value.q |= static_cast<quint64>(dataRef[index + 3]) << 24;
			value.q |= static_cast<quint64>(dataRef[index + 4]) << 32;
			value.q |= static_cast<quint64>(dataRef[index + 5]) << 40;
			value.q |= static_cast<quint64>(dataRef[index + 6]) << 48;
			value.q |= static_cast<quint64>(dataRef[index + 7]) << 56;
			byteBuffer.sprintf("%016llx", value.q);
			break;
		}
		
		if(isSelected(index)) {
			painter.fillRect(
				hexDumpLeft() + (i * (charsPerWord() + 1) * m_FontWidth),
				row,
				charsPerWord() * m_FontWidth,
				m_FontHeight,
				QBrush(m_MarkedTextBackground)
			);

			if(i != (m_RowWidth - 1)) {
				if(isSelected(index + 1)) {
					painter.fillRect(
						hexDumpLeft() + ((i * (charsPerWord() + 1) + 1) * m_FontWidth),
						row,
						charsPerWord() * m_FontWidth,
						m_FontHeight,
						QBrush(m_MarkedTextBackground)
					);
				}
			}

			painter.setPen(QPen(m_MarkedText));
		} else {
			painter.setPen(QPen((wordCount & 1)? m_EvenWord : m_OddWord));
		}

		painter.drawText(
			hexDumpLeft() + (i * (charsPerWord() + 1) * m_FontWidth),
			row, 
			byteBuffer.length() * m_FontWidth,
			m_FontHeight,
			Qt::AlignTop,
			byteBuffer
			);

		++wordCount;
	}
}

//------------------------------------------------------------------------------
// Name: drawAsciiDump(QPainter &painter, unsigned int offset, unsigned int row) const
//------------------------------------------------------------------------------
void QHexView::drawAsciiDump(QPainter &painter, unsigned int offset, unsigned int row) const {
	
	// i is the byte index
	for(int i = 0; i < (m_RowWidth * m_WordWidth); ++i) {
		QString byteBuffer;
		const int index = offset + i;
		const C &dataRef(*m_Data);
		
		if(index < dataSize()) {
			const char ch = dataRef[index];

			// drawing a selected character
			if(isSelected(index)) {

				painter.fillRect(
					asciiDumpLeft() + (i * m_FontWidth),
					row,
					m_FontWidth,
					m_FontHeight,
					QBrush(m_MarkedTextBackground)
					);

				painter.setPen(QPen(m_MarkedText));

				byteBuffer = isPrintable(ch) ? ch : m_UnprintableChar;
			} else {

				if(isPrintable(ch)) {
					painter.setPen(QPen(m_PrintableText));
					byteBuffer += ch;
				} else {
					painter.setPen(QPen(m_NonPrintableText));
					byteBuffer += m_UnprintableChar;
				}
			}

			painter.drawText(
				asciiDumpLeft() + (i * m_FontWidth),
				row,
				byteBuffer.length() * m_FontWidth,
				m_FontHeight,
				Qt::AlignTop, byteBuffer
				);
		}
	}
}

//------------------------------------------------------------------------------
// Name: paintEvent(QPaintEvent *event)
//------------------------------------------------------------------------------
void QHexView::paintEvent(QPaintEvent *event) {
	
	(void)event;
	
	QPainter painter(viewport());

	int wordCount = 0;

	// pixel offset of this row
	unsigned int row = 0;
		
	// current actual offset (in bytes)
	unsigned int offset = verticalScrollBar()->value() * (m_RowWidth * m_WordWidth);
	
	if(m_Origin != 0) {
		if(offset > 0) {	
			offset += m_Origin;	
			offset -= (m_RowWidth * m_WordWidth);
		} else {
			m_Origin = 0;
			updateScrollbars();
		}
	}
	
	while(row + m_FontHeight < static_cast<unsigned int>(height()) && offset < static_cast<unsigned int>(dataSize())) {
	
		if(m_ShowAddress) {
		
			const address_t addressRVA = m_AddressOffset + offset;
			const QString addressBuffer = formatAddress(addressRVA);
			painter.setPen(QPen(m_AddressColor));
			painter.drawText(0, row, addressBuffer.length() * m_FontWidth, m_FontHeight, Qt::AlignTop, addressBuffer);
		}
		
		if(m_ShowHex) {
			drawHexDump(painter, offset, row, wordCount);
		}

		if(m_ShowAscii) {
			drawAsciiDump(painter, offset, row);
		}
		
		if(m_ShowComments) {
			drawComments(painter, offset, row);
		}

		offset += (m_RowWidth * m_WordWidth);
		row += m_FontHeight;
	}
	
	painter.setPen(QPen(m_LineColor));
	
	if(m_ShowAddress && m_ShowLine1) {
		painter.drawLine(line1(), 0, line1(), height());
	}
	
	if(m_ShowHex && m_ShowLine2) {
		painter.drawLine(line2(), 0, line2(), height());
	}
	
	if(m_ShowAscii && m_ShowLine3) {	
		painter.drawLine(line3(), 0, line3(), height());
	}
}

//------------------------------------------------------------------------------
// Name: selectAll()
//------------------------------------------------------------------------------
void QHexView::selectAll() {
	m_SelectionStart	= 0;
	m_SelectionEnd		= dataSize();
}

//------------------------------------------------------------------------------
// Name: deselect()
//------------------------------------------------------------------------------
void QHexView::deselect() {
	m_SelectionStart	= -1;
	m_SelectionEnd		= -1;
}

//------------------------------------------------------------------------------
// Name: selectedBytes() const
//------------------------------------------------------------------------------
QByteArray QHexView::selectedBytes() const {
	QByteArray ret;
	const C &dataRef(*m_Data);
	
	for(int i = 0; i < dataSize(); ++i) {
		if(isSelected(i)) {
			ret.push_back(dataRef[i]);
		}
	}
	return ret;
}
