#include "HiresScreenWidget.h" #include "binaryfile.h" #include #include #include #include #include #include "util.h" #include 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(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 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 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 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 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::m_rawAddressToColRowList = QVector(); QVector HiresScreenWidget::m_appleAddressToColRowList = QVector();