AppleSAWS/src/ui/widgets/HiresScreenWidget.cpp

609 lines
18 KiB
C++

#include "HiresScreenWidget.h"
#include "binaryfile.h"
#include <QPainter>
#include <QMap>
#include <QDebug>
#include <QResizeEvent>
#include <QSettings>
#include "util.h"
#include <math.h>
HiresScreenWidget::HiresScreenWidget(QWidget *parent) :
QWidget(parent)
{
if (m_rawAddressToColRowList.size() == 0 ||
m_appleAddressToColRowList.size() == 0)
{
makeAddressTables();
}
resize(561,384);
QSettings settings;
m_viewMode = static_cast<ViewMode>(settings.value("HiresScreenWidget.ViewMode",NTSCColor).toInt());
m_showScanLines = settings.value("HiresScreenWidget.ShowScanLines",true).toBool();
m_pixmap = QPixmap(561,384);
QPainter painter(&m_pixmap);
painter.setBrush(Qt::black);
painter.drawRect(0,0,this->width(),this->height());
formatGroup = new QActionGroup(this);
m_monochromeAction = new QAction("Monochrome Display",this);
m_monochromeAction->setCheckable(true);
m_monochromeAction->setChecked((m_viewMode == Monochrome));
formatGroup->addAction(m_monochromeAction);
m_ntscAction = new QAction("NTSC Display",this);
m_ntscAction->setCheckable(true);
m_ntscAction->setChecked((m_viewMode == NTSCColor));
formatGroup->addAction(m_ntscAction);
m_perPixelColorAction= new QAction("Per-Pixel Color Display",this);
m_perPixelColorAction->setCheckable(true);
m_perPixelColorAction->setChecked((m_viewMode == PerPixelColor));
formatGroup->addAction(m_perPixelColorAction);
m_showScanLinesAction = new QAction("Show Scan Lines",this);
m_showScanLinesAction->setCheckable(true);
m_showScanLinesAction->setChecked(m_showScanLines);
m_prevPageAction = new QAction("Previous Data Page");
m_prevPageAction->setEnabled(false);
m_prevPageAction->setShortcut(QKeySequence(Qt::Key_Left | Qt::CTRL));
m_nextPageAction = new QAction("Next Data Page");
m_nextPageAction->setEnabled(false);
m_nextPageAction->setShortcut(QKeySequence(Qt::Key_Right | Qt::CTRL));
connect(m_ntscAction, &QAction::toggled, this, &HiresScreenWidget::handleNtscAction);
connect(m_monochromeAction, &QAction::toggled, this, &HiresScreenWidget::handleMonochromeAction);
connect(m_perPixelColorAction, &QAction::toggled, this, &HiresScreenWidget::handlePerPixelColorAction);
connect(m_showScanLinesAction, &QAction::toggled,
this, &HiresScreenWidget::handleShowScanLinesAction);
connect(m_prevPageAction, &QAction::triggered,
this, &HiresScreenWidget::handlePrevPageAction);
connect(m_nextPageAction, &QAction::triggered,
this, &HiresScreenWidget::handleNextPageAction);
m_offset = 0;
}
void HiresScreenWidget::handleNtscAction(bool toggled) {
if (toggled) {
m_viewMode = NTSCColor;
update();
}
QSettings settings;
settings.setValue("HiresScreenWidget.ViewMode",m_viewMode);
}
void HiresScreenWidget::handleMonochromeAction(bool toggled) {
if (toggled) {
m_viewMode = Monochrome;
update();
}
QSettings settings;
settings.setValue("HiresScreenWidget.ViewMode",m_viewMode);
}
void HiresScreenWidget::handlePerPixelColorAction(bool toggled) {
if (toggled) {
m_viewMode = PerPixelColor;
update();
}
QSettings settings;
settings.setValue("HiresScreenWidget.ViewMode",m_viewMode);
}
void HiresScreenWidget::handleShowScanLinesAction(bool toggled) {
m_showScanLines = toggled;
update();
QSettings settings;
settings.setValue("HiresScreenWidget.ShowScanLines",toggled);
}
void HiresScreenWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
// if (m_pixmap.size() != this->size()) {
// m_pixmap = m_pixmap.scaled(this->size(),Qt::KeepAspectRatio);
// }
drawPixmap();
QPainter painter(this);
QPixmap tmppixmap = m_pixmap;
// tmppixmap = tmppixmap.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
painter.drawPixmap(0,0,tmppixmap);
}
void HiresScreenWidget::resizeEvent(QResizeEvent *)
{
}
void HiresScreenWidget::drawPixmap()
{
QPainter pmpainter(&m_pixmap);
QByteArray workingdata = m_data.mid(m_offset);
pmpainter.setBrush(Qt::black);
pmpainter.setPen(Qt::black);
pmpainter.drawRect(0,0,m_pixmap.width(),m_pixmap.height());
if (m_viewMode == Monochrome || m_viewMode == NTSCColor)
{
pmpainter.setPen(Qt::white);
pmpainter.setBrush(Qt::white);
quint8 chunkCount = 0;
int idx = 0;
while (idx < qMin(workingdata.size(),8192)) {
ColRow cr = getColRowFromAppleAddress(idx);
int yoff = cr.row();
QBitArray bits(561,false);
int bitoffset = 0;
bool lastbit = false;
for (int jdx = 0; jdx < 40; jdx++)
{
quint8 byte = workingdata[idx++];
QBitArray dataBits = byteToBits(byte);
bool highBit = dataBits.at(0);
if (highBit) {
bits[bitoffset++] = lastbit;
}
for (int xdx = 1; xdx < 7; xdx++) {
bits[bitoffset++] = dataBits.at(xdx);
bits[bitoffset++] = dataBits.at(xdx);
}
lastbit = dataBits.at(7);
bits[bitoffset++] = dataBits.at(7);
if (!highBit) {
bits[bitoffset++] = dataBits.at(7);
}
}
if (m_viewMode == Monochrome)
{
drawMonoLine(pmpainter, yoff*2, bits);
if (!m_showScanLines) drawMonoLine(pmpainter, yoff*2+1, bits);
}
else if (m_viewMode == NTSCColor)
{
drawNtscLine(pmpainter, yoff*2, bits);
if (!m_showScanLines) drawNtscLine(pmpainter, yoff*2+1, bits);
}
chunkCount++;
if (chunkCount == 3) {
chunkCount = 0;
idx+=8; // Skip screen hole
}
}
}
else if (m_viewMode == PerPixelColor)
{
pmpainter.setPen(Qt::white);
pmpainter.setBrush(Qt::white);
for (int idx = 0; idx < qMin(workingdata.size(),8192) ; idx++) {
ColRow cr = getColRowFromAppleAddress(idx);
quint8 byte = workingdata[idx];
bool highBit = byte & 0x80;
QColor oddColor = highBit? QColor(orangeColor) : QColor(greenColor);
QColor evenColor = highBit? QColor(blueColor) : QColor(purpleColor);
int xoff = cr.col() * 7;
int yoff = cr.row() * 2;
quint8 cOffset = highBit?1:0;
quint8 doubleScan = 0;
if (!m_showScanLines)
{
doubleScan = 1;
}
qDebug() << (byte & 0x01) << (byte & 0x02) << (byte & 0x04) << (byte & 0x08)
<< (byte & 0x10) << (byte & 0x20) << (byte & 0x40) << "HI: " << (byte & 0x80);
pmpainter.setPen(xoff & 0x01?oddColor:evenColor);
pmpainter.setBrush(xoff & 0x01?oddColor:evenColor);
if (byte & 0x01) { pmpainter.drawRect((cOffset+xoff*2)+0,yoff,1,doubleScan); }
pmpainter.setPen(xoff & 0x01?evenColor:oddColor);
pmpainter.setBrush(xoff & 0x01?evenColor:oddColor);
if (byte & 0x02) { pmpainter.drawRect((cOffset+xoff*2)+2,yoff,1,doubleScan); }
pmpainter.setPen(xoff & 0x01?oddColor:evenColor);
pmpainter.setBrush(xoff & 0x01?oddColor:evenColor);
if (byte & 0x04) { pmpainter.drawRect((cOffset+xoff*2)+4,yoff,1,doubleScan); }
pmpainter.setPen(xoff & 0x01?evenColor:oddColor);
pmpainter.setBrush(xoff & 0x01?evenColor:oddColor);
if (byte & 0x08) { pmpainter.drawRect((cOffset+xoff*2)+6,yoff,1,doubleScan); }
pmpainter.setPen(xoff & 0x01?oddColor:evenColor);
pmpainter.setBrush(xoff & 0x01?oddColor:evenColor);
if (byte & 0x10) { pmpainter.drawRect((cOffset+xoff*2)+8,yoff,1,doubleScan); }
pmpainter.setPen(xoff & 0x01?evenColor:oddColor);
pmpainter.setBrush(xoff & 0x01?evenColor:oddColor);
if (byte & 0x20) { pmpainter.drawRect((cOffset+xoff*2)+10,yoff,1,doubleScan); }
pmpainter.setPen(xoff & 0x01?oddColor:evenColor);
pmpainter.setBrush(xoff & 0x01?oddColor:evenColor);
if (byte & 0x40) { pmpainter.drawRect((cOffset+xoff*2)+12,yoff,1,doubleScan); }
if (idx >= (280*192)) break;
}
}
}
void HiresScreenWidget::setUnpackedData(QByteArray unpackedData)
{
QByteArray packedData = packData(unpackedData);
setData(packedData);
}
void HiresScreenWidget::setOffset(quint16 offset)
{
m_offset = offset;
emit newOffset(m_offset);
update();
}
quint16 HiresScreenWidget::offset() const { return m_offset; }
void HiresScreenWidget::handlePrevPageAction(bool)
{
if (m_offset >= 8192)
{
setOffset(m_offset - 8192);
m_nextPageAction->setEnabled(true);
}
if (m_offset == 0) { m_prevPageAction->setEnabled(false); }
}
void HiresScreenWidget::handleNextPageAction(bool)
{
if (m_offset+8192 <= m_data.size())
{
setOffset(m_offset+8192);
m_prevPageAction->setEnabled(true);
}
if (m_offset+8192 > m_data.size())
{
m_nextPageAction->setEnabled(false);
}
}
QByteArray HiresScreenWidget::packData(QByteArray unpackedData)
{
QByteArray packedData;
packedData.resize(8192);
packedData.fill(0x00);
for (int idx = 0; idx < qMin(unpackedData.count(),8192); idx++)
{
ColRow cr = getColRowFromRawAddress(idx);
if (cr.isDefined())
{
packedData[cr.appleAddress()] = unpackedData[idx];
}
}
return packedData;
}
int HiresScreenWidget::getLineAddressOffset(int line)
{
static QList<quint16> blockOffset {
0x0000, 0x0080, 0x0100, 0x0180, 0x0200, 0x0280, 0x0300, 0x0380,
0x0028, 0x00A0, 0x0128, 0x01A8, 0x0228, 0x02A8, 0x0328, 0x03A8,
0x0040, 0x00D0, 0x0150, 0x00D0, 0x0250, 0x02D0, 0x0350, 0x03D0
};
static QList<quint16> boxSub {
0x0000, 0x0400, 0x0800, 0x0C00, 0x1000, 0x1400, 0x1800, 0x1C00
};
int block = line / 8;
int sub = line % 8;
return blockOffset[block] + boxSub[sub];
}
void HiresScreenWidget::setData(QByteArray data) {
m_data = data;
if (data.size() > 8192)
{
m_nextPageAction->setEnabled(true);
m_prevPageAction->setEnabled(false);
m_offset = 0;
}
else
{
m_nextPageAction->setEnabled(false);
m_prevPageAction->setEnabled(false);
m_offset = 0;
}
repaint();
}
void HiresScreenWidget::drawMonoLine(QPainter &painter, int lineNum, QBitArray data) {
painter.setPen(Qt::black);
painter.drawLine(0,lineNum,data.count(),lineNum);
painter.setPen(Qt::white);
for (int idx = 0; idx < data.count(); idx++) {
if (data.at(idx))
painter.drawPoint(idx,lineNum);
}
}
void HiresScreenWidget::drawPerPositionColorLine(QPainter &painter, int lineNum, QBitArray data) {
painter.setPen(Qt::black);
painter.drawLine(0,lineNum,data.count(),lineNum);
QList<QColor> colorlist;
colorlist << QColor(orangeColor) << QColor(purpleColor) << QColor(blueColor) << QColor(greenColor);
for (int idx = 0; idx < data.count(); idx++)
{
painter.setPen(colorlist[idx % 4]);
painter.setBrush(colorlist[idx % 4]);
if (data.at(idx))
{
painter.drawPoint(idx,lineNum);
}
}
}
QColor HiresScreenWidget::getColorFromBits(QBitArray bits, quint8 phase)
{
quint8 bitval = (bits[0] * 0x08) +
(bits[1] * 0x04) +
(bits[2] * 0x02) +
(bits[3] * 0x01);
phase %= 4;
if (bitval == 0) { return blackColor; }
if (bitval == 1 && phase == 0) return brownColor;
if (bitval == 2 && phase == 1) return brownColor;
if (bitval == 4 && phase == 2) return brownColor;
if (bitval == 8 && phase == 3) return brownColor;
if (bitval == 2 && phase == 0) return darkGreenColor;
if (bitval == 4 && phase == 1) return darkGreenColor;
if (bitval == 8 && phase == 2) return darkGreenColor;
if (bitval == 1 && phase == 3) return darkGreenColor;
if (bitval == 3 && phase == 0) return greenColor;
if (bitval == 6 && phase == 1) return greenColor;
if (bitval == 12 && phase == 2) return greenColor;
if (bitval == 9 && phase == 3) return greenColor;
if (bitval == 4 && phase == 0) return darkBlueColor;
if (bitval == 8 && phase == 1) return darkBlueColor;
if (bitval == 1 && phase == 2) return darkBlueColor;
if (bitval == 2 && phase == 3) return darkBlueColor;
if (bitval == 5 && phase == 0) return grayColor;
if (bitval == 10 && phase == 1) return grayColor;
if (bitval == 5 && phase == 2) return grayColor;
if (bitval == 10 && phase == 3) return grayColor;
if (bitval == 6 && phase == 0) return blueColor;
if (bitval == 12 && phase == 1) return blueColor;
if (bitval == 9 && phase == 2) return blueColor;
if (bitval == 3 && phase == 3) return blueColor;
if (bitval == 7 && phase == 0) return aquaColor;
if (bitval == 14 && phase == 1) return aquaColor;
if (bitval == 13 && phase == 2) return aquaColor;
if (bitval == 11 && phase == 3) return aquaColor;
if (bitval == 8 && phase == 0) return redColor;
if (bitval == 1 && phase == 1) return redColor;
if (bitval == 2 && phase == 2) return redColor;
if (bitval == 4 && phase == 3) return redColor;
if (bitval == 9 && phase == 0) return orangeColor;
if (bitval == 3 && phase == 1) return orangeColor;
if (bitval == 6 && phase == 2) return orangeColor;
if (bitval == 12 && phase == 3) return orangeColor;
if (bitval == 10 && phase == 0) return gray2Color;
if (bitval == 5 && phase == 1) return gray2Color;
if (bitval == 10 && phase == 2) return gray2Color;
if (bitval == 5 && phase == 3) return gray2Color;
if (bitval == 11 && phase == 0) return yellowColor;
if (bitval == 7 && phase == 1) return yellowColor;
if (bitval == 14 && phase == 2) return yellowColor;
if (bitval == 13 && phase == 3) return yellowColor;
if (bitval == 12 && phase == 0) return purpleColor;
if (bitval == 9 && phase == 1) return purpleColor;
if (bitval == 3 && phase == 2) return purpleColor;
if (bitval == 6 && phase == 3) return purpleColor;
if (bitval == 13 && phase == 0) return pinkColor;
if (bitval == 11 && phase == 1) return pinkColor;
if (bitval == 7 && phase == 2) return pinkColor;
if (bitval == 14 && phase == 3) return pinkColor;
if (bitval == 14 && phase == 0) return lightBlueColor;
if (bitval == 13 && phase == 1) return lightBlueColor;
if (bitval == 11 && phase == 2) return lightBlueColor;
if (bitval == 7 && phase == 3) return lightBlueColor;
return whiteColor;
}
void HiresScreenWidget::drawNtscLine(QPainter &painter, int lineNum, QBitArray data) {
QVector<QColor> colors;
colors.resize(data.count()+3);
for (int idx = 0; idx < data.count(); idx++) {
QBitArray tmp(4);
tmp[0]=data.at(idx+0);
if (idx < data.count()-1) tmp[1]=data.at(idx+1); else tmp[1] = false;
if (idx < data.count()-2) tmp[2]=data.at(idx+2); else tmp[2] = false;
if (idx < data.count()-3) tmp[3]=data.at(idx+3); else tmp[3] = false;
colors[idx] = getColorFromBits(tmp,idx %4);
colors[idx+1] = getColorFromBits(tmp,idx %4);
colors[idx+2] = getColorFromBits(tmp,idx %4);
colors[idx+3] = getColorFromBits(tmp,idx %4);
}
for (int idx = 0; idx < colors.count(); idx++)
{
painter.setPen(colors.at(idx));
painter.setBrush(colors.at(idx));
painter.drawPoint(idx,lineNum);
}
}
QBitArray HiresScreenWidget::byteToBits(quint8 byte) {
QBitArray bits(8);
bits.setBit(0,byte & 0x80);
bits.setBit(7,byte & 0x40);
bits.setBit(6,byte & 0x20);
bits.setBit(5,byte & 0x10);
bits.setBit(4,byte & 0x08);
bits.setBit(3,byte & 0x04);
bits.setBit(2,byte & 0x02);
bits.setBit(1,byte & 0x01);
return bits;
}
void HiresScreenWidget::setMode(HiresScreenWidget::ViewMode viewmode)
{
m_viewMode = viewmode;
update();
}
void HiresScreenWidget::contextMenuEvent(QContextMenuEvent *event) {
QMenu menu(this);
menu.addAction(m_monochromeAction);
menu.addAction(m_ntscAction);
menu.addAction(m_perPixelColorAction);
menu.addSeparator();
menu.addAction(m_showScanLinesAction);
menu.addSeparator();
menu.addAction(prevPageAction());
menu.addAction(nextPageAction());
menu.exec(event->globalPos());
}
HiresScreenWidget::ColRow HiresScreenWidget::getColRowFromAppleAddress(quint16 address)
{
if (address > 8191) {
qDebug() << "Invalid apple address " << address;
address = 8191;
}
return m_appleAddressToColRowList[address];
}
HiresScreenWidget::ColRow HiresScreenWidget::getColRowFromRawAddress(quint16 address)
{
if (address > 8191) {
qDebug() << "Invalid raw address " << address;
address = 8191;
}
return m_rawAddressToColRowList[address];
}
void HiresScreenWidget::makeAddressTables()
{
m_rawAddressToColRowList.resize(8192);
m_appleAddressToColRowList.resize(8192);
for (int row = 0; row < 192; row++)
{
for (int col = 0; col < 40; col++)
{
ColRow cr(col,row);
m_rawAddressToColRowList[cr.rawAddress()] = cr;
m_appleAddressToColRowList[cr.appleAddress()] = cr;
}
}
#ifdef DEBUGHOLES
int num = 0;
quint16 lasthole = 0;
for (int idx = 0; idx < 8192; idx++)
{
if (!m_appleAddressToColRowList[idx].isDefined())
{
if (lasthole != 0 && lasthole != (idx-1)) { qDebug() << "\n"; }
qDebug() << "Hole" << num << "at" << idx+0x2000 << uint16ToHex(idx+0x2000); ;
num++;
lasthole = idx;
}
}
#endif
}
QVector<HiresScreenWidget::ColRow> HiresScreenWidget::m_rawAddressToColRowList
= QVector<HiresScreenWidget::ColRow>();
QVector<HiresScreenWidget::ColRow> HiresScreenWidget::m_appleAddressToColRowList
= QVector<HiresScreenWidget::ColRow>();