diff --git a/.gitignore b/.gitignore index d0b8d3fe..1e71796c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ build/*.pyc derived/ build/msvc/.vs/* .vscode/ +.qtc_clangd/ __pycache__/ ui_*.h moc_*.cpp @@ -10,3 +11,10 @@ moc_predefs.h qrc_resources.cpp Makefile .*.swp +*.orig +*.rej +*.pro +*.pro.user* +build-* +*.zip +*.rom diff --git a/resources/expander.png b/resources/expander.png new file mode 100644 index 00000000..96047a3e Binary files /dev/null and b/resources/expander.png differ diff --git a/resources/quickguide/areas_borders.png b/resources/quickguide/areas_borders.png new file mode 100644 index 00000000..b47960ee Binary files /dev/null and b/resources/quickguide/areas_borders.png differ diff --git a/resources/quickguide/areas_join.png b/resources/quickguide/areas_join.png new file mode 100644 index 00000000..2adcf937 Binary files /dev/null and b/resources/quickguide/areas_join.png differ diff --git a/resources/quickguide/areas_resize.png b/resources/quickguide/areas_resize.png new file mode 100644 index 00000000..1dc29f41 Binary files /dev/null and b/resources/quickguide/areas_resize.png differ diff --git a/resources/quickguide/areas_split.png b/resources/quickguide/areas_split.png new file mode 100644 index 00000000..6d96f79b Binary files /dev/null and b/resources/quickguide/areas_split.png differ diff --git a/resources/quickguide/switching_widget.png b/resources/quickguide/switching_widget.png new file mode 100644 index 00000000..9f19fc4d Binary files /dev/null and b/resources/quickguide/switching_widget.png differ diff --git a/resources/quickguide/workspaces_tab.png b/resources/quickguide/workspaces_tab.png new file mode 100644 index 00000000..9cddf024 Binary files /dev/null and b/resources/quickguide/workspaces_tab.png differ diff --git a/resources/resources.qrc b/resources/resources.qrc index b8c65505..f38039ee 100644 --- a/resources/resources.qrc +++ b/resources/resources.qrc @@ -19,4 +19,15 @@ icons/symfil.png overlay.png + + expander.png + + + quickguide/areas_borders.png + quickguide/areas_join.png + quickguide/areas_resize.png + quickguide/areas_split.png + quickguide/workspaces_tab.png + quickguide/switching_widget.png + diff --git a/src/BitMapViewer.cpp b/src/BitMapViewer.cpp index 0e627dff..e8f550d4 100644 --- a/src/BitMapViewer.cpp +++ b/src/BitMapViewer.cpp @@ -1,32 +1,9 @@ #include "BitMapViewer.h" -#include "PaletteDialog.h" #include "VramBitMappedView.h" #include "VDPDataStore.h" #include "Convert.h" #include - -// static to feed to PaletteDialog and be used when VDP colors aren't selected -static uint8_t currentPalette[32] = { -// RB G - 0x00, 0, - 0x00, 0, - 0x11, 6, - 0x33, 7, - 0x17, 1, - 0x27, 3, - 0x51, 1, - 0x27, 6, - 0x71, 1, - 0x73, 3, - 0x61, 6, - 0x64, 6, - 0x11, 4, - 0x65, 2, - 0x55, 5, - 0x77, 7, -}; - BitMapViewer::BitMapViewer(QWidget* parent) : QDialog(parent) , pageSize(0) // avoid UMR @@ -39,19 +16,18 @@ BitMapViewer::BitMapViewer(QWidget* parent) connect(linesVisible, qOverload(&QComboBox:: currentIndexChanged), this, &BitMapViewer::on_linesVisible_currentIndexChanged); connect(bgColor, qOverload(&QSpinBox::valueChanged), this, &BitMapViewer::on_bgColor_valueChanged); - connect(useVDPRegisters, &QCheckBox::stateChanged,this, &BitMapViewer::on_useVDPRegisters_stateChanged); + connect(useVDPRegisters, &QCheckBox::stateChanged,this, &BitMapViewer::on_useVDPRegisters_stateChanged); - connect(saveImageButton, &QPushButton::clicked, this, &BitMapViewer::on_saveImageButton_clicked); - connect(editPaletteButton, &QPushButton::clicked, this, &BitMapViewer::on_editPaletteButton_clicked); - connect(useVDPPalette, &QCheckBox::stateChanged, this, &BitMapViewer::on_useVDPPalette_stateChanged); - connect(zoomLevel, qOverload(&QDoubleSpinBox::valueChanged), this, &BitMapViewer::on_zoomLevel_valueChanged); + connect(saveImageButton, &QPushButton::clicked, this, &BitMapViewer::on_saveImageButton_clicked); + connect(useVDPPalette, &QCheckBox::stateChanged, this, &BitMapViewer::on_useVDPPalette_stateChanged); + connect(zoomLevel, qOverload(&QDoubleSpinBox::valueChanged), this, &BitMapViewer::on_zoomLevel_valueChanged); // hand code entering the actual display widget in the scrollarea With // the designer-qt4 there is an extra scrollAreaWidget between the // imageWidget and the QScrollArea so the scrollbars are not correctly // handled when the image is resized. (since the intermediate widget // stays the same size). I did not try to have this intermediate widget - // resize and all, since it was superflous anyway. + // resize and all, since it was superfluous anyway. imageWidget = new VramBitMappedView(); QSizePolicy sizePolicy1(QSizePolicy::Fixed, QSizePolicy::Fixed); sizePolicy1.setHorizontalStretch(0); @@ -64,14 +40,12 @@ BitMapViewer::BitMapViewer(QWidget* parent) useVDP = useVDPRegisters->isChecked(); - const unsigned char* vram = VDPDataStore::instance().getVramPointer(); + const uint8_t* vram = VDPDataStore::instance().getVramPointer(); imageWidget->setVramSource(vram); imageWidget->setVramAddress(0); - // Palette data not received from VDPDataStore yet causing black image, so - // we start by using fixed palette until VDPDataStoreDataRefreshed kicks in. - imageWidget->setPaletteSource(currentPalette); - - // now hook up some signals and slots + imageWidget->setPaletteSource(VDPDataStore::instance().getPalette(paletteVDP)); + + //now hook up some signals and slots connect(&VDPDataStore::instance(), &VDPDataStore::dataRefreshed, this, &BitMapViewer::VDPDataStoreDataRefreshed); connect(&VDPDataStore::instance(), &VDPDataStore::dataRefreshed, @@ -96,17 +70,17 @@ void BitMapViewer::updateDisplayAsFrame() void BitMapViewer::decodeVDPregs() { - const unsigned char* regs = VDPDataStore::instance().getRegsPointer(); + const uint8_t* regs = VDPDataStore::instance().getRegsPointer(); // Get the number of lines int v1 = (regs[9] & 128) ? 212 : 192; - printf("\nlines acording to the bits %i,: %i\n", (regs[9] & 128), v1); + printf("\nlines according to the bits %i,: %i\n", (regs[9] & 128), v1); linesLabel->setText(QString("%1").arg(v1, 0, 10)); if (useVDP) linesVisible->setCurrentIndex((regs[9] & 128) ? 1 : 0); // Get the border color int v2 = regs[7] & 15; - printf("\nborder acording to the regs %i,: %i\n", regs[7], v2); + printf("\nborder according to the regs %i,: %i\n", regs[7], v2); if (regs[8] & 32) v2 = 0; printf("\ncolor 0 is pallet regs %i,: %i\n", (regs[8] & 32), v2); borderLabel->setText(QString("%1").arg(v2, 0, 10)); @@ -114,25 +88,25 @@ void BitMapViewer::decodeVDPregs() // Get current screenmode static const int bits_modetxt[128] = { - 1, 3, 0, 255, 2, 255, 255, 255, - 4, 255, 80, 255, 5, 255, 255, 255, - 6, 255, 255, 255, 7, 255, 255, 255, - 255, 255, 255, 255, 8, 255, 255, 255, - - 255, 255, 255, 255,255, 255, 255, 255, - 255, 255, 255, 255,255, 255, 255, 255, - 255, 255, 255, 255,255, 255, 255, 255, - 255, 255, 255, 255, 12, 255, 255, 255, - - 255, 255, 255, 255,255, 255, 255, 255, - 255, 255, 255, 255,255, 255, 255, 255, - 255, 255, 255, 255,255, 255, 255, 255, - 255, 255, 255, 255,255, 255, 255, 255, - - 255, 255, 255, 255,255, 255, 255, 255, - 255, 255, 255, 255,255, 255, 255, 255, - 255, 255, 255, 255,255, 255, 255, 255, - 255, 255, 255, 255, 11, 255, 255, 255, + 1, 3, 0, 255, 2, 255, 255, 255, + 4, 255, 80, 255, 5, 255, 255, 255, + 6, 255, 255, 255, 7, 255, 255, 255, + 255, 255, 255, 255, 8, 255, 255, 255, + + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 12, 255, 255, 255, + + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 11, 255, 255, 255, }; static const int bits_mode[128] = { 0, 0, 0, 0, 0, 0, 0, 0, @@ -163,9 +137,6 @@ void BitMapViewer::decodeVDPregs() screenMode->setCurrentIndex(bits_mode[v3]); updateDisplayAsFrame(); } - if (useVDPPalette) { - imageWidget->setPaletteSource(VDPDataStore::instance().getPalettePointer()); - } // Get the current visible page unsigned p = (regs[2] >> 5) & 3; @@ -270,27 +241,14 @@ void BitMapViewer::on_saveImageButton_clicked(bool /*checked*/) "Sorry, the save image dialog is not yet implemented"); } -void BitMapViewer::on_editPaletteButton_clicked(bool /*checked*/) -{ - auto* p = new PaletteDialog(); - p->setPalette(currentPalette); - p->setAutoSync(true); - connect(p, &PaletteDialog::paletteSynced, imageWidget, &VramBitMappedView::refresh); - p->show(); -} - void BitMapViewer::on_useVDPPalette_stateChanged(int state) { - const uint8_t* palette = VDPDataStore::instance().getPalettePointer(); if (state) { - imageWidget->setPaletteSource(palette); + imageWidget->setPaletteSource(VDPDataStore::instance().getPalette(paletteVDP)); } else { - // Copy palette from VDP to allow changes. - if (palette) memcpy(currentPalette, palette, 32); - imageWidget->setPaletteSource(currentPalette); + imageWidget->setPaletteSource(VDPDataStore::instance().getPalette(paletteBitmap)); } imageWidget->refresh(); - editPaletteButton->setEnabled(!state); } /* diff --git a/src/BitMapViewer.h b/src/BitMapViewer.h index c7a1c9a8..f710dfda 100644 --- a/src/BitMapViewer.h +++ b/src/BitMapViewer.h @@ -5,6 +5,7 @@ #include class VramBitMappedView; +class MSXPalette; class BitMapViewer : public QDialog, private Ui::BitMapViewer { @@ -26,7 +27,6 @@ class BitMapViewer : public QDialog, private Ui::BitMapViewer void on_useVDPRegisters_stateChanged(int state); void on_saveImageButton_clicked(bool checked); - void on_editPaletteButton_clicked(bool checked); void on_useVDPPalette_stateChanged(int state); void on_zoomLevel_valueChanged(double d); diff --git a/src/BitMapViewer.ui b/src/BitMapViewer.ui index 6f2d5075..6287f466 100644 --- a/src/BitMapViewer.ui +++ b/src/BitMapViewer.ui @@ -1,8 +1,8 @@ BitMapViewer - - + + 0 0 @@ -10,28 +10,28 @@ 619 - + Dialog - + - - + + 0 - - + + QFrame::StyledPanel - + QFrame::Raised - - + + 0 - + 2 @@ -41,43 +41,43 @@ - - - + + + false - + 5 - + 6 - + 7 - + 8 - + 10 - + 11 - + 12 @@ -89,22 +89,22 @@ false - + 0 - + 1 - + 2 - + 3 @@ -120,30 +120,30 @@ - - - + + + Lines visible - - - + + + false - + 192 - + 212 - + 256 @@ -156,23 +156,23 @@ - - - + + + Replace color 0 by - - - + + + false - - - + + + Displayed as: @@ -181,16 +181,16 @@ - + - - + + Qt::Vertical - + QSizePolicy::Fixed - + 0 13 @@ -199,13 +199,13 @@ - + - - + + Qt::Horizontal - + 22 17 @@ -214,15 +214,12 @@ - + - - - 12 - - - - + + + + 0 @@ -234,23 +231,23 @@ - - - + + + VDP mode: - - - + + + Vram address start: - - - + + + 5 @@ -262,16 +259,16 @@ - - - + + + 0x00000 - - - + + + 212 @@ -279,11 +276,11 @@ - - + + Use current VDP settings - + true @@ -291,11 +288,11 @@ - - + + Qt::Horizontal - + 22 17 @@ -308,43 +305,36 @@ - - + + 1 - - + + Save image... - - - Edit palette.. - - - - - - + + Use VDP palette registers - + true - - + + Qt::Vertical - + QSizePolicy::Minimum - + 0 0 @@ -353,26 +343,26 @@ - + - - + + Zoom - - + + 2 - + 1.000000000000000 - + 16.000000000000000 - + 0.250000000000000 @@ -380,8 +370,8 @@ - - + + Take VRAM snapshot @@ -391,36 +381,36 @@ - - - + + + 0 0 - + 0 0 - + QFrame::Panel - + Qt::ScrollBarAsNeeded - + Qt::ScrollBarAsNeeded - + false - + Qt::AlignCenter - - + + 85 0 @@ -432,36 +422,36 @@ - + - - + + X: - - - + + + 0 0 - + 18 0 - + 000 - - + + Y: @@ -474,49 +464,49 @@ 0 - + 18 0 - + 000 - - + + Color: - - - + + + 0 0 - + 18 0 - + 000 - - + + Qt::Horizontal - + 40 20 @@ -525,53 +515,53 @@ - - + + Vram address: - - - + + + 0 0 - + 42 0 - + 0x00000 - - + + byte value: - - - + + + 0 0 - + 24 0 - + 0x00 diff --git a/src/BreakpointViewer.cpp b/src/BreakpointViewer.cpp index 8842ab0e..337c6d42 100644 --- a/src/BreakpointViewer.cpp +++ b/src/BreakpointViewer.cpp @@ -458,16 +458,16 @@ void BreakpointViewer::changeTableItem(BreakpointRef::Type type, QTableWidgetIte void BreakpointViewer::disableSorting(BreakpointRef::Type type) { - auto unsort = [](auto* table) { + auto unSort = [](auto* table) { table->sortByColumn(-1, Qt::AscendingOrder); }; if (type == BreakpointRef::ALL) { for (auto* table : tables) { - unsort(table); + unSort(table); } } else { - unsort(tables[type]); + unSort(tables[type]); } } diff --git a/src/CPURegsViewer.cpp b/src/CPURegsViewer.cpp index 2fd5f20c..4b529ea5 100644 --- a/src/CPURegsViewer.cpp +++ b/src/CPURegsViewer.cpp @@ -11,6 +11,7 @@ CPURegsViewer::CPURegsViewer(QWidget* parent) : QFrame(parent) { + setObjectName("CPURegsViewer"); // avoid UMR regs.fill(0); regsChanged.fill(false); @@ -150,7 +151,7 @@ void CPURegsViewer::setRegister(int id, int value) } } -void CPURegsViewer::setData(unsigned char* datPtr) +void CPURegsViewer::setData(uint8_t* datPtr) { setRegister(CpuRegs::REG_AF , datPtr[ 0] * 256 + datPtr[ 1]); setRegister(CpuRegs::REG_BC , datPtr[ 2] * 256 + datPtr[ 3]); @@ -312,7 +313,7 @@ int CPURegsViewer::readRegister(int id) return regs[id]; } -void CPURegsViewer::getRegister(int id, unsigned char* data) +void CPURegsViewer::getRegister(int id, uint8_t* data) { data[0] = regs[id] >> 8; data[1] = regs[id] & 255; @@ -320,7 +321,7 @@ void CPURegsViewer::getRegister(int id, unsigned char* data) void CPURegsViewer::applyModifications() { - unsigned char data[26]; + uint8_t data[26]; getRegister(CpuRegs::REG_AF, &data[ 0]); getRegister(CpuRegs::REG_BC, &data[ 2]); getRegister(CpuRegs::REG_DE, &data[ 4]); diff --git a/src/CPURegsViewer.h b/src/CPURegsViewer.h index 4044588e..62e65298 100644 --- a/src/CPURegsViewer.h +++ b/src/CPURegsViewer.h @@ -12,11 +12,13 @@ class CPURegsViewer : public QFrame public: CPURegsViewer(QWidget* parent = nullptr); - void setData(unsigned char* datPtr); int readRegister(int id); - QSize sizeHint() const override; +public slots: + void setData(uint8_t* datPtr); + void setRegister(int id, int value); + private: void resizeEvent(QResizeEvent* e) override; void paintEvent(QPaintEvent* e) override; @@ -39,8 +41,7 @@ class CPURegsViewer : public QFrame int cursorLoc; void drawValue(QPainter& p, int id, int x, int y); - void setRegister(int id, int value); - void getRegister(int id, unsigned char* data); + void getRegister(int id, uint8_t* data); void applyModifications(); void cancelModifications(); diff --git a/src/CommClient.cpp b/src/CommClient.cpp index 095ccccb..7b7b9509 100644 --- a/src/CommClient.cpp +++ b/src/CommClient.cpp @@ -1,5 +1,6 @@ #include "CommClient.h" #include "OpenMSXConnection.h" +#include CommClient::~CommClient() { @@ -33,8 +34,10 @@ void CommClient::closeConnection() void CommClient::sendCommand(CommandBase* command) { if (connection) { + //qDebug() << "CommClient::sendCommand(CommandBase* " << command << ") connection available"; connection->sendCommand(command); } else { + //qDebug() << "CommClient::sendCommand(CommandBase* " << command << ") connection NOT available"; command->cancel(); } } diff --git a/src/ConnectDialog.cpp b/src/ConnectDialog.cpp index 78b56aec..6b8f5863 100644 --- a/src/ConnectDialog.cpp +++ b/src/ConnectDialog.cpp @@ -149,9 +149,9 @@ static std::vector> collectServers() #ifdef _WIN32 DWORD len = GetTempPathW(0, nullptr); assert(len > 0); // nothing we can do to recover this - //VLA(wchar_t, bufW, (len+1)); - //wchar_t bufW[len+1]; - auto bufW = static_cast(_alloca(sizeof(wchar_t) * (len+1))); + //VLA(wchar_t, bufW, (len + 1)); + //wchar_t bufW[len + 1]; + auto bufW = static_cast(_alloca(sizeof(wchar_t) * (len + 1))); len = GetTempPathW(len, bufW); assert(len > 0); // nothing we can do to recover this diff --git a/src/Dasm.cpp b/src/Dasm.cpp index feddecbd..6912db0a 100644 --- a/src/Dasm.cpp +++ b/src/Dasm.cpp @@ -4,12 +4,12 @@ #include #include -static char sign(unsigned char a) +static char sign(uint8_t a) { return (a & 128) ? '-' : '+'; } -static int abs(unsigned char a) +static int abs(uint8_t a) { return (a & 128) ? (256 - a) : a; } @@ -31,12 +31,12 @@ static std::string translateAddress( } } -static int get16(const unsigned char* memBuf, int address) +static int get16(const uint8_t* memBuf, int address) { return memBuf[address] + 256 * memBuf[address + 1]; } -void dasm(const unsigned char* membuf, uint16_t startAddr, uint16_t endAddr, +void dasm(const uint8_t* membuf, uint16_t startAddr, uint16_t endAddr, DisasmLines& disasm, MemoryLayout* memLayout, SymbolTable* symTable, int currentPC) { int pc = startAddr; @@ -116,14 +116,14 @@ void dasm(const unsigned char* membuf, uint16_t startAddr, uint16_t endAddr, dest.numBytes += 2; break; case 'X': { - unsigned char offset = membuf[pc + dest.numBytes]; + uint8_t offset = membuf[pc + dest.numBytes]; dest.instr += '(' + std::string(r) + sign(offset) + '#' + toHex(abs(offset), 2) + ')'; dest.numBytes += 1; break; } case 'Y': { - unsigned char offset = membuf[pc + 2]; + uint8_t offset = membuf[pc + 2]; dest.instr += '(' + std::string(r) + sign(offset) + '#' + toHex(abs(offset), 2) + ')'; break; diff --git a/src/Dasm.h b/src/Dasm.h index c7b073ce..5b75fcbc 100644 --- a/src/Dasm.h +++ b/src/Dasm.h @@ -23,7 +23,7 @@ static const int LAST_INFO_LINE = -65536; using DisasmLines = std::vector; -void dasm(const unsigned char* membuf, uint16_t startAddr, uint16_t endAddr, DisasmLines& disasm, +void dasm(const uint8_t* membuf, uint16_t startAddr, uint16_t endAddr, DisasmLines& disasm, MemoryLayout *memLayout, SymbolTable *symTable, int currentPC); #endif // DASM_H diff --git a/src/DasmTables.cpp b/src/DasmTables.cpp index f1e7ef86..17a1f61f 100644 --- a/src/DasmTables.cpp +++ b/src/DasmTables.cpp @@ -7,8 +7,8 @@ * B - Byte value * R - Relative jump offset * W - Word value - * X - Indexed address ( 3 byte ) - * Y - Indexed address ( 4 byte ) + * X - Indexed address (3 byte) + * Y - Indexed address (4 byte) * I - IX or IY register * ! - Invalid opcode * @ - Invalid opcode diff --git a/src/DebugSession.cpp b/src/DebugSession.cpp index dea642ad..9db140b2 100644 --- a/src/DebugSession.cpp +++ b/src/DebugSession.cpp @@ -4,6 +4,8 @@ #include #include +DebugSession* DebugSession::theDebugSession = nullptr; + DebugSession::DebugSession() : modified(false) { @@ -38,6 +40,15 @@ SymbolTable& DebugSession::symbolTable() return symTable; } + +DebugSession *DebugSession::getDebugSession() +{ + if (theDebugSession == nullptr) { + theDebugSession = new DebugSession{}; + } + return theDebugSession; +} + void DebugSession::clear() { // clear everything diff --git a/src/DebugSession.h b/src/DebugSession.h index 6bc6d5eb..dc74cc82 100644 --- a/src/DebugSession.h +++ b/src/DebugSession.h @@ -10,8 +10,12 @@ class QXmlStreamReader; class DebugSession : public QObject { Q_OBJECT + public: - DebugSession(); + DebugSession(const DebugSession&) = delete; + DebugSession& operator=(const DebugSession&) = delete; + + static DebugSession* getDebugSession(); // session void clear(); @@ -28,6 +32,7 @@ class DebugSession : public QObject void sessionModified(); private: + DebugSession(); void skipUnknownElement(QXmlStreamReader& ses); private: @@ -35,6 +40,8 @@ class DebugSession : public QObject SymbolTable symTable; QString fileName; bool modified; + + static DebugSession* theDebugSession; }; #endif // DEBUGSESSION_H diff --git a/src/DebuggableViewer.cpp b/src/DebuggableViewer.cpp index 5f26683e..5288dbad 100644 --- a/src/DebuggableViewer.cpp +++ b/src/DebuggableViewer.cpp @@ -2,10 +2,12 @@ #include "HexViewer.h" #include #include +#include DebuggableViewer::DebuggableViewer(QWidget* parent) : QWidget(parent) { + setObjectName("DebuggableViewer"); // create selection list and viewer debuggableList = new QComboBox(); debuggableList->setEditable(false); @@ -24,6 +26,21 @@ DebuggableViewer::DebuggableViewer(QWidget* parent) this, &DebuggableViewer::locationChanged); } +QJsonObject DebuggableViewer::save2json() +{ + QJsonObject obj; + obj["debuggable"] = debuggableList->currentText(); + return obj; +} + +bool DebuggableViewer::loadFromJson(const QJsonObject& obj) +{ + auto d = obj["debuggable"]; + if (d == QJsonValue::Undefined) return false; + debuggableList->setCurrentText(d.toString()); + return true; +} + void DebuggableViewer::settingsChanged() { hexView->settingsChanged(); @@ -39,8 +56,9 @@ void DebuggableViewer::debuggableSelected(int index) QString name = debuggableList->itemText(index); int size = debuggableList->itemData(index).toInt(); - if (index >= 0) + if (index >= 0) { lastSelected = name; + } // add braces when the name contains a space if (name.contains(QChar(' '))) { name.append(QChar('}')); @@ -83,4 +101,9 @@ void DebuggableViewer::setDebuggables(const QMap& list) debuggableList->setCurrentIndex(select); hexView->setLocation(lastLocation); } + if (!list.empty() && select == -1) { + hexView->setLocation(0); + debuggableList->setCurrentIndex(0); + } + } diff --git a/src/DebuggableViewer.h b/src/DebuggableViewer.h index 868fa3ae..fbf0ebf6 100644 --- a/src/DebuggableViewer.h +++ b/src/DebuggableViewer.h @@ -1,17 +1,21 @@ #ifndef DEBUGGABLEVIEWER_H #define DEBUGGABLEVIEWER_H +#include "SavesJsonInterface.h" #include class HexViewer; class QComboBox; -class DebuggableViewer : public QWidget +class DebuggableViewer : public QWidget, public SavesJsonInterface { Q_OBJECT public: DebuggableViewer(QWidget* parent = nullptr); + QJsonObject save2json() override; + bool loadFromJson(const QJsonObject& obj) override; + void settingsChanged(); void setDebuggables(const QMap& list); void refresh(); @@ -20,11 +24,12 @@ class DebuggableViewer : public QWidget void debuggableSelected(int index); void locationChanged(int loc); -private: HexViewer* hexView; QComboBox* debuggableList; QString lastSelected; int lastLocation; + + friend class DebuggerForm; // to get to debuggableSelected from the widgetFactory }; #endif // DEBUGGABLEVIEWER_H diff --git a/src/DebuggerForm.cpp b/src/DebuggerForm.cpp index 780b4f4d..04a50cd5 100644 --- a/src/DebuggerForm.cpp +++ b/src/DebuggerForm.cpp @@ -2,7 +2,6 @@ #include "BitMapViewer.h" #include "TileViewer.h" #include "SpriteViewer.h" -#include "DockableWidget.h" #include "DisasmViewer.h" #include "MainMemoryViewer.h" #include "CPURegsViewer.h" @@ -22,6 +21,12 @@ #include "VDPCommandRegViewer.h" #include "Settings.h" #include "Version.h" +#include "SignalDispatcher.h" +#include "blendsplitter/BlendSplitter.h" +#include "blendsplitter/WidgetRegistry.h" +#include "QuickGuide.h" +#include "PaletteView.h" +#include "TabRenamerHelper.h" #include #include #include @@ -30,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +45,13 @@ #include #include #include +#include +#include +#include + + +QMap DebuggerForm::debuggables; + class QueryPauseHandler : public SimpleCommand { public: @@ -84,21 +97,21 @@ class QueryBreakedHandler : public SimpleCommand class CPURegRequest : public ReadDebugBlockCommand { public: - CPURegRequest(DebuggerForm& form_) + CPURegRequest(DebuggerForm& /*form_*/) : ReadDebugBlockCommand("{CPU regs}", 0, 28, buf) - , form(form_) + //, form(form_) { } void replyOk(const QString& message) override { copyData(message); - form.regsView->setData(buf); + SignalDispatcher::instance().setData(buf); delete this; } private: - DebuggerForm& form; + //DebuggerForm& form; uint8_t buf[28]; }; @@ -149,9 +162,7 @@ DebuggerForm::DebuggerForm(QWidget* parent) : QMainWindow(parent) , comm(CommClient::instance()) { - VDPRegView = nullptr; - VDPStatusRegView = nullptr; - VDPCommandRegView = nullptr; + session = DebugSession::getDebugSession(); createActions(); createMenus(); @@ -162,7 +173,10 @@ DebuggerForm::DebuggerForm(QWidget* parent) recentFiles = Settings::get().value("MainWindow/RecentFiles").toStringList(); updateRecentFiles(); - connect(&session.symbolTable(), &SymbolTable::symbolFileChanged, this, &DebuggerForm::symbolFileChanged); + connect(&(session->symbolTable()), &SymbolTable::symbolFileChanged, this, &DebuggerForm::symbolFileChanged); + + autoConnectToOpenMSX(); + } void DebuggerForm::createActions() @@ -176,11 +190,21 @@ void DebuggerForm::createActions() fileSaveSessionAction = new QAction(tr("&Save Session"), this); fileSaveSessionAction->setShortcut(tr("Ctrl+S")); - fileSaveSessionAction->setStatusTip(tr("Save the the current debug session")); + fileSaveSessionAction->setStatusTip(tr("Save the current debug session")); fileSaveSessionAsAction = new QAction(tr("Save Session &As"), this); fileSaveSessionAsAction->setStatusTip(tr("Save the debug session in a selected file")); + fileOpenWorkspaceLayoutAction = new QAction(tr("&Open workspaces"), this); + fileOpenWorkspaceLayoutAction->setStatusTip(tr("Load a workspace layout.")); + + fileSaveWorkspaceLayoutAction = new QAction(tr("&Save workspaces"), this); + fileSaveWorkspaceLayoutAction->setStatusTip(tr("Save the current workspaces and splitters layout")); + + fileSaveWorkspaceLayoutAsAction = new QAction(tr("Save workspaces &As"), this); + fileSaveWorkspaceLayoutAsAction->setStatusTip(tr("Save the current workspaces and splitters in a selected file")); + + fileQuitAction = new QAction(tr("&Quit"), this); fileQuitAction->setShortcut(tr("Ctrl+Q")); fileQuitAction->setStatusTip(tr("Quit the openMSX debugger")); @@ -223,50 +247,6 @@ void DebuggerForm::createActions() searchGotoAction->setStatusTip(tr("Jump to a specific address or label in the disassembly view")); searchGotoAction->setShortcut(tr("Ctrl+G")); - viewRegistersAction = new QAction(tr("CPU &Registers"), this); - viewRegistersAction->setStatusTip(tr("Toggle the cpu registers display")); - viewRegistersAction->setCheckable(true); - - viewBreakpointsAction = new QAction(tr("Debug &List"), this); - viewBreakpointsAction->setStatusTip(tr("Toggle the breakpoints/watchpoints display")); - viewBreakpointsAction->setCheckable(true); - - viewFlagsAction = new QAction(tr("CPU &Flags"), this); - viewFlagsAction->setStatusTip(tr("Toggle the cpu flags display")); - viewFlagsAction->setCheckable(true); - - viewStackAction = new QAction(tr("Stack"), this); - viewStackAction->setStatusTip(tr("Toggle the stack display")); - viewStackAction->setCheckable(true); - - viewSlotsAction = new QAction(tr("Slots"), this); - viewSlotsAction->setStatusTip(tr("Toggle the slots display")); - viewSlotsAction->setCheckable(true); - - viewMemoryAction = new QAction(tr("Memory"), this); - viewMemoryAction->setStatusTip(tr("Toggle the main memory display")); - viewMemoryAction->setCheckable(true); - - viewDebuggableViewerAction = new QAction(tr("Add debuggable viewer"), this); - viewDebuggableViewerAction->setStatusTip(tr("Add a hex viewer for debuggables")); - - viewVDPStatusRegsAction = new QAction(tr("Status Registers"), this); - viewVDPStatusRegsAction->setStatusTip(tr("The VDP status registers interpreted")); - viewVDPStatusRegsAction->setCheckable(true); - viewVDPCommandRegsAction = new QAction(tr("Command Registers"), this); - viewVDPCommandRegsAction->setStatusTip(tr("Interact with the VDP command registers")); - viewVDPCommandRegsAction->setCheckable(true); - viewVDPRegsAction = new QAction(tr("Registers"), this); - viewVDPRegsAction->setStatusTip(tr("Interact with the VDP registers")); - viewVDPRegsAction->setCheckable(true); - viewBitMappedAction = new QAction(tr("Bitmapped VRAM"), this); - viewBitMappedAction->setStatusTip(tr("Decode VRAM as screen 5/6/7/8 image")); - //viewBitMappedAction->setCheckable(true); - viewCharMappedAction = new QAction(tr("Tiles in VRAM"), this); - viewCharMappedAction->setStatusTip(tr("Decode VRAM as MSX1 screen tiles")); - viewSpritesAction = new QAction(tr("Sprites in VRAM"), this); - viewSpritesAction->setStatusTip(tr("Decode sprites tiles")); - executeBreakAction = new QAction(tr("Break"), this); executeBreakAction->setShortcut(tr("CRTL+B")); executeBreakAction->setStatusTip(tr("Halt the execution and enter debug mode")); @@ -315,32 +295,42 @@ void DebuggerForm::createActions() connect(this, &DebuggerForm::runStateEntered, [this]{ executeStepBackAction->setEnabled(false); }); connect(this, &DebuggerForm::breakStateEntered, [this]{ executeStepBackAction->setEnabled(true); }); - executeRunToAction = new QAction(tr("Run to"), this); - executeRunToAction->setShortcut(tr("F4")); - executeRunToAction->setStatusTip(tr("Resume execution until the selected line is reached")); - executeRunToAction->setIcon(QIcon(":/icons/runto.png")); - executeRunToAction->setEnabled(false); - connect(this, &DebuggerForm::runStateEntered, [this]{ executeRunToAction->setEnabled(false); }); - connect(this, &DebuggerForm::breakStateEntered, [this]{ executeRunToAction->setEnabled(true); }); - - breakpointToggleAction = new QAction(tr("Toggle"), this); - breakpointToggleAction->setShortcut(tr("F5")); - breakpointToggleAction->setStatusTip(tr("Toggle breakpoint on/off at cursor")); - breakpointToggleAction->setIcon(QIcon(":/icons/break.png")); - breakpointToggleAction->setEnabled(false); - breakpointAddAction = new QAction(tr("Add ..."), this); - breakpointAddAction->setShortcut(tr("CTRL+B")); - breakpointAddAction->setStatusTip(tr("Add a breakpoint at a location")); - breakpointAddAction->setEnabled(false); + // now that we have possible multiple disasmviewers the disasmview->getCursorAddr() will not work anymore + //breakpointAddAction->setShortcut(tr("CTRL+B")); + //breakpointAddAction->setStatusTip(tr("Add a breakpoint at a location")); + //breakpointAddAction->setEnabled(false); helpAboutAction = new QAction(tr("&About"), this); - executeRunToAction->setStatusTip(tr("Show the application information")); + + addCPUWorkspaceAction = new QAction(tr("&Code debugger"), this); + addCPUWorkspaceAction->setStatusTip(tr("The default way of debugging CPU code")); + + addVDPRegsWorkspaceAction = new QAction(tr("VDP &Regs"), this); + addVDPRegsWorkspaceAction->setStatusTip(tr("Show the regular VDP registers")); + + addVDPTilesWorkspaceAction = new QAction(tr("VDP &Tiles"), this); + addVDPTilesWorkspaceAction->setStatusTip(tr("Show the VRAM in tiles")); + + addVDPBitmapWorkspaceAction = new QAction(tr("VDP &Bitmap"), this); + addVDPBitmapWorkspaceAction->setStatusTip(tr("Show the VRAM in bitmap mode")); + + addEmptyWorkspaceAction = new QAction(tr("&Empty workspace"), this); + addEmptyWorkspaceAction->setStatusTip(tr("create an almost empty workspace")); + + addFloatingSwitchingWidgetAction = new QAction(tr("&Create floating item"), this); + addFloatingSwitchingWidgetAction->setStatusTip(tr("Create item in seperate window")); + connect(fileNewSessionAction, &QAction::triggered, this, &DebuggerForm::fileNewSession); connect(fileOpenSessionAction, &QAction::triggered, this, &DebuggerForm::fileOpenSession); connect(fileSaveSessionAction, &QAction::triggered, this, &DebuggerForm::fileSaveSession); connect(fileSaveSessionAsAction, &QAction::triggered, this, &DebuggerForm::fileSaveSessionAs); + + connect(fileOpenWorkspaceLayoutAction, &QAction::triggered, this, &DebuggerForm::fileOpenWorkspace); + connect(fileSaveWorkspaceLayoutAction, &QAction::triggered, this, &DebuggerForm::fileSaveWorkspace); + connect(fileSaveWorkspaceLayoutAsAction, &QAction::triggered, this, &DebuggerForm::fileSaveWorkspaceAs); + connect(fileQuitAction, &QAction::triggered, this, &DebuggerForm::close); connect(systemConnectAction, &QAction::triggered, this, &DebuggerForm::systemConnect); connect(systemDisconnectAction, &QAction::triggered, this, &DebuggerForm::systemDisconnect); @@ -349,29 +339,20 @@ void DebuggerForm::createActions() connect(systemSymbolManagerAction, &QAction::triggered, this, &DebuggerForm::systemSymbolManager); connect(systemPreferencesAction, &QAction::triggered, this, &DebuggerForm::systemPreferences); connect(searchGotoAction, &QAction::triggered, this, &DebuggerForm::searchGoto); - connect(viewRegistersAction, &QAction::triggered, this, &DebuggerForm::toggleRegisterDisplay); - connect(viewBreakpointsAction, &QAction::triggered, this, &DebuggerForm::toggleBreakpointsDisplay); - connect(viewFlagsAction, &QAction::triggered, this, &DebuggerForm::toggleFlagsDisplay); - connect(viewStackAction, &QAction::triggered, this, &DebuggerForm::toggleStackDisplay); - connect(viewSlotsAction, &QAction::triggered, this, &DebuggerForm::toggleSlotsDisplay); - connect(viewMemoryAction, &QAction::triggered, this, &DebuggerForm::toggleMemoryDisplay); - connect(viewDebuggableViewerAction, &QAction::triggered, this, &DebuggerForm::addDebuggableViewer); - connect(viewBitMappedAction, &QAction::triggered, this, &DebuggerForm::toggleBitMappedDisplay); - connect(viewCharMappedAction, &QAction::triggered, this, &DebuggerForm::toggleCharMappedDisplay); - connect(viewSpritesAction, &QAction::triggered, this, &DebuggerForm::toggleSpritesDisplay); - connect(viewVDPRegsAction, &QAction::triggered, this, &DebuggerForm::toggleVDPRegsDisplay); - connect(viewVDPCommandRegsAction, &QAction::triggered, this, &DebuggerForm::toggleVDPCommandRegsDisplay); - connect(viewVDPStatusRegsAction, &QAction::triggered, this, &DebuggerForm::toggleVDPStatusRegsDisplay); + connect(executeBreakAction, &QAction::triggered, this, &DebuggerForm::executeBreak); connect(executeRunAction, &QAction::triggered, this, &DebuggerForm::executeRun); connect(executeStepAction, &QAction::triggered, this, &DebuggerForm::executeStep); connect(executeStepOverAction, &QAction::triggered, this, &DebuggerForm::executeStepOver); - connect(executeRunToAction, &QAction::triggered, this, &DebuggerForm::executeRunTo); connect(executeStepOutAction, &QAction::triggered, this, &DebuggerForm::executeStepOut); connect(executeStepBackAction, &QAction::triggered, this, &DebuggerForm::executeStepBack); - connect(breakpointToggleAction, &QAction::triggered, this, &DebuggerForm::toggleBreakpoint); - connect(breakpointAddAction, &QAction::triggered, this, &DebuggerForm::addBreakpoint); connect(helpAboutAction, &QAction::triggered, this, &DebuggerForm::showAbout); + connect(addCPUWorkspaceAction, &QAction::triggered, this, &DebuggerForm::addCPUWorkspace); + connect(addVDPRegsWorkspaceAction, &QAction::triggered, this, &DebuggerForm::addVDPRegsWorkspace); + connect(addVDPTilesWorkspaceAction, &QAction::triggered, this, &DebuggerForm::addVDPTilesWorkspace); + connect(addVDPBitmapWorkspaceAction, &QAction::triggered, this, &DebuggerForm::addVDPBitmapWorkspace); + connect(addEmptyWorkspaceAction, &QAction::triggered, this, &DebuggerForm::addEmptyWorkspace); + connect(addFloatingSwitchingWidgetAction, &QAction::triggered, this, &DebuggerForm::addFloatingSwitchingWidget); } void DebuggerForm::createMenus() @@ -382,11 +363,16 @@ void DebuggerForm::createMenus() fileMenu->addAction(fileOpenSessionAction); fileMenu->addAction(fileSaveSessionAction); fileMenu->addAction(fileSaveSessionAsAction); + fileMenu->addSeparator(); + fileMenu->addAction(fileOpenWorkspaceLayoutAction); + //fileMenu->addAction(fileSaveWorkspaceLayoutAction); + fileMenu->addAction(fileSaveWorkspaceLayoutAsAction); + fileMenu->addSeparator(); recentFileSeparator = fileMenu->addSeparator(); - for (auto* rfa : recentFileActions) + for (auto* rfa : recentFileActions) { fileMenu->addAction(rfa); - + } fileMenu->addSeparator(); fileMenu->addAction(fileQuitAction); @@ -407,32 +393,6 @@ void DebuggerForm::createMenus() searchMenu = menuBar()->addMenu(tr("Se&arch")); searchMenu->addAction(searchGotoAction); - // create view menu - viewMenu = menuBar()->addMenu(tr("&View")); - viewMenu->addAction(viewRegistersAction); - viewMenu->addAction(viewFlagsAction); - viewMenu->addAction(viewStackAction); - viewMenu->addAction(viewSlotsAction); - viewMenu->addAction(viewMemoryAction); - viewMenu->addAction(viewBreakpointsAction); - viewVDPDialogsMenu = viewMenu->addMenu("VDP"); - viewMenu->addSeparator(); - viewFloatingWidgetsMenu = viewMenu->addMenu("Floating widgets:"); - viewMenu->addAction(viewDebuggableViewerAction); - connect(viewMenu, &QMenu::aboutToShow, this, &DebuggerForm::updateViewMenu); - - // create VDP dialogs menu - viewVDPDialogsMenu->addAction(viewVDPRegsAction); - viewVDPDialogsMenu->addAction(viewVDPCommandRegsAction); - viewVDPDialogsMenu->addAction(viewVDPStatusRegsAction); - viewVDPDialogsMenu->addAction(viewBitMappedAction); - viewVDPDialogsMenu->addAction(viewCharMappedAction); - viewVDPDialogsMenu->addAction(viewSpritesAction); - connect(viewVDPDialogsMenu, &QMenu::aboutToShow, this, &DebuggerForm::updateVDPViewMenu); - - // create Debuggable Viewers menu (so the user can focus an existing one) - connect(viewFloatingWidgetsMenu, &QMenu::aboutToShow, this, &DebuggerForm::updateViewFloatingWidgetsMenu); - // create execute menu executeMenu = menuBar()->addMenu(tr("&Execute")); executeMenu->addAction(executeBreakAction); @@ -442,11 +402,10 @@ void DebuggerForm::createMenus() executeMenu->addAction(executeStepOverAction); executeMenu->addAction(executeStepOutAction); executeMenu->addAction(executeStepBackAction); - executeMenu->addAction(executeRunToAction); // create breakpoint menu breakpointMenu = menuBar()->addMenu(tr("&Breakpoint")); - breakpointMenu->addAction(breakpointToggleAction); + //breakpointMenu->addAction(breakpointToggleAction); breakpointMenu->addAction(breakpointAddAction); // create help menu @@ -474,7 +433,7 @@ void DebuggerForm::createToolbars() executeToolbar->addAction(executeStepOverAction); executeToolbar->addAction(executeStepOutAction); executeToolbar->addAction(executeStepBackAction); - executeToolbar->addAction(executeRunToAction); + //executeToolbar->addAction(executeRunToAction); } void DebuggerForm::createStatusbar() @@ -483,203 +442,394 @@ void DebuggerForm::createStatusbar() statusBar()->showMessage("No emulation running."); } -void DebuggerForm::createForm() +void DebuggerForm::createWidgetRegistry() +{ + auto& registry = WidgetRegistry::instance(); + + //0: register the disasm viewer widget + registry.addItem(new RegistryItem{ + tr("Code view"), + [] { return widgetFactory(disasmViewer); }}); + + //1: register the memory view widget + registry.addItem(new RegistryItem{ + tr("Main memory"), + [] { return widgetFactory(mainMemoryViewer); }}); + + //2: register the register viewer + registry.addItem(new RegistryItem{ + tr("CPU registers"), + [] { return widgetFactory(cpuRegsViewer); }}); + + //3: register the flags viewer + registry.addItem(new RegistryItem{ + tr("Flags"), + [] { return widgetFactory(flagsViewer); }}); + + //4: register the stack viewer + registry.addItem(new RegistryItem{ + tr("Stack"), + [] { return widgetFactory(stackViewer); }}); + + //5: register the slot viewer + registry.addItem(new RegistryItem{ + tr("Memory layout"), + [] { return widgetFactory(slotViewer); }}); + + //6: register the breakpoints viewer + registry.addItem(new RegistryItem{ + tr("Debug list"), + [] { return widgetFactory(breakpointViewer); }}); + + //7: register the debuggable viewer + registry.addItem(new RegistryItem{ + tr("Debuggable hex view"), + [] { return widgetFactory(debuggableViewer); }}); + //registry.setDefault(item); + + //8: register the VDP Status Registers + registry.addItem(new RegistryItem{ + tr("VDP status registers"), + [] { return widgetFactory(vdpStatusRegViewer); }}); + + //9: register the VDP command registers view + registry.addItem(new RegistryItem{ + tr("VDP command registers "), + [] { return widgetFactory(vdpCommandRegViewer); }}); + + //10: register the Bitmapped VRAM View + registry.addItem(new RegistryItem{ + tr("VRAM as bitmap"), + [] { return widgetFactory(bitMapViewer); }}); + + //11: register the Tile VRAM View + registry.addItem(new RegistryItem{ + tr("VRAM as tiles"), + [] { return widgetFactory(tileViewer); }}); + + //12: register the Sprites View + registry.addItem(new RegistryItem{ + tr("Sprites View"), + [] { return widgetFactory(spriteViewer); }}); + + //13: register the general VDP registers + registry.addItem(new RegistryItem{ + tr("VDP registers"), + [] { return widgetFactory(vdpRegisters); }}); + + //14: register the quick guide manuel + auto* item = new RegistryItem{ + tr("Quick Guide"), + [] { return widgetFactory(quickguide); }}; + registry.addItem(item); + registry.setDefault(item); + + //15: register the palette editor + registry.addItem(new RegistryItem{ + tr("Palette editor"), + [] { return widgetFactory(paletteViewer); }}); +} + +BlendSplitter* DebuggerForm::createWorkspaceCPU() { - updateWindowTitle(); + auto& registry = WidgetRegistry::instance(); - mainArea = std::make_unique(); - dockMan.addDockArea(mainArea.get()); - setCentralWidget(mainArea.get()); - - // Create main widgets and append them to the list first - auto* dw = new DockableWidget(dockMan); - - // create the disasm viewer widget - disasmView = new DisasmViewer(); - dw->setWidget(disasmView); - dw->setTitle(tr("Code view")); - dw->setId("CODEVIEW"); - dw->setFloating(false); - dw->setDestroyable(false); - dw->setMovable(false); - dw->setClosable(false); - connect(dw, &DockableWidget::visibilityChanged, this, &DebuggerForm::dockWidgetVisibilityChanged); - - // create the memory view widget - mainMemoryView = new MainMemoryViewer(); - dw = new DockableWidget(dockMan); - dw->setWidget(mainMemoryView); - dw->setTitle(tr("Main memory")); - dw->setId("MEMORY"); - dw->setFloating(false); - dw->setDestroyable(false); - dw->setMovable(true); - dw->setClosable(true); - connect(dw, &DockableWidget::visibilityChanged, this, &DebuggerForm::dockWidgetVisibilityChanged); - - // create register viewer - regsView = new CPURegsViewer(); - dw = new DockableWidget(dockMan); - dw->setWidget(regsView); - dw->setTitle(tr("CPU registers")); - dw->setId("REGISTERS"); - dw->setFloating(false); - dw->setDestroyable(false); - dw->setMovable(true); - dw->setClosable(true); - connect(dw, &DockableWidget::visibilityChanged, this, &DebuggerForm::dockWidgetVisibilityChanged); - - // create flags viewer - flagsView = new FlagsViewer(); - dw = new DockableWidget(dockMan); - dw->setWidget(flagsView); - dw->setTitle(tr("Flags")); - dw->setId("FLAGS"); - dw->setFloating(false); - dw->setDestroyable(false); - dw->setMovable(true); - dw->setClosable(true); - connect(dw, &DockableWidget::visibilityChanged, this, &DebuggerForm::dockWidgetVisibilityChanged); - - // create stack viewer - stackView = new StackViewer(); - dw = new DockableWidget(dockMan); - dw->setWidget(stackView); - dw->setTitle(tr("Stack")); - dw->setId("STACK"); - dw->setFloating(false); - dw->setDestroyable(false); - dw->setMovable(true); - dw->setClosable(true); - connect(dw, &DockableWidget::visibilityChanged, this, &DebuggerForm::dockWidgetVisibilityChanged); - - // create slot viewer - slotView = new SlotViewer(); - dw = new DockableWidget(dockMan); - dw->setWidget(slotView); - dw->setTitle(tr("Memory layout")); - dw->setId("SLOTS"); - dw->setFloating(false); - dw->setDestroyable(false); - dw->setMovable(true); - dw->setClosable(true); - connect(dw, &DockableWidget::visibilityChanged, this, &DebuggerForm::dockWidgetVisibilityChanged); - - // create breakpoints viewer - bpView = new BreakpointViewer(session, this); - dw = new DockableWidget(dockMan); - dw->setWidget(bpView); - dw->setTitle(tr("Debug list")); - dw->setId("DEBUG"); - dw->setFloating(false); - dw->setDestroyable(false); - dw->setMovable(true); - dw->setClosable(true); - connect(dw, &DockableWidget::visibilityChanged, this, &DebuggerForm::dockWidgetVisibilityChanged); - - // restore layout - restoreGeometry(Settings::get().value("Layout/WindowGeometry", saveGeometry()).toByteArray()); - - QStringList list = Settings::get().value("Layout/WidgetLayout").toStringList(); - // defaults needed? - if (list.empty() || !list.at(0).startsWith("CODEVIEW ")) { - list.clear(); - list.append("CODEVIEW D V R 0 -1 -1"); - list.append("REGISTERS D V R 0 -1 -1"); - list.append("FLAGS D V R 0 -1 -1"); - int regW = dockMan.findDockableWidget("REGISTERS")->sizeHint().width(); - int regH = dockMan.findDockableWidget("REGISTERS")->sizeHint().height(); - list.append(QString("SLOTS D V R 0 -1 %1").arg(regH)); - int codeW = dockMan.findDockableWidget("CODEVIEW")->sizeHint().width(); - int codeH = dockMan.findDockableWidget("CODEVIEW")->sizeHint().height(); - int flagW = dockMan.findDockableWidget("FLAGS")->sizeHint().width(); - int slotW = dockMan.findDockableWidget("SLOTS")->sizeHint().width(); - list.append(QString("STACK D V R 0 -1 %1").arg(codeH)); - list.append(QString("MEMORY D V B %1 %2 %3").arg(codeW) - .arg(regW + flagW + slotW) - .arg(codeH - regH)); - int stackW = dockMan.findDockableWidget("STACK")->sizeHint().width(); - list.append(QString("DEBUG D V B %1 %2 -1").arg(codeW) - .arg(regW + flagW + slotW + stackW)); - } + auto* split = new BlendSplitter(Qt::Horizontal); + split->addWidget(registry.item(2)); //2: the register viewer + split->addWidget(registry.item(3)); //3: the flags viewer + split->addWidget(registry.item(5)); //5: the slot viewer - // add widgets - for (int i = 0; i < list.size(); ++i) { - QStringList s = list.at(i).split(" ", Qt::SplitBehaviorFlags::SkipEmptyParts); - // get widget - if ((dw = dockMan.findDockableWidget(s.at(0)))) { - if (s.at(1) == "D") { - // dock widget - DockableWidgetLayout::DockSide side; - if (s.at(3) == "T") { - side = DockableWidgetLayout::TOP; - } else if (s.at(3) == "L") { - side = DockableWidgetLayout::LEFT; - } else if (s.at(3) == "R") { - side = DockableWidgetLayout::RIGHT; - } else { - side = DockableWidgetLayout::BOTTOM; - } - dockMan.insertWidget(dw, 0, side, s.at(4).toInt(), - s.at(5).toInt(), s.at(6).toInt()); - if (s.at(2) == "H") dw->hide(); - } else if (s.at(1) == "F") { - // float widget - dw->setFloating(true, s.at(2) == "V"); - dw->resize(s.at(5).toInt(), s.at(6).toInt()); - dw->move (s.at(3).toInt(), s.at(4).toInt()); - } - } - } + auto* split2 = new BlendSplitter(Qt::Vertical); + split2->addSplitter(split); + split2->addWidget(registry.item(1)); //1: the memory view widget + split2->addWidget(registry.item(6)); //6: the breakpoints viewer + + auto* split3 = new BlendSplitter(Qt::Horizontal); + split3->addWidget(registry.item(0)); //0: the disasm viewer + split3->addSplitter(split2); + split3->addWidget(registry.item(4)); //4: the stack viewer - // disable all widgets - connectionClosed(); - - // Disasm viewer - connect(disasmView, &DisasmViewer::breakpointToggled, this, &DebuggerForm::toggleBreakpointAddress); - connect(this, &DebuggerForm::symbolsChanged, disasmView, &DisasmViewer::refresh); - connect(this, &DebuggerForm::settingsChanged, disasmView, &DisasmViewer::updateLayout); - - // Main memory viewer - connect(this, &DebuggerForm::connected, mainMemoryView, &MainMemoryViewer::refresh); - connect(this, &DebuggerForm::breakStateEntered, mainMemoryView, &MainMemoryViewer::refresh); - - // Slot viewer - connect(this, &DebuggerForm::connected, slotView, &SlotViewer::refresh); - connect(this, &DebuggerForm::breakStateEntered, slotView, &SlotViewer::refresh); - // Received status update back from widget after breakStateEntered/connected - connect(slotView, &SlotViewer::slotsUpdated, this, &DebuggerForm::onSlotsUpdated); - - // Breakpoint viewer - connect(this, &DebuggerForm::breakpointsUpdated, bpView, &BreakpointViewer::sync); - connect(this, &DebuggerForm::runStateEntered, bpView, &BreakpointViewer::setRunState); - connect(this, &DebuggerForm::breakStateEntered, bpView, &BreakpointViewer::setBreakState); - connect(bpView, &BreakpointViewer::contentsUpdated, this, &DebuggerForm::reloadBreakpoints); - - // CPU regs viewer - // Hook up the register viewer with the main memory viewer - connect(regsView, &CPURegsViewer::registerChanged, mainMemoryView, &MainMemoryViewer::registerChanged); - connect(regsView, &CPURegsViewer::flagsChanged, flagsView, &FlagsViewer::setFlags); - connect(regsView, &CPURegsViewer::spChanged, stackView, &StackViewer::setStackPointer); - // Received status update back from widgets after update - connect(regsView, &CPURegsViewer::pcChanged, this, &DebuggerForm::onPCChanged); - - connect(&comm, &CommClient::connectionReady, this, &DebuggerForm::initConnection); - connect(&comm, &CommClient::updateParsed, this, &DebuggerForm::handleUpdate); - connect(&comm, &CommClient::connectionTerminated, this, &DebuggerForm::connectionClosed); - - // init main memory - session.breakpoints().setMemoryLayout(&memLayout); - disasmView->setMemory(mainMemory); - disasmView->setBreakpoints(&session.breakpoints()); - disasmView->setMemoryLayout(&memLayout); - disasmView->setSymbolTable(&session.symbolTable()); - mainMemoryView->setRegsView(regsView); - mainMemoryView->setSymbolTable(&session.symbolTable()); - mainMemoryView->setDebuggable("memory", 0x10000); - stackView->setData(mainMemory, 0x10000); - slotView->setMemoryLayout(&memLayout); - bpView->setBreakpoints(&session.breakpoints()); + return split3; +} + +BlendSplitter* DebuggerForm::createWorkspaceVDPRegs() +{ + auto& registry = WidgetRegistry::instance(); + + auto* split2 = new BlendSplitter(Qt::Vertical); + split2->addWidget(registry.item(8)); //8: the VDP Status Registers + split2->addWidget(registry.item(9)); //9: the VDP command registers view + + auto* split3 = new BlendSplitter(Qt::Horizontal); + split3->addWidget(registry.item(13)); //13: the general VDP registers + split3->addSplitter(split2); + + return split3; +} + +BlendSplitter *DebuggerForm::createWorkspaceVDPTiles() +{ + auto& registry = WidgetRegistry::instance(); + auto* split3 = new BlendSplitter(Qt::Horizontal); + split3->addWidget(registry.item(11)); //11: the Tile VRAM View + split3->addWidget(registry.item(12)); //12: the Sprites View + return split3; +} + +BlendSplitter *DebuggerForm::createWorkspaceVDPBitmap() +{ + auto& registry = WidgetRegistry::instance(); + auto* split3 = new BlendSplitter(Qt::Horizontal); + split3->addWidget(registry.item(10)); //10: the Bitmapped VRAM View + split3->addWidget(registry.item(12)); //12: the Sprites View + return split3; +} + +void DebuggerForm::tabCloseRequest(int index) +{ + if ((index < 0) || (index >= workspaces->count())) { + return; + } + if (workspaces->count() > 1) { + QWidget* splitter = workspaces->widget(index); + workspaces->removeTab(index); + delete splitter; + } +} + +void DebuggerForm::addInfoWorkspace() +{ + auto* split = new BlendSplitter(Qt::Horizontal); + split->addWidget(WidgetRegistry::instance().item(14)); //14: the quick guide manuel + workspaces->addTab(split, "Welcome new user"); +} + +void DebuggerForm::addCPUWorkspace() +{ + workspaces->addTab(createWorkspaceCPU(), "CPU"); +} + +void DebuggerForm::addVDPRegsWorkspace() +{ + workspaces->addTab(createWorkspaceVDPRegs(), "VDP Registers"); +} + +void DebuggerForm::addVDPTilesWorkspace() +{ + workspaces->addTab(createWorkspaceVDPTiles(), "VDP tiles"); +} + +void DebuggerForm::addVDPBitmapWorkspace(){ + workspaces->addTab(createWorkspaceVDPBitmap(), "VDP bitmap"); +} + +void DebuggerForm::addEmptyWorkspace() +{ + auto* split = new BlendSplitter(Qt::Horizontal); + split->addWidget(); + workspaces->addTab(split, "custom"); +} + +void DebuggerForm::addFloatingSwitchingWidget() +{ + auto& dispatcher = SignalDispatcher::instance(); + auto* wdg = new SwitchingWidget(); + connect(&dispatcher, &SignalDispatcher::enableWidget, wdg, &SwitchingWidget::setEnableWidget); + wdg->setEnableWidget(dispatcher.getEnableWidget()); + wdg->show(); +} + +void DebuggerForm::createForm() +{ + updateWindowTitle(); + + createWidgetRegistry(); + + workspaces = new QTabWidget(); + tabRenamer = new TabRenamerHelper{workspaces}; + + workspaces->setMinimumHeight(500); + workspaces->setTabsClosable(true); + workspaces->setMovable(true); + QMenu* workspacemenu = new QMenu(); + QMenu* workspacesubmenu = new QMenu("Predefined layouts"); + workspacesubmenu->addAction(addCPUWorkspaceAction); + workspacesubmenu->addAction(addVDPRegsWorkspaceAction); + workspacesubmenu->addAction(addVDPTilesWorkspaceAction); + workspacesubmenu->addAction(addVDPBitmapWorkspaceAction); + workspacemenu->addMenu(workspacesubmenu); + workspacemenu->addSeparator(); + workspacemenu->addAction(addEmptyWorkspaceAction); + workspacemenu->addSeparator(); + workspacemenu->addAction(addFloatingSwitchingWidgetAction); + + QIcon icon = QIcon::fromTheme(QLatin1String("window-new")); + QToolButton* btn = new QToolButton(); + btn->setIcon(icon); + btn->setMenu(workspacemenu); + btn->setPopupMode(QToolButton::InstantPopup); + workspaces->setCornerWidget(btn, Qt::TopRightCorner); + connect(workspaces, &QTabWidget::tabCloseRequested, this, &DebuggerForm::tabCloseRequest); + QWidget* window = new QWidget; + QVBoxLayout* layout = new QVBoxLayout; + layout->addWidget(workspaces); + window->setLayout(layout); + + Settings& s = Settings::get(); + + int workspacetype = s.value("creatingWorkspaceType", 0).toInt(); + switch (workspacetype) { + case 0: + addInfoWorkspace(); + [[fallthrough]]; + case 1: + addDefaultWorkspaces(); + break; + default: + if (s.value("creatingWorkspaceFile", "").toString().isEmpty() + || !loadWorkspaces(s.value("creatingWorkspaceFile", "").toString())) { + addDefaultWorkspaces(); + } + break; + } + + setCentralWidget(window); + + // Have the SignalDispatcher refresh it data for the widgets in the blendsplitter + auto& dispatcher = SignalDispatcher::instance(); + connect(this, &DebuggerForm::connected, &dispatcher, &SignalDispatcher::refresh); + connect(this, &DebuggerForm::breakStateEntered, &dispatcher, &SignalDispatcher::refresh); + //and have it propagate the signals + connect(this, &DebuggerForm::connected, &dispatcher, &SignalDispatcher::connected); + connect(this, &DebuggerForm::breakStateEntered, &dispatcher, &SignalDispatcher::breakStateEntered); + connect(this, &DebuggerForm::debuggablesChanged, &dispatcher, &SignalDispatcher::debuggablesChanged); + connect(this, &DebuggerForm::breakpointsUpdated, &dispatcher, &SignalDispatcher::breakpointsUpdated); + + // disable all widgets + connectionClosed(); + + connect(&comm, &CommClient::connectionReady, this, &DebuggerForm::initConnection); + connect(&comm, &CommClient::updateParsed, this, &DebuggerForm::handleUpdate); + connect(&comm, &CommClient::connectionTerminated, this, &DebuggerForm::connectionClosed); + + session->breakpoints().setMemoryLayout(dispatcher.getMemLayout()); +} + +void DebuggerForm::autoConnectToOpenMSX() +{ + //TODO actually find out if there is only one connection instead of going through the dialog... + if (Settings::get().value("autoconnect").toBool()) { + systemConnect(); + }; + +} + +void DebuggerForm::addDefaultWorkspaces() +{ + addCPUWorkspace(); + addVDPRegsWorkspace(); + addVDPTilesWorkspace(); + addVDPBitmapWorkspace(); +} + +QWidget* DebuggerForm::widgetFactory(factoryclasses fctWidget) +{ + auto& dispatcher = SignalDispatcher::instance(); + + switch (fctWidget) { + case disasmViewer: { + DisasmViewer* dv = new DisasmViewer(); + connect(dv, &DisasmViewer::breakpointToggled, &dispatcher, &SignalDispatcher::breakpointToggled); + connect(&dispatcher, &SignalDispatcher::connected, dv, &DisasmViewer::refresh); + connect(&dispatcher, &SignalDispatcher::breakStateEntered, dv, &DisasmViewer::refresh); + connect(&dispatcher, &SignalDispatcher::symbolsChanged, dv, &DisasmViewer::refresh); + connect(&dispatcher, &SignalDispatcher::settingsChanged, dv, &DisasmViewer::updateLayout); + connect(&dispatcher, &SignalDispatcher::setCursorAddress, dv, &DisasmViewer::setCursorAddress); + connect(&dispatcher, &SignalDispatcher::setProgramCounter, dv, &DisasmViewer::setProgramCounter); + connect(&dispatcher, &SignalDispatcher::breakpointsUpdated, dv, qOverload<>(&DisasmViewer::update)); + dv->setMemory(dispatcher.getMainMemory()); + dv->setBreakpoints(&DebugSession::getDebugSession()->breakpoints()); + dv->setMemoryLayout(dispatcher.getMemLayout()); + dv->setSymbolTable(&DebugSession::getDebugSession()->symbolTable()); + return dv; + } + case mainMemoryViewer: { + MainMemoryViewer* mmv = new MainMemoryViewer(); + // Main memory viewer + connect(&dispatcher, &SignalDispatcher::connected, mmv, &MainMemoryViewer::refresh); + connect(&dispatcher, &SignalDispatcher::breakStateEntered, mmv, &MainMemoryViewer::refresh); + connect(&dispatcher, &SignalDispatcher::registerChanged, mmv, &MainMemoryViewer::registerChanged); + //mainMemoryView->setRegsView(regsView); + mmv->setSymbolTable(&DebugSession::getDebugSession()->symbolTable()); + mmv->setDebuggable("memory", 0x10000); + return mmv; + } + case cpuRegsViewer: { + CPURegsViewer* crv = new CPURegsViewer(); + //copy current registers to new widget + for (int id = 0; id < 15; ++id) { // CpuRegs::REG_AF up to CpuRegs::REG_IFF + crv->setRegister(id, dispatcher.readRegister(id)); + } + connect(&dispatcher, &SignalDispatcher::registersUpdate, crv, &CPURegsViewer::setData); + return crv; + } + case flagsViewer: { + FlagsViewer* fv = new FlagsViewer(); + fv->setFlags(dispatcher.readRegister(CpuRegs::REG_AF) & 0xFF); + connect(&dispatcher, &SignalDispatcher::flagsChanged, fv, &FlagsViewer::setFlags); + return fv; + } + case stackViewer: { + StackViewer* sv = new StackViewer(); + sv->setData(dispatcher.getMainMemory(), 0x10000); + sv->setStackPointer(dispatcher.readRegister(CpuRegs::REG_SP)); + connect(&dispatcher, &SignalDispatcher::spChanged, sv, &StackViewer::setStackPointer); + return sv; + } + case slotViewer: { + SlotViewer* sv = new SlotViewer(); + connect(&dispatcher, &SignalDispatcher::connected, sv, &SlotViewer::refresh); + connect(&dispatcher, &SignalDispatcher::breakStateEntered, sv, &SlotViewer::refresh); + connect(&dispatcher, &SignalDispatcher::slotsUpdated, sv, &SlotViewer::refresh); + sv->setMemoryLayout(dispatcher.getMemLayout()); + return sv; + } + case breakpointViewer: + return new BreakpointViewer(*DebugSession::getDebugSession()); + case debuggableViewer: { + DebuggableViewer* dv = new DebuggableViewer(); + connect(&dispatcher, &SignalDispatcher::breakStateEntered, dv, &DebuggableViewer::refresh); + connect(&dispatcher, &SignalDispatcher::debuggablesChanged, dv, &DebuggableViewer::setDebuggables); + dv->setDebuggables(debuggables); + if (!debuggables.isEmpty()) { + dv->debuggableSelected(0); + dv->refresh(); + } + return dv; + } + case vdpStatusRegViewer: + return new VDPStatusRegViewer(); + case vdpCommandRegViewer: + return new VDPCommandRegViewer(); + case bitMapViewer: + return new BitMapViewer(); + case tileViewer: + return new TileViewer(); + case spriteViewer: + return new SpriteViewer(); + case vdpRegisters: + return new VDPRegViewer(); + case quickguide: + return new QuickGuide(); + case paletteViewer: { + PaletteView* pv = new PaletteView(); + connect(&dispatcher, &SignalDispatcher::connected, pv, &PaletteView::refresh); + connect(&dispatcher, &SignalDispatcher::breakStateEntered, pv, &PaletteView::refresh); + return pv; + } + default: + return new QLabel("Not yet implemented in widgetFactory!"); + } } void DebuggerForm::closeEvent(QCloseEvent* e) @@ -687,7 +837,7 @@ void DebuggerForm::closeEvent(QCloseEvent* e) // handle unsaved session fileNewSession(); // cancel if session is still modified - if (session.isModified()) { + if (session->isModified()) { e->ignore(); return; } @@ -695,27 +845,6 @@ void DebuggerForm::closeEvent(QCloseEvent* e) // store layout Settings::get().setValue("Layout/WindowGeometry", saveGeometry()); - QStringList layoutList; - // fill layout list with docked widgets - dockMan.getConfig(0, layoutList); - // append floating widgets - for (auto* widget : dockMan.managedWidgets()) { - if (widget->isFloating()) { - QString s("%1 F %2 %3 %4 %5 %6"); - s = s.arg(widget->id()); - if (widget->isHidden()) { - s = s.arg("H"); - } else { - s = s.arg("V"); - } - s = s.arg(widget->x()).arg(widget->y()) - .arg(widget->width()).arg(widget->height()); - layoutList.append(s); - } - widget->hide(); - } - Settings::get().setValue("Layout/WidgetLayout", layoutList); - QMainWindow::closeEvent(e); } @@ -755,12 +884,12 @@ void DebuggerForm::removeRecentFile(const QString& file) void DebuggerForm::updateWindowTitle() { QString title = "openMSX debugger [%1%2]"; - if (session.existsAsFile()) { - title = title.arg(session.filename()); + if (session->existsAsFile()) { + title = title.arg(session->filename()); } else { title = title.arg("unnamed session"); } - if (session.isModified()) { + if (session->isModified()) { title = title.arg('*'); } else { title = title.arg(""); @@ -861,27 +990,22 @@ void DebuggerForm::connectionClosed() executeStepOverAction->setEnabled(false); executeStepOutAction->setEnabled(false); executeStepBackAction->setEnabled(false); - executeRunToAction->setEnabled(false); systemDisconnectAction->setEnabled(false); systemConnectAction->setEnabled(true); - breakpointToggleAction->setEnabled(false); breakpointAddAction->setEnabled(false); - for (auto* w : dockMan.managedWidgets()) { - w->widget()->setEnabled(false); - } + SignalDispatcher::instance().setEnableWidget(false); } void DebuggerForm::finalizeConnection(bool halted) { systemPauseAction->setEnabled(true); systemRebootAction->setEnabled(true); - breakpointToggleAction->setEnabled(true); breakpointAddAction->setEnabled(true); // merge breakpoints on connect mergeBreakpoints = true; if (halted) { - breakOccured(); + breakOccurred(); } else { setRunMode(); updateData(); @@ -889,9 +1013,7 @@ void DebuggerForm::finalizeConnection(bool halted) emit connected(); - for (auto* w : dockMan.managedWidgets()) { - w->widget()->setEnabled(true); - } + SignalDispatcher::instance().setEnableWidget(true); } void DebuggerForm::handleUpdate(const QString& type, const QString& name, @@ -901,7 +1023,7 @@ void DebuggerForm::handleUpdate(const QString& type, const QString& name, if (name == "cpu") { // running state by default. if (message == "suspended") { - breakOccured(); + breakOccurred(); } } else if (name == "paused") { pauseStatusChanged(message == "true"); @@ -914,7 +1036,7 @@ void DebuggerForm::pauseStatusChanged(bool isPaused) systemPauseAction->setChecked(isPaused); } -void DebuggerForm::breakOccured() +void DebuggerForm::breakOccurred() { emit breakStateEntered(); updateData(); @@ -940,7 +1062,7 @@ void DebuggerForm::setRunMode() void DebuggerForm::fileNewSession() { - if (session.isModified()) { + if (session->isModified()) { // save current session? int choice = QMessageBox::warning(this, tr("Unsaved session"), tr("The current session has unsaved data.\n" @@ -952,10 +1074,10 @@ void DebuggerForm::fileNewSession() } else if (choice == QMessageBox::Save) { fileSaveSession(); // skip new if session is still modified (save was cancelled) - if (session.isModified()) return; + if (session->isModified()) return; } } - session.clear(); + session->clear(); updateWindowTitle(); } @@ -972,13 +1094,13 @@ void DebuggerForm::fileOpenSession() void DebuggerForm::openSession(const QString& file) { fileNewSession(); - session.open(file); + session->open(file); if (systemDisconnectAction->isEnabled()) { // active connection, merge loaded breakpoints reloadBreakpoints(true); } // update recent - if (session.existsAsFile()) { + if (session->existsAsFile()) { addRecentFile(file); } else { removeRecentFile(file); @@ -989,8 +1111,8 @@ void DebuggerForm::openSession(const QString& file) void DebuggerForm::fileSaveSession() { - if (session.existsAsFile()) { - session.save(); + if (session->existsAsFile()) { + session->save(); } else { fileSaveSessionAs(); } @@ -1006,10 +1128,10 @@ void DebuggerForm::fileSaveSessionAs() d.setAcceptMode(QFileDialog::AcceptSave); d.setFileMode(QFileDialog::AnyFile); if (d.exec()) { - session.saveAs(d.selectedFiles().at(0)); + session->saveAs(d.selectedFiles().at(0)); // update recent - if (session.existsAsFile()) { - addRecentFile(session.filename()); + if (session->existsAsFile()) { + addRecentFile(session->filename()); } } updateWindowTitle(); @@ -1019,7 +1141,45 @@ void DebuggerForm::fileRecentOpen() { if (auto* action = qobject_cast(sender())) { openSession(action->data().toString()); + } +} + +void DebuggerForm::fileOpenWorkspace() +{ + QString fileName = QFileDialog::getOpenFileName( + this, tr("Open workspace layout"), + QDir::currentPath(), tr("Debug Workspace Layout Files (*.omdl)")); + + if (!fileName.isEmpty()){ + loadWorkspaces(fileName); + } +} + +void DebuggerForm::fileSaveWorkspace() +{ + +} + +QString DebuggerForm::fileSaveWorkspaceAs() +{ + QFileDialog d(this, tr("Save workspace layout")); + d.setNameFilter(tr("Debug Workspace Layout Files (*.omdl)")); + d.setDefaultSuffix("omdl"); + d.setDirectory(QDir::currentPath()); + d.setAcceptMode(QFileDialog::AcceptSave); + d.setFileMode(QFileDialog::AnyFile); + QString filename; + if (d.exec()) { + //session->saveAs(d.selectedFiles().at(0)); + filename = d.selectedFiles().at(0); + saveWorkspacesAs(filename); + // update recent + //if (session->existsAsFile()) { + // addRecentFile(session->filename()); + //} } + updateWindowTitle(); + return filename; } void DebuggerForm::systemConnect() @@ -1053,15 +1213,14 @@ void DebuggerForm::systemReboot() void DebuggerForm::systemSymbolManager() { - symManager = new SymbolManager(session.symbolTable(), this); - - connect(symManager, &SymbolManager::symbolTableChanged, - &session, &DebugSession::sessionModified); - connect(symManager, &SymbolManager::symbolTableChanged, - bpView, &BreakpointViewer::onSymbolTableChanged); + SymbolManager symManager(session->symbolTable(), this); + connect(&symManager, &SymbolManager::symbolTableChanged, + session, &DebugSession::sessionModified); + //connect(&symManager, &SymbolManager::symbolTableChanged, + // bpView, &BreakpointViewer::onSymbolTableChanged); connect(this, &DebuggerForm::symbolFilesChanged, - symManager, &SymbolManager::refresh); - symManager->exec(); + &symManager, &SymbolManager::refresh); + symManager.exec(); emit symbolsChanged(); updateWindowTitle(); @@ -1076,10 +1235,14 @@ void DebuggerForm::systemPreferences() void DebuggerForm::searchGoto() { - GotoDialog gtd(memLayout, &session, this); + auto& dispatcher = SignalDispatcher::instance(); + GotoDialog gtd(*dispatcher.getMemLayout(), session, this); if (gtd.exec()) { if (auto addr = gtd.address()) { - disasmView->setCursorAddress(*addr, 0, DisasmViewer::MiddleAlways); + if (addr.has_value()) { + //disasmView->setCursorAddress(addr, 0, DisasmViewer::MiddleAlways); + dispatcher.setCursorAddress(addr.value(), 0, DisasmViewer::MiddleAlways); + } } } } @@ -1109,13 +1272,6 @@ void DebuggerForm::executeStepOver() setRunMode(); } -void DebuggerForm::executeRunTo() -{ - comm.sendCommand(new SimpleCommand( - "run_to " + QString::number(disasmView->cursorAddress()))); - setRunMode(); -} - void DebuggerForm::executeStepOut() { comm.sendCommand(new SimpleCommand("step_out")); @@ -1130,17 +1286,12 @@ void DebuggerForm::executeStepBack() setRunMode(); } -void DebuggerForm::toggleBreakpoint() -{ - // address unspecified, use cursor address - toggleBreakpointAddress(disasmView->cursorAddress()); -} - -void DebuggerForm::toggleBreakpointAddress(uint16_t addr) +//void DebuggerForm::toggleBreakpointAddress(uint16_t addr) +void DebuggerForm::toggleBreakpoint(uint16_t addr) { QString cmd; QString id; - if (session.breakpoints().isBreakpoint(addr, &id)) { + if (session->breakpoints().isBreakpoint(addr, &id)) { cmd = Breakpoints::createRemoveCommand(id); } else { // get slot @@ -1154,10 +1305,10 @@ void DebuggerForm::toggleBreakpointAddress(uint16_t addr) reloadBreakpoints(); } -void DebuggerForm::addBreakpoint() +void DebuggerForm::addBreakpoint(uint16_t cursorAddress) { - BreakpointDialog bpd(memLayout, &session, this); - uint16_t addr = disasmView->cursorAddress(); + BreakpointDialog bpd(*SignalDispatcher::instance().getMemLayout(), session, this); + uint16_t addr = cursorAddress; auto [ps, ss, seg] = addressSlot(addr); bpd.setData(Breakpoint::BREAKPOINT, AddressRange{addr}, Slot{ps, ss}, seg); @@ -1179,255 +1330,6 @@ void DebuggerForm::showAbout() this, "openMSX Debugger", QString(Version::full().c_str())); } -void DebuggerForm::toggleBreakpointsDisplay() -{ - toggleView(qobject_cast(bpView->parentWidget())); -} - -void DebuggerForm::toggleRegisterDisplay() -{ - toggleView(qobject_cast(regsView->parentWidget())); -} - -void DebuggerForm::toggleFlagsDisplay() -{ - toggleView(qobject_cast(flagsView->parentWidget())); -} - -void DebuggerForm::toggleStackDisplay() -{ - toggleView(qobject_cast(stackView->parentWidget())); -} - -void DebuggerForm::toggleSlotsDisplay() -{ - toggleView(qobject_cast(slotView->parentWidget())); -} - -void DebuggerForm::toggleBitMappedDisplay() -{ - //toggleView(qobject_cast(slotView->parentWidget())); - // not sure if this a good idea for a docable widget - - // create new debuggable viewer window - auto* viewer = new BitMapViewer(); - auto* dw = new DockableWidget(dockMan); - dw->setWidget(viewer); - dw->setTitle(tr("Bitmapped VRAM View")); - dw->setId("BITMAPVRAMVIEW"); - dw->setFloating(true); - dw->setDestroyable(true); - dw->setMovable(true); - dw->setClosable(true); - - /* - connect(dw, &DockableWidget::visibilityChanged, - this, &DebuggerForm::dockWidgetVisibilityChanged); - connect(this, &DebuggerForm::debuggablesChanged, - viewer, &BitMapViewer::setDebuggables); - */ - - // TODO: refresh should be handled by VDPDataStore... - connect(this, &DebuggerForm::connected, viewer, &BitMapViewer::refresh); - connect(this, &DebuggerForm::breakStateEntered, viewer, &BitMapViewer::refresh); - - /* - viewer->setDebuggables(debuggables); - viewer->setEnabled(disasmView->isEnabled()); - */ -} - -void DebuggerForm::toggleCharMappedDisplay() -{ - //toggleView(qobject_cast(slotView->parentWidget())); - // not sure if this a good idea for a docable widget - - // create new debuggable viewer window - auto* viewer = new TileViewer(); - // viewer->adjustSize(); - auto* dw = new DockableWidget(dockMan); - dw->setWidget(viewer); - dw->setTitle(tr("Tile VRAM View")); - dw->setId("CHARMAPVRAMVIEW"); - dw->setFloating(true); - dw->setDestroyable(true); - dw->setMovable(true); - dw->setClosable(true); - // dw->adjustSize(); - - // TODO: refresh should be handled by VDPDataStore... - connect(this, &DebuggerForm::connected, viewer, &TileViewer::refresh); - connect(this, &DebuggerForm::breakStateEntered, viewer, &TileViewer::refresh); -} - -void DebuggerForm::toggleSpritesDisplay() -{ - //toggleView(qobject_cast(slotView->parentWidget())); - // not sure if this a good idea for a docable widget - - // create new debuggable viewer window - auto* viewer = new SpriteViewer(); - auto* dw = new DockableWidget(dockMan); - dw->setWidget(viewer); - dw->setTitle(tr("Sprites View")); - dw->setId("SPRITESVRAMVIEW"); - dw->setFloating(true); - dw->setDestroyable(true); - dw->setMovable(true); - dw->setClosable(true); - - // TODO: refresh should be handled by VDPDataStore... - connect(this, &DebuggerForm::breakStateEntered, viewer, &SpriteViewer::refresh); -} - -void DebuggerForm::toggleVDPCommandRegsDisplay() -{ - if (VDPCommandRegView == nullptr) { - VDPCommandRegView = new VDPCommandRegViewer(); - auto* dw = new DockableWidget(dockMan); - dw->setWidget(VDPCommandRegView); - dw->setTitle(tr("VDP registers view")); - dw->setId("VDPCommandRegView"); - dw->setFloating(true); - dw->setDestroyable(false); - dw->setMovable(true); - dw->setClosable(true); - connect(this, &DebuggerForm::breakStateEntered, - VDPCommandRegView, &VDPCommandRegViewer::refresh); - } else { - toggleView(qobject_cast(VDPCommandRegView->parentWidget())); - } -} - -void DebuggerForm::toggleVDPRegsDisplay() -{ - if (VDPRegView == nullptr) { - VDPRegView = new VDPRegViewer(); - auto* dw = new DockableWidget(dockMan); - dw->setWidget(VDPRegView); - dw->setTitle(tr("VDP registers view")); - dw->setId("VDPREGVIEW"); - dw->setFloating(true); - dw->setDestroyable(false); - dw->setMovable(true); - dw->setClosable(true); - connect(this, &DebuggerForm::breakStateEntered, - VDPRegView, &VDPRegViewer::refresh); - } else { - toggleView(qobject_cast(VDPRegView->parentWidget())); - } -} - -void DebuggerForm::toggleVDPStatusRegsDisplay() -{ - if (VDPStatusRegView == nullptr) { - VDPStatusRegView = new VDPStatusRegViewer(); - auto* dw = new DockableWidget(dockMan); - dw->setWidget(VDPStatusRegView); - dw->setTitle(tr("VDP status registers view")); - dw->setId("VDPSTATUSREGVIEW"); - dw->setFloating(true); - dw->setDestroyable(false); - dw->setMovable(true); - dw->setClosable(true); - connect(this, &DebuggerForm::breakStateEntered, - VDPStatusRegView, &VDPStatusRegViewer::refresh); - } else { - toggleView(qobject_cast(VDPStatusRegView->parentWidget())); - } -} - -void DebuggerForm::toggleMemoryDisplay() -{ - toggleView(qobject_cast(mainMemoryView->parentWidget())); -} - -void DebuggerForm::toggleView(DockableWidget* widget) -{ - if (widget->isHidden()) { - widget->show(); - } else { - widget->hide(); - } - dockMan.visibilityChanged(widget); -} - -void DebuggerForm::addDebuggableViewer() -{ - // create new debuggable viewer window - auto* viewer = new DebuggableViewer(); - auto* dw = new DockableWidget(dockMan); - dw->setWidget(viewer); - dw->setTitle(tr("Debuggable hex view")); - dw->setId("DEBUGVIEW-" + QString::number(++counter)); - dw->setFloating(true); - dw->setDestroyable(true); - dw->setMovable(true); - dw->setClosable(true); - connect(dw, &DockableWidget::visibilityChanged, - this, &DebuggerForm::dockWidgetVisibilityChanged); - connect(this, &DebuggerForm::debuggablesChanged, - viewer, &DebuggableViewer::setDebuggables); - connect(this, &DebuggerForm::breakStateEntered, - viewer, &DebuggableViewer::refresh); - viewer->setDebuggables(debuggables); - viewer->setEnabled(disasmView->isEnabled()); -} - -void DebuggerForm::showFloatingWidget() -{ - QObject * s = sender(); - QString widget_id = s->property("widget_id").toString(); - DockableWidget* w = dockMan.findDockableWidget(widget_id); - w->show(); - w->activateWindow(); - w->raise(); -} - -void DebuggerForm::dockWidgetVisibilityChanged(DockableWidget* w) -{ - dockMan.visibilityChanged(w); - updateViewMenu(); -} - -void DebuggerForm::updateViewMenu() -{ - viewRegistersAction->setChecked(regsView->parentWidget()->isVisible()); - viewFlagsAction->setChecked(flagsView->isVisible()); - viewStackAction->setChecked(stackView->isVisible()); - viewSlotsAction->setChecked(slotView->isVisible()); - viewMemoryAction->setChecked(mainMemoryView->isVisible()); - viewBreakpointsAction->setChecked(bpView->isVisible()); -} - -void DebuggerForm::updateVDPViewMenu() -{ - if (VDPCommandRegView) { - viewVDPCommandRegsAction->setChecked(VDPCommandRegView->isVisible()); - } - if (VDPRegView) { - viewVDPRegsAction->setChecked(VDPRegView->isVisible()); - } - if (VDPStatusRegView) { - viewVDPStatusRegsAction->setChecked(VDPStatusRegView->isVisible()); - } -} - -void DebuggerForm::updateViewFloatingWidgetsMenu() -{ - viewFloatingWidgetsMenu->clear(); - for (auto* w : dockMan.managedWidgets()) { - if (w->isFloating()) { - // Build up the window title - auto* action = new QAction(w->title()); - // Set the widget_id as a property on the menu item action, so we can read it out later again via sender() on the receiving functor - action->setProperty("widget_id", w->id()); - connect(action, &QAction::triggered, this, &DebuggerForm::showFloatingWidget); - viewFloatingWidgetsMenu->addAction(action); - } - } -} - void DebuggerForm::setDebuggables(const QString& list) { debuggables.clear(); @@ -1458,6 +1360,7 @@ void DebuggerForm::setDebuggableSize(const QString& debuggable, int size) // emit update if size of last debuggable was set if (debuggable == debuggables.keys().last()) { emit debuggablesChanged(debuggables); + emit SignalDispatcher::instance().debuggablesChanged(debuggables); } } @@ -1474,29 +1377,90 @@ void DebuggerForm::symbolFileChanged() shown = false; if (choice == QMessageBox::No) return; } - session.symbolTable().reloadFiles(); + session->symbolTable().reloadFiles(); emit symbolFilesChanged(); } DebuggerForm::AddressSlotResult DebuggerForm::addressSlot(int addr) const { + auto& dispatcher = SignalDispatcher::instance(); int p = (addr & 0xC000) >> 14; - uint8_t ps = memLayout.primarySlot[p]; - int8_t ss_ = memLayout.secondarySlot[p]; + uint8_t ps = dispatcher.getMemLayout()->primarySlot[p]; + int8_t ss_ = dispatcher.getMemLayout()->secondarySlot[p]; auto ss = make_positive_optional(ss_); std::optional segment = [&] { // figure out (rom) mapper segment - if (ss && memLayout.mapperSize[ps][*ss] > 0) { - return std::optional(uint8_t(memLayout.mapperSegment[p])); + //if (dispatcher.getMemLayout()->mapperSize[ps][ss == -1 ? 0 : ss] > 0) { + if (ss && dispatcher.getMemLayout()->mapperSize[ps][*ss] > 0) { + return dispatcher.getMemLayout()->mapperSegment[p]; } else { int q = 2 * p + ((addr & 0x2000) >> 13); - int b = memLayout.romBlock[q]; - return make_if(b >= 0, uint8_t(b)); + int b = dispatcher.getMemLayout()->romBlock[q]; + //return make_if(b >= 0, uint8_t(b)); + return (b >= 0) ? b : -1; } }(); return {ps, ss, segment}; } +bool DebuggerForm::saveWorkspacesAs(const QString &newFileName) +{ + // open file for save + QFile file(newFileName); + if (!file.open(QFile::WriteOnly | QFile::Text)) { + QMessageBox::warning(nullptr, tr("Save session ..."), + tr("Cannot write file %1:\n%2.") + .arg(newFileName, file.errorString())); + return false; + } + QJsonObject jsonObj; + QJsonArray spacesArray; + //iterate over workspaces + for (int i = 0; i < workspaces->count(); ++i) { + QJsonObject jsonTab; + jsonTab["name"] = workspaces->tabText(i); + jsonTab["workspace"] = static_cast(workspaces->widget(i))->save2json(); + spacesArray.append(jsonTab); + } + jsonObj["workspaces"] = spacesArray; + QJsonDocument jsonDoc(jsonObj); + file.write(jsonDoc.toJson()); + // file.close(); done automatically when going out of scope + return true; +} + +bool DebuggerForm::loadWorkspaces(const QString &filename) +{ + QFile file(filename); + if (!file.open(QFile::ReadOnly | QFile::Text)) { + QMessageBox::warning(nullptr, tr("Loading workspaces ..."), + tr("Cannot read file %1:\n%2.") + .arg(filename, file.errorString())); + return false; + } + //Now try to parse the json file + QJsonParseError parseError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(file.readAll(), &parseError); + if (parseError.error != QJsonParseError::NoError) { + QMessageBox::warning(nullptr, tr("Open workspaces ..."), + tr("Parse error at %1:%2.").arg(QString::number(parseError.offset), parseError.errorString())); + return false; + } + + // delete all the current tabs before reading the new ones + while (workspaces->count()) { + delete workspaces->widget(0); + } + //now recreate workspaces + QJsonObject obj = jsonDoc.object(); + for (const auto& value : obj["workspaces"].toArray()) { + QJsonObject obj = value.toObject(); + auto* splitter = BlendSplitter::createFromJson(obj["workspace"].toObject()); + workspaces->addTab(splitter, obj["name"].toString()); + } + return true; +} + void DebuggerForm::reloadBreakpoints(bool merge) { auto* command = new Command("debug_list_all_breaks", @@ -1512,16 +1476,16 @@ void DebuggerForm::reloadBreakpoints(bool merge) void DebuggerForm::processBreakpoints(const QString& message) { - session.breakpoints().setBreakpoints(message); - disasmView->update(); - session.sessionModified(); + session->breakpoints().setBreakpoints(message); +// disasmView->update(); + session->sessionModified(); updateWindowTitle(); emit breakpointsUpdated(); } void DebuggerForm::processMerge(const QString& message) { - QString bps = session.breakpoints().mergeBreakpoints(message); + QString bps = session->breakpoints().mergeBreakpoints(message); if (!bps.isEmpty()) { comm.sendCommand(new SimpleCommand(bps)); reloadBreakpoints(false); @@ -1529,23 +1493,3 @@ void DebuggerForm::processMerge(const QString& message) processBreakpoints(message); } } - -void DebuggerForm::onSlotsUpdated(bool slotsChanged) -{ - if (disasmStatus == PC_CHANGED) { - disasmView->setProgramCounter(disasmAddress, slotsChanged); - disasmStatus = RESET; - } else { - disasmStatus = slotsChanged ? SLOTS_CHANGED : SLOTS_CHECKED; - } -} - -void DebuggerForm::onPCChanged(uint16_t address) -{ - if (disasmStatus != RESET) { - disasmView->setProgramCounter(address, disasmStatus == SLOTS_CHANGED); - } else { - disasmStatus = PC_CHANGED; - disasmAddress = address; - } -} diff --git a/src/DebuggerForm.h b/src/DebuggerForm.h index 575a5fb8..d84b605e 100644 --- a/src/DebuggerForm.h +++ b/src/DebuggerForm.h @@ -1,8 +1,6 @@ #ifndef DEBUGGERFORM_H #define DEBUGGERFORM_H -#include "DockableWidgetArea.h" -#include "DockManager.h" #include "DebugSession.h" #include "SymbolManager.h" #include @@ -25,6 +23,9 @@ class VDPStatusRegViewer; class VDPRegViewer; class VDPCommandRegViewer; class BreakpointViewer; +class BlendSplitter; +class QTabWidget; +class TabRenamerHelper; class DebuggerForm : public QMainWindow { @@ -35,17 +36,44 @@ class DebuggerForm : public QMainWindow void showAbout(); void reloadBreakpoints(bool merge = false); void onSlotsUpdated(bool slotsChanged); - void onPCChanged(uint16_t address); + + enum factoryclasses { + disasmViewer, + mainMemoryViewer, + cpuRegsViewer, + flagsViewer, + stackViewer, + slotViewer, + breakpointViewer, + debuggableViewer, + vdpStatusRegViewer, + vdpCommandRegViewer, + bitMapViewer, + tileViewer, + spriteViewer, + vdpRegisters, + quickguide, + paletteViewer + }; + + bool saveWorkspacesAs(const QString& newFileName); + bool loadWorkspaces(const QString& filename); + QString fileSaveWorkspaceAs(); private: void closeEvent(QCloseEvent* e) override; + void createWidgetRegistry(); void createActions(); void createMenus(); void createToolbars(); void createStatusbar(); void createForm(); + void autoConnectToOpenMSX(); + + static QWidget* widgetFactory(factoryclasses fctwidget); + void openSession(const QString& file); void updateRecentFiles(); void addRecentFile(const QString& file); @@ -53,7 +81,7 @@ class DebuggerForm : public QMainWindow void finalizeConnection(bool halted); void pauseStatusChanged(bool isPaused); - void breakOccured(); + void breakOccurred(); void setRunMode(); void updateData(); @@ -69,9 +97,7 @@ class DebuggerForm : public QMainWindow QMenu* fileMenu; QMenu* systemMenu; QMenu* searchMenu; - QMenu* viewMenu; - QMenu* viewVDPDialogsMenu; - QMenu* viewFloatingWidgetsMenu; + QMenu* executeMenu; QMenu* breakpointMenu; QMenu* helpMenu; @@ -83,9 +109,14 @@ class DebuggerForm : public QMainWindow QAction* fileOpenSessionAction; QAction* fileSaveSessionAction; QAction* fileSaveSessionAsAction; + + QAction* fileOpenWorkspaceLayoutAction; + QAction* fileSaveWorkspaceLayoutAction; + QAction* fileSaveWorkspaceLayoutAsAction; + QAction* fileQuitAction; - enum { MaxRecentFiles = 5 }; + static const int MaxRecentFiles = 5; QAction* recentFileActions[MaxRecentFiles]; QAction* recentFileSeparator; @@ -97,61 +128,36 @@ class DebuggerForm : public QMainWindow QAction* systemPreferencesAction; QAction* searchGotoAction; - - QAction* viewRegistersAction; - QAction* viewFlagsAction; - QAction* viewStackAction; - QAction* viewSlotsAction; - QAction* viewMemoryAction; - QAction* viewBreakpointsAction; - QAction* viewDebuggableViewerAction; - - QAction* viewBitMappedAction; - QAction* viewCharMappedAction; - QAction* viewSpritesAction; - QAction* viewVDPStatusRegsAction; - QAction* viewVDPRegsAction; - QAction* viewVDPCommandRegsAction; - QAction* executeBreakAction; QAction* executeRunAction; QAction* executeStepAction; QAction* executeStepOverAction; - QAction* executeRunToAction; QAction* executeStepOutAction; QAction* executeStepBackAction; - QAction* breakpointToggleAction; QAction* breakpointAddAction; QAction* helpAboutAction; - DockManager dockMan; - std::unique_ptr mainArea; - QStringList recentFiles; + QAction* addCPUWorkspaceAction; + QAction* addVDPRegsWorkspaceAction; + QAction* addVDPTilesWorkspaceAction; + QAction* addVDPBitmapWorkspaceAction; + QAction* addEmptyWorkspaceAction; + QAction* addFloatingSwitchingWidgetAction; - DisasmViewer* disasmView; - MainMemoryViewer* mainMemoryView; - CPURegsViewer* regsView; - FlagsViewer* flagsView; - StackViewer* stackView; - SlotViewer* slotView; - VDPStatusRegViewer* VDPStatusRegView; - VDPRegViewer* VDPRegView; - VDPCommandRegViewer* VDPCommandRegView; - BreakpointViewer* bpView; - QPointer symManager; + QStringList recentFiles; CommClient& comm; - DebugSession session; - MemoryLayout memLayout; - uint8_t mainMemory[0x10000 + 4] = {}; // 4 extra to avoid wrap-check during disasm + DebugSession* session; + + QTabWidget *workspaces; + TabRenamerHelper *tabRenamer; bool mergeBreakpoints; - QMap debuggables; + static QMap debuggables; static int counter; - enum {RESET = 0, SLOTS_CHECKED, PC_CHANGED, SLOTS_CHANGED} disasmStatus = RESET; uint16_t disasmAddress; void fileNewSession(); @@ -159,6 +165,9 @@ class DebuggerForm : public QMainWindow void fileSaveSession(); void fileSaveSessionAs(); void fileRecentOpen(); + void fileOpenWorkspace(); + void fileSaveWorkspace(); + void systemConnect(); void systemDisconnect(); void systemPause(); @@ -183,22 +192,20 @@ class DebuggerForm : public QMainWindow void executeRun(); void executeStep(); void executeStepOver(); - void executeRunTo(); +// void executeRunTo(); void executeStepOut(); void executeStepBack(); - void toggleBreakpoint(); - void toggleBreakpointAddress(uint16_t addr); - void addBreakpoint(); - - void toggleView(DockableWidget* widget); +private slots: + void toggleBreakpoint(uint16_t addr); +private: + void addBreakpoint(uint16_t cursorAddress); void initConnection(); void handleUpdate(const QString& type, const QString& name, const QString& message); void setDebuggables(const QString& list); void setDebuggableSize(const QString& debuggable, int size); void connectionClosed(); - void dockWidgetVisibilityChanged(DockableWidget* w); void updateViewMenu(); void updateVDPViewMenu(); void updateViewFloatingWidgetsMenu(); @@ -215,6 +222,8 @@ class DebuggerForm : public QMainWindow friend class ListDebuggablesHandler; friend class DebuggableSizeHandler; + friend class TabRenamerHelper; + signals: void connected(); void settingsChanged(); @@ -224,6 +233,23 @@ class DebuggerForm : public QMainWindow void breakStateEntered(); void breakpointsUpdated(); void debuggablesChanged(const QMap& list); + +protected: + BlendSplitter *createWorkspaceCPU(); + BlendSplitter *createWorkspaceVDPRegs(); + BlendSplitter *createWorkspaceVDPTiles(); + BlendSplitter *createWorkspaceVDPBitmap(); + void addInfoWorkspace(); + void addCPUWorkspace(); + void addVDPRegsWorkspace(); + void addVDPTilesWorkspace(); + void addVDPBitmapWorkspace(); + void addEmptyWorkspace(); + void addFloatingSwitchingWidget(); + void addDefaultWorkspaces(); + +protected slots: + void tabCloseRequest(int index); }; #endif // DEBUGGERFORM_H diff --git a/src/DisasmViewer.cpp b/src/DisasmViewer.cpp index a75aec31..83768bb6 100644 --- a/src/DisasmViewer.cpp +++ b/src/DisasmViewer.cpp @@ -18,7 +18,7 @@ class CommMemoryRequest : public ReadDebugBlockCommand { public: - CommMemoryRequest(uint16_t offset_, uint16_t size_, unsigned char* target, + CommMemoryRequest(uint16_t offset_, uint16_t size_, uint8_t* target, DisasmViewer& viewer_) : ReadDebugBlockCommand("memory", offset_, size_, target) , offset(offset_), size(size_) @@ -157,7 +157,7 @@ void DisasmViewer::refresh() int end = disasmLines.back().addr + disasmLines.back().numBytes; int infoLine = disasmLines[disasmTopLine].infoLine; - requestMemory(start, end, disasmLines[disasmTopLine].addr, infoLine, TopAlways); + requestMemory(start, end, disasmLines[disasmTopLine].addr, infoLine, TopAlways); } void DisasmViewer::paintEvent(QPaintEvent* e) @@ -192,7 +192,7 @@ void DisasmViewer::paintEvent(QPaintEvent* e) while (y < height() - frameB) { // fetch the data for this line row = displayDisasm - ? &disasmLines[disasmTopLine+visibleLines] + ? &disasmLines[disasmTopLine + visibleLines] : &DISABLED_ROW; int h, a; switch (row->rowType) { @@ -207,7 +207,7 @@ void DisasmViewer::paintEvent(QPaintEvent* e) } // draw cursor line - bool isCursorLine = cursorAddr >= row->addr && cursorAddr < row->addr+row->numBytes && + bool isCursorLine = cursorAddr >= row->addr && cursorAddr < row->addr + row->numBytes && row->infoLine == cursorLine; if (isCursorLine) { cursorAddr = row->addr; @@ -215,7 +215,7 @@ void DisasmViewer::paintEvent(QPaintEvent* e) palette().color(QPalette::Highlight)); if (hasFocus()) { QStyleOptionFocusRect so; - so.backgroundColor = palette().color(QPalette::Highlight); + so.backgroundColor = palette().color(QPalette::Midlight); so.rect = QRect(frameL + 32, y, width() - 32 - frameL - frameR, h); style()->drawPrimitive(QStyle::PE_FrameFocusRect, &so, &p, this); } @@ -275,7 +275,7 @@ void DisasmViewer::paintEvent(QPaintEvent* e) y += h; ++visibleLines; // check for end of data - if (disasmTopLine+visibleLines == int(disasmLines.size())) break; + if (disasmTopLine + visibleLines == int(disasmLines.size())) break; } partialBottomLine = y > height() - frameB; visibleLines -= partialBottomLine; @@ -328,6 +328,7 @@ int DisasmViewer::findPosition(uint16_t addr, int infoLine, int method) void DisasmViewer::setAddress(uint16_t addr, int infoLine, int method) { + qDebug() << "void DisasmViewer::setAddress(uint16_t addr " << addr << ", int infoLine " << infoLine << ", int method " << method << " )"; if (method != Reload) { int line = findPosition(addr, infoLine, method); @@ -365,13 +366,13 @@ void DisasmViewer::setAddress(uint16_t addr, int infoLine, int method) } // The requested address it outside the pre-disassembled bounds. - // This means that a new block of memory must be transfered from + // This means that a new block of memory must be transferred from // openMSX and disassembled. // determine disasm bounds int disasmStart; int disasmEnd; - int extra = 4 * (visibleLines > 9 ? visibleLines+partialBottomLine : 10); + int extra = 4 * (visibleLines > 9 ? visibleLines + partialBottomLine : 10); switch (method) { case Middle: case MiddleAlways: @@ -457,6 +458,7 @@ uint16_t DisasmViewer::programCounter() const void DisasmViewer::setProgramCounter(uint16_t pc, bool reload) { + qDebug() << "void DisasmViewer::setProgramCounter(uint16_t pc "<= disasmTopLine && line < disasmTopLine+visibleLines) { + if (line >= disasmTopLine && line < disasmTopLine + visibleLines) { line -= visibleLines; if (line >= 0) { cursorAddr = disasmLines[line].addr; @@ -604,7 +606,7 @@ void DisasmViewer::keyPressEvent(QKeyEvent* e) } case Qt::Key_PageDown: { int line = findDisasmLine(cursorAddr, cursorLine); - if (line >= disasmTopLine && line < disasmTopLine+visibleLines) { + if (line >= disasmTopLine && line < disasmTopLine + visibleLines) { line += visibleLines; if (line < int(disasmLines.size())) { cursorAddr = disasmLines[line].addr; @@ -722,7 +724,7 @@ int DisasmViewer::lineAtPos(const QPoint& pos) int y = frameT; do { ++line; - switch (disasmLines[disasmTopLine+line].rowType) { + switch (disasmLines[disasmTopLine + line].rowType) { case DisasmRow::LABEL: y += labelFontHeight; break; diff --git a/src/DisasmViewer.h b/src/DisasmViewer.h index b63b5826..b1d5fae7 100644 --- a/src/DisasmViewer.h +++ b/src/DisasmViewer.h @@ -17,7 +17,7 @@ class DisasmViewer : public QFrame public: DisasmViewer(QWidget* parent = nullptr); - void setMemory(unsigned char* memPtr); + void setMemory(uint8_t* memPtr); void setBreakpoints(Breakpoints* bps); void setMemoryLayout(MemoryLayout* ml); void setSymbolTable(SymbolTable* st); @@ -38,6 +38,9 @@ class DisasmViewer : public QFrame void updateLayout(); void refresh(); +signals: + void breakpointToggled(uint16_t addr); + private: void requestMemory(uint16_t start, uint16_t end, uint16_t addr, int infoLine, int method); int findPosition(uint16_t addr, int infoLine, int method); @@ -76,15 +79,12 @@ class DisasmViewer : public QFrame DisasmLines disasmLines; // display data - unsigned char* memory; + uint8_t* memory; bool waitingForData; CommMemoryRequest* nextRequest; Breakpoints* breakpoints; MemoryLayout* memLayout; SymbolTable* symTable; - -signals: - void breakpointToggled(int addr); }; #endif // DISASMVIEWER_H diff --git a/src/DockManager.cpp b/src/DockManager.cpp deleted file mode 100644 index beb371ba..00000000 --- a/src/DockManager.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include "DockManager.h" -#include "DockableWidget.h" -#include "DockableWidgetArea.h" - - -void DockManager::addDockArea(DockableWidgetArea* area) -{ - if (areas.indexOf(area) == -1) { - areas.append(area); - } -} - -int DockManager::dockAreaIndex(DockableWidgetArea* area) const -{ - return areas.indexOf(area); -} - -void DockManager::dockWidget(DockableWidget* widget, const QPoint& /*p*/, const QRect& r) -{ - auto it = areaMap.begin(); // TODO - if (it != areaMap.end()) { - areaMap[widget] = it.value(); - return it.value()->addWidget(widget, r); - } -} - -void DockManager::undockWidget(DockableWidget* widget) -{ - auto it = areaMap.find(widget); - if (it != areaMap.end()) { - it.value()->removeWidget(widget); - } -} - -void DockManager::insertWidget( - DockableWidget* widget, int index, - DockableWidgetLayout::DockSide side, int distance, int w, int h) -{ - if (index < 0 || index >= areas.size()) return; - - //Q_ASSERT(areaMap.find(widget) == areaMap.end()); - - areas[index]->addWidget(widget, side, distance, w, h); - areaMap[widget] = areas[index]; -} - -bool DockManager::insertLocation(QRect& r, const QSizePolicy& sizePol) -{ - auto it = areaMap.begin(); // TODO - if (it == areaMap.end()) return false; - - return it.value()->insertLocation(r, sizePol); -} - -void DockManager::visibilityChanged(DockableWidget* widget) -{ - auto it = areaMap.find(widget); - if (it != areaMap.end()) { - it.value()->layout->changed(); - } -} - -void DockManager::getConfig(int index, QStringList& list) const -{ - areas[index]->getConfig(list); -} - -void DockManager::attachWidget(DockableWidget* widget) -{ - dockWidgets.append(widget); -} - -void DockManager::detachWidget(DockableWidget* widget) -{ - dockWidgets.removeAll(widget); -} - -const QList& DockManager::managedWidgets() const -{ - return dockWidgets; -} - -DockableWidget* DockManager::findDockableWidget(const QString& id) const -{ - for (auto* w : dockWidgets) { - if (w->id() == id) return w; - } - return nullptr; -} diff --git a/src/DockManager.h b/src/DockManager.h deleted file mode 100644 index 38f1aa77..00000000 --- a/src/DockManager.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef DOCKMANAGER_H -#define DOCKMANAGER_H - -#include "DockableWidgetLayout.h" -#include -#include - -class QRect; -class QPoint; -class DockableWidget; -class DockableWidgetArea; - -class DockManager -{ -public: - void addDockArea(DockableWidgetArea* area); - int dockAreaIndex(DockableWidgetArea* area) const; - - void insertWidget(DockableWidget* widget, int index, - DockableWidgetLayout::DockSide side, int distance, - int w = -1, int h = -1); - void dockWidget(DockableWidget* widget, const QPoint& p, const QRect& r); - void undockWidget(DockableWidget* widget); - - bool insertLocation(QRect& r, const QSizePolicy& sizePol); - - void visibilityChanged(DockableWidget* widget); - void getConfig(int index, QStringList& list) const; - - void attachWidget(DockableWidget* widget); - void detachWidget(DockableWidget* widget); - const QList& managedWidgets() const; - DockableWidget* findDockableWidget(const QString& id) const; - -private: - using AreaMap = QMap; - AreaMap areaMap; - QList areas; - QList dockWidgets; -}; - -#endif // DOCKMANAGER_H diff --git a/src/DockableWidget.cpp b/src/DockableWidget.cpp deleted file mode 100644 index 41665c7e..00000000 --- a/src/DockableWidget.cpp +++ /dev/null @@ -1,286 +0,0 @@ -#include "DockableWidget.h" -#include "DockManager.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -DockableWidget::DockableWidget(DockManager& manager, QWidget* parent) - : QWidget(parent), dockManager(manager) -{ - mainWidget = nullptr; - floating = false; - movable = true; - closable = true; - destroyable = true; - dragging = false; - setAttribute(Qt::WA_DeleteOnClose, true); -#ifndef Q_OS_MAC - setWindowFlags(Qt::FramelessWindowHint | Qt::Tool); -#else - // on Mac the FramelessWindowHint hides the SizeGrip - setWindowFlags(Qt::Tool); - setAttribute(Qt::WA_MacAlwaysShowToolWindow, true); -#endif - - titleLabel = new QLabel(); - closeButton = new QToolButton(); - closeButton->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton)); - closeButton->setToolButtonStyle(Qt::ToolButtonIconOnly); - int sz = style()->pixelMetric(QStyle::PM_SmallIconSize); - closeButton->setIconSize(style()->standardIcon( - QStyle::SP_TitleBarCloseButton).actualSize(QSize(sz, sz))); - closeButton->setAutoRaise(true); - connect(closeButton, &QToolButton::clicked, this, &DockableWidget::close); - - headerLayout = new QHBoxLayout(); - headerLayout->setMargin(0); - headerLayout->addWidget(titleLabel, 1); - headerLayout->addWidget(closeButton, 0); - - headerWidget = new QWidget(); - headerWidget->setLayout(headerLayout); - - widgetLayout = new QVBoxLayout(); - widgetLayout->setMargin(3); - widgetLayout->setSpacing(1); - widgetLayout->addWidget(headerWidget); - setLayout(widgetLayout); - - dockManager.attachWidget(this); - - rubberBand = new QRubberBand(QRubberBand::Rectangle); -} - -DockableWidget::~DockableWidget() -{ - delete mainWidget; - delete rubberBand; - dockManager.detachWidget(this); -} - -QWidget* DockableWidget::widget() const -{ - return mainWidget; -} - -void DockableWidget::setWidget(QWidget* widget) -{ - if (mainWidget) { - widgetLayout->removeWidget(mainWidget); - delete statusBar; - } - mainWidget = widget; - - if (widget) { - widgetLayout->addWidget(widget, 1); - int minW = sizeHint().width() - widget->sizeHint().width() - + widget->minimumWidth(); - int minH = sizeHint().height() - widget->sizeHint().height() - + widget->minimumHeight(); - int maxW = sizeHint().width() - widget->sizeHint().width() - + widget->maximumWidth(); - int maxH = sizeHint().height() - widget->sizeHint().height() - + widget->maximumHeight(); - maxW = std::min(maxW, QWIDGETSIZE_MAX); - maxH = std::min(maxH, QWIDGETSIZE_MAX); - setMinimumSize(minW, minH); - setMaximumSize(maxW, maxH); - setSizePolicy(widget->sizePolicy()); - - statusBar = new QStatusBar(nullptr); - statusBar->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); - statusBar->setVisible(false); - statusBar->setSizeGripEnabled(false); - widgetLayout->addWidget(statusBar, 2); - } -} - -const QString& DockableWidget::id() const -{ - return widgetId; -} - -void DockableWidget::setId(const QString& str) -{ - widgetId = str; -} - -QString DockableWidget::title() const -{ - return windowTitle(); -} - -void DockableWidget::setTitle(const QString& title) -{ - setWindowTitle(title); - titleLabel->setText(title + ':'); -} - -bool DockableWidget::isFloating() const -{ - return floating; -} - -void DockableWidget::setFloating(bool enable, bool showNow) -{ - if (floating == enable) return; - - floating = enable; - - if (mainWidget->sizePolicy().horizontalPolicy() != QSizePolicy::Fixed && - mainWidget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed) { - statusBar->setVisible(floating); - statusBar->setSizeGripEnabled(floating); - } - - if (floating && showNow) { - // force widget to never get behind main window -#ifndef Q_OS_MAC - setWindowFlags(Qt::FramelessWindowHint | Qt::Tool); -#else - // on Mac the FramelessWindowHint hides the SizeGrip - setWindowFlags(Qt::Tool); - setAttribute(Qt::WA_MacAlwaysShowToolWindow, true); -#endif - show(); - } -} - -bool DockableWidget::isMovable() const -{ - return movable; -} - -void DockableWidget::setMovable(bool enable) -{ - movable = enable; -} - -bool DockableWidget::isClosable() const -{ - return closable; -} - -void DockableWidget::setClosable(bool enable) -{ - if (closable == enable) return; - - closable = enable; - if (enable) { - closeButton->show(); - } else { - closeButton->hide(); - } - dragging = false; - rubberBand->hide(); -} - -bool DockableWidget::isDestroyable() const -{ - return destroyable; -} - -void DockableWidget::setDestroyable(bool enable) -{ - destroyable = enable; - setAttribute(Qt::WA_DeleteOnClose, enable); -} - -void DockableWidget::closeEvent(QCloseEvent* event) -{ - if (closable || destroyable) { - if (destroyable && floating) { - dockManager.undockWidget(this); - event->accept(); - } else { - hide(); - event->ignore(); - emit visibilityChanged(this); - } - } else { - event->ignore(); - } -} - -void DockableWidget::mousePressEvent(QMouseEvent* event) -{ - if (movable && event->button() == Qt::LeftButton) { - dragging = true; - dragStart = event->globalPos(); - dragOffset = event->pos(); - } -} - -void DockableWidget::mouseMoveEvent(QMouseEvent* event) -{ - if (!dragging) return; - - if (event->buttons() & Qt::LeftButton) { - // dragging of widget in progress, update rubberband - if (!rubberBand->isVisible()) { - if (abs(event->globalX() - dragStart.x()) > 20 || - abs(event->globalY() - dragStart.y()) > 20) { - rubberBand->resize(width(), height()); - rubberBand->move(event->globalX()-dragOffset.x(), - event->globalY()-dragOffset.y()); - rubberBand->show(); - } - } else { - QRect r(event->globalX()-dragOffset.x(), - event->globalY()-dragOffset.y(), - width(), height()); - if (floating && dockManager.insertLocation(r, sizePolicy())) { - rubberBand->move(r.x(), r.y()); - rubberBand->resize(r.width(), r.height()); - } else { - rubberBand->move(event->globalX()-dragOffset.x(), - event->globalY()-dragOffset.y()); - rubberBand->resize(width(), height()); - } - } - } else { - dragging = false; - rubberBand->hide(); - } -} - -void DockableWidget::mouseReleaseEvent(QMouseEvent* event) -{ - if (!dragging) return; - - dragging = false; - rubberBand->hide(); - - // only do anything if this was a meaningful drag - if (!movable || - (abs(event->globalX() - dragStart.x()) <= 20 && - abs(event->globalY() - dragStart.y()) <= 20)) { - return; - } - - if (floating) { - QRect mouseRect(event->globalX() - dragOffset.x(), - event->globalY() - dragOffset.y(), - width(), height()); - QRect r(mouseRect); - if (dockManager.insertLocation(r, sizePolicy())) { - setFloating(false); - dockManager.dockWidget(this, QPoint(), mouseRect); - } else { - move(event->globalPos() - dragOffset); - } - } else { - dockManager.undockWidget(this); - setFloating(true); - move(event->globalPos() - dragOffset); - } -} diff --git a/src/DockableWidget.h b/src/DockableWidget.h deleted file mode 100644 index d8f18b80..00000000 --- a/src/DockableWidget.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef DOCKABLEWIDGET_H -#define DOCKABLEWIDGET_H - -#include -#include - -class QLabel; -class QToolButton; -class QHBoxLayout; -class QVBoxLayout; -class QRubberBand; -class DockManager; -class QStatusBar; - -class DockableWidget : public QWidget -{ - Q_OBJECT -public: - DockableWidget(DockManager& manager, QWidget* parent = nullptr); - ~DockableWidget() override; - - QWidget* widget() const; - void setWidget(QWidget* widget); - const QString& id() const; - void setId(const QString& str); - - QString title() const; - void setTitle(const QString& title); - - bool isFloating() const; - void setFloating(bool enable, bool showNow = true); - bool isMovable() const; - void setMovable(bool enable); - bool isClosable() const; - void setClosable(bool enable); - bool isDestroyable() const; - void setDestroyable(bool enable); - -private: - void mousePressEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - void closeEvent(QCloseEvent* event) override; - - DockManager& dockManager; - QString widgetId; - - bool floating; - bool closable, movable, destroyable; - bool dragging; - QPoint dragStart, dragOffset; - QRubberBand* rubberBand; - - QWidget* mainWidget; - QHBoxLayout* headerLayout; - QVBoxLayout* widgetLayout; - QWidget* headerWidget; - QLabel* titleLabel; - QToolButton* closeButton; - QStatusBar* statusBar; - -signals: - void visibilityChanged(DockableWidget* w); -}; - -#endif // DOCKABLEWIDGET_H diff --git a/src/DockableWidgetArea.cpp b/src/DockableWidgetArea.cpp deleted file mode 100644 index 47a1c478..00000000 --- a/src/DockableWidgetArea.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "DockableWidgetArea.h" -#include "DockableWidgetLayout.h" -#include "DockableWidget.h" - -DockableWidgetArea::DockableWidgetArea(QWidget* parent) - : QWidget(parent) -{ - layout = new DockableWidgetLayout(); - setLayout(layout); -} - -void DockableWidgetArea::removeWidget(DockableWidget* widget) -{ - layout->removeWidget(widget); - widget->setParent(nullptr); -} - -void DockableWidgetArea::addWidget(DockableWidget* widget, const QRect& rect) -{ - widget->setParent(this); - QRect r(rect); - r.moveTopLeft(mapFromGlobal(r.topLeft())); - layout->addWidget(widget, r); -} - -void DockableWidgetArea::addWidget( - DockableWidget* widget, DockableWidgetLayout::DockSide side, - int distance, int w, int h) -{ - widget->setParent(this); - layout->addWidget(widget, side, distance, w, h); -} - -void DockableWidgetArea::paintEvent(QPaintEvent* e) -{ - QWidget::paintEvent(e); -} - -bool DockableWidgetArea::insertLocation(QRect& r, const QSizePolicy& sizePol) -{ - r.moveTopLeft(mapFromGlobal(r.topLeft())); - bool ok = layout->insertLocation(r, sizePol); - r.moveTopLeft(mapToGlobal(r.topLeft())); - return ok; -} - -void DockableWidgetArea::getConfig(QStringList& list) -{ - layout->getConfig(list); -} diff --git a/src/DockableWidgetArea.h b/src/DockableWidgetArea.h deleted file mode 100644 index d71aa0c0..00000000 --- a/src/DockableWidgetArea.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef DOCKABLETWIDGETAREA_H -#define DOCKABLETWIDGETAREA_H - -#include "DockableWidgetLayout.h" -#include - -class DockableWidget; -class QPaintEvent; - -class DockableWidgetArea : public QWidget -{ - Q_OBJECT -public: - DockableWidgetArea(QWidget* parent = nullptr); - -private: - void paintEvent(QPaintEvent* e) override; - - void removeWidget(DockableWidget* widget); - void addWidget(DockableWidget* widget, const QRect& rect); - void addWidget(DockableWidget* widget, DockableWidgetLayout::DockSide side, - int distance, int width = -1, int height = -1); - bool insertLocation(QRect& r, const QSizePolicy& sizePol); - void getConfig(QStringList& list); - - DockableWidgetLayout* layout; - - friend class DockManager; -}; - -#endif // DOCKABLETWIDGETAREA_H diff --git a/src/DockableWidgetLayout.cpp b/src/DockableWidgetLayout.cpp deleted file mode 100644 index b499a94b..00000000 --- a/src/DockableWidgetLayout.cpp +++ /dev/null @@ -1,831 +0,0 @@ -#include "DockableWidgetLayout.h" -#include "DockableWidget.h" -#include -#include -#include -#include - -static const int SNAP_DISTANCE = 16; - -DockableWidgetLayout::DockableWidgetLayout(QWidget* parent, int margin, int spacing) - : QLayout(parent) -{ - setMargin(margin); - setSpacing(spacing); - minWidth = minHeight = 0; - maxWidth = maxHeight = 0; -} - -DockableWidgetLayout::DockableWidgetLayout(int spacing) -{ - setSpacing(spacing); - minWidth = minHeight = 0; - maxWidth = maxHeight = 0; -} - -DockableWidgetLayout::~DockableWidgetLayout() -{ - while (!dockedWidgets.empty()) { - DockInfo* info = dockedWidgets.takeFirst(); - delete info->item; - delete info->widget; - delete info; - } -} - -void DockableWidgetLayout::addItem(QLayoutItem* item) -{ - addItem(item, -1); -} - -void DockableWidgetLayout::addItem( - QLayoutItem* item, int index, DockSide side, int dist, int w, int h) -{ - auto* info = new DockInfo(); - info->item = item; - info->widget = qobject_cast(item->widget()); - info->dockSide = side; - info->dockDistance = dist; - info->left = 0; - info->top = 0; - info->width = -1; - info->height = -1; - info->useHintWidth = true; - info->useHintHeight = true; - - if (info->widget->sizePolicy().horizontalPolicy() != QSizePolicy::Fixed && - w > 0) { - info->width = w; - info->useHintWidth = false; - } - if (info->widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed && - h > 0) { - info->height = h; - info->useHintHeight = false; - } - - // first widget is the resize widget, set initial size - if (dockedWidgets.empty()) { - if (info->width == -1) { - info->width = item->sizeHint().width(); - } - if (info->height == -1) { - info->height = item->sizeHint().height(); - } - info->useHintWidth = false; - info->useHintHeight = false; - } - - for (const auto* w : dockedWidgets) { - Q_ASSERT(w->widget != item->widget()); - } - - if (index > -1 && index < dockedWidgets.size()) { - dockedWidgets.insert(index, info); - } else { - dockedWidgets.append(info); - } - - // recalculate size limits - calcSizeLimits(); -} - -void DockableWidgetLayout::addWidget(DockableWidget* widget, const QRect& rect) -{ - int index; - DockSide side; - QRect r(rect); - if (insertLocation(r, index, side, widget->sizePolicy())) { - DockInfo* d = dockedWidgets.first(); - int dist; - switch (side) { - case TOP: - case BOTTOM: - dist = r.left() - d->left; - break; - case LEFT: - case RIGHT: - dist = r.top() - d->top; - break; - } - widget->show(); - addItem(new QWidgetItem(widget), index, side, dist, - r.width(), r.height()); - update(); - } -} - -void DockableWidgetLayout::addWidget( - DockableWidget* widget, DockSide side, int dist, int w, int h) -{ - // append item at the back - addItem(new QWidgetItem(widget), -1, side, dist, w, h); -} - -void DockableWidgetLayout::changed() -{ - calcSizeLimits(); - update(); -} - -Qt::Orientations DockableWidgetLayout::expandingDirections() const -{ - return Qt::Horizontal | Qt::Vertical; -} - -bool DockableWidgetLayout::hasHeightForWidth() const -{ - return false; -} - -int DockableWidgetLayout::count() const -{ - return dockedWidgets.size(); -} - -QLayoutItem* DockableWidgetLayout::takeAt(int index) -{ - if (index < 0 || index >= dockedWidgets.size()) return nullptr; - - DockInfo* info = dockedWidgets.takeAt(index); - QLayoutItem* item = info->item; - delete info; - - calcSizeLimits(); - return item; -} - -QLayoutItem* DockableWidgetLayout::itemAt(int index) const -{ - if (index < 0 || index >= dockedWidgets.size()) return nullptr; - - return dockedWidgets.at(index)->item; -} - -QSize DockableWidgetLayout::minimumSize() const -{ - return {minWidth, minHeight}; -} - -QSize DockableWidgetLayout::maximumSize() const -{ - return {maxWidth, maxHeight}; -} - -QSize DockableWidgetLayout::sizeHint() const -{ - return {layoutWidth, layoutHeight}; -} - -void DockableWidgetLayout::setGeometry(const QRect& rect) -{ - QLayout::setGeometry(rect); - - // Qt sometimes sets the geometry outside the minimumSize/maximumSize range. :/ - int W = std::clamp(rect.width(), minWidth, maxWidth ); - int H = std::clamp(rect.height(), minHeight, maxHeight); - - // set main widget size - int dx = W - layoutWidth; - int dy = H - layoutHeight; - - if (dx != 0 || dy != 0) { - sizeMove(dx, dy); - calcSizeLimits(); - } - - // resize the widgets - for (auto* d : dockedWidgets) { - if (!d->widget->isHidden()) { - d->item->setGeometry(d->bounds()); - } - } -} - -void DockableWidgetLayout::calcSizeLimits() -{ - if (dockedWidgets.empty()) return; - - // layout with current sizes - doLayout(); - DockInfo* d = dockedWidgets.first(); - - // store current size - int curWidth = d->width; - int curHeight = d->height; - QVector distStore; - for (auto* d : dockedWidgets) { - distStore.push_back(d->dockDistance); - } - - // first check minimum width (blunt method) - for (int i = d->widget->minimumWidth(); i <= curWidth; ++i) { - // trial layout - sizeMove(i - d->width, 0); - doLayout(true); - // restore - d->width = curWidth; - for (int j = 1; j < dockedWidgets.size(); ++j) { - dockedWidgets.at(j)->dockDistance = distStore.at(j); - } - // check result - if (layoutHeight == checkHeight && - layoutWidth - checkWidth == d->width - i) { - break; - } - } - minWidth = checkWidth; - - // first check maximum width (blunt method) - for (int i = d->widget->maximumWidth(); i >= curWidth; --i) { - // trial layout - sizeMove(i - d->width, 0); - doLayout(true); - // restore - d->width = curWidth; - for (int j = 1; j < dockedWidgets.size(); ++j) { - dockedWidgets.at(j)->dockDistance = distStore.at(j); - } - // check result - if (layoutHeight == checkHeight && - layoutWidth - checkWidth == d->width - i) { - break; - } - } - maxWidth = checkWidth; - - // first check minimum height (blunt method) - for (int i = d->widget->minimumHeight(); i <= curHeight; ++i) { - // trial layout - sizeMove(0, i - d->height); - doLayout(true); - // restore - d->height = curHeight; - for (int j = 1; j < dockedWidgets.size(); ++j) { - dockedWidgets.at(j)->dockDistance = distStore.at(j); - } - // check result - if (layoutWidth == checkWidth && - layoutHeight - checkHeight == d->height - i) { - break; - } - } - minHeight = checkHeight; - - // first check maximum width (blunt method) - for (int i = d->widget->maximumHeight(); i >= curHeight; --i) { - // trial layout - sizeMove(0, i - d->height); - doLayout(true); - // restore - d->height = curHeight; - for (int j = 1; j < dockedWidgets.size(); ++j) { - dockedWidgets.at(j)->dockDistance = distStore.at(j); - } - // check result - if (layoutWidth == checkWidth && - layoutHeight - checkHeight == d->height - i) { - break; - } - } - maxHeight = checkHeight; - - // restore layout - doLayout(); -} - -void DockableWidgetLayout::sizeMove(int dx, int dy) -{ - DockInfo* d0 = dockedWidgets.first(); - for (int i = 1; i < dockedWidgets.size(); ++i) { - DockInfo* d = dockedWidgets.at(i); - if (d->dockSide == TOP || d->dockSide == BOTTOM) { - if (d->dockDistance >= d0->width) { - d->dockDistance += dx; - } - } - if (d->dockSide == LEFT || d->dockSide == RIGHT) { - if (d->dockDistance >= d0->height) { - d->dockDistance += dy; - } - } - } - d0->width += dx; - d0->height += dy; -} - -void DockableWidgetLayout::doLayout(bool check) -{ - if (dockedWidgets.empty()) return; - - DockInfo* d = dockedWidgets.first(); - d->left = 0; - d->top = 0; - int W = d->width; - int H = d->height; - - int dx = 0, dy = 0; - for (int i = 1; i < dockedWidgets.size(); ++i) { - d = dockedWidgets[i]; - // only process visible widgets - if (d->widget->isHidden()) { - d->left = -10000; - d->top = -10000; - continue; - } - // determine size - if (d->useHintWidth) { - d->width = d->item->sizeHint().width(); - } - if (d->useHintHeight) { - d->height = d->item->sizeHint().height(); - } - // determine location - switch (d->dockSide) { - case TOP: - d->left = d->dockDistance; - if (d->dockDistance >= W || d->dockDistance+d->width <= 0) { - d->top = H - d->height; - } else { - d->top = -d->height; - } - // adjust position until it doesn't overlap other widgets - for (int j = 1; j < i; ++j) { - DockInfo* d2 = dockedWidgets[j]; - QRect r(d->left, d->top - QWIDGETSIZE_MAX, - d->width, d->height + QWIDGETSIZE_MAX); - if (r.intersects(d2->bounds())) { - d->top = d2->top - d->height; - } - } - break; - case LEFT: - d->top = d->dockDistance; - if (d->dockDistance >= H || d->dockDistance+d->height <= 0) { - d->left = W - d->width; - } else { - d->left = -d->width; - } - // adjust position until it doesn't overlap other widgets - for (int j = 1; j < i; ++j) { - DockInfo* d2 = dockedWidgets[j]; - QRect r(d->left - QWIDGETSIZE_MAX, d->top, - d->width + QWIDGETSIZE_MAX, d->height); - if (r.intersects(d2->bounds())) { - d->left = d2->left - d->width; - } - } - break; - case RIGHT: - d->top = d->dockDistance; - if (d->dockDistance >= H || d->dockDistance+d->height <= 0) { - d->left = 0; - } else { - d->left = W; - } - // adjust position until it doesn't overlap other widgets - for (int j = 1; j < i; ++j) { - DockInfo* d2 = dockedWidgets[j]; - QRect r(d->left, d->top, - d->width + QWIDGETSIZE_MAX, d->height); - if (r.intersects(d2->bounds())) { - d->left = d2->left + d2->width; - } - } - break; - case BOTTOM: - d->left = d->dockDistance; - if (d->dockDistance >= W || d->dockDistance+d->width <= 0) { - d->top = 0; - } else { - d->top = H; - } - // adjust position until it doesn't overlap other widgets - for (int j = 1; j < i; ++j) { - DockInfo* d2 = dockedWidgets[j]; - QRect r(d->left, d->top, - d->width, d->height + QWIDGETSIZE_MAX); - if (r.intersects(d2->bounds())) { - d->top = d2->top + d2->height; - } - } - break; - } - // check negative coordinates - if (d->left < dx) dx = d->left; - if (d->top < dy) dy = d->top; - } - - // translate widgets and calculate size - int& w = check ? checkWidth : layoutWidth; - int& h = check ? checkHeight : layoutHeight; - w = h = 0; - for (auto* d : dockedWidgets) { - if (!d->widget->isHidden()) { - d->left -= dx; - d->top -= dy; - w = std::max(w, d->right()); - h = std::max(h, d->bottom()); - } - } -} - -bool DockableWidgetLayout::overlaysWithFirstNWidgets(const QRect& r, int n) const -{ - for (int i = 0; i < n; ++i) { - if (r.intersects(dockedWidgets[i]->bounds())) { - return true; - } - } - return false; -} - -static bool isClose(int a, int b) -{ - return abs(a - b) < SNAP_DISTANCE; -} - -bool DockableWidgetLayout::insertLocation( - QRect& rect, int& index, DockSide& side, const QSizePolicy& sizePol) -{ - // best insertion data - // Distance is a number that represents the how far - // the insertion rectangle is from the final location. - unsigned bestDistance = 0xFFFFFFFF; - int bestIndex = 0; - DockSide bestSide; - QRect bestRect; - - // loop over all widgets and find appropriate matching sides - for (int i = 0; i < dockedWidgets.size(); ++i) { - DockInfo* d = dockedWidgets[i]; - /***************************************************** - * Check for placement against the top of the widget * - *****************************************************/ - if (i == 0 || d->dockSide != BOTTOM) { - if (!(rect.left() > d->right() - SNAP_DISTANCE || - rect.right() < d->left + SNAP_DISTANCE) && - isClose(rect.bottom(), d->top)) { - // rectangle is close to the edge - unsigned dist = 8 * abs(rect.bottom() - d->top); - // now find all points on this side - // (use set as a sorted unique list) - QSet sidePoints; - for (int j = 0; j <= i; ++j) { - DockInfo* d2 = dockedWidgets[j]; - if (d->top == d2->top) { - sidePoints.insert(d2->left); - sidePoints.insert(d2->right()); - // check if any other widget rest against this side - for (int k = i + 1; k < dockedWidgets.size(); ++k) { - DockInfo* d3 = dockedWidgets[k]; - if (d3->bottom() == d2->top) { - sidePoints.insert(d3->left); - sidePoints.insert(d3->right()); - } - } - } - } - // widget placement can occur at all points, find the closest - auto it = sidePoints.begin(); - for (int j = 0; j < sidePoints.size() - 1; ++j) { - // check after point - unsigned newDist1 = dist + abs(*it - rect.left()); - if (newDist1 < bestDistance && isClose(*it, rect.left())) { - QRect r(QPoint(*it, d->top - rect.height()), rect.size()); - if (!overlaysWithFirstNWidgets(r, i)) { - bestDistance = newDist1; - bestIndex = i + 1; - bestSide = TOP; - bestRect = r; - } - } - ++it; - // check before point - unsigned newDist2 = dist + abs(*it - rect.right()); - if (newDist2 < bestDistance && isClose(*it, rect.right())) { - QRect r(QPoint(*it - rect.width(), d->top - rect.height()), - rect.size()); - if (!overlaysWithFirstNWidgets(r, i)) { - bestDistance = newDist2; - bestIndex = i + 1; - bestSide = TOP; - bestRect = r; - } - } - } - // check for resized placement options - if (sizePol.horizontalPolicy() != QSizePolicy::Fixed) { - int mid = rect.left() + rect.width() / 2; - for (auto ita = std::next(sidePoints.begin()); ita != sidePoints.end(); ++ita) { - for (auto itb = sidePoints.begin(); ita != itb; ++itb) { - int sp_mid = (*ita + *itb) / 2; - int sp_diff = *ita - *itb; - if (isClose(sp_mid, mid)) { - QRect r(*itb, d->top - rect.height(), - sp_diff, rect.height()); - if (!overlaysWithFirstNWidgets(r, i)) { - bestDistance = dist + abs(sp_mid - mid); - bestIndex = i + 1; - bestSide = TOP; - bestRect = r; - } - } - } - } - } - } - } - - /******************************************************** - * Check for placement against the bottom of the widget * - ********************************************************/ - if (i == 0 || d->dockSide != TOP) { - if (!(rect.left() > d->right() - SNAP_DISTANCE || - rect.right() < d->left + SNAP_DISTANCE) && - isClose(rect.top(), d->bottom())) { - // rectangle is close to the edge - unsigned dist = 8 * abs(rect.top() - d->bottom()); - // now find all points on this side - // (use set as a sorted unique list) - QSet sidePoints; - for (int j = 0; j <= i; ++j) { - DockInfo* d2 = dockedWidgets[j]; - if (d->bottom() == d2->bottom()) { - sidePoints.insert(d2->left); - sidePoints.insert(d2->right()); - // check if any other widget rest against this side - for (int k = i + 1; k < dockedWidgets.size(); ++k) { - DockInfo* d3 = dockedWidgets[k]; - if (d3->top == d2->bottom()) { - sidePoints.insert(d3->left); - sidePoints.insert(d3->right()); - } - } - } - } - // widget placement can occur at all points, find the closest - auto it = sidePoints.begin(); - for (int j = 0; j < sidePoints.size() - 1; ++j) { - // check after point - unsigned newDist1 = dist + abs(*it - rect.left()); - if (newDist1 < bestDistance && isClose(*it, rect.left())) { - QRect r(QPoint(*it, d->bottom()), rect.size()); - if (!overlaysWithFirstNWidgets(r, i)) { - bestDistance = newDist1; - bestIndex = i + 1; - bestSide = BOTTOM; - bestRect = r; - } - } - ++it; - // check before point - unsigned newDist2 = dist + abs(*it - rect.right()); - if (newDist2 < bestDistance && isClose(*it, rect.right())) { - QRect r(QPoint(*it - rect.width(), d->bottom()), rect.size()); - if (!overlaysWithFirstNWidgets(r, i)) { - bestDistance = newDist2; - bestIndex = i + 1; - bestSide = BOTTOM; - bestRect = r; - } - } - } - // check for resized placement options - if (sizePol.horizontalPolicy() != QSizePolicy::Fixed) { - int mid = rect.left() + rect.width() / 2; - for (auto ita = std::next(sidePoints.begin()); ita != sidePoints.end(); ++ita) { - for (auto itb = sidePoints.begin(); ita != itb; ++itb) { - int sp_mid = (*ita + *itb) / 2; - int sp_diff = *ita - *itb; - if (isClose(sp_mid, mid)) { - QRect r(*itb, d->bottom(), - sp_diff, rect.height()); - if (!overlaysWithFirstNWidgets(r, i)) { - bestDistance = dist + abs(sp_mid - mid); - bestIndex = i + 1; - bestSide = BOTTOM; - bestRect = r; - } - } - } - } - } - } - } - - /****************************************************** - * Check for placement against the left of the widget * - ******************************************************/ - if (i == 0 || d->dockSide != RIGHT) { - if (!(rect.top() > d->bottom() - SNAP_DISTANCE || - rect.bottom() < d->top + SNAP_DISTANCE) && - isClose(rect.right(), d->left)) { - // rectangle is close to the edge - unsigned dist = 8 * abs(rect.right() - d->left); - // now find all points on this side - // (use set as a sorted unique list) - QSet sidePoints; - for (int j = 0; j <= i; ++j) { - DockInfo* d2 = dockedWidgets[j]; - if (d->left == d2->left) { - sidePoints.insert(d2->top); - sidePoints.insert(d2->bottom()); - // check if any other widget rest against this side - for (int k = i + 1; k < dockedWidgets.size(); ++k) { - DockInfo* d3 = dockedWidgets[k]; - if (d3->right() == d2->left) { - sidePoints.insert(d3->top); - sidePoints.insert(d3->bottom()); - } - } - } - } - // widget placement can occur at all points, find the closest - auto it = sidePoints.begin(); - for (int j = 0; j < sidePoints.size() - 1; ++j) { - // check after point - unsigned newDist1 = dist + abs(*it - rect.top()); - if (newDist1 < bestDistance && isClose(*it, rect.top())) { - QRect r(QPoint(d->left - rect.width(), *it), rect.size()); - if (!overlaysWithFirstNWidgets(r, i)) { - bestDistance = newDist1; - bestIndex = i + 1; - bestSide = LEFT; - bestRect = r; - } - } - ++it; - // check before point - unsigned newDist2 = dist + abs(*it - rect.bottom()); - if (newDist2 < bestDistance && isClose(*it, rect.bottom())) { - QRect r(QPoint(d->left - rect.width(), *it - rect.height()), - rect.size()); - if (!overlaysWithFirstNWidgets(r, i)) { - bestDistance = newDist2; - bestIndex = i + 1; - bestSide = LEFT; - bestRect = r; - } - } - } - // check for resized placement options - if (sizePol.verticalPolicy() != QSizePolicy::Fixed) { - int mid = rect.top() + rect.height() / 2; - for (auto ita = std::next(sidePoints.begin()); ita != sidePoints.end(); ++ita) { - for (auto itb = sidePoints.begin(); ita != itb; ++itb) { - int sp_mid = (*ita + *itb) / 2; - int sp_diff = *ita - *itb; - if (isClose(sp_mid, mid)) { - QRect r(d->left - rect.width(), *itb, - rect.width(), sp_diff); - if (!overlaysWithFirstNWidgets(r, i)) { - bestDistance = dist + abs(sp_mid - mid); - bestIndex = i + 1; - bestSide = LEFT; - bestRect = r; - } - } - } - } - } - } - } - /******************************************************* - * Check for placement against the right of the widget * - *******************************************************/ - if (i == 0 || d->dockSide != LEFT) { - if (!(rect.top() > d->bottom() - SNAP_DISTANCE || - rect.bottom() < d->top + SNAP_DISTANCE) && - isClose(rect.left(), d->right())) { - // rectangle is close to the edge - unsigned dist = 8 * abs(rect.left() - d->right()); - // now find all points on this side - // (use set as a sorted unique list) - QSet sidePoints; - for (int j = 0; j <= i; ++j) { - DockInfo* d2 = dockedWidgets[j]; - if (d->right() == d2->right()) { - sidePoints.insert(d2->top); - sidePoints.insert(d2->bottom()); - // check if any other widget rest against this side - for (int k = i + 1; k < dockedWidgets.size(); ++k) { - DockInfo* d3 = dockedWidgets[k]; - if (d3->left == d2->right()) { - sidePoints.insert(d3->top); - sidePoints.insert(d3->bottom()); - } - } - } - } - // widget placement can occur at all points, find the closest - auto it = sidePoints.begin(); - for (int j = 0; j < sidePoints.size() - 1; ++j) { - // check after point - unsigned newDist1 = dist + abs(*it - rect.top()); - if (newDist1 < bestDistance && isClose(*it, rect.top())) { - QRect r(QPoint(d->left + d->width, *it), rect.size()); - if (!overlaysWithFirstNWidgets(r, i)) { - bestDistance = newDist1; - bestIndex = i + 1; - bestSide = RIGHT; - bestRect = r; - } - } - ++it; - // check before point - unsigned newDist2 = dist + abs(*it - rect.bottom()); - if (newDist2 < bestDistance && isClose(*it, rect.bottom())) { - QRect r(QPoint(d->right(), *it - rect.height()), rect.size()); - if (!overlaysWithFirstNWidgets(r, i)) { - bestDistance = newDist2; - bestIndex = i + 1; - bestSide = RIGHT; - bestRect = r; - } - } - } - // check for resized placement options - if (sizePol.verticalPolicy() != QSizePolicy::Fixed) { - int mid = rect.top() + rect.height() / 2; - for (auto ita = std::next(sidePoints.begin()); ita != sidePoints.end(); ++ita) { - for (auto itb = sidePoints.begin(); ita != itb; ++itb) { - int sp_mid = (*ita + *itb) / 2; - int sp_diff = *ita - *itb; - if (isClose(sp_mid, mid)) { - QRect r(d->right(), *itb, rect.width(), sp_diff); - if (!overlaysWithFirstNWidgets(r, i)) { - bestDistance = dist + abs(sp_mid - mid); - bestIndex = i + 1; - bestSide = RIGHT; - bestRect = r; - } - } - } - } - } - } - } - } - - if (bestIndex) { - rect = bestRect; - index = bestIndex; - side = bestSide; - return true; - } else { - return false; - } -} - -bool DockableWidgetLayout::insertLocation(QRect& rect, const QSizePolicy& sizePol) -{ - int index; - DockSide side; - return insertLocation(rect, index, side, sizePol); -} - -void DockableWidgetLayout::getConfig(QStringList& list) -{ - for (auto* d : dockedWidgets) { - // string format D [Hidden/Visible] [Side] [Distance] [Width] [Height] - QString s("%1 D %2 %3 %4 %5 %6"); - s = s.arg(d->widget->id()); - - if (d->widget->isHidden()) { - s = s.arg("H"); - } else { - s = s.arg("V"); - } - - switch (d->dockSide) { - case TOP: - s = s.arg("T"); - break; - case LEFT: - s = s.arg("L"); - break; - case RIGHT: - s = s.arg("R"); - break; - case BOTTOM: - s = s.arg("B"); - break; - } - - s = s.arg(d->dockDistance); - - if (d->useHintWidth) { - s = s.arg(-1); - } else { - s = s.arg(d->width); - } - - if (d->useHintHeight) { - s = s.arg(-1); - } else { - s = s.arg(d->height); - } - list.append(s); - } -} diff --git a/src/DockableWidgetLayout.h b/src/DockableWidgetLayout.h deleted file mode 100644 index 67445f3b..00000000 --- a/src/DockableWidgetLayout.h +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef DOCKABLEWIDGETLAYOUT_H -#define DOCKABLEWIDGETLAYOUT_H - -#include -#include - -class QLayoutItem; -class DockableWidget; -class QStringList; - -class DockableWidgetLayout : public QLayout -{ - Q_OBJECT -public: - enum DockSide { TOP, LEFT, RIGHT, BOTTOM }; - - DockableWidgetLayout(QWidget* parent = nullptr, int margin = 0, int spacing = -1); - DockableWidgetLayout(int spacing); - ~DockableWidgetLayout() override; - - void addItem(QLayoutItem* item) override; - void addItem(QLayoutItem* item, int index, DockSide side = RIGHT, - int dist = 0, int w = -1, int h = -1); - void addWidget(DockableWidget* widget, const QRect& rect); - void addWidget(DockableWidget* widget, DockSide side, int distance, - int width = -1, int height = -1); - bool insertLocation(QRect& rect, const QSizePolicy& sizePol); - - QLayoutItem* itemAt(int index) const override; - QLayoutItem* takeAt(int index) override; - int count() const override; - - Qt::Orientations expandingDirections() const override; - bool hasHeightForWidth() const override; - - QSize minimumSize() const override; - QSize maximumSize() const override; - void setGeometry(const QRect &rect) override; - - QSize sizeHint() const override; - void changed(); - - void getConfig(QStringList& list); - -private: - class DockInfo - { - public: - QRect bounds() const { return {left, top, width, height}; } - int right() const { return left + width; } - int bottom() const { return top + height; } - - DockableWidget* widget; - QLayoutItem* item; - DockSide dockSide; - int dockDistance; - int left; - int top; - int width; - int height; - bool useHintHeight; - bool useHintWidth; - }; - - QList dockedWidgets; - int layoutWidth, layoutHeight; - int minWidth, minHeight; - int maxWidth, maxHeight; - int checkWidth, checkHeight; - - void calcSizeLimits(); - void sizeMove(int dx, int dy); - void doLayout(bool check = false); - bool insertLocation(QRect& rect, int& index, DockSide& side, - const QSizePolicy& sizePol); - bool overlaysWithFirstNWidgets(const QRect& r, int n) const; -}; - -#endif // DOCKABLEWIDGETLAYOUT_H diff --git a/src/FlagsViewer.cpp b/src/FlagsViewer.cpp index 13839f82..9c017b44 100644 --- a/src/FlagsViewer.cpp +++ b/src/FlagsViewer.cpp @@ -6,6 +6,7 @@ FlagsViewer::FlagsViewer(QWidget* parent) : QFrame(parent) { + setObjectName("FlagsViewer"); flags = flagsChanged = 0; // avoid UMR setFrameStyle(WinPanel | Sunken); diff --git a/src/FlagsViewer.h b/src/FlagsViewer.h index e5921f8d..d0ee6d69 100644 --- a/src/FlagsViewer.h +++ b/src/FlagsViewer.h @@ -27,8 +27,8 @@ class FlagsViewer : public QFrame private: int frameL, frameR, frameT, frameB; - unsigned char flags; - unsigned char flagsChanged; + uint8_t flags; + uint8_t flagsChanged; }; #endif // FLAGSVIEWER_H diff --git a/src/HexViewer.cpp b/src/HexViewer.cpp index cef89858..dedb24d4 100644 --- a/src/HexViewer.cpp +++ b/src/HexViewer.cpp @@ -66,7 +66,7 @@ HexViewer::HexViewer(QWidget* parent) void HexViewer::createActions() { - fillWidthAction = new QAction(tr("&Fill with"), this); + fillWidthAction = new QAction(tr("&Fill width"), this); fillWidthAction->setShortcut(tr("Ctrl+F")); fillWidthAction->setStatusTip(tr("Fill the width with as many bytes as possible.")); @@ -86,17 +86,39 @@ void HexViewer::createActions() setWith32Action->setShortcut(tr("Ctrl+3")); setWith32Action->setStatusTip(tr("Set width to 32 bytes.")); - connect(fillWidthAction, &QAction::triggered, this, &HexViewer::changeWidth); - connect(fillWidth2Action, &QAction::triggered, this, &HexViewer::changeWidth); - connect(setWith8Action, &QAction::triggered, this, &HexViewer::changeWidth); - connect(setWith16Action, &QAction::triggered, this, &HexViewer::changeWidth); - connect(setWith32Action, &QAction::triggered, this, &HexViewer::changeWidth); + decodeAsCharAction = new QAction(tr("&Show as char."), this); + decodeAsCharAction->setShortcut(tr("Ctrl+C")); + decodeAsCharAction->setCheckable(true); + decodeAsCharAction->setChecked(decodeAsChar); + decodeAsCharAction->setStatusTip(tr("decode 8 bytes as character.")); + + alignAddressAction = new QAction(tr("&Align addresses"), this); + alignAddressAction->setShortcut(tr("Ctrl+A")); + alignAddressAction->setCheckable(true); + alignAddressAction->setChecked(alignAddress); + alignAddressAction->setStatusTip(tr("Align adresses with width of line.")); + + + connect(fillWidthAction, &QAction::triggered, this, &HexViewer::changeWidth); + connect(fillWidth2Action, &QAction::triggered, this, &HexViewer::changeWidth); + connect(setWith8Action, &QAction::triggered, this, &HexViewer::changeWidth); + connect(setWith16Action, &QAction::triggered, this, &HexViewer::changeWidth); + connect(setWith32Action, &QAction::triggered, this, &HexViewer::changeWidth); + connect(decodeAsCharAction, &QAction::triggered, this, &HexViewer::changeWidth); + connect(alignAddressAction, &QAction::triggered, this, &HexViewer::changeWidth); //[=]() {alignAddress = alignAddressAction->isChecked();} ); addAction(fillWidthAction); addAction(fillWidth2Action); addAction(setWith8Action); addAction(setWith16Action); addAction(setWith32Action); + + QAction *sep = new QAction("", this); + sep->setSeparator(true); + addAction(sep); + + addAction(decodeAsCharAction); + addAction(alignAddressAction); setContextMenuPolicy(Qt::ActionsContextMenu); } @@ -107,12 +129,26 @@ void HexViewer::changeWidth() else if (sender() == setWith8Action) setDisplayWidth(8); else if (sender() == setWith16Action) setDisplayWidth(16); else if (sender() == setWith32Action) setDisplayWidth(32); + else if (sender() == decodeAsCharAction) setDisplayCharacters(decodeAsCharAction->isChecked()); + else if (sender() == alignAddressAction) setAlignAddress(alignAddressAction->isChecked()); +} + +void HexViewer::setAlignAddress(bool enabled) +{ + alignAddress = enabled; + setTopLocation(hexTopAddress); } void HexViewer::setIsEditable(bool enabled) { isEditable = enabled; - setUseMarker(true); + setUseMarker(true); +} + +void HexViewer::setDisplayCharacters(bool showchars) +{ + decodeAsChar = showchars; + setSizes(); } void HexViewer::setUseMarker(bool enabled) @@ -173,19 +209,33 @@ void HexViewer::setSizes() horBytes = 1; int hb2 = 1; w = width() - frameL - frameR - xData - dataWidth - 2 * charWidth - 8; - // calculate how many additional bytes can by displayed + // calculate how many additional bytes can be displayed while (w-sbw >= dataWidth + charWidth) { ++horBytes; - if (horBytes == 2 * hb2) hb2 = horBytes; + if (horBytes == 2 * hb2) { + hb2 = horBytes; + if (decodeAsChar){ + if (hb2 == 8 ) w -= EXTRA_SPACING; + if (hb2 >= 8 ){ + int scale = int((lineHeight+1)/8); + w -= scale*8; + } + } + }; w -= dataWidth + charWidth; if ((horBytes & 3) == 0) w -= EXTRA_SPACING; - if ((horBytes & 7) == 0) w -= EXTRA_SPACING; + if ((horBytes & 7) == 0) w -= EXTRA_SPACING; // remove scrollbar if (horBytes * visibleLines >= debuggableSize) sbw = 0; } // limit to power of two if needed if (displayMode == FILL_WIDTH_POWEROF2) horBytes = hb2; + + // if we want to show decoded chars then we limit ourself to multiples of 8 + if (decodeAsChar && horBytes >=8 ) { + horBytes=8*int(horBytes/8); + } } // check if a scrollbar is needed @@ -213,7 +263,7 @@ void HexViewer::setSizes() if (isEnabled()) { ///vertScrollBar->setValue(hexTopAddress / horBytes); - setTopLocation(horBytes * int(hexTopAddress / horBytes)); + setTopLocation(alignAddress ? horBytes * int(hexTopAddress / horBytes) : hexTopAddress); } else { update(); } @@ -273,7 +323,7 @@ void HexViewer::paintEvent(QPaintEvent* e) int y = frameT; int address = hexTopAddress; - for (int i = 0; i < visibleLines+partialBottomLine; ++i) { + for (int i = 0; i < visibleLines + partialBottomLine; ++i) { // print address QString hexStr = QString("%1").arg(address, addressLength, 16, QChar('0')); p.setPen(palette().color(QPalette::Text)); @@ -332,42 +382,65 @@ void HexViewer::paintEvent(QPaintEvent* e) // print characters x += charWidth; for (int j = 0; j < horBytes; ++j) { - if (address + j >= debuggableSize) break; - uint8_t chr = hexData[address + j]; - if (chr < 32 || chr > 127) chr = '.'; - // draw marker if needed - if (useMarker || beingEdited) { - QRect b(x, y, charWidth, lineHeight); - if ((address + j) == hexMarkAddress) { - p.fillRect(b, hasFocus ? Qt::cyan - : Qt::lightGray); - } - // are we being edited ?? - if (hasFocus && isEditable && editedChars && - ((address + j) == hexMarkAddress)) { - if (beingEdited) { - p.fillRect(b, Qt::darkGreen); - } else { - p.drawRect(b); - } - } - } - // determine value colour - if (highlitChanges) { - QColor penClr = palette().color(QPalette::Text); - if (hexData[address + j] != previousHexData[address + j]) { - penClr = Qt::red; - } - if (((address + j) == hexMarkAddress) && beingEdited && - (cursorPosition == 0)) { - penClr = Qt::white; - } - p.setPen(penClr); - } - p.drawText(x, y + a, QString(chr)); + if (address + j < debuggableSize) { + uint8_t chr = hexData[address + j]; + if (chr < 32 || chr > 127) chr = '.'; + // draw marker if needed + if (useMarker || beingEdited) { + QRect b(x, y, charWidth, lineHeight); + if ((address + j) == hexMarkAddress) { + p.fillRect(b, hasFocus ? Qt::cyan + : Qt::lightGray); + } + // are we being edited ?? + if (hasFocus && isEditable && editedChars && + ((address + j) == hexMarkAddress)) { + if (beingEdited) { + p.fillRect(b, Qt::darkGreen); + } else { + p.drawRect(b); + } + } + } + // determine value colour + if (highlitChanges) { + QColor penClr = palette().color(QPalette::Text); + if (hexData[address + j] != previousHexData[address + j]) { + penClr = Qt::red; + } + if (((address + j) == hexMarkAddress) && beingEdited && + (cursorPosition == 0)) { + penClr = Qt::white; + } + p.setPen(penClr); + } + p.drawText(x, y + a, QString(chr)); + } x += charWidth; } + // if we are a multiple of 8 bytes we draw one or more decoded characters + if ((horBytes%8) == 0 && decodeAsChar){ + x += EXTRA_SPACING; + int scale = int((lineHeight+1)/8); + + for (int j = 0; j < horBytes; ++j) { + if (address + j >= debuggableSize) break; + int yy = (j%8); + QColor penClr = (hasFocus && hexMarkAddress==(address + j) )? Qt::cyan : Qt::lightGray; //Qt::white; +// if (hexData[address + j] != previousHexData[address + j]) { +// penClr = Qt::red; +// } + for (int k = 0; k < 8; ++k) { + QColor pcl= hexData[address + j]&(1 << (7 - k)) ? palette().color(QPalette::Text) : penClr ; + p.fillRect(x+k*scale,y+yy*scale, scale,scale, pcl); + } + if (yy == 7) { + x += 8*scale; + } + } + } + y += lineHeight; address += horBytes; if (address >= debuggableSize) break; @@ -397,7 +470,7 @@ void HexViewer::setDebuggable(const QString& name, int size) void HexViewer::scrollBarChanged(int addr) { int start = addr * horBytes; - if (start == hexTopAddress) { + if (start >= (hexTopAddress-horBytes) && start <= (hexTopAddress+horBytes)) { // nothing changed or a callback since we changed the value to // the current hexTopAddress return; @@ -432,7 +505,7 @@ void HexViewer::setLocation(int addr) } hexMarkAddress = addr; int size = horBytes * visibleLines; - if ((addr < hexTopAddress) || (addr >= (hexTopAddress+size))) { + if ((addr < hexTopAddress) || (addr >= (hexTopAddress + size))) { setTopLocation(addr); } refresh(); @@ -444,7 +517,10 @@ void HexViewer::setTopLocation(int addr) if (debuggableName.isEmpty()) { return; } - int start = horBytes * int(addr / horBytes); + int start = addr; + if (alignAddress) { + start = horBytes * int(addr / horBytes); + } if (!waitingForData || (start != hexTopAddress)) { hexTopAddress = start; refresh(); @@ -513,11 +589,13 @@ void HexViewer::keyPressEvent(QKeyEvent* e) } else if (useMarker && e->key() == Qt::Key_Right) { setValue = beingEdited & !editedChars; ++newAddress; + if (!alignAddress && e->modifiers() == Qt::ShiftModifier) ++hexTopAddress; cursorPosition = 0; } else if (useMarker && e->key() == Qt::Key_Left) { setValue = beingEdited & !editedChars; --newAddress; - cursorPosition = 0; + if (!alignAddress && e->modifiers() == Qt::ShiftModifier) --hexTopAddress; + cursorPosition = 0; } else if (useMarker && e->key() == Qt::Key_Up) { setValue = beingEdited & !editedChars; newAddress -= horBytes; @@ -600,13 +678,16 @@ void HexViewer::keyPressEvent(QKeyEvent* e) if ((editedChars || useMarker) && (hexMarkAddress != newAddress)) { if (newAddress < 0) newAddress += debuggableSize; if (newAddress >= debuggableSize) newAddress -= debuggableSize; - // influencing hexTopAddress during Key_PageUp/Down might need following 2 lines. + // influencing hexTopAddress during Key_PageUp/Down or SHIFT+left/right might need following 2 lines. if (hexTopAddress < 0) hexTopAddress += debuggableSize; if (hexTopAddress >= debuggableSize) hexTopAddress -= debuggableSize; // Make scrolling downwards using cursors more "intuitive" int addr = hexTopAddress + horBytes * visibleLines; - if ((newAddress >= addr) && (newAddress <= (addr + horBytes))) { + if (!alignAddress && (newAddress == addr) && e->key() == Qt::Key_Right) { + ++hexTopAddress; + } else + if ((newAddress >= addr) && (newAddress <= (addr + horBytes))) { hexTopAddress += horBytes; } if (useMarker) { @@ -638,7 +719,7 @@ int HexViewer::coorToOffset(int x, int y) const } else if (x >= xChar && x < rightCharPos) { offset = (x - xChar) / charWidth; } - int yMaxOffset = frameT + (visibleLines+partialBottomLine) * lineHeight; + int yMaxOffset = frameT + (visibleLines + partialBottomLine) * lineHeight; if (offset >= 0 && y < yMaxOffset) { offset += horBytes * ((y - frameT) / lineHeight); } @@ -688,7 +769,7 @@ bool HexViewer::event(QEvent* e) void HexViewer::mousePressEvent(QMouseEvent* e) { if (e->button() == Qt::LeftButton && isInteractive) { - int offset=coorToOffset(e->x(), e->y()); + int offset = coorToOffset(e->x(), e->y()); if (offset >= 0) { int addr = hexTopAddress + offset; if (useMarker && (hexMarkAddress != addr)) { @@ -699,7 +780,7 @@ void HexViewer::mousePressEvent(QMouseEvent* e) cursorPosition = 0; beingEdited = isEditable; } - editedChars = (e->x() >= xChar); + editedChars = e->x() >= xChar; } update(); } diff --git a/src/HexViewer.h b/src/HexViewer.h index d2ff0472..f2276170 100644 --- a/src/HexViewer.h +++ b/src/HexViewer.h @@ -22,6 +22,8 @@ class HexViewer : public QFrame void setUseMarker(bool enabled); void setIsEditable(bool enabled); + void setAlignAddress(bool enabled); + void setDisplayCharacters(bool showchars); void setDisplayMode(Mode mode); void setDisplayWidth(short width); @@ -62,6 +64,8 @@ class HexViewer : public QFrame QAction* setWith8Action; QAction* setWith16Action; QAction* setWith32Action; + QAction* decodeAsCharAction; + QAction* alignAddressAction; int wheelRemainder = 0; @@ -89,6 +93,8 @@ class HexViewer : public QFrame bool beingEdited = false; bool editedChars = false; bool hasFocus = false; + bool decodeAsChar= true; + bool alignAddress= true; int cursorPosition, editValue; friend class HexRequest; diff --git a/src/InteractiveButton.cpp b/src/InteractiveButton.cpp index 904910d3..7c9a1fc8 100644 --- a/src/InteractiveButton.cpp +++ b/src/InteractiveButton.cpp @@ -60,7 +60,7 @@ void InteractiveButton::leaveEvent(QEvent* /*event*/) void InteractiveButton::newBitValueSlot(bool state) { - _state=state; + _state = state; QString name = objectName(); int bit = name.right(1).toInt(); int reg = name.mid( diff --git a/src/InteractiveLabel.h b/src/InteractiveLabel.h index 0311ac1d..62d0c9a3 100644 --- a/src/InteractiveLabel.h +++ b/src/InteractiveLabel.h @@ -17,7 +17,6 @@ class InteractiveLabel : public QLabel protected: void enterEvent(QEvent* event) override; void leaveEvent(QEvent* event) override; - }; #endif // INTERACTIVELABEL diff --git a/src/MSXPalette.cpp b/src/MSXPalette.cpp new file mode 100644 index 00000000..8e830de0 --- /dev/null +++ b/src/MSXPalette.cpp @@ -0,0 +1,122 @@ +#include "MSXPalette.h" +#include "CommClient.h" +#include +#include + +// default MSX palette +static const uint8_t defaultPalette[32] = { +// RB G + 0x00, 0, + 0x00, 0, + 0x11, 6, + 0x33, 7, + 0x17, 1, + 0x27, 3, + 0x51, 1, + 0x27, 6, + 0x71, 1, + 0x73, 3, + 0x61, 6, + 0x64, 6, + 0x11, 4, + 0x65, 2, + 0x55, 5, + 0x77, 7, +}; + +MSXPalette::MSXPalette(QObject* parent) + : QObject{parent} +{ + memcpy(myPal, defaultPalette, sizeof(myPal)); + emit paletteChanged(); +} + +void MSXPalette::copyDataFrom(const MSXPalette& source) +{ + memcpy(myPal, source.myPal, sizeof(myPal)); + emit paletteChanged(); +} + +void MSXPalette::setPalette(uint8_t* pal) +{ + sourcePal = pal; + if (memcmp(myPal, pal, sizeof(myPal)) != 0) { + memcpy(myPal, pal, sizeof(myPal)); + emit paletteChanged(); + } +} + +void MSXPalette::syncToSource() +{ + if (!sourcePal) return; + + memcpy(sourcePal, myPal, sizeof(myPal)); + if (syncToMSX) { + syncToOpenMSX(); + } + emit paletteSynced(); +} + +void MSXPalette::syncToSource(int i) +{ + if (!sourcePal) return; + + memcpy(sourcePal + 2 * i, myPal + 2 * i, 2); + if (syncToMSX) { + syncColorToOpenMSX(i); + } + emit paletteSynced(); +} + +void MSXPalette::setAutoSync(bool value) +{ + if (autoSync == value) return; + + autoSync = value; + if (value) { + syncToSource(); + } +} + +void MSXPalette::setColor(unsigned i, unsigned r, unsigned g, unsigned b) +{ + assert(i < 16); + assert(r < 8); + assert(g < 8); + assert(b < 8); + myPal[2 * i + 0] = (r << 4) | b; + myPal[2 * i + 1] = g; + + emit paletteChanged(); + + if (autoSync) { + syncToSource(i); + } +} + +QRgb MSXPalette::color(unsigned i) const +{ + assert(i < 16); + int r = (myPal[2 * i + 0] >> 4) & 7; + int g = (myPal[2 * i + 1] >> 0) & 7; + int b = (myPal[2 * i + 0] >> 0) & 7; + auto scale = [](int x) { return (x >> 1) | (x << 2) | (x << 5); }; + return qRgb(scale(r), scale(g), scale(b)); +} + +void MSXPalette::syncToOpenMSX() +{ + for (int i = 0; i < 16; ++i) { + syncColorToOpenMSX(i); + } +} + +void MSXPalette::syncColorToOpenMSX(int colorNr) +{ + CommClient::instance().sendCommand( + new SimpleCommand( + QString("setcolor %1 %2%3%4").arg(colorNr) + .arg(myPal[2 * colorNr] >> 4) + .arg(myPal[2 * colorNr + 1]) + .arg(myPal[2 * colorNr] & 15))); +} diff --git a/src/MSXPalette.h b/src/MSXPalette.h new file mode 100644 index 00000000..7ceb11a5 --- /dev/null +++ b/src/MSXPalette.h @@ -0,0 +1,42 @@ +#ifndef MSXPALETTE_H +#define MSXPALETTE_H + +#include +#include +#include + +enum {paletteVDP,paletteTiles,paletteBitmap,paletteSprites}; + +class MSXPalette : public QObject +{ + Q_OBJECT +public: + explicit MSXPalette(QObject* parent = nullptr); + + void copyDataFrom(const MSXPalette& source); + + void setPalette(uint8_t* pal); + void syncToSource(); + void syncToSource(int i); + void setAutoSync(bool value); + + void setColor(unsigned i, unsigned r, unsigned g, unsigned b); + QRgb color(unsigned i) const; + + bool syncToMSX = false; // TODO avoid public data members + +signals: + void paletteChanged(); + void paletteSynced(); + +private: + void syncToOpenMSX(); + void syncColorToOpenMSX(int colorNr); + +private: + uint8_t* sourcePal = nullptr; + uint8_t myPal[32]; + bool autoSync = false; +}; + +#endif // MSXPALETTE_H diff --git a/src/MainMemoryViewer.cpp b/src/MainMemoryViewer.cpp index 1871f61e..d55d120f 100644 --- a/src/MainMemoryViewer.cpp +++ b/src/MainMemoryViewer.cpp @@ -4,11 +4,13 @@ #include "CPURegsViewer.h" #include "SymbolTable.h" #include "Convert.h" +#include "SignalDispatcher.h" #include #include #include #include #include +#include static const int linkRegisters[] = { CpuRegs::REG_BC, CpuRegs::REG_DE, CpuRegs::REG_HL, @@ -20,6 +22,7 @@ static const int linkRegisters[] = { MainMemoryViewer::MainMemoryViewer(QWidget* parent) : QWidget(parent) { + setObjectName("MainMemoryViewer"); // create selection list, address edit line and viewer addressSourceList = new QComboBox(); addressSourceList->setEditable(false); @@ -52,7 +55,7 @@ MainMemoryViewer::MainMemoryViewer(QWidget* parent) isLinked = false; linkedId = 0; - regsViewer = nullptr; +// regsViewer = nullptr; symTable = nullptr; connect(hexView, &HexViewer::locationChanged, @@ -79,14 +82,36 @@ void MainMemoryViewer::setDebuggable(const QString& name, int size) hexView->setDebuggable(name, size); } -void MainMemoryViewer::setRegsView(CPURegsViewer* viewer) +//void MainMemoryViewer::setRegsView(CPURegsViewer* viewer) +//{ +// regsViewer = viewer; +//} + +void MainMemoryViewer::setSymbolTable(SymbolTable* newTable) +{ + symTable = newTable; +} + +QJsonObject MainMemoryViewer::save2json() { - regsViewer = viewer; + QJsonObject obj; + obj["addressValue"] = addressValue->text(); + obj["addressSourceList"] = addressSourceList->currentIndex(); + return obj; } -void MainMemoryViewer::setSymbolTable(SymbolTable* symtable) +bool MainMemoryViewer::loadFromJson(const QJsonObject& obj) { - symTable = symtable; + auto asl = obj["addressSourceList"]; + auto av = obj["addressValue"]; + if (asl == QJsonValue::Undefined || av == QJsonValue::Undefined) { + return false; + } + + addressSourceList->setCurrentIndex(asl.toInt()); + addressValue->setText(av.toString()); + hexView->setLocation(addressValue->text().toInt()); + return true; } void MainMemoryViewer::refresh() @@ -134,8 +159,9 @@ void MainMemoryViewer::addressSourceListChanged(int index) linkedId = linkRegisters[index - 1]; addressValue->setReadOnly(true); hexView->setIsInteractive(false); - if (regsViewer) { - setLocation(regsViewer->readRegister(linkedId)); - } + //if (regsViewer) { + // setLocation(regsViewer->readRegister(linkedId)); + //} + setLocation(SignalDispatcher::instance().readRegister(linkedId)); } } diff --git a/src/MainMemoryViewer.h b/src/MainMemoryViewer.h index 1c9cc440..33942850 100644 --- a/src/MainMemoryViewer.h +++ b/src/MainMemoryViewer.h @@ -1,6 +1,7 @@ #ifndef MAINMEMORYVIEWER_H #define MAINMEMORYVIEWER_H +#include "SavesJsonInterface.h" #include class HexViewer; @@ -8,17 +9,22 @@ class CPURegsViewer; class SymbolTable; class QComboBox; class QLineEdit; +class QJsonObject; -class MainMemoryViewer : public QWidget +class MainMemoryViewer : public QWidget, public SavesJsonInterface { Q_OBJECT public: MainMemoryViewer(QWidget* parent = nullptr); void setDebuggable(const QString& name, int size); - void setRegsView(CPURegsViewer* viewer); - void setSymbolTable(SymbolTable* symtable); +// void setRegsView(CPURegsViewer* viewer); + void setSymbolTable(SymbolTable* newtable); + QJsonObject save2json() final; + bool loadFromJson(const QJsonObject& obj) final; + +public slots: void setLocation(int addr); void settingsChanged(); void refresh(); @@ -33,7 +39,7 @@ class MainMemoryViewer : public QWidget QComboBox* addressSourceList; QLineEdit* addressValue; - CPURegsViewer* regsViewer; +// CPURegsViewer* regsViewer; SymbolTable* symTable; int linkedId; bool isLinked; diff --git a/src/OpenMSXConnection.cpp b/src/OpenMSXConnection.cpp index b4f4ca11..dc3e6d67 100644 --- a/src/OpenMSXConnection.cpp +++ b/src/OpenMSXConnection.cpp @@ -59,21 +59,21 @@ static QString createDebugCommand(const QString& debuggable, } ReadDebugBlockCommand::ReadDebugBlockCommand(const QString& commandString, - unsigned size_, unsigned char* target_) + unsigned size_, uint8_t* target_) : SimpleCommand(commandString) , size(size_), target(target_) { } ReadDebugBlockCommand::ReadDebugBlockCommand(const QString& debuggable, - unsigned offset, unsigned size_, unsigned char* target_) + unsigned offset, unsigned size_, uint8_t* target_) : SimpleCommand(createDebugCommand(debuggable, offset, size_)) , size(size_), target(target_) { } static QString createDebugWriteCommand(const QString& debuggable, - unsigned offset, unsigned size, unsigned char *data) + unsigned offset, unsigned size, uint8_t *data) { QString cmd = QString("debug write_block %1 %2 [ debug_hex2bin \"") .arg(debuggable).arg(offset); @@ -84,13 +84,13 @@ static QString createDebugWriteCommand(const QString& debuggable, return cmd; } WriteDebugBlockCommand::WriteDebugBlockCommand(const QString& debuggable, - unsigned offset, unsigned size_, unsigned char* source_) + unsigned offset, unsigned size_, uint8_t* source_) : SimpleCommand(createDebugWriteCommand(debuggable, offset, size_, source_)) { } -static unsigned char hex2val(char c) +static uint8_t hex2val(char c) { return (c <= '9') ? (c - '0') : (c - 'A' + 10); } diff --git a/src/OpenMSXConnection.h b/src/OpenMSXConnection.h index 5a83b804..39f3e1bf 100644 --- a/src/OpenMSXConnection.h +++ b/src/OpenMSXConnection.h @@ -58,23 +58,23 @@ class ReadDebugBlockCommand : public SimpleCommand { public: ReadDebugBlockCommand(const QString& commandString, unsigned size, - unsigned char* target); + uint8_t* target); ReadDebugBlockCommand(const QString& debuggable, unsigned offset, unsigned size, - unsigned char* target); + uint8_t* target); protected: void copyData(const QString& message); private: unsigned size; - unsigned char* target; + uint8_t* target; }; class WriteDebugBlockCommand : public SimpleCommand { public: WriteDebugBlockCommand(const QString& debuggable, unsigned offset, unsigned size, - unsigned char* source); + uint8_t* source); }; class OpenMSXConnection : public QObject diff --git a/src/PaletteDialog.cpp b/src/PaletteDialog.cpp deleted file mode 100644 index 3dcecb5d..00000000 --- a/src/PaletteDialog.cpp +++ /dev/null @@ -1,217 +0,0 @@ -#include -#include -#include -#include -#include "PaletteDialog.h" -#include "Convert.h" -#include "ranges.h" - - -PalettePatch::PalettePatch(QWidget* parent, int palNr) - : QPushButton(parent), msxPalNr(palNr) -{ -} - -//void PalettePatch::setColor(QRgb c) -//{ -// myColor = c; -// update(); -//} - -void PalettePatch::updatePaletteChanged(const uint8_t* pal) -{ - int r = (pal[2 * msxPalNr + 0] & 0xf0) >> 4; - int b = (pal[2 * msxPalNr + 0] & 0x0f); - int g = (pal[2 * msxPalNr + 1] & 0x0f); - auto scale = [](int x) { return (x >> 1) | (x << 2) | (x << 5); }; - myColor = qRgb(scale(r), scale(g), scale(b)); - setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); - update(); - //printf("PalettePatch::updatePaletteChanged %i\n", msxPalNr); -} - -void PalettePatch::setHighlightTest(int colorNr) -{ - bool s = colorNr == msxPalNr; - if (isSelected == s) return; - isSelected = s; - update(); -} - -void PalettePatch::paintEvent(QPaintEvent* /*event*/) -{ - QPainter painter(this); - painter.setPen(isSelected ? Qt::white : QColor(myColor)); - painter.setBrush(QBrush(myColor)); - painter.drawRect(0, 0, this->width() - 1, this->height() - 1); -} - - - -PaletteDialog::PaletteDialog(QWidget* parent) - : QDialog(parent), ui(std::make_unique()) -{ - ui->setupUi(this); - connect(ui->horizontalSlider_R, &QSlider::valueChanged, this, &PaletteDialog::on_horizontalSlider_R_valueChanged); - connect(ui->horizontalSlider_G, &QSlider::valueChanged, this, &PaletteDialog::on_horizontalSlider_G_valueChanged); - connect(ui->horizontalSlider_B, &QSlider::valueChanged, this, &PaletteDialog::on_horizontalSlider_B_valueChanged); - connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &PaletteDialog::on_buttonBox_clicked); - connect(ui->cb_autosync, &QCheckBox::stateChanged, this, &PaletteDialog::on_cb_autosync_stateChanged); - - const QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); - ui->plainTextEdit->setFont(fixedFont); - - setWindowTitle("Palette editor"); - ranges::fill(myPal, 0); - ranges::fill(myOriginalPal, 0); - // add colors to colorframe - - signalMapper = new QSignalMapper(this); - auto* gridLayout = new QGridLayout; - gridLayout->setSpacing(0); - for (int i = 0; i < 16; ++i) { - auto* button = new PalettePatch(nullptr, i); - connect(button, &PalettePatch::clicked, signalMapper, qOverload<>(&QSignalMapper::map)); - signalMapper->setMapping(button, i); - gridLayout->addWidget(button, i / 8, i % 8); - connect(this, &PaletteDialog::paletteChanged, - button, &PalettePatch::updatePaletteChanged); - connect(signalMapper, &QSignalMapper::mappedInt, - button, &PalettePatch::setHighlightTest); - } - - connect(signalMapper, &QSignalMapper::mappedInt, - this, &PaletteDialog::colorSelected); - - ui->colorsframe->setLayout(gridLayout); - connect(this, &PaletteDialog::paletteChanged, - this, &PaletteDialog::updateText); -} - -void PaletteDialog::updateText() -{ - ui->plainTextEdit->clear(); - for (int i = 0; i < 4; ++i) { - QString txt(" db "); - for (int j = 0; j < 4; ++j) { - txt.append(QString("%1,%2 ") - .arg(hexValue(myPal[2 * (j + 4 * i) + 0], 2)) - .arg(hexValue(myPal[2 * (j + 4 * i) + 1], 2))); - if (j < 3) txt.append(','); - } - ui->plainTextEdit->appendPlainText(txt); - } -} - -void PaletteDialog::restoreOpeningsPalette() -{ - memcpy(myPal, myOriginalPal, sizeof(myPal)); - emit paletteChanged(myPal); // Resets the PalettePatches - colorSelected(currentColor); // Resets the sliders - if (autoSync) { - syncToSource(); - } -} - -void PaletteDialog::colorSelected(int colorNumber) -{ - currentColor = colorNumber; - int r = (myPal[2 * currentColor + 0] & 0xf0) >> 4; - int b = (myPal[2 * currentColor + 0] & 0x0f); - int g = (myPal[2 * currentColor + 1] & 0x0f); - ui->horizontalSlider_R->setValue(r); - ui->horizontalSlider_G->setValue(g); - ui->horizontalSlider_B->setValue(b); - ui->label_colornr->setText(QString("Color %1").arg(colorNumber)); -} - -void PaletteDialog::setPalette(uint8_t* pal) -{ - sourcePal = pal; - memcpy(myOriginalPal, pal, sizeof(myOriginalPal)); - memcpy(myPal, pal, sizeof(myPal)); - emit paletteChanged(myPal); -} - -uint8_t* PaletteDialog::getPalette() -{ - return myPal; -} - -//void PaletteDialog::decodepalette() -//{ -// for (int i = 0; i < 16; ++i) { -// int r = (myPal[2 * i + 0] & 0xf0) >> 4; -// int b = (myPal[2 * i + 0] & 0x0f); -// int g = (myPal[2 * i + 1] & 0x0f); -// -// auto scale = [](int x) { return (x >> 1) | (x << 2) | (x << 5); }; -// msxPalette[i] = qRgb(scale(r), scale(g), scale(b)); -// -// QGridLayout* l = dynamic_cast(ui->colorsframe->layout()); -// dynamic_cast(l->itemAtPosition(i % 8, i / 8)->widget())->setText(QString("%1(%2%3%4)").arg(i).arg(r).arg(g).arg(b)); -// } -//} - -void PaletteDialog::combineRGB() -{ - int r = ui->horizontalSlider_R->value(); - int g = ui->horizontalSlider_G->value(); - int b = ui->horizontalSlider_B->value(); - myPal[2 * currentColor + 0] = 16 * r + b; - myPal[2 * currentColor + 1] = g; - emit paletteChanged(myPal); - if (autoSync) { - syncToSource(); - } -} - -void PaletteDialog::syncToSource() -{ - memcpy(sourcePal, myPal, sizeof(myPal)); - emit paletteSynced(); -} - -void PaletteDialog::setAutoSync(bool value) -{ - if (autoSync == value) return; - autoSync = value; - ui->cb_autosync->setChecked(value); -} - -void PaletteDialog::on_horizontalSlider_R_valueChanged(int value) -{ - ui->label_R->setText(QString("R=%1").arg(value)); - combineRGB(); -} - -void PaletteDialog::on_horizontalSlider_G_valueChanged(int value) -{ - ui->label_G->setText(QString("G=%1").arg(value)); - combineRGB(); -} - -void PaletteDialog::on_horizontalSlider_B_valueChanged(int value) -{ - ui->label_B->setText(QString("B=%1").arg(value)); - combineRGB(); -} - -void PaletteDialog::on_buttonBox_clicked(QAbstractButton* button) -{ - if (button== ui->buttonBox->button(QDialogButtonBox::Apply) || - button== ui->buttonBox->button(QDialogButtonBox::Ok)) { - syncToSource(); - } else if (button== ui->buttonBox->button(QDialogButtonBox::Reset) || - button== ui->buttonBox->button(QDialogButtonBox::Cancel)) { - restoreOpeningsPalette(); - } -} - -void PaletteDialog::on_cb_autosync_stateChanged(int arg1) -{ - autoSync = arg1 != Qt::Unchecked; - if (autoSync) { - syncToSource(); - } -} diff --git a/src/PaletteDialog.h b/src/PaletteDialog.h deleted file mode 100644 index 89536aaa..00000000 --- a/src/PaletteDialog.h +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef PALETTEDIALOG_H -#define PALETTEDIALOG_H - -#include "ui_PaletteDialog.h" -#include -#include -#include -#include -#include - -namespace Ui { - class PaletteDialog; -} - -class PalettePatch : public QPushButton -{ - Q_OBJECT -public: - explicit PalettePatch(QWidget* parent = nullptr, int palNr = 0); - //void setColor(QRgb c); - - void updatePaletteChanged(const uint8_t* pal); - void setHighlightTest(int colorNr); - -protected: - void paintEvent(QPaintEvent* event) override; - -private: - QRgb myColor = Qt::green; - bool isSelected = false; - int msxPalNr; -}; - - -class PaletteDialog : public QDialog -{ - Q_OBJECT -public: - explicit PaletteDialog(QWidget* parent = nullptr); - - void setPalette(uint8_t* pal); - uint8_t* getPalette(); - void syncToSource(); - void setAutoSync(bool value); - -signals: - void paletteChanged(uint8_t* pal); - void paletteSynced(); - -private: - void colorSelected(int colorNumber); - void on_horizontalSlider_R_valueChanged(int value); - void on_horizontalSlider_G_valueChanged(int value); - void on_horizontalSlider_B_valueChanged(int value); - void restoreOpeningsPalette(); - - void on_buttonBox_clicked(QAbstractButton* button); - - void on_cb_autosync_stateChanged(int arg1); - - void updateText(); - - void combineRGB(); - //void decodepalette(); - -private: - std::unique_ptr ui; - QSignalMapper* signalMapper; - - uint8_t* sourcePal = nullptr; - uint8_t myPal[32]; - uint8_t myOriginalPal[32]; - - int currentColor = 0; - QRgb msxPalette[16]; - - bool autoSync = false; -}; - -#endif // PALETTEDIALOG_H diff --git a/src/PaletteDialog.ui b/src/PaletteDialog.ui deleted file mode 100644 index 538acd53..00000000 --- a/src/PaletteDialog.ui +++ /dev/null @@ -1,185 +0,0 @@ - - - PaletteDialog - - - - 0 - 0 - 513 - 271 - - - - Dialog - - - - - - - - QFrame::StyledPanel - - - QFrame::Sunken - - - - - - - - - B=0 - - - - - - - G=0 - - - - - - - 7 - - - 1 - - - Qt::Horizontal - - - QSlider::TicksBothSides - - - - - - - R=0 - - - - - - - 7 - - - 1 - - - Qt::Horizontal - - - QSlider::TicksBothSides - - - - - - - 7 - - - 1 - - - Qt::Horizontal - - - QSlider::TicksBothSides - - - - - - - Color 0 - - - - - - - - - - - - 0 - 0 - - - - - 16777215 - 100 - - - - - - - - - - Autosync - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset - - - - - - - - - - - buttonBox - accepted() - PaletteDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - PaletteDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/src/PalettePatch.cpp b/src/PalettePatch.cpp new file mode 100644 index 00000000..03e4d29e --- /dev/null +++ b/src/PalettePatch.cpp @@ -0,0 +1,47 @@ +#include "PalettePatch.h" +#include +#include "MSXPalette.h" + +PalettePatch::PalettePatch(QWidget* parent, int palNr) + : QPushButton(parent), msxPalNr(palNr) +{ + setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); +} + +void PalettePatch::setMSXPalette(MSXPalette* pal) +{ + if (myPal != nullptr) { + disconnect(myPal, &MSXPalette::paletteChanged, this, &PalettePatch::paletteChanged); + } + myPal = pal; + connect(myPal, &MSXPalette::paletteChanged, this, &PalettePatch::paletteChanged); + paletteChanged(); +} + +void PalettePatch::paletteChanged() +{ + myColor = myPal->color(msxPalNr); + update(); +} + +void PalettePatch::setHighlightTest(int colorNr) +{ + bool s = colorNr == msxPalNr; + if (isSelected == s) return; + isSelected = s; + update(); +} + +void PalettePatch::paintEvent(QPaintEvent* /*event*/) +{ + QPainter painter(this); + if (isEnabled()) { + painter.setPen(isSelected ? Qt::white : QColor(myColor)); + painter.setBrush(QBrush(myColor)); + } else { + painter.setPen(Qt::black); + int v = qGray(myColor); + painter.setBrush(QBrush(QColor(v, v, v))); + } + painter.drawRect(0, 0, this->width() - 1, this->height() - 1); +} diff --git a/src/PalettePatch.h b/src/PalettePatch.h new file mode 100644 index 00000000..07e4c491 --- /dev/null +++ b/src/PalettePatch.h @@ -0,0 +1,30 @@ +#ifndef PALETTEPATCH_H +#define PALETTEPATCH_H + +#include +#include "MSXPalette.h" +#include + +class PalettePatch : public QPushButton +{ + Q_OBJECT + +public: + PalettePatch(QWidget* parent, int palNr); + +public slots: + void setMSXPalette(MSXPalette* pal); + void setHighlightTest(int colorNr); + void paletteChanged(); + +protected: + void paintEvent(QPaintEvent* event) override; + +private: + QRgb myColor = Qt::green; + bool isSelected = false; + int msxPalNr; + MSXPalette* myPal = nullptr; +}; + +#endif // PALETTEPATCH_H diff --git a/src/PaletteView.cpp b/src/PaletteView.cpp new file mode 100644 index 00000000..434793c8 --- /dev/null +++ b/src/PaletteView.cpp @@ -0,0 +1,212 @@ +#include "PaletteView.h" +#include "PalettePatch.h" +#include "VDPDataStore.h" +#include "Convert.h" +#include "ScopedAssign.h" +#include "ui_PaletteView.h" +#include +#include + +PaletteView::PaletteView(QWidget* parent) + : QWidget(parent) + , ui(std::make_unique()) +{ + ui->setupUi(this); + autoSync = ui->cb_autosync->isChecked(); + + const QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); + ui->plainTextEdit->setFont(fixedFont); + + setPalette(VDPDataStore::instance().getPalette(paletteVDP)); + + signalMapper = new QSignalMapper(this); + auto* gridLayout = new QGridLayout; + gridLayout->setSpacing(0); + for (int i = 0; i < 16; ++i) { + auto* button = new PalettePatch(nullptr, i); + button->setMSXPalette(myPal); + connect(button, &PalettePatch::clicked, signalMapper, qOverload<>(&QSignalMapper::map)); + signalMapper->setMapping(button, i); + gridLayout->addWidget(button, i / 8, i % 8); + connect(this, &PaletteView::paletteReplaced, button, &PalettePatch::setMSXPalette); + connect(this, &PaletteView::paletteChanged, button, &PalettePatch::paletteChanged); + connect(signalMapper, &QSignalMapper::mappedInt, button, &PalettePatch::setHighlightTest); + } + + connect(signalMapper, &QSignalMapper::mappedInt, this, &PaletteView::colorSelected); + + ui->colorsframe->setLayout(gridLayout); + connect(this, &PaletteView::paletteChanged, this, &PaletteView::updateText); + updateText(); + + //select color 0 + emit signalMapper->mappedInt(0); +} + +void PaletteView::setPalette(MSXPalette* sourcePal) +{ + if (myPal != nullptr) { + disconnect(myPal, &MSXPalette::paletteChanged, this, &PaletteView::paletteChanged); + } + + myPal = sourcePal; + myOriginalPal.copyDataFrom(*sourcePal); + connect(myPal, &MSXPalette::paletteChanged, this, &PaletteView::paletteChanged); + emit paletteReplaced(sourcePal); +} + +MSXPalette* PaletteView::getPalette() +{ + return myPal; +} + +void PaletteView::syncToSource() +{ + myPal->syncToSource(); + myOriginalPal.copyDataFrom(*myPal); +} + +void PaletteView::setAutoSync(bool value) +{ + if (autoSync == value) return; + autoSync = value; + ui->cb_autosync->setChecked(value); + if (value) { + syncToSource(); + } +} + +QJsonObject PaletteView::save2json() +{ + QJsonObject obj; + obj["viewtext"] = ui->cb_viewtext->isChecked(); + obj["autosync"] = ui->cb_autosync->isChecked(); + obj["palette"] = ui->cbPalette->currentIndex(); + return obj; +} + +bool PaletteView::loadFromJson(const QJsonObject& obj) +{ + auto vt = obj["viewtext"]; + auto as = obj["autosync"]; + auto pa = obj["palette"]; + if (vt == QJsonValue::Undefined || + as == QJsonValue::Undefined || + pa == QJsonValue::Undefined) { + return false; + } + + ui->cb_viewtext->setChecked(vt.toBool()); + ui->cb_autosync->setChecked(as.toBool()); + ui->cbPalette->setCurrentIndex(pa.toInt()); + return true; +} + +void PaletteView::refresh() +{ +} + +void PaletteView::colorSelected(int colorNumber) +{ + currentColor = colorNumber; + QRgb c = myPal->color(colorNumber); + ui->horizontalSlider_R->setValue(qRed (c) >> 5); + ui->horizontalSlider_G->setValue(qGreen(c) >> 5); + ui->horizontalSlider_B->setValue(qBlue (c) >> 5); + ui->label_colornr->setText(QString("Color %1").arg(colorNumber)); +} + +void PaletteView::on_horizontalSlider_R_valueChanged(int value) +{ + ui->label_R->setText(QString("R=%1").arg(value)); + combineRGB(); +} + +void PaletteView::on_horizontalSlider_G_valueChanged(int value) +{ + ui->label_G->setText(QString("G=%1").arg(value)); + combineRGB(); +} + +void PaletteView::on_horizontalSlider_B_valueChanged(int value) +{ + ui->label_B->setText(QString("B=%1").arg(value)); + combineRGB(); +} + +void PaletteView::restorePalette() +{ + ScopedAssign sa(isDisplayUpdating, true); + myPal->copyDataFrom(myOriginalPal); + colorSelected(currentColor); +} + +void PaletteView::on_buttonBox_clicked(QAbstractButton* button) +{ + if (button == ui->buttonBox->button(QDialogButtonBox::Apply) || + button == ui->buttonBox->button(QDialogButtonBox::Ok)) { + syncToSource(); + } else if (button == ui->buttonBox->button(QDialogButtonBox::Reset) || + button == ui->buttonBox->button(QDialogButtonBox::Cancel)) { + restorePalette(); + } +} + +void PaletteView::on_cb_autosync_stateChanged(int arg1) +{ + autoSync = arg1 != Qt::Unchecked; + if (autoSync) { + syncToSource(); + } +} + +void PaletteView::on_cbPalette_currentIndexChanged(int index) +{ + ui->pbCopyPaletteVDP->setEnabled(index != 0); + ScopedAssign sa(isDisplayUpdating, true); + setPalette(VDPDataStore::instance().getPalette(index)); + ui->cb_autosync->setEnabled(index==0); + if (index){ + ui->buttonBox->setDisabled(true); + } else { + ui->buttonBox->setDisabled(ui->cb_autosync->isChecked()); + } + colorSelected(currentColor); +} + +void PaletteView::on_pbCopyPaletteVDP_clicked() +{ + ScopedAssign sa(isDisplayUpdating, true); + myPal->copyDataFrom(*VDPDataStore::instance().getPalette(paletteVDP)); + emit myPal->paletteChanged(); +} + +void PaletteView::updateText() +{ + ui->plainTextEdit->clear(); + for (int i = 0; i < 4; ++i) { + QString txt(" db "); + for (int j = 0; j < 4; ++j) { + QRgb c = myPal->color(j + 4 * i); + txt.append(QString("%1,%2 ").arg( + hexValue((qRed (c) >> 5) * 16 + (qBlue(c) >> 5), 2), + hexValue((qGreen(c) >> 5), 2))); + if (j < 3) txt.append(','); + } + ui->plainTextEdit->appendPlainText(txt); + } +} + +void PaletteView::combineRGB() +{ + if (isDisplayUpdating) return; + + int r = ui->horizontalSlider_R->value() & 7; + int g = ui->horizontalSlider_G->value() & 7; + int b = ui->horizontalSlider_B->value() & 7; + myPal->setColor(currentColor, r, g, b); + emit paletteChanged(); + if (autoSync) { + syncToSource(); + } +} diff --git a/src/PaletteView.h b/src/PaletteView.h new file mode 100644 index 00000000..7592f3ca --- /dev/null +++ b/src/PaletteView.h @@ -0,0 +1,73 @@ +#ifndef PALETTEVIEW_H +#define PALETTEVIEW_H + +#include "ui_PaletteView.h" +#include "MSXPalette.h" +#include "SavesJsonInterface.h" +#include +#include +#include +#include + +class QAbstractButton; +class QJsonObject; + + +namespace Ui { + class PaletteView; +} + +class PaletteView : public QWidget, public SavesJsonInterface +{ + Q_OBJECT + +public: + explicit PaletteView(QWidget* parent = nullptr); + + void setPalette(MSXPalette* sourcePal); + MSXPalette* getPalette(); + + void syncToSource(); + void setAutoSync(bool value); + + QJsonObject save2json() final; + bool loadFromJson(const QJsonObject& obj) final; + +public slots: + void refresh(); + +signals: + void paletteReplaced(MSXPalette* sourcePal); + void paletteChanged(); + void paletteSynced(); + +private slots: + void colorSelected(int colorNumber); + void on_horizontalSlider_R_valueChanged(int value); + void on_horizontalSlider_G_valueChanged(int value); + void on_horizontalSlider_B_valueChanged(int value); + void restorePalette(); + + void on_buttonBox_clicked(QAbstractButton* button); + + void on_cb_autosync_stateChanged(int arg1); + void on_cbPalette_currentIndexChanged(int index); + void on_pbCopyPaletteVDP_clicked(); + + void updateText(); + +private: + std::unique_ptr ui; + QSignalMapper* signalMapper; + void combineRGB(); + + MSXPalette* myPal = nullptr; + MSXPalette myOriginalPal; + + int currentColor = 0; + + bool autoSync = false; + bool isDisplayUpdating = false; +}; + +#endif // PALETTEVIEW_H diff --git a/src/PaletteView.ui b/src/PaletteView.ui new file mode 100644 index 00000000..8e0d2d05 --- /dev/null +++ b/src/PaletteView.ui @@ -0,0 +1,362 @@ + + + PaletteView + + + + 0 + 0 + 521 + 357 + + + + Form + + + + + + + + + 0 + 0 + + + + + 150 + 20 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + 0 + + + + + + + + 0 + 0 + + + + B=0 + + + + + + + + 0 + 0 + + + + G=0 + + + + + + + + 0 + 0 + + + + 7 + + + 1 + + + Qt::Horizontal + + + QSlider::TicksBothSides + + + + + + + + 0 + 0 + + + + R=0 + + + + + + + + 0 + 0 + + + + 7 + + + 1 + + + Qt::Horizontal + + + QSlider::TicksBothSides + + + + + + + + 0 + 0 + + + + 7 + + + 1 + + + Qt::Horizontal + + + QSlider::TicksBothSides + + + + + + + Color 0 + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + VDP palette + + + + + Tileview palette + + + + + Bitmap palette + + + + + Sprite palette + + + + + + + + false + + + Copy current VDP palette + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + + + View text + + + true + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + autosync VDP palette to openMSX + + + Autosync + + + true + + + + + + + false + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Reset + + + + + + + + + + + cb_autosync + toggled(bool) + buttonBox + setDisabled(bool) + + + 58 + 273 + + + 439 + 273 + + + + + cb_viewtext + toggled(bool) + plainTextEdit + setVisible(bool) + + + 129 + 404 + + + 479 + 256 + + + + + diff --git a/src/PreferencesDialog.cpp b/src/PreferencesDialog.cpp index ce1610ce..de7e5dda 100644 --- a/src/PreferencesDialog.cpp +++ b/src/PreferencesDialog.cpp @@ -1,30 +1,75 @@ #include "PreferencesDialog.h" #include "Settings.h" +#include "DebuggerForm.h" +#include + #include #include #include +#include +#include +#include + PreferencesDialog::PreferencesDialog(QWidget* parent) : QDialog(parent) { setupUi(this); + // Since there is no 'slots' definition in the header anymore we can not use the QMetaObject::connectSlotsByName() in setupUi anymore + // So now we do it explicitely here + connect(leFileName, &QLineEdit::textChanged, this, &PreferencesDialog::on_leFileName_textChanged); + connect(btnSaveLayout, &QPushButton::clicked, this, &PreferencesDialog::on_btnSaveLayout_clicked); + connect(btnBrowseLayout, &QPushButton::clicked, this, &PreferencesDialog::on_btnBrowseLayout_clicked); + + + //openMSX connection stuff + connect(cbAutoconnect, &QCheckBox::toggled, this, &PreferencesDialog::openMSXConnectionChanged); + connect(cbStartIfNoConnection, &QCheckBox::toggled, this, &PreferencesDialog::openMSXConnectionChanged); + connect(leOpenMSXbin, &QLineEdit::editingFinished, this, &PreferencesDialog::openMSXConnectionChanged); + connect(leOpenMSXargs, &QLineEdit::editingFinished, this, &PreferencesDialog::openMSXConnectionChanged); + connect(pbTestCLI, &QPushButton::clicked, this, &PreferencesDialog::testOpenMSXCommandLine); + + //font stuff connect(listFonts, &QListWidget::currentRowChanged, - this, &PreferencesDialog::fontSelectionChange); - connect(rbUseAppFont, &QRadioButton::toggled, - this, &PreferencesDialog::fontTypeChanged); + this, &PreferencesDialog::fontSelectionChange); + connect(rbUseAppFont, &QRadioButton::toggled, + this, &PreferencesDialog::fontTypeChanged); connect(rbUseFixedFont, &QRadioButton::toggled, - this, &PreferencesDialog::fontTypeChanged); + this, &PreferencesDialog::fontTypeChanged); connect(rbUseCustomFont, &QRadioButton::toggled, - this, &PreferencesDialog::fontTypeChanged); + this, &PreferencesDialog::fontTypeChanged); connect(btnSelectFont, &QPushButton::clicked, - this, &PreferencesDialog::fontSelectCustom); + this, &PreferencesDialog::fontSelectCustom); connect(btnFontColor, &QPushButton::clicked, - this, &PreferencesDialog::fontSelectColor); + this, &PreferencesDialog::fontSelectColor); initConfig(); initFontList(); listFonts->setCurrentRow(0); + + //layout stuff + QList rblayouttypes; + rblayouttypes << rbFirstTimeUser << rbDefaultWorkspaces << rbLayoutFromFile; + foreach(auto rb, rblayouttypes){ + connect(rb, &QRadioButton::toggled, + this, &PreferencesDialog::layoutTypeChanged); + }; + + //update ui with saved settings + updating=true; + Settings& s = Settings::get(); + rblayouttypes.at(s.value("creatingWorkspaceType",0).toInt())->setChecked(true); + leFileName->setText(s.value("creatingWorkspaceFile","").toString()); + + cbAutoconnect->setChecked(s.value("autoconnect",true).toBool()); + cbStartIfNoConnection->setChecked(s.value("startOpenMSX",false).toBool()); + leOpenMSXbin->setText(s.value("openMSXbin","").toString()); + leOpenMSXargs->setText(s.value("openMSXargs","").toString()); + + createCLI(); + + updating=false; } /* @@ -126,6 +171,23 @@ void PreferencesDialog::fontSelectColor() } } +void PreferencesDialog::layoutTypeChanged(bool state) +{ + if (!state || updating) return; + + Settings& s = Settings::get(); + + int wst = 2; + if (rbFirstTimeUser->isChecked()) { + wst = 0; + } else if (rbDefaultWorkspaces->isChecked()) { + wst = 1; + } else { + s.setValue("creatingWorkspaceFile", leFileName->text()); + } + s.setValue("creatingWorkspaceType", wst); +} + void PreferencesDialog::autoReloadSymbols(int state) { Settings::get().setAutoReloadSymbols(state == Qt::Checked); @@ -145,4 +207,81 @@ void PreferencesDialog::setFontPreviewColor(const QColor& c) } else { lblPreview->setPalette(QPalette()); } -} \ No newline at end of file +} + +void PreferencesDialog::on_btnBrowseLayout_clicked() +{ + QString fileName = QFileDialog::getOpenFileName( + this, tr("Select workspace layout"), + QDir::currentPath(), tr("Debug Workspace Layout Files (*.omdl)")); + + if (!fileName.isEmpty()) { + leFileName->setText(fileName); + rbLayoutFromFile->setChecked(true); //not sure if setText with already string in lineEdit will trigger the on_leFileName_textChanged + } +} + +void PreferencesDialog::openMSXConnectionChanged() +{ + if (updating) return; + + createCLI(); + Settings& s = Settings::get(); + s.setValue("autoconnect",cbAutoconnect->isChecked()); + s.setValue("startOpenMSX",cbStartIfNoConnection->isChecked()); + s.setValue("openMSXbin",leOpenMSXbin->text()); + s.setValue("openMSXargs",leOpenMSXargs->text()); +} + +void PreferencesDialog::on_leFileName_textChanged(const QString &arg1) +{ + if (updating) return; + Settings& s = Settings::get(); + s.setValue("creatingWorkspaceFile", arg1); + + rbLayoutFromFile->setChecked(true); +} + + +void PreferencesDialog::on_btnSaveLayout_clicked() +{ + Settings& s = Settings::get(); + QString savefilename = leFileName->text(); + if (savefilename.isEmpty()) { + // maybe the user cleared the lineEdit or first time launch or something went wrong :-) + savefilename = s.value("creatingWorkspaceFile", "").toString(); + } + if (savefilename.isEmpty()) { + savefilename = static_cast(parent())->fileSaveWorkspaceAs(); + } else { + static_cast(parent())->saveWorkspacesAs(savefilename); + } + // update filename in case of fileSaveWorkspaceAs + if (!savefilename.isEmpty()) { + leFileName->setText(savefilename); + s.setValue("creatingWorkspaceFile", savefilename); + } +} + +void PreferencesDialog::createCLI() +{ + labelCLI->setText(QString("%1 %2") + .arg(leOpenMSXbin->text(),leOpenMSXargs->text()) + ); +} + +void PreferencesDialog::testOpenMSXCommandLine() +{ + QString program = leOpenMSXbin->text(); + QStringList arguments; + if (!leOpenMSXargs->text().trimmed().isEmpty()) { + arguments=leOpenMSXargs->text().trimmed().split(QChar(' '),Qt::SkipEmptyParts); + }; + QProcess *myProcess = new QProcess(qApp); + myProcess->start(program, arguments); + if (!myProcess->waitForStarted() || myProcess->state()!=QProcess::Running) { + QMessageBox::critical(this,"Can not start openMSX", + QString("The command line '%1' doesn't seem to work.\n\n%2").arg(program).arg(myProcess->errorString()) + ); + } +} diff --git a/src/PreferencesDialog.h b/src/PreferencesDialog.h index b1d4f8a0..61dc5c78 100644 --- a/src/PreferencesDialog.h +++ b/src/PreferencesDialog.h @@ -20,10 +20,21 @@ class PreferencesDialog : public QDialog, private Ui::PreferencesDialog void fontSelectCustom(); void fontSelectColor(); + + void layoutTypeChanged(bool state); + void on_btnBrowseLayout_clicked(); + void openMSXConnectionChanged(); + + void on_leFileName_textChanged(const QString& arg1); + void on_btnSaveLayout_clicked(); + void autoReloadSymbols(int state); void preserveLostSymbols(int state); + private: + void createCLI(); + void testOpenMSXCommandLine(); bool updating; }; diff --git a/src/PreferencesDialog.ui b/src/PreferencesDialog.ui index a1e0773b..5c5df9a9 100644 --- a/src/PreferencesDialog.ui +++ b/src/PreferencesDialog.ui @@ -1,10 +1,8 @@ - - - - + + PreferencesDialog - - + + 0 0 @@ -12,16 +10,16 @@ 512 - + Preferences - - - 9 - - + + 6 + + 9 + @@ -65,62 +63,62 @@ Fonts - - - 9 - - + + 6 + + 9 + - - + + QAbstractItemView::SelectRows - - - 0 - - + + 6 + + 0 + - - - 0 - - + + 6 + + 0 + - - + + false - + - - + + false - + Use default fixed font - - + + false - + Use custom font @@ -128,63 +126,61 @@ - - - 0 - - + + 6 + + 0 + - - - - 7 - 0 + + + 0 0 - + 0 80 - + QFrame::WinPanel - + QFrame::Sunken - + openMSX debugger! - - + + false - + Select custom font - - + + Select font color - + Qt::Vertical - + 20 40 @@ -198,22 +194,221 @@ + + + Launch options + + + + + + Workspaces at startup + + + + + + First Time User + + + + + + + Create the default workspaces + + + true + + + + + + + + + Load from file + + + + + + + + + + Br&owse... + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Overwrite with current layout... + + + + + + + + + + + + OpenMSX connection + + + + + + Autoconnect at startup + + + true + + + + + + + true + + + Start openMSX if no connection available at startup + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + + + + + + + 0 + 0 + + + + + true + + + + Command Line to start openMSX + + + + + + + Test commandline + + + + + + + + + openMSX binary + + + + + + + DO NOT USE FILENAMES WITH SPACES! +There is no way to enter those correctly at the moment + + + + + + + options + + + + + + + + 0 + 0 + + + + command: + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 237 + + + + + + - - - 0 - - + + 6 + + 0 + - + Qt::Horizontal - + 131 31 @@ -222,8 +417,8 @@ - - + + Close @@ -232,7 +427,6 @@ - @@ -241,15 +435,47 @@ PreferencesDialog accept() - + 278 253 - + 96 254 + + cbAutoconnect + toggled(bool) + cbStartIfNoConnection + setEnabled(bool) + + + 326 + 258 + + + 326 + 287 + + + + + cbAutoconnect + toggled(bool) + frameCLI + setEnabled(bool) + + + 326 + 258 + + + 326 + 333 + + + diff --git a/src/QuickGuide.cpp b/src/QuickGuide.cpp new file mode 100644 index 00000000..dfc06ae7 --- /dev/null +++ b/src/QuickGuide.cpp @@ -0,0 +1,15 @@ +#include "QuickGuide.h" +#include "ui_QuickGuide.h" + + +QuickGuide::QuickGuide(QWidget *parent) : + QWidget(parent), + ui(new Ui::QuickGuide) +{ + ui->setupUi(this); +} + +QuickGuide::~QuickGuide() +{ + delete ui; +} diff --git a/src/QuickGuide.h b/src/QuickGuide.h new file mode 100644 index 00000000..51c5f361 --- /dev/null +++ b/src/QuickGuide.h @@ -0,0 +1,22 @@ +#ifndef QUICKGUIDE_H +#define QUICKGUIDE_H + +#include + +namespace Ui { +class QuickGuide; +} + +class QuickGuide : public QWidget +{ + Q_OBJECT + +public: + explicit QuickGuide(QWidget *parent = nullptr); + ~QuickGuide(); + +private: + Ui::QuickGuide *ui; +}; + +#endif // QUICKGUIDE_H diff --git a/src/QuickGuide.ui b/src/QuickGuide.ui new file mode 100644 index 00000000..1c77d691 --- /dev/null +++ b/src/QuickGuide.ui @@ -0,0 +1,58 @@ + + + QuickGuide + + + + 0 + 0 + 1099 + 1555 + + + + + 0 + 0 + + + + Form + + + + + + true + + + + 0 + 0 + + + + <html><head/><body><h1 style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:xx-large; font-weight:700; color:#000001;">Quickguide to the new blendsplitter way of working</span></h1><p><span style=" color:#000001;">Hi you may have accidentally stumbled upon the new way of working in the openMSX debugger. </span></p><p><span style=" color:#000001;">The old dockingwidgets has been abandoned in favor of a new layouting scheme that is inspired by the open source 3D package Blender. Here is a quick summary of how to work with the new layouts.</span></p><h2 style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:x-large; font-weight:700; color:#000001;">Workspaces</span></h2><p><span style=" color:#000001;">Workspaces are essentially predefined window layouts.</span></p><p><span style=" color:#000001;">You can access the workspaces with the tab bar at the top. Use the button at the rigth of the tabs to create a new workspace, you can create empty workspaces or pick an existent predefined workspace.</span></p><p><img src=":/quickguide/workspaces.png"/></p><h2 style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:x-large; font-weight:700; color:#000001;">Areas</span></h2><p><span style=" color:#000001;">The debugger window is divided into a number of rectangles called Areas. An area has 4 corner handlers.</span></p><p><span style=" color:#000001;">Each area contains a switcher that contains a seperate debuggerview. You can change the debuggerview by selecting an alternative debuggerview in the dropdown list.</span></p><p><img src=":/quickguide/borders.png"/></p><h3 style=" margin-top:14px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:large; font-weight:700; color:#000001;">Resizing areas</span></h3><p><span style=" color:#000001;">You can resize areas by dragging their borders with </span><span style=" font-family:'monospace'; color:#000001;">LMB</span><span style=" color:#000001;">. Move your mouse cursor over the border between two areas, so that the cursor changes to a double-headed arrow, and then click and drag.</span></p><p><img src=":/quickguide/resize.png"/></p><h3 style=" margin-top:14px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:large; font-weight:700; color:#000001;">Splitting areas</span></h3><p><span style=" color:#000001;">Splitting an area will create a new area. Placing the mouse cursor in an area corner will change the cursor to a cross (+) to indicate that pressing down </span><span style=" font-family:'monospace'; color:#000001;">LMB</span><span style=" color:#000001;"> will activate splitting or joining operator. Dragging from area corner </span><span style=" font-weight:700; color:#000001;">inward</span><span style=" color:#000001;"> will </span><span style=" font-style:italic; color:#000001;">split</span><span style=" color:#000001;"> the area. You define the split direction by dragging either horizontally or vertically.</span></p><p><img src=":/quickguide/split.png"/></p><h3 style=" margin-top:14px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:large; font-weight:700; color:#000001;">Joining areas</span></h3><p><span style=" color:#000001;">Dragging from an area corner </span><span style=" font-weight:700; color:#000001;">outward</span><span style=" color:#000001;"> will </span><span style=" font-style:italic; color:#000001;">join</span><span style=" color:#000001;"> two areas. The area that will be closed shows a dark overlay. You can select which area will be closed by moving the mouse over areas. Release the </span><span style=" font-family:'monospace'; color:#000001;">LMB</span><span style=" color:#000001;"> to complete the join. If you press </span><span style=" font-family:'monospace'; color:#000001;">Esc</span><span style=" color:#000001;"> or </span><span style=" font-family:'monospace'; color:#000001;">RMB</span><span style=" color:#000001;"> before releasing the mouse, the operation will be canceled.</span></p><p><span style=" color:#000001;">You can only join areas if they share a common border.</span></p><p><img src=":/quickguide/join.png"/></p><h3 style=" margin-top:14px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:large; font-weight:700; color:#000001;">Switching the viewer in an area</span></h3><p><span style=" color:#000001;">Each area contains one viewer you can switch between viewers by selecting the desired viewer from the dropdown box at the top of each area.</span></p><p><img src=":/quickguide/switching.png"/></p><p><span style=" color:#000001;">There is no limitation to the amount of viewers per type that can be used.<br/></span></p><h2 style=" margin-top:16px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:x-large; font-weight:700; color:#000001;">Saving your current workspaces and area layout</span></h2><p><span style=" color:#000001;">You can save your current workspaces and layouts using the File menu.</span></p><p><span style=" color:#000001;">In the </span><span style=" font-weight:700; color:#000001;">&quot;System&quot; -&gt; &quot;Preferences...&quot;</span><span style=" color:#000001;"> menu you can select the </span><span style=" font-weight:700; color:#000001;">&quot;Workspace Layout&quot;</span><span style=" color:#000001;"> tab to select the layout that the debugger needs to use when starting.<br/>By default the selection is </span><span style=" font-style:italic; color:#000001;">&quot;First Time User&quot;</span><span style=" color:#000001;"> so that you will see this manual as first workspace when starting.<br/>Also for first time users each area split will by default show this manual, in regular usage an area split will create a duplicate of the area you started to split.</span></p><p><span style=" color:#000001;">Floating widgets aren't saved and need to be create manually each time.</span></p><p><span style=" color:#000001;"><br/></span></p><p><span style=" color:#000001;"><br/></span></p></body></html> + + + Qt::RichText + + + false + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + Qt::TextBrowserInteraction + + + + + + + + diff --git a/src/SavesJsonInterface.h b/src/SavesJsonInterface.h new file mode 100644 index 00000000..4824ab91 --- /dev/null +++ b/src/SavesJsonInterface.h @@ -0,0 +1,13 @@ +#ifndef SAVESJSONINTERFACE_H +#define SAVESJSONINTERFACE_H + +class QJsonObject; + +class SavesJsonInterface +{ +public: + virtual QJsonObject save2json() = 0; + virtual bool loadFromJson(const QJsonObject& obj) = 0; +}; + +#endif // SAVESJSONINTERFACE_H diff --git a/src/Settings.cpp b/src/Settings.cpp index 8e428352..23e5ea75 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -101,7 +101,7 @@ void Settings::getFontsFromSettings() } // read colors - for (int f =CODE_FONT; f < FONT_END; ++f) { + for (int f = CODE_FONT; f < FONT_END; ++f) { fontColors[f] = value(fontColorLocation((DebuggerFont)f), QColor(Qt::black)) .value(); diff --git a/src/SignalDispatcher.cpp b/src/SignalDispatcher.cpp new file mode 100644 index 00000000..6a2275f3 --- /dev/null +++ b/src/SignalDispatcher.cpp @@ -0,0 +1,169 @@ +#include "SignalDispatcher.h" +#include "OpenMSXConnection.h" +#include "CommClient.h" +#include "VDPDataStore.h" + +class DispatchDebugMemMapperHandler : public SimpleCommand +{ +public: + DispatchDebugMemMapperHandler(SignalDispatcher& dispatcher_) + : SimpleCommand("debug_memmapper"), + dispatcher(dispatcher_) + { + } + + void replyOk(const QString& message) override + { + dispatcher.updateSlots(message); + delete this; + } + +private: + SignalDispatcher& dispatcher; +}; + +SignalDispatcher& SignalDispatcher::instance() +{ + static SignalDispatcher oneInstance; + return oneInstance; +} + +uint8_t* SignalDispatcher::getMainMemory() +{ + return mainMemory; +} + +MemoryLayout* SignalDispatcher::getMemLayout() +{ + return &memLayout; +} + +void SignalDispatcher::setData(uint8_t* datPtr) +{ + setRegister(CpuRegs::REG_AF , datPtr[ 0] * 256 + datPtr[ 1]); + setRegister(CpuRegs::REG_BC , datPtr[ 2] * 256 + datPtr[ 3]); + setRegister(CpuRegs::REG_DE , datPtr[ 4] * 256 + datPtr[ 5]); + setRegister(CpuRegs::REG_HL , datPtr[ 6] * 256 + datPtr[ 7]); + setRegister(CpuRegs::REG_AF2, datPtr[ 8] * 256 + datPtr[ 9]); + setRegister(CpuRegs::REG_BC2, datPtr[10] * 256 + datPtr[11]); + setRegister(CpuRegs::REG_DE2, datPtr[12] * 256 + datPtr[13]); + setRegister(CpuRegs::REG_HL2, datPtr[14] * 256 + datPtr[15]); + setRegister(CpuRegs::REG_IX , datPtr[16] * 256 + datPtr[17]); + setRegister(CpuRegs::REG_IY , datPtr[18] * 256 + datPtr[19]); + setRegister(CpuRegs::REG_PC , datPtr[20] * 256 + datPtr[21]); + setRegister(CpuRegs::REG_SP , datPtr[22] * 256 + datPtr[23]); + setRegister(CpuRegs::REG_I , datPtr[24]); + setRegister(CpuRegs::REG_R , datPtr[25]); + setRegister(CpuRegs::REG_IM , datPtr[26]); + + // IFF separately to only check bit 0 for change + regsChanged[CpuRegs::REG_IFF] = (regs[CpuRegs::REG_IFF] & 1) != (datPtr[27] & 1); + regs[CpuRegs::REG_IFF] = datPtr[27]; + + // reset modifications + memset(®sModified, 0, sizeof(regsModified)); + memcpy(®sCopy, ®s, sizeof(regs)); + + emit pcChanged(regs[CpuRegs::REG_PC]); + emit spChanged(regs[CpuRegs::REG_SP]); + emit flagsChanged(regs[CpuRegs::REG_AF] & 0xFF); + emit registersUpdate(datPtr); // now tell all listeners the new values + + //now signals for the disasmView + if (disasmStatus != RESET) { + //disasmView->setProgramCounter(address, disasmStatus == SLOTS_CHANGED); + } else { + disasmStatus = PC_CHANGED; + } + emit setProgramCounter(regs[CpuRegs::REG_PC], disasmStatus == SLOTS_CHANGED); +} + +void SignalDispatcher::updateSlots(const QString& message) +{ + QStringList lines = message.split('\n'); + bool changed = false; + + // parse page slots and segments + for (int p = 0; p < 4; ++p) { + bool subSlotted = (lines[p * 2][1] != 'X'); + slotsChanged[p] = (memLayout.primarySlot [p] != lines[p * 2][0].toLatin1()-'0') || + (memLayout.secondarySlot[p] != (subSlotted ? lines[p * 2][1].toLatin1() - '0' : -1)); + changed |= slotsChanged[p]; + memLayout.primarySlot [p] = lines[p * 2][0].toLatin1()-'0'; + memLayout.secondarySlot[p] = subSlotted + ? lines[p * 2][1].toLatin1() - '0' : -1; + segmentsChanged[p] = memLayout.mapperSegment[p] != + lines[p * 2 + 1].toInt(); + memLayout.mapperSegment[p] = lines[p * 2 + 1].toInt(); + } + // parse slot layout + int l = 8; + for (int ps = 0; ps < 4; ++ps) { + memLayout.isSubslotted[ps] = lines[l++][0] == '1'; + if (memLayout.isSubslotted[ps]) { + for (int ss = 0; ss < 4; ++ss) { + memLayout.mapperSize[ps][ss] = lines[l++].toUShort(); + } + } else { + memLayout.mapperSize[ps][0] = lines[l++].toUShort(); + } + } + // parse rom blocks + for (int i = 0; i < 8; ++i, ++l) { + if (lines[l][0] == 'X') { + memLayout.romBlock[i] = -1; + } else { + memLayout.romBlock[i] = lines[l].toInt(); + } + } + + //signals to update disasmview + if (disasmStatus == PC_CHANGED) { + //disasmView->setProgramCounter(disasmAddress, slotsChanged); + emit setProgramCounter(regs[CpuRegs::REG_PC], changed); + disasmStatus = RESET; + } else { + disasmStatus = changed ? SLOTS_CHANGED : SLOTS_CHECKED; + } + + emit slotsUpdated(changed); +} + +bool SignalDispatcher::getEnableWidget() +{ + return isEnableWidget; +} + +void SignalDispatcher::refresh() +{ + CommClient::instance().sendCommand(new DispatchDebugMemMapperHandler(*this)); + VDPDataStore::instance().refresh(); +} + +void SignalDispatcher::setEnableWidget(bool value) +{ + if (isEnableWidget != value) { + isEnableWidget = value; + emit enableWidget(value); + } +} + +int SignalDispatcher::readRegister(int id) +{ + return regs[id]; +} + +void SignalDispatcher::setRegister(int id, int value) +{ + regsChanged[id] = regs[id] != value; + regs[id] = value; + if (regsChanged[id]) { + emit registerChanged(id, value); + } +} + +void SignalDispatcher::getRegister(int id, uint8_t* data) +{ + data[0] = regs[id] >> 8; + data[1] = regs[id] & 255; +} diff --git a/src/SignalDispatcher.h b/src/SignalDispatcher.h new file mode 100644 index 00000000..c9994776 --- /dev/null +++ b/src/SignalDispatcher.h @@ -0,0 +1,88 @@ +#ifndef SIGNALDISPATCHER_H +#define SIGNALDISPATCHER_H + +#include "CPURegs.h" +#include "DebuggerData.h" +#include +#include + + +/** \brief A singleton signal dispatcher + * + * Several debugger widgets like + * This signal dispatcher also does the CPU registers tracking since multi widgets need those even if there wouldn't be a CPURegsViewer + */ +class SignalDispatcher : public QObject +{ + Q_OBJECT + +public: + SignalDispatcher(const SignalDispatcher&) = delete; + SignalDispatcher& operator=(const SignalDispatcher&) = delete; + + static SignalDispatcher& instance(); + + uint8_t* getMainMemory(); + MemoryLayout* getMemLayout(); + + int readRegister(int id); + void setData(uint8_t* datPtr); + + bool getEnableWidget(); + +public slots: + void refresh(); + void setEnableWidget(bool value); + void updateSlots(const QString& message); + +private: + SignalDispatcher() = default; + ~SignalDispatcher() = default; + + void setRegister(int id, int value); + void getRegister(int id, uint8_t* data); + +signals: + void enableWidget(bool enable); + void connected(); + void settingsChanged(); + void symbolsChanged(); + void runStateEntered(); + void breakStateEntered(); + void breakpointsUpdated(); + void debuggablesChanged(const QMap& list); + // signals concerning CPU registers + void registersUpdate(uint8_t* datPtr); + void registerChanged(int id, int value); + void pcChanged(uint16_t); + void flagsChanged(quint8); + void spChanged(quint16); + // signals concerning slotselection + void slotsUpdated(bool slotsChanged); + //signals from/for the disasmView + void toggleBreakpoint(uint16_t adr); + void breakpointToggled(uint16_t adr); + void setProgramCounter(uint16_t pc, bool reload = false); + //from the GotoDialog + void setCursorAddress(uint16_t addr, int infoLine, int method); + +private: + //main 64K used by disasmview and stack + uint8_t mainMemory[65536 + 4] = {}; // 4 extra to avoid bound-checks during disassembly + MemoryLayout memLayout; + + //buffers to handle tracking of the CPU registers + int regs[16] = {}; + int regsCopy[16]; + bool regsModified[16] = {}; + bool regsChanged[16] = {}; + + //buffers to handle tracking of the slots layout + enum {RESET = 0, SLOTS_CHECKED, PC_CHANGED, SLOTS_CHANGED} disasmStatus = RESET; + bool slotsChanged[4]; + bool segmentsChanged[4]; + + bool isEnableWidget = false; +}; + +#endif diff --git a/src/SimpleHexRequest.cpp b/src/SimpleHexRequest.cpp index 3edb36d8..67a6f0ef 100644 --- a/src/SimpleHexRequest.cpp +++ b/src/SimpleHexRequest.cpp @@ -6,7 +6,7 @@ SimpleHexRequest::SimpleHexRequest( const QString& debuggable, unsigned size, - unsigned char* target, SimpleHexRequestUser& user_) + uint8_t* target, SimpleHexRequestUser& user_) : ReadDebugBlockCommand(debuggable, size, target) , offset(0) , user(user_) @@ -16,7 +16,7 @@ SimpleHexRequest::SimpleHexRequest( SimpleHexRequest::SimpleHexRequest( const QString& debuggable, unsigned offset_, unsigned size, - unsigned char* target, SimpleHexRequestUser& user_) + uint8_t* target, SimpleHexRequestUser& user_) : ReadDebugBlockCommand(debuggable, offset_, size, target) , offset(offset_) , user(user_) diff --git a/src/SimpleHexRequest.h b/src/SimpleHexRequest.h index 82488419..a661e892 100644 --- a/src/SimpleHexRequest.h +++ b/src/SimpleHexRequest.h @@ -4,13 +4,13 @@ #include "OpenMSXConnection.h" /** - * A set of helper classes if code needs to read openMSX debugables into an array of unsigned chars + * A set of helper classes if code needs to read openMSX debugables into an array of uint8_t's * if class A needs to read a given debugable then this schema will suffice * - Class A inherits from SimpleHexRequestUser * - Class A can reimplement the DataHexRequestReceived if it wants to react when new data has arrived * - Class A can reimplement the DataHexRequestCanceled if it wants to react to failures of the request * - to read the debuggable into the memory just create a new SimpleHexRequest, fi. - * new SimpleHexRequest("{VDP status regs}",0,16,statusregs, *this); + * new SimpleHexRequest("{VDP status regs}", 0, 16, statusregs, *this); * */ class SimpleHexRequestUser @@ -26,9 +26,9 @@ class SimpleHexRequest : public ReadDebugBlockCommand { public: SimpleHexRequest(const QString& debuggable, unsigned size, - unsigned char* target, SimpleHexRequestUser& user); + uint8_t* target, SimpleHexRequestUser& user); SimpleHexRequest(const QString& debuggable, unsigned offset, unsigned size, - unsigned char* target, SimpleHexRequestUser& user); + uint8_t* target, SimpleHexRequestUser& user); void replyOk(const QString& message) override; void cancel() override; diff --git a/src/SlotViewer.cpp b/src/SlotViewer.cpp index 2ba2e0e4..33471cd7 100644 --- a/src/SlotViewer.cpp +++ b/src/SlotViewer.cpp @@ -2,31 +2,11 @@ #include "DebuggerData.h" #include "OpenMSXConnection.h" #include "CommClient.h" +#include "SignalDispatcher.h" #include #include #include - -class DebugMemMapperHandler : public SimpleCommand -{ -public: - DebugMemMapperHandler(SlotViewer& viewer_) - : SimpleCommand("debug_memmapper") - , viewer(viewer_) - { - } - - void replyOk(const QString& message) override - { - viewer.updateSlots(message); - delete this; - } - -private: - SlotViewer& viewer; -}; - - SlotViewer::SlotViewer(QWidget* parent) : QFrame(parent) { @@ -155,10 +135,10 @@ void SlotViewer::paintEvent(QPaintEvent* e) if (ms > 0) { str = QString("%1").arg(memLayout->mapperSegment[i]); } else if (memLayout->romBlock[2*i] >= 0) { - if (memLayout->romBlock[2*i] == memLayout->romBlock[2*i+1]) { - str = QString("R%1").arg(memLayout->romBlock[2*i]); + if (memLayout->romBlock[2*i] == memLayout->romBlock[2 * i + 1]) { + str = QString("R%1").arg(memLayout->romBlock[2 * i]); } else { - str = QString("R%1/%2").arg(memLayout->romBlock[2*i]).arg(memLayout->romBlock[2*i+1]); + str = QString("R%1/%2").arg(memLayout->romBlock[2 * i]).arg(memLayout->romBlock[2 * i + 1]); } } else { str = "-"; @@ -181,56 +161,16 @@ void SlotViewer::paintEvent(QPaintEvent* e) QSize SlotViewer::sizeHint() const { return {headerSize1 + headerSize2 + headerSize3 + headerSize4 + frameL + frameR, - headerHeight + 4*fontMetrics().height()}; + headerHeight + 4 * fontMetrics().height()}; } void SlotViewer::refresh() { - CommClient::instance().sendCommand(new DebugMemMapperHandler(*this)); + //CommClient::instance().sendCommand(new DebugMemMapperHandler(*this)); + update(); } void SlotViewer::setMemoryLayout(MemoryLayout* ml) { memLayout = ml; } - -void SlotViewer::updateSlots(const QString& message) -{ - QStringList lines = message.split('\n'); - bool changed = false; - - // parse page slots and segments - for (int p = 0; p < 4; ++p) { - bool subSlotted = (lines[p * 2][1] != 'X'); - slotsChanged[p] = (memLayout->primarySlot [p] != lines[p * 2][0].toLatin1()-'0') || - (memLayout->secondarySlot[p] != (subSlotted ? lines[p * 2][1].toLatin1() - '0' : -1)); - changed |= slotsChanged[p]; - memLayout->primarySlot [p] = lines[p * 2][0].toLatin1()-'0'; - memLayout->secondarySlot[p] = subSlotted - ? lines[p * 2][1].toLatin1() - '0' : -1; - segmentsChanged[p] = memLayout->mapperSegment[p] != - lines[p * 2 + 1].toInt(); - memLayout->mapperSegment[p] = lines[p * 2 + 1].toInt(); - } - // parse slot layout - int l = 8; - for (int ps = 0; ps < 4; ++ps) { - memLayout->isSubslotted[ps] = lines[l++][0] == '1'; - if (memLayout->isSubslotted[ps]) { - for (int ss = 0; ss < 4; ++ss) { - memLayout->mapperSize[ps][ss] = lines[l++].toUShort(); - } - } else { - memLayout->mapperSize[ps][0] = lines[l++].toUShort(); - } - } - // parse rom blocks - for (int i = 0; i < 8; ++i, ++l) { - if (lines[l][0] == 'X') - memLayout->romBlock[i] = -1; - else - memLayout->romBlock[i] = lines[l].toInt(); - } - update(); - emit slotsUpdated(changed); -} diff --git a/src/SlotViewer.h b/src/SlotViewer.h index 04308cba..d3fee6c2 100644 --- a/src/SlotViewer.h +++ b/src/SlotViewer.h @@ -13,7 +13,6 @@ class SlotViewer : public QFrame SlotViewer(QWidget* parent = nullptr); void setMemoryLayout(MemoryLayout* ml); - void updateSlots(const QString& message); QSize sizeHint() const override; @@ -36,6 +35,10 @@ class SlotViewer : public QFrame bool slotsChanged[4]; bool segmentsChanged[4]; + +signals: +// void slotsUpdated(bool slotsChanged); +// void contentsChanged(); }; #endif // SLOTVIEWER_H diff --git a/src/SpriteViewer.cpp b/src/SpriteViewer.cpp index 4fc667a3..23da73bc 100644 --- a/src/SpriteViewer.cpp +++ b/src/SpriteViewer.cpp @@ -1,29 +1,8 @@ #include "SpriteViewer.h" #include "VDPDataStore.h" #include "VramSpriteView.h" -#include "PaletteDialog.h" #include "Convert.h" - -// static to feed to PaletteDialog and be used when VDP colors aren't selected -uint8_t SpriteViewer::defaultPalette[32] = { -// RB G - 0x00, 0, - 0x00, 0, - 0x11, 6, - 0x33, 7, - 0x17, 1, - 0x27, 3, - 0x51, 1, - 0x27, 6, - 0x71, 1, - 0x73, 3, - 0x61, 6, - 0x64, 6, - 0x11, 4, - 0x65, 2, - 0x55, 5, - 0x77, 7, -}; +#include "MSXPalette.h" SpriteViewer::SpriteViewer(QWidget* parent) : QDialog(parent) @@ -42,7 +21,6 @@ SpriteViewer::SpriteViewer(QWidget* parent) connect(ui->cb_mag, qOverload(&QComboBox::currentIndexChanged), this, &SpriteViewer::on_cb_mag_currentIndexChanged); connect(ui->cb_alwaysShowColorTable, &QCheckBox::toggled, this, &SpriteViewer::on_cb_alwaysShowColorTable_toggled); connect(ui->useVDPPalette, &QCheckBox::stateChanged, this, &SpriteViewer::on_useVDPPalette_stateChanged); - connect(ui->editPaletteButton, &QPushButton::clicked, this, &SpriteViewer::on_editPaletteButton_clicked); const QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); ui->plainTextEdit->setFont(fixedFont); @@ -104,7 +82,7 @@ SpriteViewer::SpriteViewer(QWidget* parent) imageWidgetColor->setVramSource(VDPDataStore::instance().getVramPointer()); - setPaletteSource(VDPDataStore::instance().getPalettePointer(), true); + setPaletteSource(VDPDataStore::instance().getPalette(paletteVDP), true); setCorrectVDPData(); setCorrectEnabled(ui->useVDPRegisters->isChecked()); @@ -156,7 +134,7 @@ SpriteViewer::SpriteViewer(QWidget* parent) VDPDataStore::instance().refresh(); } -void SpriteViewer::setPaletteSource(const uint8_t* palSource, bool useVDP) +void SpriteViewer::setPaletteSource(MSXPalette* palSource, bool useVDP) { imageWidget->setPaletteSource(palSource, useVDP); imageWidgetSingle->setPaletteSource(palSource, useVDP); @@ -204,13 +182,13 @@ void SpriteViewer::spatwidget_mouseMoveEvent(int /*x*/, int /*y*/, int character const uint8_t* vramBase = VDPDataStore::instance().getVramPointer(); if (vramBase != nullptr && character < 32) { auto addr = spAtAddr + 4 * character; - ui->label_spat_posx->setText(QString::number(vramBase[addr + 0])); - ui->label_spat_posy->setText(QString::number(vramBase[addr + 1])); + ui->label_spat_posy->setText(QString::number(vramBase[addr + 0])); + ui->label_spat_posx->setText(QString::number(vramBase[addr + 1])); ui->label_spat_pattern->setText(QString::number(vramBase[addr + 2])); ui->label_spat_color->setText(QString::number(vramBase[addr + 3])); } else { - ui->label_spat_posx->setText(""); ui->label_spat_posy->setText(""); + ui->label_spat_posx->setText(""); ui->label_spat_pattern->setText(""); ui->label_spat_color->setText(""); } @@ -423,28 +401,14 @@ void SpriteViewer::on_cb_alwaysShowColorTable_toggled(bool /*checked*/) void SpriteViewer::on_useVDPPalette_stateChanged(int state) { - const uint8_t* palette = VDPDataStore::instance().getPalettePointer(); if (state == Qt::Checked) { - setPaletteSource(palette, true); + setPaletteSource(VDPDataStore::instance().getPalette(paletteVDP), true); } else { - if (palette != nullptr) memcpy(defaultPalette, palette, 32); - setPaletteSource(defaultPalette, false); + setPaletteSource(VDPDataStore::instance().getPalette(paletteSprites), false); } imageWidget->refresh(); imageWidgetSingle->refresh(); imageWidgetSpat->refresh(); imageWidgetColor->refresh(); - ui->editPaletteButton->setEnabled(state != Qt::Checked); } -void SpriteViewer::on_editPaletteButton_clicked(bool /*checked*/) -{ - auto* p = new PaletteDialog(); - p->setPalette(defaultPalette); - p->setAutoSync(true); - connect(p, &PaletteDialog::paletteSynced, imageWidget, &VramSpriteView::refresh); - connect(p, &PaletteDialog::paletteSynced, imageWidgetSingle, &VramSpriteView::refresh); - connect(p, &PaletteDialog::paletteSynced, imageWidgetSpat, &VramSpriteView::refresh); - connect(p, &PaletteDialog::paletteSynced, imageWidgetColor, &VramSpriteView::refresh); - p->show(); -} diff --git a/src/SpriteViewer.h b/src/SpriteViewer.h index 9280ce98..93e77f0e 100644 --- a/src/SpriteViewer.h +++ b/src/SpriteViewer.h @@ -7,6 +7,7 @@ #include class VramSpriteView; +class MSXPalette; class SpriteViewer : public QDialog { @@ -36,9 +37,8 @@ class SpriteViewer : public QDialog void on_cb_mag_currentIndexChanged(int index); void on_cb_alwaysShowColorTable_toggled(bool checked); void on_useVDPPalette_stateChanged(int state); - void on_editPaletteButton_clicked(bool checked); - void setPaletteSource(const uint8_t* palSource, bool useVDP); + void setPaletteSource(MSXPalette* palSource, bool useVDP); void decodeVDPregs(); void setCorrectEnabled(bool checked); void setCorrectVDPData(); diff --git a/src/SpriteViewer.ui b/src/SpriteViewer.ui index c631c874..fca68996 100644 --- a/src/SpriteViewer.ui +++ b/src/SpriteViewer.ui @@ -29,16 +29,6 @@ - - - - false - - - Edit palette.. - - - diff --git a/src/StackViewer.cpp b/src/StackViewer.cpp index 276ebfe6..4f6495bc 100644 --- a/src/StackViewer.cpp +++ b/src/StackViewer.cpp @@ -11,7 +11,7 @@ class StackRequest : public ReadDebugBlockCommand { public: StackRequest(unsigned offset_, unsigned size, - unsigned char* target, StackViewer& viewer_) + uint8_t* target, StackViewer& viewer_) : ReadDebugBlockCommand("memory", offset_, size, target) , offset(offset_) , viewer(viewer_) @@ -41,10 +41,12 @@ StackViewer::StackViewer(QWidget* parent) : QFrame(parent) , wheelRemainder(0) { + static int count = 0; + setObjectName(QString("StackViewer_%1").arg(count++)); setFrameStyle(WinPanel | Sunken); setFocusPolicy(Qt::StrongFocus); setBackgroundRole(QPalette::Base); - setSizePolicy(QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred)); + setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred)); setFont(Settings::get().font(Settings::HEX_FONT)); @@ -58,7 +60,7 @@ StackViewer::StackViewer(QWidget* parent) frameL = frameT = frameB = frameWidth(); frameR = frameL + vertScrollBar->sizeHint().width(); - setMinimumHeight(frameT+frameB+fontMetrics().height()); + setMinimumHeight(frameT + frameB + fontMetrics().height()); connect(vertScrollBar, &QScrollBar::valueChanged, this, &StackViewer::setLocation); @@ -66,7 +68,7 @@ StackViewer::StackViewer(QWidget* parent) QSize StackViewer::sizeHint() const { - return {frameL + 4 + fontMetrics().horizontalAdvance("FFFFWFFFF ") + 4 + frameR, + return {frameL + 4 + fontMetrics().horizontalAdvance("FFFFWFFFF ") + 4 + frameR, frameT + 8 * fontMetrics().height() + frameB}; } @@ -117,7 +119,7 @@ void StackViewer::paintEvent(QPaintEvent* e) int d = fontMetrics().descent(); // set font - p.setPen(Qt::black); + //p.setPen(palette().color(QPalette::Text)); // calc and set drawing bounds QRect r(e->rect()); @@ -132,16 +134,24 @@ void StackViewer::paintEvent(QPaintEvent* e) // calc layout (not optimal) int xAddr = frameL + 8; - int xStack = xAddr + fontMetrics().horizontalAdvance("FFFFF"); + int xStack = xAddr + fontMetrics().horizontalAdvance("FFFFFF"); int y = frameT + h - 1; int address = topAddress; for (int i = 0; i < int(ceil(visibleLines)); ++i) { + // draw background + if (address == stackPointer){ + //p.fillRect(frameL, y-h, width() - frameL- frameR, h, + // palette().color(QPalette::Highlight)); + p.setPen(palette().color(QPalette::LinkVisited)); + } else { + p.setPen(palette().color(QPalette::Text)); + } // print address - QString hexStr; - hexStr = QString("%1").arg(address, 4, 16, QChar('0')).toUpper(); + QString hexStr = QString("%1").arg(isEnabled()?address:0, 4, 16, QChar('0')).toUpper(); p.drawText(xAddr, y - d, hexStr); - hexStr = QString("%1").arg(memory[address + 1] << 8 | memory[address], 4, 16, QChar('0')).toUpper(); + int val = isEnabled() ? (memory[address + 1] << 8 | memory[address]) : 0; + hexStr = QString("%1").arg(val, 4, 16, QChar('0')).toUpper(); p.drawText(xStack, y - d, hexStr); y += h; address += 2; @@ -149,7 +159,7 @@ void StackViewer::paintEvent(QPaintEvent* e) } } -void StackViewer::setData(unsigned char* memPtr, int memLength) +void StackViewer::setData(uint8_t* memPtr, int memLength) { memory = memPtr; memoryLength = memLength; @@ -158,6 +168,7 @@ void StackViewer::setData(unsigned char* memPtr, int memLength) void StackViewer::setLocation(int addr) { + qDebug() << "StackViewer::setLocation(int " << addr << ") wdgt->objectName() " << objectName(); if (waitingForData) { return; // ignore } @@ -167,21 +178,28 @@ void StackViewer::setLocation(int addr) if (start + size >= memoryLength) { size = memoryLength - start; } - auto* req = new StackRequest(start, size, &memory[start], *this); - CommClient::instance().sendCommand(req); - waitingForData = true; + qDebug() << "StackViewer::setLocation isEnabled() " << isEnabled(); + if (isEnabled()) { //only request data when enabled + auto* req = new StackRequest(start, size, &memory[start], *this); + CommClient::instance().sendCommand(req); + waitingForData = true; + } } void StackViewer::setStackPointer(quint16 addr) { + qDebug() << "StackViewer::setStackPointer(quint16 " << addr << ") wdgt->objectName() " << objectName(); stackPointer = addr; setScrollBarValues(); vertScrollBar->setValue(addr); + qDebug() << "StackViewer::setStackPointer is now calling "; setLocation(addr); } void StackViewer::memDataTransferred(StackRequest* r) { + qDebug() << "StackViewer::memdataTransfered() wdgt->objectName() " << objectName(); + topAddress = r->offset; update(); diff --git a/src/StackViewer.h b/src/StackViewer.h index 71d02a01..7cdbc106 100644 --- a/src/StackViewer.h +++ b/src/StackViewer.h @@ -13,7 +13,7 @@ class StackViewer : public QFrame public: StackViewer(QWidget* parent = nullptr); - void setData(unsigned char* memPtr, int memLength); + void setData(uint8_t* memPtr, int memLength); QSize sizeHint() const override; void setLocation(int addr); @@ -39,7 +39,7 @@ class StackViewer : public QFrame int stackPointer; int topAddress; - unsigned char* memory; + uint8_t* memory; int memoryLength; friend class StackRequest; diff --git a/src/SymbolTable.cpp b/src/SymbolTable.cpp index ecd6795f..8d59a1e1 100644 --- a/src/SymbolTable.cpp +++ b/src/SymbolTable.cpp @@ -150,10 +150,12 @@ Symbol* SymbolTable::getAddressSymbol(int addr, MemoryLayout* ml) Symbol* SymbolTable::getAddressSymbol(const QString& label, bool case_sensitive) { for (auto it = addressSymbols.begin(); it != addressSymbols.end(); ++it) { - if (it.value()->text().compare(label, Qt::CaseSensitive)==0) + if (it.value()->text().compare(label, Qt::CaseSensitive) == 0) { return it.value(); - if (!case_sensitive && it.value()->text().compare(label, Qt::CaseInsensitive)==0) + } + if (!case_sensitive && it.value()->text().compare(label, Qt::CaseInsensitive) == 0) { return it.value(); + } } return nullptr; } @@ -620,16 +622,16 @@ void SymbolTable::saveSymbols(QXmlStreamWriter& xml) xml.writeStartElement("SymbolFile"); switch (symbolFiles[i].fileType) { case TNIASM0_FILE: - xml.writeAttribute("type","tniasm0"); + xml.writeAttribute("type", "tniasm0"); break; case TNIASM1_FILE: - xml.writeAttribute("type","tniasm1"); + xml.writeAttribute("type", "tniasm1"); break; case ASMSX_FILE: - xml.writeAttribute("type","asmsx"); + xml.writeAttribute("type", "asmsx"); break; case LINKMAP_FILE: - xml.writeAttribute("type","linkmap"); + xml.writeAttribute("type", "linkmap"); break; default: break; diff --git a/src/TabRenamerHelper.cpp b/src/TabRenamerHelper.cpp new file mode 100644 index 00000000..d3437634 --- /dev/null +++ b/src/TabRenamerHelper.cpp @@ -0,0 +1,88 @@ +#include "TabRenamerHelper.h" +#include +#include +#include +#include +#include + +TabRenamerHelper::TabRenamerHelper(QTabWidget *parent) + : QObject{parent}, workspaces{parent}, + tabLineEdit{nullptr}, editedTab{-1} +{ + connect(workspaces->tabBar(), &QTabBar::tabBarDoubleClicked, + this, &TabRenamerHelper::tabBarDoubleClicked); + workspaces->tabBar()->installEventFilter(this); +// workspaces->installEventFilter(this); + +} + +void TabRenamerHelper::tabBarDoubleClicked(int index) +{ + editedTab = index; + if (index == -1) { + return; + } + QRect r = workspaces->tabBar()->tabRect(index); + QString t = workspaces->tabBar()->tabText(index); + tabLineEdit = new QLineEdit{workspaces->tabBar()}; + tabLineEdit->show(); + tabLineEdit->move(r.topLeft()); + tabLineEdit->resize(r.size()); + tabLineEdit->setText(t); + tabLineEdit->selectAll(); + tabLineEdit->setFocus(); + tabLineEdit->installEventFilter(this); + connect(tabLineEdit, &QLineEdit::editingFinished, this, &TabRenamerHelper::tabNameEditingFinished); +} + + +void TabRenamerHelper::tabNameEditingFinished() +{ + QString newname = tabLineEdit->text(); + if (!newname.isEmpty()) { + workspaces->tabBar()->setTabText(editedTab, newname); + } + tabLineEdit->deleteLater(); + tabLineEdit = nullptr; +} + +bool TabRenamerHelper::eventFilter(QObject* obj, QEvent* event) +{ + bool result = QObject::eventFilter(obj, event); + QTabBar* bar = workspaces->tabBar(); + if (obj == bar) { + if (event->type() == QEvent::MouseButtonPress) { + // the user is trying to move away from editing by clicking another tab + QMouseEvent* me = static_cast(event); + int clickedTabId = bar->tabAt(me->pos()); + if (editedTab != -1 && editedTab != clickedTabId && tabLineEdit) { + emit tabLineEdit->editingFinished(); + return false; + } + } + if (event->type() == QEvent::MouseButtonDblClick) { + // Perhaps we need to start a new name editing action? + QMouseEvent* me = static_cast(event); + int clickedTabId = bar->tabAt(me->pos()); + if (clickedTabId == -1) { + return result; + } + if (editedTab != -1 && editedTab != clickedTabId && tabLineEdit) { + emit tabLineEdit->editingFinished(); + return false; + } + } + } + //handle some events on the line edit to make it behave itself nicely as a rename editor + if (obj == tabLineEdit && event->type() == QEvent::KeyPress) { + QKeyEvent* ke = static_cast(event); + if (ke->key() == Qt::Key_Escape) { + disconnect(tabLineEdit, &QLineEdit::editingFinished, this, &TabRenamerHelper::tabNameEditingFinished); + tabLineEdit->deleteLater(); + tabLineEdit = nullptr; + return true; // no further handling of this event is required + } + } + + return result; +} diff --git a/src/TabRenamerHelper.h b/src/TabRenamerHelper.h new file mode 100644 index 00000000..86d3b2d9 --- /dev/null +++ b/src/TabRenamerHelper.h @@ -0,0 +1,27 @@ +#ifndef TABRENAMERHELPER_H +#define TABRENAMERHELPER_H + +#include + +class QLineEdit; +class QTabWidget; + +class TabRenamerHelper : public QObject +{ + Q_OBJECT +public: + explicit TabRenamerHelper(QTabWidget* parent = nullptr); + +public slots: + void tabBarDoubleClicked(int index); + void tabNameEditingFinished(); + +protected: + bool eventFilter(QObject* obj, QEvent* event) override; + + QTabWidget* workspaces; + QLineEdit* tabLineEdit; + int editedTab; +}; + +#endif // TABRENAMERHELPER_H diff --git a/src/TileViewer.cpp b/src/TileViewer.cpp index 7bbe6c81..3a82448a 100644 --- a/src/TileViewer.cpp +++ b/src/TileViewer.cpp @@ -2,33 +2,9 @@ #include "ui_TileViewer.h" #include "VramTiledView.h" #include "VDPDataStore.h" -#include "PaletteDialog.h" #include "Convert.h" #include - -// static to feed to PaletteDialog and be used when VDP colors aren't selected -static uint8_t currentPalette[32] = { -// RB G - 0x00, 0, - 0x00, 0, - 0x11, 6, - 0x33, 7, - 0x17, 1, - 0x27, 3, - 0x51, 1, - 0x27, 6, - 0x71, 1, - 0x73, 3, - 0x61, 6, - 0x64, 6, - 0x11, 4, - 0x65, 2, - 0x55, 5, - 0x77, 7, -}; - - TileViewer::TileViewer(QWidget* parent) : QDialog(parent), image4label(32, 32, QImage::Format_RGB32) { @@ -43,7 +19,6 @@ TileViewer::TileViewer(QWidget* parent) connect(cb_color0, &QCheckBox::stateChanged, this, &TileViewer::on_cb_color0_stateChanged); connect(useVDPRegisters, &QCheckBox::stateChanged, this, &TileViewer::on_useVDPRegisters_stateChanged); - connect(editPaletteButton, &QPushButton::clicked, this, &TileViewer::on_editPaletteButton_clicked); connect(useVDPPalette, &QCheckBox::stateChanged, this, &TileViewer::on_useVDPPalette_stateChanged); connect(zoomLevel, qOverload(&QDoubleSpinBox::valueChanged), this, &TileViewer::on_zoomLevel_valueChanged); @@ -83,7 +58,11 @@ TileViewer::TileViewer(QWidget* parent) scrollArea->setWidget(imageWidget); const auto* vram = VDPDataStore::instance().getVramPointer(); - imageWidget->setPaletteSource(currentPalette); + auto* palette = VDPDataStore::instance().getPalette(paletteVDP); + //imageWidget->setNameTableAddress(0); + //imageWidget->setPatternTableAddress(0); + //imageWidget->setColorTableAddress(0); + imageWidget->setPaletteSource(palette); imageWidget->setVramSource(vram); imageWidget->setUseBlink(cb_blinkcolors->isChecked()); imageWidget->setDrawGrid(cb_drawgrid->isChecked()); @@ -99,7 +78,7 @@ TileViewer::TileViewer(QWidget* parent) void TileViewer::VDPDataStoreDataRefreshed() { if (useVDPPalette->isChecked()) { - imageWidget->setPaletteSource(VDPDataStore::instance().getPalettePointer()); + imageWidget->setPaletteSource(VDPDataStore::instance().getPalette(paletteVDP)); } decodeVDPregs(); } @@ -224,7 +203,7 @@ void TileViewer::decodeVDPregs() le_patterntable->setText(hexValue(patternTable, 4)); le_colortable->setText(hexValue(colorTable, 4)); cb_blinkcolors->setChecked(usesBlink ? regs[13] & 0xf0 : false); - cb_screenrows->setCurrentIndex((regs[9] & 128) ? 1 : 0 ); + cb_screenrows->setCurrentIndex((regs[9] & 128) ? 1 : 0); sp_bordercolor->setValue(regs[7] & 15); cb_color0->setChecked(regs[8] & 32); } @@ -329,33 +308,14 @@ void TileViewer::on_useVDPRegisters_stateChanged(int state) cb_color0->setEnabled(useManual); } -void TileViewer::on_editPaletteButton_clicked(bool /*checked*/) -{ - auto* p = new PaletteDialog(); - p->setPalette(currentPalette); - p->setAutoSync(true); - connect(p, &PaletteDialog::paletteSynced, imageWidget, &VramTiledView::refresh); - connect(p, &PaletteDialog::paletteSynced, this, &TileViewer::update_label_characterimage); - p->show(); - //useVDPPalette->setChecked(false); - //QMessageBox::information( - // this, - // "Not yet implemented", - // "Sorry, the palette editor is not yet implemented, " - // "only disabling 'Use VDP palette registers' for now"); -} - void TileViewer::on_useVDPPalette_stateChanged(int state) { - const uint8_t* palette = VDPDataStore::instance().getPalettePointer(); if (state) { - imageWidget->setPaletteSource(palette); + imageWidget->setPaletteSource(VDPDataStore::instance().getPalette(paletteVDP)); } else { - if (palette) memcpy(currentPalette, palette, 32); - imageWidget->setPaletteSource(currentPalette); + imageWidget->setPaletteSource(VDPDataStore::instance().getPalette(paletteTiles)); } imageWidget->refresh(); - editPaletteButton->setEnabled(!state); } void TileViewer::on_zoomLevel_valueChanged(double d) diff --git a/src/TileViewer.h b/src/TileViewer.h index cbcf83f5..6614e481 100644 --- a/src/TileViewer.h +++ b/src/TileViewer.h @@ -28,7 +28,6 @@ class TileViewer : public QDialog, private Ui::TileViewer void on_cb_color0_stateChanged(int state); void on_useVDPRegisters_stateChanged(int state); - void on_editPaletteButton_clicked(bool checked); void on_useVDPPalette_stateChanged(int state); void on_zoomLevel_valueChanged(double d); diff --git a/src/VDPCommandRegViewer.cpp b/src/VDPCommandRegViewer.cpp index 5fbb7ad1..1cb60202 100644 --- a/src/VDPCommandRegViewer.cpp +++ b/src/VDPCommandRegViewer.cpp @@ -250,8 +250,8 @@ void VDPCommandRegViewer::on_lineEdit_r46_editingFinished() void VDPCommandRegViewer::refresh() { - //new SimpleHexRequest("{VDP regs}",0,64,regs, *this); - //new SimpleHexRequest("{VDP status regs}",0,16,regs, *this); + //new SimpleHexRequest("{VDP regs}", 0, 64, regs, *this); + //new SimpleHexRequest("{VDP status regs}", 0, 16, regs, *this); // now combined in one request: new SimpleHexRequest( "debug_bin2hex " diff --git a/src/VDPDataStore.cpp b/src/VDPDataStore.cpp index 1652b5dc..f8c39966 100644 --- a/src/VDPDataStore.cpp +++ b/src/VDPDataStore.cpp @@ -38,9 +38,7 @@ class VDPDataStoreVRAMSizeCheck : public SimpleCommand void replyOk(const QString& message) override { - //printf("dataStore.vramSize %i\n",dataStore.vramSize); dataStore.vramSize = message.toInt(); - //printf("dataStore.vramSize %i\n",dataStore.vramSize); dataStore.refresh2(); delete this; } @@ -54,6 +52,34 @@ class VDPDataStoreVRAMSizeCheck : public SimpleCommand }; +class VDPDataStoreVDPVersionCheck : public SimpleCommand +{ +public: + VDPDataStoreVDPVersionCheck(VDPDataStore& dataStore_) + : SimpleCommand("dict get [machine_info device VDP] version") + , dataStore(dataStore_) + { + } + + void replyOk(const QString& message) override + { + if (!dataStore.machineVDPVersionString || message != QString::fromStdString(*dataStore.machineVDPVersionString)){ + dataStore.machineVDPVersionString = message.toStdString(); + emit dataStore.VDPVersionChanged(message); + }; + delete this; + } + void replyNok(const QString& /*message*/) override + { + dataStore.machineVDPVersionString = "unknown"; + delete this; + } + +private: + VDPDataStore& dataStore; +}; + + static constexpr unsigned MAX_VRAM_SIZE = 0x30000; static constexpr unsigned MAX_TOTAL_SIZE = MAX_VRAM_SIZE + 32 + 16 + 64 + 2; @@ -83,6 +109,7 @@ void VDPDataStore::refresh() void VDPDataStore::refresh1() { + CommClient::instance().sendCommand(new VDPDataStoreVDPVersionCheck(*this)); CommClient::instance().sendCommand(new VDPDataStoreVRAMSizeCheck(*this)); } @@ -100,6 +127,10 @@ void VDPDataStore::refresh2() void VDPDataStore::DataHexRequestReceived() { + //update MSXPalette that contains VDP palette + palettes[0].setPalette(&vram[vramSize]); + palettes[0].syncToMSX = true; //NO AUTOSYNC UNTIL WE GET THE PALETTE! + emit dataRefreshed(); } @@ -121,7 +152,17 @@ const uint8_t* VDPDataStore::getRegsPointer() const } const uint8_t* VDPDataStore::getVdpVramPointer() const { - return &vram[vramSize + 32 + 16 + 64]; + return &vram[vramSize + 32 + 16 + 64]; +} + +MSXPalette* VDPDataStore::getPalette(int index) +{ + return &palettes[index]; +} + +std::optional VDPDataStore::getVDPVersion() const +{ + return machineVDPVersionString; } size_t VDPDataStore::getVRAMSize() const diff --git a/src/VDPDataStore.h b/src/VDPDataStore.h index 9d9d7f9d..c35a8a22 100644 --- a/src/VDPDataStore.h +++ b/src/VDPDataStore.h @@ -1,6 +1,7 @@ #ifndef VDPDATASTORE_H #define VDPDATASTORE_H +#include "MSXPalette.h" #include "SimpleHexRequest.h" #include #include @@ -8,6 +9,13 @@ #include #include +/** + * @brief The VDPDataStore class is a singleton that keeps track of all VDP related data for all the viewers + * + * It does keep track of the VRAM, registers, palettes and statusregisters + * For the viewers it keeps track of 4 palettes. + * The actual openMSX palette, a palette or the tileviewers, the bitmap viewers and the spriteviewers + */ class VDPDataStore : public QObject, public SimpleHexRequestUser { Q_OBJECT @@ -20,12 +28,15 @@ class VDPDataStore : public QObject, public SimpleHexRequestUser const uint8_t* getStatusRegsPointer() const; const uint8_t* getVdpVramPointer() const; + MSXPalette* getPalette(int index); + std::optional getVDPVersion() const; size_t getVRAMSize() const; void refresh(); signals: - void dataRefreshed(); // The refresh got the new data + void dataRefreshed(); // The refresh got the new data + void VDPVersionChanged(QString VDPversion); // New VDP version received during refresh /** This might become handy later on, for now we only need the dataRefreshed * @@ -45,13 +56,16 @@ class VDPDataStore : public QObject, public SimpleHexRequestUser void refresh2(); private: + MSXPalette palettes[4]; std::vector vram; size_t vramSize; std::optional debuggableNameVRAM; // VRAM debuggable name + std::optional machineVDPVersionString; // VRAM debuggable name friend class VDPDataStoreVersionCheck; friend class VDPDataStoreVRAMSizeCheck; + friend class VDPDataStoreVDPVersionCheck; }; #endif // VDPDATASTORE_H diff --git a/src/VDPRegViewer.cpp b/src/VDPRegViewer.cpp index 898b3298..854be80b 100644 --- a/src/VDPRegViewer.cpp +++ b/src/VDPRegViewer.cpp @@ -38,7 +38,8 @@ VDPRegViewer::VDPRegViewer(QWidget *parent) { setupUi(this); connect(VDPcomboBox, qOverload(&QComboBox::currentIndexChanged), this, &VDPRegViewer::on_VDPcomboBox_currentIndexChanged); - + connect(cb_useOpenMSXVDP, &QCheckBox::stateChanged, this, &VDPRegViewer::on_cb_useOpenMSXVDP_stateChanged); + connect(&VDPDataStore::instance(),&VDPDataStore::VDPVersionChanged, this, &VDPRegViewer::on_VDPVersionChanged); vdpId = 99; // make sure that we parse the first time the status registers are read vdpId = VDP_V9958; //quick hack for now @@ -362,23 +363,24 @@ void VDPRegViewer::decodeVDPRegs() "let's find out 27", "SCREEN 8", "let's find out 29", "let's find out 30", "let's find out 31" }; - int basicscreen; - switch (m) { - case 2: basicscreen=0;break; - case 0: basicscreen=1;break; - case 4: basicscreen=2;break; - case 1: basicscreen=3;break; - case 8: basicscreen=4;break; - case 12: basicscreen=5;break; - case 16: basicscreen=6;break; - case 20: basicscreen=7;break; - case 28: basicscreen=8;break; - case 10: basicscreen=9;break; //screen 0 width 80 - default: basicscreen=10; //not a valid basic screen mode - }; + int basicScreen = [&] { + switch (m) { + case 2: return 0; + case 0: return 1; + case 4: return 2; + case 1: return 3; + case 8: return 4; + case 12: return 5; + case 16: return 6; + case 20: return 7; + case 28: return 8; + case 10: return 9; // screen 0 width 80 + default: return 10; // not a valid basic screen mode + } + }(); // 2 vdps, 11 basic screenmodes, 12 registers - static const int mustbeone[][11][12] = { + static const int mustBeOne[][11][12] = { { // TMS99x8 MSX1 VDP chip {0, 0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0,0,0,0}, // screen 0; {0, 0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0,0,0,0}, // screen 1; @@ -447,12 +449,12 @@ void VDPRegViewer::decodeVDPRegs() auto* i = findChild(name); i->setChecked(regs[r] & (1 << b)); if (r < 12) { - i->mustBeSet(mustbeone[(vdpId == VDP_TMS99X8) ? 0 : 1][basicscreen][r] & (1 << b)); + i->mustBeSet(mustBeOne[(vdpId == VDP_TMS99X8) ? 0 : 1][basicScreen][r] & (1 << b)); // if A8 of R5 is a 'must-be-one' then we indicate this for A9 also // This bit is cleared in the table since it isn't used in the Sprite // Attribute Table address calculation otherwise, but will only impact the // Sprite Color Table - if (r == 5 && b == 2 && vdpId != VDP_TMS99X8 && mustbeone[1][basicscreen][5]) { + if (r == 5 && b == 2 && vdpId != VDP_TMS99X8 && mustBeOne[1][basicScreen][5]) { i->mustBeSet(true); } } @@ -525,17 +527,17 @@ void VDPRegViewer::decodeVDPRegs() // some variables used for readability of the code below int row = vdpId == VDP_TMS99X8 ? 0 : 1; QString regtexttext; - int must,must2; + int must, must2; // the pattern name table address - must=mustbeone[row][basicscreen][2] ; - long nameTable = ((255^must) & bitsused[row][basicscreen][2] & regs[2]) << 10; + must = mustBeOne[row][basicScreen][2] ; + long nameTable = ((255^must) & bitsused[row][basicScreen][2] & regs[2]) << 10; if ((m == 20 || m == 28) && vdpId != VDP_TMS99X8) nameTable = ((nameTable & 0xffff) << 1) | ((nameTable & 0x10000) >> 16); regtexttext = hex5(nameTable); if ((must & regs[2]) != must) { - label_dec_r2->setText("" + regtexttext +""); + label_dec_r2->setText("" + regtexttext + ""); label_dec_r2->setToolTip("Some of the obligatory 1 bits are reset!"); } else { label_dec_r2->setText(regtexttext); @@ -543,17 +545,17 @@ void VDPRegViewer::decodeVDPRegs() } // the color table address - must=mustbeone[row][basicscreen][3] ; - must2=mustbeone[row][basicscreen][10] ; - regtexttext=hex5( + must = mustBeOne[row][basicScreen][3]; + must2 = mustBeOne[row][basicScreen][10]; + regtexttext = hex5( ( - ((255 ^ must ) & bitsused[row][basicscreen][ 3] & regs[ 3]) << 6 + ((255 ^ must ) & bitsused[row][basicScreen][ 3] & regs[ 3]) << 6 ) | ( - ((255 ^ must2) & bitsused[row][basicscreen][10] & regs[10]) << 14 + ((255 ^ must2) & bitsused[row][basicScreen][10] & regs[10]) << 14 ) ); if (((must & regs[3]) != must) || ((must2 & regs[10]) != must2)) { - label_dec_r3->setText("" + regtexttext +""); + label_dec_r3->setText("" + regtexttext + ""); label_dec_r3->setToolTip("Some of the obligatory 1 bits are reset!"); } else { label_dec_r3->setText(regtexttext); @@ -561,13 +563,13 @@ void VDPRegViewer::decodeVDPRegs() } // the pattern generator address - must=mustbeone[row][basicscreen][4] ; - regtexttext=hex5( + must = mustBeOne[row][basicScreen][4] ; + regtexttext = hex5( ( - (255 ^ must) & bitsused[row][basicscreen][4] & regs[4]) << 11 + (255 ^ must) & bitsused[row][basicScreen][4] & regs[4]) << 11 ); if ((must & regs[4]) != must) { - label_dec_r4->setText("" + regtexttext +""); + label_dec_r4->setText("" + regtexttext + ""); label_dec_r4->setToolTip("Some of the obligatory 1 bits are reset!"); } else { label_dec_r4->setText(regtexttext); @@ -575,30 +577,30 @@ void VDPRegViewer::decodeVDPRegs() } // the sprite attribute tabel address - must = mustbeone[row][basicscreen][ 5]; - must2 = mustbeone[row][basicscreen][11]; + must = mustBeOne[row][basicScreen][ 5]; + must2 = mustBeOne[row][basicScreen][11]; regtexttext = hex5( ( - (((255^must) & bitsused[row][basicscreen][ 5] & regs[ 5]) << 7) | - (((255^must2) & bitsused[row][basicscreen][11] & regs[11]) << 15)) + (((255^must) & bitsused[row][basicScreen][ 5] & regs[ 5]) << 7) | + (((255^must2) & bitsused[row][basicScreen][11] & regs[11]) << 15)) ); if (((must & regs[5]) != must) || ((must2 & regs[11]) != must2)) { - label_dec_r5->setText("" + regtexttext +""); + label_dec_r5->setText("" + regtexttext + ""); label_dec_r5->setToolTip("Some of the obligatory 1 bits are reset!"); } else { label_dec_r5->setText(regtexttext); label_dec_r5->setToolTip(nullptr); - }; + } // special case for sprite mode 2 if (must && !(4 & regs[ 5])) { // only in mode2 there are some 'must'-bits :-) - label_dec_r5->setText("" + regtexttext +""); + label_dec_r5->setText("" + regtexttext + ""); label_dec_r5->setToolTip("Bit A9 should be set, to obtain the Sprite Color Table address this bit is masked
With the current bit reset the Color Tabel will use the same address as the Sprite Attribute Table!"); - }; + } // the sprite pattern generator address label_dec_r6->setText(hex5( - ((255^mustbeone[row][basicscreen][6]) & bitsused[row][basicscreen][6] & regs[6]) << 11)); + ((255^mustBeOne[row][basicScreen][6]) & bitsused[row][basicScreen][6] & regs[6]) << 11)); // end of address calculations @@ -907,8 +909,8 @@ void VDPRegViewer::connectHighLights() void VDPRegViewer::refresh() { - //new SimpleHexRequest("{VDP regs}",0,64,regs, *this); - //new SimpleHexRequest("{VDP status regs}",0,16,regs, *this); + //new SimpleHexRequest("{VDP regs}", 0, 64, regs, *this); + //new SimpleHexRequest("{VDP status regs}", 0, 16, regs, *this); // now combined in one request: new SimpleHexRequest( "debug_bin2hex " @@ -953,17 +955,67 @@ void VDPRegViewer::registerBitChanged(int reg, int bit, bool state) void VDPRegViewer::on_VDPcomboBox_currentIndexChanged(int index) { + int newVdpId = VDP_TMS99X8; switch (index) { case 0: - vdpId = VDP_V9958; + newVdpId = VDP_V9958; break; case 1: - vdpId = VDP_V9938; + newVdpId = VDP_V9938; break; case 2: - vdpId = VDP_TMS99X8; + newVdpId = VDP_TMS99X8; break; - } + }; + + // on_VDPVersionChanged might have changed vdpId already so avoid calling decode twice + if (newVdpId != vdpId){ + vdpId = newVdpId; + decodeStatusVDPRegs(); + decodeVDPRegs(); + }; + +} + +void VDPRegViewer::on_VDPVersionChanged(QString VDPversion) +{ + if (!cb_useOpenMSXVDP->isChecked()){ + label_VDPreported->setText(VDPversion); + return; + }; + + if (VDPversion == label_VDPreported->text()){ + return; + } else { + label_VDPreported->setText(VDPversion); + }; + VDPVersion_to_combobox(VDPversion); +} + +void VDPRegViewer::on_cb_useOpenMSXVDP_stateChanged(int arg1) +{ + if (arg1 == Qt::Checked){ + QString VDPversion = label_VDPreported->text(); + VDPVersion_to_combobox(VDPversion); + }; +} + +void VDPRegViewer::VDPVersion_to_combobox(QString VDPversion) +{ + if (VDPversion == "unknown"){ + return; + } else if (VDPversion == "V9958"){ + vdpId = VDP_V9958; + VDPcomboBox->setCurrentIndex(0); + } else if (VDPversion == "V9938"){ + vdpId = VDP_V9938; + VDPcomboBox->setCurrentIndex(1); + } else { + vdpId = VDP_TMS99X8; + VDPcomboBox->setCurrentIndex(2); + }; decodeStatusVDPRegs(); decodeVDPRegs(); } + + diff --git a/src/VDPRegViewer.h b/src/VDPRegViewer.h index 3920ae0c..edd44176 100644 --- a/src/VDPRegViewer.h +++ b/src/VDPRegViewer.h @@ -28,7 +28,7 @@ class buttonHighlightDispatcher : public QObject class VDPRegViewer : public QDialog, public SimpleHexRequestUser, - private Ui::VDPRegisters + private Ui::VDPRegisters { Q_OBJECT public: @@ -37,8 +37,12 @@ class VDPRegViewer : public QDialog, public SimpleHexRequestUser, void refresh(); void registerBitChanged(int reg, int bit, bool state); - //quick hack while no auto-detection... + //override auto-detection or in case of older openMSX... void on_VDPcomboBox_currentIndexChanged(int index); + void on_VDPVersionChanged(QString VDPversion); + +private slots: + void on_cb_useOpenMSXVDP_stateChanged(int arg1); private: void decodeVDPRegs(); @@ -59,6 +63,8 @@ class VDPRegViewer : public QDialog, public SimpleHexRequestUser, uint8_t regs[64 + 16 + 2]; buttonHighlightDispatcher* modeBitsDispat; int vdpId; + void VDPVersion_to_combobox(QString VDPversion); + }; #endif /* VDPSTATUSREGVIEWER_H */ diff --git a/src/VDPRegistersExplained.ui b/src/VDPRegistersExplained.ui index 57900995..db69a642 100644 --- a/src/VDPRegistersExplained.ui +++ b/src/VDPRegistersExplained.ui @@ -10301,7 +10301,7 @@ p, li { white-space: pre-wrap; } - Display enabeled + Display enabled @@ -10951,6 +10951,31 @@ p, li { white-space: pre-wrap; }
+ + + + use VDP reported by openMSX: + + + true + + + + + + + + true + + + + older versions of openMSX do not expose this info + + + unknown + + + @@ -10968,10 +10993,11 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } +hr { height: 1px; border-width: 0; } </style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-weight:600; font-style:italic;">Quick hack</span><span style=" font-size:10pt; font-style:italic;"> use this to switch between VDP chip, since no autodetection yet in the code</span></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-style:italic;">Decode VDP register for video chip</span></p></body></html> Qt::RichText diff --git a/src/VramBitMappedView.cpp b/src/VramBitMappedView.cpp index 932ee08b..a971c04f 100644 --- a/src/VramBitMappedView.cpp +++ b/src/VramBitMappedView.cpp @@ -1,5 +1,6 @@ #include "VramBitMappedView.h" #include "ranges.h" +#include "VDPDataStore.h" #include #include #include @@ -8,7 +9,7 @@ VramBitMappedView::VramBitMappedView(QWidget* parent) : QWidget(parent) , image(512, 512, QImage::Format_RGB32) { - ranges::fill(msxPalette, qRgb(80, 80, 80)); + palette = VDPDataStore::instance().getPalette(paletteVDP); setZoom(1.0f); // Mouse update events when mouse is moved over the image, Quibus likes this @@ -27,10 +28,6 @@ void VramBitMappedView::decode() { if (!vramBase) return; - printf("\n" - "screenMode: %i\n" - "vram to start decoding: %i\n", - screenMode, vramAddress); switch (screenMode) { case 5: decodeSCR5(); break; case 6: decodeSCR6(); break; @@ -44,19 +41,6 @@ void VramBitMappedView::decode() update(); } -void VramBitMappedView::decodePallet() -{ - if (!palette) return; - - for (int i = 0; i < 16; ++i) { - int r = (palette[2 * i + 0] & 0xf0) >> 4; - int b = (palette[2 * i + 0] & 0x0f); - int g = (palette[2 * i + 1] & 0x0f); - auto scale = [](int x) { return (x >> 1) | (x << 2) | (x << 5); }; - msxPalette[i] = qRgb(scale(r), scale(g), scale(b)); - } -} - static unsigned interleave(unsigned x) { return (x >> 1) | ((x & 1) << 16); @@ -117,7 +101,7 @@ void VramBitMappedView::decodeSCR10() int j = (p[2] & 7) + ((p[3] & 3) << 3) - ((p[3] & 4) << 3); int k = (p[0] & 7) + ((p[1] & 3) << 3) - ((p[1] & 4) << 3); for (int n = 0; n < 4; ++n) { - QRgb c = (p[n] & 0x08) ? msxPalette[p[n] >> 4] // YAE + QRgb c = (p[n] & 0x08) ? palette->color(p[n] >> 4) // YAE : decodeYJK(p[n] >> 3, j, k); // YJK setPixel2x2(x + n, y, c); } @@ -147,7 +131,7 @@ void VramBitMappedView::decodeSCR8() QRgb VramBitMappedView::getColor(int c) { // TODO do we need to look at the TP bit??? - return msxPalette[c ? c : borderColor]; + return palette->color(c ? c : borderColor); } void VramBitMappedView::decodeSCR7() @@ -193,15 +177,13 @@ void VramBitMappedView::paintEvent(QPaintEvent* /*event*/) QRect srcRect(0, 0, 512, 2 * lines); QRect dstRect(0, 0, int(512.0f * zoomFactor), int(2.0f * float(lines) * zoomFactor)); QPainter qp(this); - //qp.drawImage(rect(),image,srcRect); + //qp.drawImage(rect(), image, srcRect); qp.drawPixmap(dstRect, pixImage, srcRect); } void VramBitMappedView::refresh() { - decodePallet(); decode(); - update(); } void VramBitMappedView::mouseMoveEvent(QMouseEvent* e) @@ -270,55 +252,47 @@ void VramBitMappedView::mousePressEvent(QMouseEvent* e) // since mouseMove only emits the correct signal we reuse/abuse that method mouseMoveEvent(e); - decodePallet(); decode(); - update(); } void VramBitMappedView::setBorderColor(int value) { borderColor = std::clamp(value, 0, 15); - decodePallet(); decode(); - update(); } void VramBitMappedView::setScreenMode(int mode) { screenMode = mode; decode(); - update(); } void VramBitMappedView::setLines(int nrLines) { lines = nrLines; - decode(); setFixedSize(int(512.0f * zoomFactor), int(float(lines) * 2.0f * zoomFactor)); - update(); + decode(); //setZoom(zoomFactor); } void VramBitMappedView::setVramAddress(int adr) { vramAddress = adr; - decodePallet(); decode(); - update(); } void VramBitMappedView::setVramSource(const uint8_t* adr) { vramBase = adr; - decodePallet(); decode(); - update(); } -void VramBitMappedView::setPaletteSource(const uint8_t* adr) +void VramBitMappedView::setPaletteSource(MSXPalette *adr) { + if (palette) { + disconnect(palette, &MSXPalette::paletteChanged, this, &VramBitMappedView::decode); + } palette = adr; - decodePallet(); + connect(palette, &MSXPalette::paletteChanged, this, &VramBitMappedView::decode); decode(); - update(); } diff --git a/src/VramBitMappedView.h b/src/VramBitMappedView.h index 037d0932..be4817a9 100644 --- a/src/VramBitMappedView.h +++ b/src/VramBitMappedView.h @@ -8,6 +8,8 @@ #include #include +class MSXPalette; + class VramBitMappedView : public QWidget { Q_OBJECT @@ -20,7 +22,7 @@ class VramBitMappedView : public QWidget void setLines(int nrLines); void setVramSource(const uint8_t* adr); void setVramAddress(int adr); - void setPaletteSource(const uint8_t* adr); + void setPaletteSource(MSXPalette* adr); void setBorderColor(int value); void mousePressEvent(QMouseEvent* e) override; @@ -39,7 +41,6 @@ class VramBitMappedView : public QWidget void paintEvent(QPaintEvent* e) override; void decode(); - void decodePallet(); void decodeSCR5(); void decodeSCR6(); void decodeSCR7(); @@ -51,10 +52,9 @@ class VramBitMappedView : public QWidget QRgb getColor(int c); private: - QRgb msxPalette[16]; QImage image; QPixmap pixImage; - const uint8_t* palette = nullptr; + MSXPalette* palette = nullptr; const uint8_t* vramBase = nullptr; float zoomFactor; unsigned vramAddress = 0; diff --git a/src/VramSpriteView.cpp b/src/VramSpriteView.cpp index 029a0aff..1f760f54 100644 --- a/src/VramSpriteView.cpp +++ b/src/VramSpriteView.cpp @@ -1,5 +1,6 @@ #include "VramSpriteView.h" #include "VDPDataStore.h" +#include "MSXPalette.h" #include #include #include @@ -13,6 +14,10 @@ VramSpriteView::VramSpriteView(QWidget* parent, mode drawer, bool singleSprite) // sprite size = 8x8, 16x16, 32x32 or 40x8, 48x16, 64x32 if EC bit respected calculateSizeOfSprites(); + msxPalette = nullptr; + setPaletteSource(VDPDataStore::instance().getPalette(paletteVDP), true); + + if (isSingleSpriteDrawer) { setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred)); zoomFactor = 4; @@ -49,8 +54,6 @@ int VramSpriteView::heightForWidth(int width) const // How many sprites do fit in this width? float x = int(width / (sizeOfSpritesHorizontal * zoomFactor)); // Does an int divided by an int gives an int that would be converted to float? int h = int(ceilf(float(nrOfSpritesToShow) / x)) * sizeOfSpritesVertical * zoomFactor; - //printf("\nheightForWidth sizeOfSpritesHorizontal %d sizeOfSpritesVertical %d zoomFactor %d \n", sizeOfSpritesHorizontal, sizeOfSpritesVertical, zoomFactor); - //printf("heightForWidth w=%d => total=%d x=%f => h=%d \n", width, nr_of_sprites_to_show, x, h); return h; } @@ -69,16 +72,16 @@ void VramSpriteView::setVramSource(const uint8_t* adr) { if (vramBase == adr) return; vramBase = adr; - decodePalette(); decode(); } -void VramSpriteView::setPaletteSource(const uint8_t* adr, bool useVDP) +void VramSpriteView::setPaletteSource(MSXPalette* pal, bool useVDP) { useVDPpalette = useVDP; - if (palette == adr) return; - palette = adr; - decodePalette(); + if (msxPalette == pal) return; + disconnect(msxPalette, &MSXPalette::paletteChanged, this, &VramSpriteView::decode); + msxPalette = pal; + connect(msxPalette, &MSXPalette::paletteChanged, this, &VramSpriteView::decode); decode(); } @@ -247,20 +250,6 @@ void VramSpriteView::drawGrid() } } -void VramSpriteView::decodePalette() -{ - if (!palette) return; - - for (int i = 0; i < 16; ++i) { - int r = (palette[2 * i + 0] & 0xf0) >> 4; - int b = (palette[2 * i + 0] & 0x0f); - int g = (palette[2 * i + 1] & 0x0f); - - auto scale = [](int x) { return (x >> 1) | (x << 2) | (x << 5); }; - msxPalette[i] = qRgb(scale(r), scale(g), scale(b)); - } -} - void VramSpriteView::decodePgt() { image.fill(QColor(Qt::lightGray)); @@ -330,7 +319,7 @@ void VramSpriteView::drawColSprite(int entry, QColor& /*bgcolor*/) int z = zoomFactor * ((useMagnification && useECbit) ? 2 : 1); if (spriteMode == 1) { bool ec = color & 128; - QRgb fgColor = msxPalette[color & 15]; // drop EC and unused bits + QRgb fgColor = msxPalette->color(color & 15); // drop EC and unused bits qp.fillRect(x, y, sizeOfSpritesHorizontal * z, sizeOfSpritesVertical * z, fgColor); qp.fillRect(x, y, z, sizeOfSpritesVertical * z, ec ? QRgb(Qt::white) : QRgb(Qt::black)); } else if (spriteMode == 2) { @@ -339,7 +328,7 @@ void VramSpriteView::drawColSprite(int entry, QColor& /*bgcolor*/) bool ec = colorbyte & 128; bool cc = colorbyte & 64; bool ic = colorbyte & 32; - QRgb fgColor = msxPalette[colorbyte & 15]; // drop EC, CC and IC bits + QRgb fgColor = msxPalette->color(colorbyte & 15); // drop EC, CC and IC bits qp.fillRect(x, y + charrow * z, sizeOfSpritesHorizontal * z, z, fgColor); // line with color qp.fillRect(x, y + charrow * z, z, z, (ec ? QColor(Qt::white) : QColor(Qt::black))); // EC bit set -> white qp.fillRect(x + z, y + charrow * z, z, z, (cc ? QColor(Qt::green) : QColor(Qt::black))); // CC bit set -> green @@ -367,7 +356,7 @@ void VramSpriteView::drawSpatSprite(int entry, QColor &bgColor) // Mode 0 already tested, we will not end up here with spritemode == 0 if (spriteMode == 1) { bool ec = color & 128; - QRgb fgColor = msxPalette[color & 15]; // drop EC and unused bits + QRgb fgColor = msxPalette->color(color & 15); // drop EC and unused bits if (attrY == 208) { // in spritemode 1 if Y is equal to 208 this and all lower priority sprites will not be displayed, so red bgcolor! bgColor = QColor(Qt::red); } @@ -405,12 +394,6 @@ void VramSpriteView::setSpritePixel(int x, int y, QRgb c) //image.setPixel(); } -QRgb VramSpriteView::getColor(int c) -{ - // TODO do we need to look at the TP bit??? - return msxPalette[c]; -} - void VramSpriteView::setAttributeTableAddress(int value) { attributeTableAddress = value; @@ -587,9 +570,8 @@ void VramSpriteView::refresh() // Reset pointers in case during boot these pointers weren't correctly set due to openMSX not having send over vram size... setVramSource(VDPDataStore::instance().getVramPointer()); if (useVDPpalette) { - setPaletteSource(VDPDataStore::instance().getPalettePointer(), true); + setPaletteSource(VDPDataStore::instance().getPalette(paletteVDP), true); } - decodePalette(); decode(); } @@ -695,7 +677,7 @@ void VramSpriteView::drawLineColoredSpriteAt(int character, int spriteBox, int x bool ec = colorByte & 128; int ec_offset = (useECbit && !ec) ? 32 : 0; // Draw more to the right if no ec but spritebox is extra wide to display ec effect - QRgb fg = msxPalette[colorByte & 15]; // Drop EC, CC and IC bits + QRgb fg = msxPalette->color(colorByte & 15); // Drop EC, CC and IC bits for (int charCol = 0; charCol < 8; ++charCol) { uint8_t mask = 1 << (7 - charCol); if (gridEnabled && zoomFactor > 4) { diff --git a/src/VramSpriteView.h b/src/VramSpriteView.h index adbfe2fb..888381f4 100644 --- a/src/VramSpriteView.h +++ b/src/VramSpriteView.h @@ -10,6 +10,8 @@ #include #include +#include "MSXPalette.h" + class VramSpriteView : public QWidget { Q_OBJECT @@ -27,7 +29,7 @@ class VramSpriteView : public QWidget [[nodiscard]] QSize sizeHint() const override; void setVramSource(const uint8_t* adr); - void setPaletteSource(const uint8_t* adr, bool useVDP); + void setPaletteSource(MSXPalette *pal, bool useVDP); void mousePressEvent(QMouseEvent* e) override; void mouseMoveEvent (QMouseEvent* e) override; @@ -71,13 +73,11 @@ class VramSpriteView : public QWidget void paintEvent(QPaintEvent* e) override; void decode(); - void decodePalette(); void decodePgt(); void decodeSpat(); void decodeCol(); void setSpritePixel(int x, int y, QRgb c); - QRgb getColor(int c); void drawMonochromeSpriteAt(int character, int spriteBox, int xOffset, int yOffset, QRgb fg, QRgb bg, bool ec = false); void drawLineColoredSpriteAt(int character, int spriteBox, int xOffset, int yOffset, int rowOffset, QRgb bg); @@ -94,10 +94,9 @@ class VramSpriteView : public QWidget [[nodiscard]] QString colorInfo(uint8_t color) const; private: - QRgb msxPalette[16]; + MSXPalette* msxPalette; QImage image; QPixmap pixImage; - const uint8_t* palette = nullptr; const uint8_t* vramBase = nullptr; int patternTableAddress = 0; int attributeTableAddress = 0; diff --git a/src/VramTiledView.cpp b/src/VramTiledView.cpp index 16278ec5..f86f3308 100644 --- a/src/VramTiledView.cpp +++ b/src/VramTiledView.cpp @@ -1,5 +1,6 @@ #include "VramTiledView.h" #include "VDPDataStore.h" +#include "MSXPalette.h" #include "Convert.h" #include "ranges.h" #include @@ -10,7 +11,8 @@ VramTiledView::VramTiledView(QWidget* parent) : QWidget(parent) , image(512, 512, QImage::Format_RGB32) { - ranges::fill(msxPalette, qRgb(80, 80, 80)); + palette = nullptr; + setPaletteSource(VDPDataStore::instance().getPalette(paletteVDP)); setZoom(1.0f); // Mouse update events when mouse is moved over the image, Quibus likes this @@ -80,21 +82,6 @@ void VramTiledView::decode() update(); } -void VramTiledView::decodePalette() -{ - if (!palette) return; - //printf("VramTiledView::decodePallet palletpointer %p \n", palette); - - for (int i = 0; i < 16; ++i) { - int r = (palette[2 * i + 0] & 0xf0) >> 4; - int b = (palette[2 * i + 0] & 0x0f); - int g = (palette[2 * i + 1] & 0x0f); - - auto scale = [](int x) { return (x >> 1) | (x << 2) | (x << 5); }; - msxPalette[i] = qRgb(scale(r), scale(g), scale(b)); - //printf("VramTiledView::decodePallet color %d => r %d g %d b %d \n", i, r, g, b); - } -} void VramTiledView::decodePatternTable() { @@ -211,12 +198,7 @@ void VramTiledView::setPixel2x2(int x, int y, QRgb c) QRgb VramTiledView::getColor(int c) { // TODO do we need to look at the TP bit??? - return msxPalette[c ? c : (tpBit ? 0 : borderColor)]; -} - -const uint8_t *VramTiledView::getPaletteSource() const -{ - return palette; + return palette->color(c ? c : (tpBit ? 0 : borderColor)); } int VramTiledView::getScreenMode() const @@ -434,8 +416,7 @@ void VramTiledView::paintEvent(QPaintEvent* /*event*/) void VramTiledView::refresh() { - decodePalette(); - decode(); + decode(); } QString VramTiledView::byteAsPattern(uint8_t byte) @@ -463,7 +444,7 @@ QString VramTiledView::textInfo(int x, int y, int character) } else if (screenMode == 1) { colordata = QString("- %1: %2").arg( hexValue(colorTableAddress + (character >> 3), 4), - hexValue(vramBase[colorTableAddress+(character >> 3)], 2)); + hexValue(vramBase[colorTableAddress + (character >> 3)], 2)); } else if (screenMode == 3) { colordata = QString("- not used"); } @@ -506,14 +487,16 @@ std::optional VramTiledView::infoFromMouseEvent(Q if (!vramBase) return {}; // I see negative y-coords sometimes, so for safety clip the coords - int x = std::clamp(int(float(e->x()) / zoomFactor), 0, 511); - int y = std::clamp(int(float(e->y()) / zoomFactor) / 2, 0, 255); - if (x >= horiStep * screenWidth) return {}; - if (y >= 8 * screenHeight) return {}; + int x = std::clamp(int(float(e->x()) / zoomFactor), 0, 511) / horiStep; + int y = std::clamp(int(float(e->y()) / zoomFactor) / 2, 0, 255) / 8; + + if (x >= screenWidth) return {}; + if (y >= screenHeight) return {}; x = int(x / horiStep); y = int(y / 8); int character = 0; + switch (tableToShow) { case 0: character = x + y * screenWidth; @@ -529,7 +512,7 @@ std::optional VramTiledView::infoFromMouseEvent(Q } break; } - return MouseEventInfo{int(x / horiStep), int(y / 8), character}; + return MouseEventInfo{x, y, character}; } void VramTiledView::setBorderColor(int value) @@ -568,9 +551,13 @@ void VramTiledView::setColorTableAddress(int adr) decode(); } -void VramTiledView::setPaletteSource(const uint8_t* adr) +void VramTiledView::setPaletteSource(MSXPalette *adr) { if (palette == adr) return; + if (palette) { + disconnect(palette, &MSXPalette::paletteChanged, this, &VramTiledView::decode); + } palette = adr; + connect(palette, &MSXPalette::paletteChanged, this, &VramTiledView::decode); refresh(); } diff --git a/src/VramTiledView.h b/src/VramTiledView.h index 05e3f9ae..7e5e235e 100644 --- a/src/VramTiledView.h +++ b/src/VramTiledView.h @@ -10,6 +10,8 @@ #include #include +class MSXPalette; + class VramTiledView : public QWidget { Q_OBJECT @@ -23,7 +25,7 @@ class VramTiledView : public QWidget void setNameTableAddress(int adr); void setPatternTableAddress(int adr); void setColorTableAddress(int adr); - void setPaletteSource(const uint8_t *adr); + void setPaletteSource(MSXPalette *adr); void setBorderColor(int value); void setDrawGrid(bool value); void setTpBit(bool value); @@ -59,7 +61,6 @@ class VramTiledView : public QWidget void paintEvent(QPaintEvent* e) override; void decode(); - void decodePalette(); void decodePatternTable(); void decodeNameTable(); void overLayNameTable(); @@ -83,10 +84,9 @@ class VramTiledView : public QWidget QString byteAsPattern(uint8_t byte); private: - QRgb msxPalette[16]; QImage image; QPixmap pixImage; - const uint8_t* palette = nullptr; + MSXPalette* palette = nullptr; const uint8_t* vramBase = nullptr; float zoomFactor; bool drawGrid = true; diff --git a/src/blendsplitter/BlendSplitter.cpp b/src/blendsplitter/BlendSplitter.cpp new file mode 100644 index 00000000..f58217f3 --- /dev/null +++ b/src/blendsplitter/BlendSplitter.cpp @@ -0,0 +1,155 @@ +#include "BlendSplitter.h" + +#include "WidgetRegistry.h" +#include "RegistryItem.h" +#include "SplitterDecorator.h" +#include "SplitterHandle.h" +#include "WidgetDecorator.h" + +#include "SignalDispatcher.h" + +#include +#include +#include +#include + +BlendSplitter::BlendSplitter(Qt::Orientation orientation, std::function defaultWidget) + : QSplitter{orientation, nullptr} + , defaultWidget{defaultWidget} +{ + //Q_INIT_RESOURCE(BlendSplitterResources); + setChildrenCollapsible(false); + setHandleWidth(1); + setStyleSheet("QSplitter::handle{background: black;}"); +} + +void BlendSplitter::addWidget() +{ + addWidget(defaultWidget()); +} + +void BlendSplitter::addWidget(QWidget* widget) +{ + insertWidget(-1, widget); +} + +void BlendSplitter::addWidget(RegistryItem* item) +{ + insertWidget(-1, item); +} + +void BlendSplitter::insertWidget(int index) +{ + insertWidget(index, defaultWidget()); +} + +void BlendSplitter::insertWidget(int index, QWidget* widget) +{ + if (widget->inherits("SwitchingWidget")) { + auto* sw = static_cast(widget); + auto& dispatcher = SignalDispatcher::instance(); + connect(&dispatcher, &SignalDispatcher::enableWidget, + sw, &SwitchingWidget::setEnableWidget); + sw->setEnableWidget(dispatcher.getEnableWidget()); + } + auto* decorator = new WidgetDecorator{widget}; + QSplitter::insertWidget(index, decorator); +} + +void BlendSplitter::insertWidget(int index, RegistryItem* item) +{ + auto* wdg = new SwitchingWidget{item}; + insertWidget(index, wdg); +} + +void BlendSplitter::addDecoratedWidget(WidgetDecorator* widget) +{ + insertDecoratedWidget(-1, widget); +} + +void BlendSplitter::insertDecoratedWidget(int index, WidgetDecorator* widget) +{ + QSplitter::insertWidget(index, widget); +} + +void BlendSplitter::addSplitter(BlendSplitter* splitter) +{ + insertSplitter(-1, splitter); +} + +void BlendSplitter::insertSplitter(int index, BlendSplitter* splitter) +{ + auto* decorator = new SplitterDecorator{splitter}; + QSplitter::insertWidget(index, decorator); +} + +QWidget* BlendSplitter::getNestedWidget(int index) +{ + return widget(index)->layout()->itemAt(0)->widget(); +} + +QJsonObject BlendSplitter::save2json() const +{ + QJsonObject obj; + obj["type"] = "BlendSplitter"; + obj["orientation"] = orientation(); + obj["size_width"] = size().width(); + obj["size_height"] = size().height(); + + QJsonArray ar_sizes; + for (auto i : sizes()) { + ar_sizes.append(i); + } + obj["sizes"] = ar_sizes; + + QJsonArray ar_subwidgets; + for (int i = 0; i < count(); ++i) { + //qDebug() << " i " << i << " : " << widget(i); + if (widget(i)->inherits("WidgetDecorator")) { + auto* wdg = widget(i)->layout()->itemAt(0)->widget(); + if (wdg->inherits("SwitchingWidget")) { + ar_subwidgets.append(static_cast(wdg)->save2json()); + } + } else if (widget(i)->inherits("SplitterDecorator")) { + auto* wdg = widget(i)->layout()->itemAt(0)->widget(); + ar_subwidgets.append(static_cast(wdg)->save2json()); + } else { + QJsonObject o; + ar_subwidgets.append(o); + } + } + obj["subs"] = ar_subwidgets; + + return obj; +} + +BlendSplitter* BlendSplitter::createFromJson(const QJsonObject &obj) +{ + auto* split = new BlendSplitter(obj["orientation"].toInt(1) == 1 ? Qt::Horizontal : Qt::Vertical); + + for (const QJsonValue& value : obj["subs"].toArray()) { + QJsonObject obj = value.toObject(); + if (obj["type"].toString() == "SwitchingWidget") { + split->addWidget(SwitchingWidget::createFromJson(obj)); + } else if (obj["type"].toString() == "BlendSplitter") { + split->addSplitter(BlendSplitter::createFromJson(obj)); + } else { + QMessageBox::warning(nullptr, tr("Open workspaces ..."), + tr("Unknown subs type:%1").arg(obj["type"].toString())); + } + } + + QList sizes; + split->resize(obj["size_width"].toInt(), obj["size_height"].toInt()); + for (const auto& value : obj["sizes"].toArray()) { + sizes.append(value.toInt()); + } + split->setSizes(sizes); + //qDebug() << "split->size" << split->size(); + return split; +} + +QSplitterHandle* BlendSplitter::createHandle() +{ + return new SplitterHandle(orientation(), this); +} diff --git a/src/blendsplitter/BlendSplitter.h b/src/blendsplitter/BlendSplitter.h new file mode 100644 index 00000000..f6d931b7 --- /dev/null +++ b/src/blendsplitter/BlendSplitter.h @@ -0,0 +1,129 @@ +#ifndef BLENDSPLITTER_H +#define BLENDSPLITTER_H + +#include "RegistryItem.h" +#include "SwitchingBar.h" +#include "SwitchingWidget.h" +#include "WidgetRegistry.h" + +#include + +#include + +class Expander; +class ExpanderCorner; +class ExpanderBottom; +class ExpanderTop; +class WidgetDecorator; +class QJsonObject; + +/** \brief A user-defined Splitter + * + * This widget implements the functionality of Blender (Open-source 3D modelling software) widget management. This widget displays a splitter similar to QSplitter. However, each widget in BlendSplitter has a pair of Expanders (one in top right and one in bottom left corner). By dragging from these Expanders inwards a new widget is created in the direction of the drag. If the direction is different to that of the BlendSplitter, a new BlendSplitter with parallel direction is created in place of the widget with the widget and the new widget in it. By dragging from these expanders outwards, a neighbouring widget (or a collection of widgets) can be closed. While the mouse is held, the widgets to be closed are marked with black overlay. When the mouse is released, they are closed. BlendSplitter can be used like any other QWidget, although setting one as the central widget is recommended. A BlendSplitter can contain any QWidget, but to achieve best results, use it together with SwitchingWidget. + * + * BlendSplitter provides 3 static variables that allow some customization of the library design. These are expanderSize, switchingBarHeight and expanderImage. These are all initialized with default values. The default Expander image is provided by the library. + * + * The default Expander image: + * + * ![The default Expander](resources/expander.png) + */ +class BlendSplitter : public QSplitter +{ + Q_OBJECT + +public: + static constexpr int expanderSize = 12; /**< Size of the expanders in the corners. Default value: 12 */ + static constexpr int switchingBarHeight = 32; /**< Height of the SwitchingBar. Default value: 36 */ + static constexpr const char* const expanderImage = ":/BlendSplitter/Expander"; /**< The image to be used for the top left expander. The bottom right one will rotate this by pi (180 degrees). Default value: \":/BlendSplitter/Expander\" */ + +public: + BlendSplitter(const BlendSplitter&) = delete; + BlendSplitter& operator=(const BlendSplitter&) = delete; + + /** \brief BlendSplitter class constructor + * \param defaultWidget A pointer to function constructing the default widget. This function is called when a new widget is added to BlendSplitter. + * \param orientation Orientation of the main BlendSplitter + */ + BlendSplitter(Qt::Orientation orientation, + std::function defaultWidget = [] { return new SwitchingWidget{}; }); + + /** \brief Add a widget to the BlendSplitter + * + * Adds the default widget to the very bottom/right of the BlendSplitter. + */ + void addWidget(); + + /** \brief Add a widget to the BlendSplitter + * + * Adds the specified widget to the very bottom/right of the BlendSplitter + * \param widget A pointer to the widget to be added + */ + void addWidget(QWidget* widget); + + /** \brief Add a widget to the BlendSplitter + * + * Adds the specified widget from the WidgetRegistry to the very bottom/right of the BlendSplitter + * \param item A RegistryItem to be added (inside a SwitchingWidget). + */ + void addWidget(RegistryItem* item); + + /** \brief Insert a widget into the BlendSplitter + * + * Inserts the default widget into the BlendSplitter at the given position counting from top/left (counting starts at 0). This function should NOT be called with a BlendSplitter as a parameter. + * \param index The desired position + */ + void insertWidget(int index); + + /** \brief Insert a widget into the BlendSplitter + * + * Inserts the specified widget into the BlendSplitter at the given position counting from top/left (counting starts at 0). This function should NOT be called with a BlendSplitter as a parameter. + * \param index The desired position + * \param widget A pointer to the widget to be inserted + */ + void insertWidget(int index, QWidget* widget); + + /** \brief Insert a widget into the BlendSplitter + * + * Inserts the specified widget from WidgetRegistry into the BlendSplitter at the given position counting from top/left (counting starts at 0). This function should NOT be called with a BlendSplitter as a parameter. + * \param index The desired position + * \param item A RegistryItem to be added (inside a SwitchingWidget). + */ + void insertWidget(int index, RegistryItem* item); + + /** \brief Add another BlendSplitter to this BlendSplitter + * + * Adds a BlendSplitter (usually with parallel orientation) to the BlendSplitter + * \param splitter A pointer to the BlendSplitter to be added + */ + void addSplitter(BlendSplitter* splitter); + + /** \brief Insert another BlendSplitter into this BlendSplitter + * + * Inserts a BlendSplitter (usually with parallel orientation) into the BlendSplitter at the given position counting from top/left (counting starts at 0). + * \param index The desired position + * \param splitter A pointer to the BlendSplitter to be inserted + */ + void insertSplitter(int index, BlendSplitter* splitter); + + /** + * @brief getNestedWidget A helper function to get the widget that is in the splitter at the given position. The actual widget is a WidgetDecorator or SplitterDecorator so we return the widget that is a direct child of this decorator. + * @param index The desired position + * @return A pointer to widget that is decorated + */ + QWidget* getNestedWidget(int index); + + QJsonObject save2json() const; + static BlendSplitter* createFromJson(const QJsonObject& obj); + +private: + friend Expander; + friend ExpanderCorner; + std::function defaultWidget; + QSplitterHandle* createHandle() override; + void addDecoratedWidget(WidgetDecorator* widget); + +public: + void insertDecoratedWidget(int index, WidgetDecorator* widget); +}; + +#endif diff --git a/src/blendsplitter/Expander.cpp b/src/blendsplitter/Expander.cpp new file mode 100644 index 00000000..b3ce36eb --- /dev/null +++ b/src/blendsplitter/Expander.cpp @@ -0,0 +1,82 @@ +#include "Expander.h" + +#include "BlendSplitter.h" +#include "Overlay.h" +#include "WidgetDecorator.h" + +Expander::Expander(WidgetDecorator* parent) + : QLabel(parent) + , pixmap{new QPixmap{BlendSplitter::expanderImage}} + , overlay{nullptr} +{ + *pixmap = pixmap->scaledToHeight(BlendSplitter::expanderSize, Qt::FastTransformation); + setPixmap(*pixmap); + resize(BlendSplitter::expanderSize, BlendSplitter::expanderSize); + setCursor(Qt::WhatsThisCursor); +} + +void Expander::reposition() +{ + raise(); +} + +//void Expander::mousePressEvent(QMouseEvent* event) +//{ +// if (event->button() == Qt::LeftButton) { +// event->accept(); // No-op +// } else { +// releaseMouse(); +// event->ignore(); // Propagate event +// } +//} + +//void Expander::mouseReleaseEvent(QMouseEvent* event) +//{ +// if (event->button() == Qt::LeftButton && overlay != nullptr) { +// auto* parentDecorator = qobject_cast(parentWidget()); +// if (!parentDecorator) { +// qCritical("A BlendSplitter library error occurred. Error code: 1"); +// return; +// } +// auto* parentSplitter = qobject_cast(overlay->parentWidget()->parentWidget()); +// if (!parentSplitter) { +// qCritical("A BlendSplitter library error occurred. Error code: 2"); +// return; +// } +// QList sizes{parentSplitter->sizes()}; +// int parentIndex{parentSplitter->indexOf(parentDecorator)}; +// int overlayIndex{parentSplitter->indexOf(overlay->parentWidget())}; +// sizes[parentIndex] += sizes[overlayIndex] + 1; +// sizes.removeAt(overlayIndex); +// delete parentSplitter->widget(overlayIndex); +// if (parentSplitter->count() == 1 && parentSplitter->parentWidget()->inherits("SplitterDecorator")) { +// auto* newParent = qobject_cast(parentSplitter->parentWidget()->parentWidget()); +// if (!newParent) { +// qCritical("A BlendSplitter library error occurred. Error code: 3"); +// return; +// } +// QList sizes2{newParent->sizes()}; +// newParent->insertDecoratedWidget(newParent->indexOf(parentSplitter->parentWidget()), parentDecorator); +// delete parentSplitter->parentWidget(); +// newParent->setSizes(sizes2); +// } else { +// parentSplitter->setSizes(sizes); +// } +// overlay = nullptr; +// } +//} + +Expander::~Expander() +{ + delete pixmap; +} + +//void Expander::enterEvent(QEvent* event) +//{ +// setCursor(Qt::SizeAllCursor); +//} + +//void Expander::leaveEvent(QEvent* event) +//{ +// setCursor(Qt::ArrowCursor); +//} diff --git a/src/blendsplitter/Expander.h b/src/blendsplitter/Expander.h new file mode 100644 index 00000000..5876a4d7 --- /dev/null +++ b/src/blendsplitter/Expander.h @@ -0,0 +1,34 @@ +#ifndef EXPANDER_H +#define EXPANDER_H + +#include "Global.h" + +class Overlay; +class WidgetDecorator; + +class Expander : public QLabel +{ + Q_OBJECT + +public: + Expander(const Expander&) = delete; + Expander& operator=(const Expander&) = delete; + +protected: + explicit Expander(WidgetDecorator* parent); + virtual void reposition(); //= 0; + ~Expander(); + +protected slots: + //void enterEvent(QEvent* event) final; + //void leaveEvent(QEvent* event) final; + //void mousePressEvent(QMouseEvent* event) override; + //void mouseMoveEvent(QMouseEvent* event) override = 0; + //void mouseReleaseEvent(QMouseEvent* event) override; + +protected: + QPixmap* pixmap; + Overlay* overlay; +}; + +#endif diff --git a/src/blendsplitter/ExpanderCorner.cpp b/src/blendsplitter/ExpanderCorner.cpp new file mode 100644 index 00000000..c1ad9f8c --- /dev/null +++ b/src/blendsplitter/ExpanderCorner.cpp @@ -0,0 +1,448 @@ +#include "ExpanderCorner.h" + +#include "BlendSplitter.h" +#include "Overlay.h" +#include "WidgetDecorator.h" +#include "SplitterDecorator.h" + +#include "SignalDispatcher.h" +#include "Settings.h" + +#include "SavesJsonInterface.h" +#include + +#include + +ExpanderCorner::ExpanderCorner(WidgetDecorator* parent, Qt::Corner location) + : Expander{parent} + , corner{location} + , unitX{1}, unitY{1} + , hotspotX{0}, hotspotY{0} + , dragaction{undecidedDrag}, dragorientation{Qt::Horizontal} + , internalOverlay{nullptr}, externalOverlay{nullptr} + , joinarrow{Qt::NoArrow} +{ + //now do some masking and pixmap rotating depending on location + //also set out unit steps + QPolygon mask; + switch (location) { + case Qt::TopLeftCorner: { + QTransform rot; + *pixmap = pixmap->transformed(rot.rotate(-90), Qt::FastTransformation); + setPixmap(*pixmap); + mask << QPoint{BlendSplitter::expanderSize, 0} + << QPoint{BlendSplitter::expanderSize, BlendSplitter::expanderSize / 10} + << QPoint{BlendSplitter::expanderSize / 10, BlendSplitter::expanderSize} + << QPoint{0, BlendSplitter::expanderSize} + << QPoint{0, 0}; + unitX = 1; + unitY = 1; + break; + } + case Qt::TopRightCorner: { + QTransform rot; + *pixmap = pixmap->transformed(rot.rotate(0), Qt::FastTransformation); + setPixmap(*pixmap); + mask << QPoint{0, 0} + << QPoint{0, BlendSplitter::expanderSize/10} + << QPoint{BlendSplitter::expanderSize * 9 / 10, BlendSplitter::expanderSize} + << QPoint{BlendSplitter::expanderSize, BlendSplitter::expanderSize} + << QPoint{BlendSplitter::expanderSize, 0}; + unitX = -1; + unitY = 1; + hotspotX = BlendSplitter::expanderSize - 1; + break; + } + case Qt::BottomLeftCorner: { + QTransform rot; + *pixmap = pixmap->transformed(rot.rotate(180), Qt::FastTransformation); + setPixmap(*pixmap); + mask << QPoint{0, 0} + << QPoint{BlendSplitter::expanderSize / 10, 0} + << QPoint{BlendSplitter::expanderSize, BlendSplitter::expanderSize * 9 / 10} + << QPoint{BlendSplitter::expanderSize, BlendSplitter::expanderSize} + << QPoint{0, BlendSplitter::expanderSize}; + unitX = 1; + unitY = -1; + hotspotY = BlendSplitter::expanderSize - 1; + break; + } + case Qt::BottomRightCorner: { + QTransform rot; + *pixmap = pixmap->transformed(rot.rotate(90), Qt::FastTransformation); + setPixmap(*pixmap); + mask << QPoint{BlendSplitter::expanderSize, 0} + << QPoint{BlendSplitter::expanderSize * 9 / 10, 0} + << QPoint{0, BlendSplitter::expanderSize * 9 / 10} + << QPoint{0, BlendSplitter::expanderSize} + << QPoint{BlendSplitter::expanderSize, BlendSplitter::expanderSize}; + unitX = -1; + unitY = -1; + hotspotX = BlendSplitter::expanderSize - 1; + hotspotY = BlendSplitter::expanderSize - 1; + break; + } + default: + break; + + } + setMask(QRegion{mask}); +} + +/** + * @brief ExpanderCorner::reposition + * + * If the parent WidgetDecorator/SplitterDecorator gets resized, this method + * makes sure that we end up in the new correct location. + * + */ +void ExpanderCorner::reposition() +{ + switch (corner) { + case Qt::TopLeftCorner: + move(0, 0); + break; + case Qt::TopRightCorner: + move(parentWidget()->width() - width(), 0); + break; + case Qt::BottomLeftCorner: + move(0, parentWidget()->height()-height()); + break; + case Qt::BottomRightCorner: + move(parentWidget()->width() - width(), parentWidget()->height() - height()); + break; + default: + break; + } + raise(); +} + +bool ExpanderCorner::isOnTrailingHandler(BlendSplitter* parentSplitter) +{ + return (parentSplitter->orientation() == Qt::Horizontal) ? unitX < 0 : unitY < 0; +} + +/** + * @brief ExpanderCorner::performInnerSplit + * @param parentDecorator + * @param parentSplitter + * @param splitorientation + * + * This will split the widget. + * If the orientation of the split corresponds to the splitter we simply add an item + * If the orientation is orthogonal we replace the widget with a new splitter and have the widget added to the new splitter + */ +void ExpanderCorner::performInnerSplit(WidgetDecorator* parentDecorator, BlendSplitter* parentSplitter, Qt::Orientation splitorientation) +{ + auto* switchwdg = dynamic_cast(parentDecorator->layout()->itemAt(0)->widget()); + SwitchingWidget* addedWidget = nullptr; + + if (parentSplitter->orientation() == splitorientation) { + QList sizes{parentSplitter->sizes()}; + int index{parentSplitter->indexOf(parentDecorator)}; + if (!isOnTrailingHandler(parentSplitter)) { + sizes.insert(index, BlendSplitter::expanderSize); + sizes[index + 1] -= BlendSplitter::expanderSize + 1; + parentSplitter->insertWidget(index); + addedWidget = dynamic_cast(parentSplitter->getNestedWidget(index)); + } else { + sizes.insert(index + 1, BlendSplitter::expanderSize); + sizes[index] -= BlendSplitter::expanderSize + 1; + parentSplitter->insertWidget(index + 1); + addedWidget = dynamic_cast(parentSplitter->getNestedWidget(index + 1)); + } + parentSplitter->setSizes(sizes); + parentSplitter->handle(index + 1)->grabMouse(); + } else { + //add a new splitter orthogonal to the current one + Qt::Orientation newOrientation{parentSplitter->orientation() == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal}; + auto* newSplitter = new BlendSplitter{newOrientation, parentSplitter->defaultWidget}; + QList sizes{parentSplitter->sizes()}; + parentSplitter->insertSplitter(parentSplitter->indexOf(parentDecorator), newSplitter); + // add a widget in current splitter but on the correct side + bool after = (newOrientation == Qt::Horizontal) ? unitX > 0 : unitY > 0; + + if (after) { + newSplitter->addWidget(); + newSplitter->addDecoratedWidget(parentDecorator); + addedWidget = dynamic_cast(newSplitter->getNestedWidget(0)); + } else { + newSplitter->addDecoratedWidget(parentDecorator); + newSplitter->addWidget(); + addedWidget = dynamic_cast(newSplitter->getNestedWidget(1)); + } + parentSplitter->setSizes(sizes); + newSplitter->handle(1)->grabMouse(); + } + + //now if the original item was a SwitchingWidget we set the same and the enablestate + if (switchwdg && addedWidget) { + addedWidget->setEnableWidget(switchwdg->getEnableWidget()); + //if new user we need show the quickguide (is the default widget) otherwise we split current one + auto& s = Settings::get(); + if (s.value("creatingWorkspaceType", 0).toInt()) { + addedWidget->setCurrentIndex(switchwdg->getCurrentIndex()); + addedWidget->setWidgetSettings(switchwdg->getWidgetSettings()); + } + } + + //have the cursor take the correct shape + setCursor(splitorientation == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor); +} + +void ExpanderCorner::setupJoiners(WidgetDecorator* parentDecorator, BlendSplitter* parentSplitter, int x, int y, Qt::Orientation /*splitorientation*/) +{ + Q_ASSERT(dragaction == undecidedDrag); + Q_ASSERT(externalOverlay == nullptr); + Q_ASSERT(internalOverlay == nullptr); + if (isInContinuationOfSplitter(parentSplitter, pos().x() + x, pos().y() + y)) { + QWidget* wdgt = nullptr; + if (isOnTrailingHandler(parentSplitter) && + parentSplitter->indexOf(parentDecorator) + 1 < parentSplitter->count()) { + wdgt = parentSplitter->widget(parentSplitter->indexOf(parentDecorator) + 1); + //do not join if the target widget is also splitted + if (!wdgt->inherits("SplitterDecorator")) { + joinarrow = parentSplitter->orientation() == Qt::Horizontal ? Qt::RightArrow : Qt::DownArrow; + externalOverlay = new Overlay{wdgt, joinarrow}; + } + } else if (!isOnTrailingHandler(parentSplitter) && + parentSplitter->indexOf(parentDecorator) > 0) { + wdgt = parentSplitter->widget(parentSplitter->indexOf(parentDecorator) - 1); + //do not join if the target widget is also splitted + if (!wdgt->inherits("SplitterDecorator")) { + joinarrow = parentSplitter->orientation() == Qt::Horizontal ? Qt::LeftArrow : Qt::UpArrow; + externalOverlay = new Overlay{wdgt, joinarrow}; + } + } + + //now show overlay if we started a valid joindrag + if (externalOverlay) { + externalOverlay->show(); + internalOverlay = new Overlay{parentDecorator, Overlay::invertArrow(joinarrow)}; + internalOverlay->hide(); + dragaction = joinDrag; + //qDebug() << "starting joindrag " << wdgt << " ( <-wdgt parentDecorator->) " << parentDecorator; + } + } +} + +int ExpanderCorner::pickCoordinate(int x, int y, Qt::Orientation orient) +{ + return orient == Qt::Horizontal ? x : y; +} + +int ExpanderCorner::pickSize(const QSize &size, Qt::Orientation orient) +{ + return (orient == Qt::Horizontal) ? size.width() : size.height(); +} + +void ExpanderCorner::enterEvent(QEvent* event) +{ + setCursor(Qt::CrossCursor); //after innersplit the cursor is still wrong when entering... + Expander::enterEvent(event); +} + +void ExpanderCorner::followDragJoiners(WidgetDecorator* parentDecorator, BlendSplitter* parentSplitter, int x, int y, Qt::Orientation /*splitorientation*/) +{ + x = pos().x() + x; + y = pos().y() + y; + + if (isInContinuationOfSplitter(parentSplitter, x, y)) { + // maybe we need to change direction of the join ? + Qt::Orientation o = parentSplitter->orientation(); + if ((isOnTrailingHandler(parentSplitter) && pickCoordinate(x, y, o) > pickSize(parentDecorator->size(), o)) + || (!isOnTrailingHandler(parentSplitter) && pickCoordinate(x, y, o) < 0)) { + externalOverlay->show(); + internalOverlay->hide(); + setCursor(parentSplitter->orientation() == Qt::Horizontal ? Qt::SizeHorCursor : Qt::SizeVerCursor); + } else if ((isOnTrailingHandler(parentSplitter) && pickCoordinate(x, y, o) <= pickSize(parentDecorator->size(), o)) + || (!isOnTrailingHandler(parentSplitter) && pickCoordinate(x, y, o) >= 0)) { + externalOverlay->hide(); + internalOverlay->show(); + setCursor(parentSplitter->orientation() == Qt::Horizontal ? Qt::SizeHorCursor : Qt::SizeVerCursor); + } + } else { + // hide all overlay since we dragged 'to the side' of the splitter + externalOverlay->hide(); + internalOverlay->hide(); + setCursor(Qt::ForbiddenCursor); + } +} + +bool ExpanderCorner::isInContinuationOfSplitter(BlendSplitter* parentSplitter, int x, int y) +{ + if (parentSplitter->orientation() == Qt::Horizontal + && y > 0 && y < parentSplitter->height()) { + return true; + } + if (parentSplitter->orientation() == Qt::Vertical + && x > 0 && x < parentSplitter->width()) { + return true; + } + return false; +} + +/** + * @brief ExpanderCorner::decideDragAction + * @param event + * + *If a drag is just started this function helps determine if we drag inwards + *our outward and if it is a horizontal or vertical drag. + */ +void ExpanderCorner::decideDragAction(QMouseEvent* event, WidgetDecorator* parentDecorator, BlendSplitter* parentSplitter) +{ + int x = event->x()-hotspotX; + int y = event->y()-hotspotY; + + if (abs(x) < BlendSplitter::expanderSize && abs(y) < BlendSplitter::expanderSize) { + return; + } + + // we dragged far enough + dragorientation = (abs(x) > abs(y)) ? Qt::Horizontal : Qt::Vertical; + + // but did we drag inwards? + // (and als prefent splitting if widget already too small!) + if (x * unitX > 0 && y * unitY > 0 && + ((dragorientation == Qt::Horizontal && parentDecorator->width() > 2 * BlendSplitter::expanderSize) || + (dragorientation == Qt::Vertical && parentDecorator->height() > 2 * BlendSplitter::expanderSize))) { + dragaction = splitDrag; // we dragged inwards + performInnerSplit(parentDecorator, parentSplitter, dragorientation); + } else if (dragorientation == parentSplitter->orientation()) { + // do we join widgets together? + setupJoiners(parentDecorator, parentSplitter, x, y, dragorientation); + } else { + // here we could start a relocationdrag.... + } +} + +void ExpanderCorner::mouseMoveEvent(QMouseEvent* event) +{ + if (!(event->buttons() & Qt::LeftButton)) { + return; + } + + //get our parentdecorator and our immediate blendSplitter + auto* parentDecorator = qobject_cast(parentWidget()); + if (!parentDecorator) { + qCritical("A BlendSplitter library error occurred. Error code: 4"); + return; + } + auto* parentSplitter = qobject_cast(parentDecorator->parentWidget()); + if (!parentSplitter) { + qCritical("A BlendSplitter library error occurred. Error code: 5"); + return; + } + + switch (dragaction) { + case undecidedDrag: + decideDragAction(event, parentDecorator, parentSplitter); + break; + case joinDrag: { + int x = event->x()-hotspotX; + int y = event->y()-hotspotY; + followDragJoiners(parentDecorator, parentSplitter, x, y, (abs(x) > abs(y)) ? Qt::Horizontal : Qt::Vertical); + break; + } + case splitDrag: + default: + break; + } +} + +void ExpanderCorner::mousePressEvent(QMouseEvent* event) +{ + if (event->button() == Qt::LeftButton) { + dragaction = undecidedDrag; //start of drag + event->accept(); // No-op + } else { + releaseMouse(); + event->ignore(); // Propagate event + } +} + +void ExpanderCorner::mouseReleaseEvent(QMouseEvent* event) +{ + if (event->button() == Qt::LeftButton) { + //releasing leftbutton set us back to default cursor for handler + // since we might have changed due to split or join + setCursor(Qt::CrossCursor); + } + + if (event->button() != Qt::LeftButton || dragaction != joinDrag) { + return; + } + + //correct button and we have an overlay, so continue... + if (externalOverlay->isVisible() && internalOverlay->isVisible()) { + qCritical("A BlendSplitter library error occurred. Error code: 8"); + return; + } + + QWidget* widgetToRemove = nullptr; //improved readability later on + QWidget* widgetToKeep = nullptr; //improved readability later on + + if (externalOverlay->isVisible() && !internalOverlay->isVisible()) { + widgetToKeep = internalOverlay->parentWidget(); + widgetToRemove = externalOverlay->parentWidget(); + } else if (!externalOverlay->isVisible() && internalOverlay->isVisible()) { + widgetToKeep = externalOverlay->parentWidget(); + widgetToRemove = internalOverlay->parentWidget(); + } + + // The visible overlays will be deleted when we remove the widget, + // so we need to clean up the invisble overlays + if (!internalOverlay->isVisible()) { + internalOverlay->deleteLater(); + } + if (!externalOverlay->isVisible()) { + externalOverlay->deleteLater(); + } + + //do nothing if both overlays were hidden + if (widgetToRemove == nullptr) { + externalOverlay = nullptr; + internalOverlay = nullptr; + return; + } + + //first get our decorator and the parent blendSplitter + auto* toKeepDecorator = qobject_cast(widgetToKeep); + if (!toKeepDecorator) { + qCritical("A BlendSplitter library error occurred. Error code: 1"); + return; + } + auto* parentSplitter = qobject_cast(externalOverlay->parentWidget()->parentWidget()); + if (!parentSplitter) { + qCritical("A BlendSplitter library error occurred. Error code: 2"); + return; + } + + //now delete the item with the visible overlay from the splitter + + QList sizes{parentSplitter->sizes()}; + int toKeepIndex{parentSplitter->indexOf(toKeepDecorator)}; + int toRemoveIndex{parentSplitter->indexOf(widgetToRemove)}; + sizes[toKeepIndex] += sizes[toRemoveIndex] + 1; + sizes.removeAt(toRemoveIndex); + delete parentSplitter->widget(toRemoveIndex); + externalOverlay = nullptr; + internalOverlay = nullptr; + + // if we now have a blendSplitter with a single item, which is inside + // another blendSplitter then we remove this singular-item splitter + if (parentSplitter->count() == 1 && + parentSplitter->parentWidget()->inherits("SplitterDecorator")) { + auto* newParent = qobject_cast(parentSplitter->parentWidget()->parentWidget()); + if (!newParent) { + qCritical("A BlendSplitter library error occurred. Error code: 3"); + return; + } + QList sizes2{newParent->sizes()}; + newParent->insertDecoratedWidget(newParent->indexOf(parentSplitter->parentWidget()), toKeepDecorator); + delete parentSplitter->parentWidget(); + newParent->setSizes(sizes2); + } else { + parentSplitter->setSizes(sizes); + } +} diff --git a/src/blendsplitter/ExpanderCorner.h b/src/blendsplitter/ExpanderCorner.h new file mode 100644 index 00000000..02b2265f --- /dev/null +++ b/src/blendsplitter/ExpanderCorner.h @@ -0,0 +1,59 @@ +#ifndef EXPANDERCORNER_H +#define EXPANDERCORNER_H + +#include "Global.h" + +#include "Expander.h" + +class WidgetDecorator; +class BlendSplitter; + +class ExpanderCorner final : public Expander +{ + Q_OBJECT + +public: + ExpanderCorner(const ExpanderCorner&) = delete; + ExpanderCorner& operator=(const ExpanderCorner&) = delete; + explicit ExpanderCorner(WidgetDecorator* parent, Qt::Corner location); + void reposition() override; + +protected slots: + void mouseMoveEvent(QMouseEvent* event) override; + void mousePressEvent(QMouseEvent* event) final; + void mouseReleaseEvent(QMouseEvent* event) final; + +private: + void decideDragAction(QMouseEvent* event, WidgetDecorator* parentDecorator, BlendSplitter* parentSplitter); + void performInnerSplit(WidgetDecorator* parentDecorator, BlendSplitter* parentSplitter, Qt::Orientation splitorientation); + void setupJoiners(WidgetDecorator* parentDecorator, BlendSplitter* parentSplitter, int x, int y, Qt::Orientation splitorientation); + void followDragJoiners(WidgetDecorator* parentDecorator, BlendSplitter* parentSplitter, int x, int y, Qt::Orientation splitorientation); + bool isInContinuationOfSplitter(BlendSplitter* parentSplitter, int x, int y); + bool isOnTrailingHandler(BlendSplitter* parentSplitter); + + int pickCoordinate(int x, int y, Qt::Orientation orient); + int pickSize(const QSize &size, Qt::Orientation orient); + +private: + Qt::Corner corner; + //next variables are used to see if we have mouseMovements inwards or outwards of our decorating widget + int unitX; // x-step to centrum of WidgetDecorator (1 or -1) + int unitY; // y-step to centrum of WidgetDecorator (1 or -1) + int hotspotX; + int hotspotY; + enum { + undecidedDrag, + joinDrag, + splitDrag, + } dragaction; + Qt::Orientation dragorientation; + + Overlay* internalOverlay; + Overlay* externalOverlay; + Qt::ArrowType joinarrow; + +protected slots: + void enterEvent(QEvent* event) final; +}; + +#endif diff --git a/src/blendsplitter/Global.h b/src/blendsplitter/Global.h new file mode 100644 index 00000000..fd17fe92 --- /dev/null +++ b/src/blendsplitter/Global.h @@ -0,0 +1,11 @@ +#ifndef GLOBAL_H +#define GLOBAL_H + +#include +#include +#include +#include +#include +#include + +#endif diff --git a/src/blendsplitter/Overlay.cpp b/src/blendsplitter/Overlay.cpp new file mode 100644 index 00000000..10f931b7 --- /dev/null +++ b/src/blendsplitter/Overlay.cpp @@ -0,0 +1,134 @@ +#include "Overlay.h" +#include +#include +#include + +Overlay::Overlay(QWidget* parent, Qt::ArrowType direction) + : QLabel(parent) + , arrowtype(direction) +{ + move(0, 0); + reposition(); + //setStyleSheet("background-color: rgba(0, 0, 0, 128);"); +} + +void Overlay::setArrowshape(Qt::ArrowType arrow) +{ + arrowtype = arrow; + //makeArrowshape(); + //update(); +} + +Qt::ArrowType Overlay::arrowshape() +{ + return arrowtype; +} + +Qt::ArrowType Overlay::invertArrow(Qt::ArrowType arrow) +{ + switch (arrow) { + case Qt::UpArrow: + return Qt::DownArrow; + case Qt::DownArrow: + return Qt::UpArrow; + case Qt::LeftArrow: + return Qt::RightArrow; + case Qt::RightArrow: + return Qt::LeftArrow; + case Qt::NoArrow: + default: + return Qt::NoArrow; + } +} + +void Overlay::reposition() +{ + resize(parentWidget()->width(), parentWidget()->height()); + raise(); + makeArrowshape(); + update(); +} + +void Overlay::makeArrowshape() +{ + if (arrowtype == Qt::NoArrow) { + arrow.clear(); + return; + } + + int unit, midpoint; + int height = parentWidget()->height(); + int width = parentWidget()->width(); + + switch (arrowtype) { + case Qt::DownArrow: + case Qt::UpArrow: + //create vertical pointing arrow + midpoint = width / 2; + unit = std::min(midpoint, height / 2) / 4; + break; + case Qt::LeftArrow: + case Qt::RightArrow: + //create horizontal pointing arrow + midpoint = height / 2; + unit = std::min(midpoint, width / 2) / 4; + case Qt::NoArrow: + default: + break; + } + + switch(arrowtype) { + case Qt::DownArrow: + arrow.setPoints(7, + midpoint + unit, 0, + midpoint + unit, 2 * unit, + midpoint + 2 * unit, 2 * unit, + midpoint , 4 * unit, + midpoint - 2 * unit, 2 * unit, + midpoint - unit, 2 * unit, + midpoint - unit, 0); + break; + case Qt::UpArrow: + arrow.setPoints(7, + midpoint + unit, height, + midpoint + unit, height - 2 * unit, + midpoint + 2 * unit, height - 2 * unit, + midpoint , height - 4 * unit, + midpoint - 2 * unit, height - 2 * unit, + midpoint - unit, height - 2 * unit, + midpoint - unit, height); + break; + case Qt::RightArrow: + arrow.setPoints(7, + 0 , midpoint + unit, + 2 * unit, midpoint + unit, + 2 * unit, midpoint + 2 * unit, + 4 * unit, midpoint, + 2 * unit, midpoint - 2 * unit, + 2 * unit, midpoint - unit, + 0 , midpoint - unit); + break; + case Qt::LeftArrow: + arrow.setPoints(7, + width , midpoint + unit, + width - 2 * unit, midpoint + unit, + width - 2 * unit, midpoint + 2 * unit, + width - 4 * unit, midpoint, + width - 2 * unit, midpoint - 2 * unit, + width - 2 * unit, midpoint - unit, + width , midpoint - unit); + case Qt::NoArrow: + default: + break; + } +} + +void Overlay::paintEvent(QPaintEvent*) +{ + QPainter qp(this); + qp.setBrush(QColor(0, 0, 0, 128)); + qp.setPen(Qt::NoPen); + qp.drawRect(0, 0, size().width(), size().height()); + qp.setBrush(QColor(255, 255, 255, 128)); + qp.drawPolygon(arrow); +} diff --git a/src/blendsplitter/Overlay.h b/src/blendsplitter/Overlay.h new file mode 100644 index 00000000..a970a7e5 --- /dev/null +++ b/src/blendsplitter/Overlay.h @@ -0,0 +1,32 @@ +#ifndef OVERLAY_H +#define OVERLAY_H + +#include "Global.h" + +class Overlay final : public QLabel +{ + Q_OBJECT + +public: + Overlay(const Overlay&) = delete; + Overlay& operator=(const Overlay&) = delete; + explicit Overlay(QWidget* parent, Qt::ArrowType direction = Qt::NoArrow); + + static Qt::ArrowType invertArrow(Qt::ArrowType arrow); + + void setArrowshape(Qt::ArrowType arrow); + Qt::ArrowType arrowshape(); + void reposition(); + +protected: + void paintEvent(QPaintEvent*) override; + +private: + void makeArrowshape(); + +private: + QPolygon arrow; + Qt::ArrowType arrowtype; +}; + +#endif diff --git a/src/blendsplitter/RegistryItem.cpp b/src/blendsplitter/RegistryItem.cpp new file mode 100644 index 00000000..21b15859 --- /dev/null +++ b/src/blendsplitter/RegistryItem.cpp @@ -0,0 +1,10 @@ +#include "RegistryItem.h" + +RegistryItem::RegistryItem(const QString& name, + std::function widget, + std::function populateBar) + : name{name} + , widget{widget} + , populateBar{populateBar} +{ +} diff --git a/src/blendsplitter/RegistryItem.h b/src/blendsplitter/RegistryItem.h new file mode 100644 index 00000000..7e06626f --- /dev/null +++ b/src/blendsplitter/RegistryItem.h @@ -0,0 +1,46 @@ +#ifndef REGISTRYITEM_H +#define REGISTRYITEM_H + +#include + +#include + +class SwitchingBar; + +/** \brief An item intended to be put into WidgetRegistry + * + * Each RegistryItem corresponds to one widget that can be displayed in a BlendSplitter. It describes how this widget should be constructed, what is its name and what items should be in the SwitchingBar when this widget is selected. + */ +class RegistryItem +{ +public: + QString name; /**< The name of the widget, used in the SwitchingBar combo box. */ + + /** \brief A function constructing the widget + * + * A pointer to a function returning QWidget*. This function is called to construct the widget each time it is selected in any SwitchingWidget. Usually in this function the widget is dynamically created using `new` operator and the pointer is returned. + * \return A pointer to the newly-created QWidget + */ + std::function widget; + + /** \brief A function populating the SwitchingBar + * + * A pointer to a function populating the SwitchingBar. This function is called each time this widget is selected in any SwitchingWidget. Usually this function makes use of the interface provided by SwitchingBar to populate it. + * \param A pointer to the SwitchingBar to be populated + * \param A pointer to the newly-created widget in the SwitchingWidget + */ + std::function populateBar; + + /** \brief A constructor setting all the internal values + * + * This constructor takes 3 parameters corresponding to the 3 members of the RegistryItem class. See their description for more details. + * \param name The name of the widget, used in the SwitchingBar combo box + * \param widget A pointer to a function constructing the widget + * \param populateBar A pointer to a function populating the SwitchingBar + */ + RegistryItem(const QString& name, + std::function widget, + std::function populateBar = {}); +}; + +#endif diff --git a/src/blendsplitter/SplitterDecorator.cpp b/src/blendsplitter/SplitterDecorator.cpp new file mode 100644 index 00000000..c90639c6 --- /dev/null +++ b/src/blendsplitter/SplitterDecorator.cpp @@ -0,0 +1,17 @@ +#include "SplitterDecorator.h" + +#include "BlendSplitter.h" + +SplitterDecorator::SplitterDecorator(BlendSplitter* splitter) + : splitter{splitter} +{ + auto* layout = new QHBoxLayout{}; + layout->addWidget(splitter); + layout->setMargin(0); + setLayout(layout); +} + +SplitterDecorator::~SplitterDecorator() +{ + //delete layout(); +} diff --git a/src/blendsplitter/SplitterDecorator.h b/src/blendsplitter/SplitterDecorator.h new file mode 100644 index 00000000..e752ef64 --- /dev/null +++ b/src/blendsplitter/SplitterDecorator.h @@ -0,0 +1,22 @@ +#ifndef SPLITTERDECORATOR_H +#define SPLITTERDECORATOR_H + +#include "Global.h" + +class BlendSplitter; + +class SplitterDecorator : public QWidget +{ + Q_OBJECT + +public: + SplitterDecorator(const SplitterDecorator&) = delete; + SplitterDecorator& operator=(const SplitterDecorator&) = delete; + explicit SplitterDecorator(BlendSplitter* splitter); + ~SplitterDecorator(); + +private: + BlendSplitter* splitter; +}; + +#endif diff --git a/src/blendsplitter/SplitterHandle.cpp b/src/blendsplitter/SplitterHandle.cpp new file mode 100644 index 00000000..4f10f1b3 --- /dev/null +++ b/src/blendsplitter/SplitterHandle.cpp @@ -0,0 +1,223 @@ +#include "SplitterHandle.h" +#include "BlendSplitter.h" +#include "WidgetDecorator.h" +#include +#include +#include +#include + +SplitterHandle::SplitterHandle(Qt::Orientation orientation, QSplitter* parent) + : QSplitterHandle(orientation, parent) + , popupmenu{nullptr} + , joinBeforeAction{nullptr}, joinAfterAction{nullptr} + , splitHoriBeforeAction{nullptr}, splitHoriAfterAction{nullptr} + , splitVertBeforeAction{nullptr}, splitVertAfterAction{nullptr} +{ +} + +SplitterHandle::~SplitterHandle() +{ + if (popupmenu) { + delete popupmenu; //Qmenu has ownership of the actions + popupmenu = nullptr; + } + /* + delete popupmenu; + This sometimes causes a crash???? + Default CPU workspace and grep handler to close "CPU registers"/"Flags" + wiggled a bit left/right when joining and sometimes this happened???? + 1 SplitterHandle::~SplitterHandle SplitterHandle.cpp 15 0x5555556b6d3e + 2 SplitterHandle::~SplitterHandle SplitterHandle.cpp 16 0x5555556b6d90 + 3 QSplitterLayoutStruct::~QSplitterLayoutStruct qsplitter_p.h 74 0x7ffff7a65bfd + 4 QSplitter::childEvent qsplitter.cpp 1312 0x7ffff7a65bfd + 5 QObject::event qobject.cpp 1361 0x7ffff658abcb + 6 QWidget::event qwidget.cpp 9094 0x7ffff79099d3 + 7 QFrame::event qframe.cpp 550 0x7ffff79b179e + 8 QApplicationPrivate::notify_helper qapplication.cpp 3685 0x7ffff78cab0c + 9 QApplication::notify qapplication.cpp 3431 0x7ffff78d1a90 + 10 QCoreApplication::notifyInternal2 qcoreapplication.cpp 1075 0x7ffff655bc48 + 11 QCoreApplication::sendEvent qcoreapplication.cpp 1470 0x7ffff655bdfe + 12 QObjectPrivate::setParent_helper qobject.cpp 2166 0x7ffff6590082 + 13 QObject::~QObject qobject.cpp 1118 0x7ffff6590592 + 14 QWidget::~QWidget qwidget.cpp 1408 0x7ffff790526c + 15 WidgetDecorator::~WidgetDecorator WidgetDecorator.cpp 26 0x5555556b8852 + 16 WidgetDecorator::~WidgetDecorator WidgetDecorator.cpp 29 0x5555556b887c + 17 ExpanderCorner::mouseReleaseEvent ExpanderCorner.cpp 409 0x5555556b5239 + 18 QWidget::event qwidget.cpp 9033 0x7ffff7909670 + 19 QFrame::event qframe.cpp 550 0x7ffff79b179e + 20 QApplicationPrivate::notify_helper qapplication.cpp 3685 0x7ffff78cab0c + 21 QApplication::notify qapplication.cpp 3129 0x7ffff78d26f8 + 22 QCoreApplication::notifyInternal2 qcoreapplication.cpp 1075 0x7ffff655bc48 + 23 QCoreApplication::sendSpontaneousEvent qcoreapplication.cpp 1482 0x7ffff655be0e + 24 QApplicationPrivate::sendMouseEvent qapplication.cpp 2615 0x7ffff78d0fda + 25 QWidgetWindow::handleMouseEvent qwidgetwindow.cpp 674 0x7ffff7922dd1 + 26 QWidgetWindow::event qwidgetwindow.cpp 295 0x7ffff7925a1b + 27 QApplicationPrivate::notify_helper qapplication.cpp 3685 0x7ffff78cab0c + 28 QApplication::notify qapplication.cpp 3431 0x7ffff78d1a90 + 29 QCoreApplication::notifyInternal2 qcoreapplication.cpp 1075 0x7ffff655bc48 + 30 QCoreApplication::sendSpontaneousEvent qcoreapplication.cpp 1482 0x7ffff655be0e + 31 QGuiApplicationPrivate::processMouseEvent qguiapplication.cpp 2203 0x7ffff6fb9738 + 32 QGuiApplicationPrivate::processWindowSystemEvent qguiapplication.cpp 1935 0x7ffff6fbac15 + 33 QWindowSystemInterface::sendWindowSystemEvents qwindowsysteminterface.cpp 1170 0x7ffff6f96f4b + 34 xcbSourceDispatch qxcbeventdispatcher.cpp 105 0x7ffff2a0a33a + 35 g_main_context_dispatch 0x7ffff392817d + 36 ?? 0x7ffff3928400 + 37 g_main_context_iteration 0x7ffff39284a3 + 38 QEventDispatcherGlib::processEvents qeventdispatcher_glib.cpp 423 0x7ffff65b473c + 39 QEventLoop::exec qeventloop.cpp 225 0x7ffff655a662 + 40 QCoreApplication::exec qcoreapplication.cpp 1383 0x7ffff6563590 + 41 main main.cpp 20 0x5555556b9e54 + */ +} + +void SplitterHandle::mousePressEvent(QMouseEvent* event) +{ + + if (false && event->button() == Qt::RightButton) { //this causes too much crashes so disabled for now + // Probably the QT internal code deletes the handler when we remove widgets from + // the QSplitters and the rest of the Qt internals event handlers are then calling invalidated memory. + // It is never a good idea to delete objects while they are still needed :-) + // + // So either I remove this entirely or I use the old trick of decoupling the event from the deletion + // like setting it up with a QTimer so that the main Qt loop will trigger the deletion while there is no handler code active... + + + createPopupMenu(); + QPoint pos = event->globalPos(); + // When positioning a menu with exec() or popup(), bear in mind that you + // cannot rely on the menu's current size(). For performance reasons, + // the menu adapts its size only when necessary, so in many cases, the + // size before and after the show is different. Instead, use sizeHint() + // which calculates the proper size depending on the menu's current + // contents. + pos.setX(pos.x() - popupmenu->sizeHint().width() / 2); // + QAction* act = popupmenu->exec(pos); + if (act == nullptr) { + //if nothing select and some action are nullpointers.... + } else if (act == joinBeforeAction) { + releaseMouse(); + event->accept(); + int index = splitter()->indexOf(this); + removeFromSplitter(index, index - 1); + } else if (act == joinAfterAction) { + releaseMouse(); + event->accept(); + //calling this will cause a segfault, so disabled... + QMessageBox::information(this, "function disabled", "The current code would crash the program, so this function is disabled for now.If you are a developper please help us out..."); + //int index = splitter()->indexOf(this); + //removeFromSplitter(index - 1, index); + } else { + QSplitterHandle::mousePressEvent(event); + } + destroyPopupMenu(); + //setFocus(); + //grabMouse(); + } else { + QSplitterHandle::mousePressEvent(event); + } +} + +void SplitterHandle::mouseReleaseEvent(QMouseEvent* event) +{ + QSplitterHandle::mouseReleaseEvent(event); + releaseMouse(); +} + +bool SplitterHandle::event(QEvent* event) +{ + //qDebug() << " SplitterHandle::event " << event; + return QSplitterHandle::event(event); +} + +void SplitterHandle::createPopupMenu() +{ + int index = splitter()->indexOf(this); + bool joinBeforePossible = splitter()->widget(index - 1)->inherits("WidgetDecorator"); //do not join over splitters! + bool joinAfterPossible = splitter()->widget(index )->inherits("WidgetDecorator"); //do not join over splitters! + + // given all the crashes with joining and the simplicity of dragsplitting + // maybe we should simply drop the idea of reimplementing this blender functionality? + bool splitHoriBeforePossible = false; + bool splitVertBeforePossible = false; + bool splitHoriAfterPossible = false; + bool splitVertAfterPossible = false; + + popupmenu = new QMenu(this); + bool hori = orientation() == Qt::Horizontal; + if (joinBeforePossible) { + joinBeforeAction = popupmenu->addAction(hori ? tr("remove left") : tr("remove above")); + } + if (joinAfterPossible) { + joinAfterAction = popupmenu->addAction(hori ? tr("remove right") : tr("remove below")); + } + + if (joinAfterPossible || joinBeforePossible) { + popupmenu->addSeparator(); + } + + if (splitHoriBeforePossible) { + splitHoriBeforeAction = popupmenu->addAction(hori ? tr("split left horizontal") : tr("split above horizontal")); + } + if (splitVertBeforePossible) { + splitVertBeforeAction = popupmenu->addAction(hori ? tr("split left vertical") : tr("split above vertical")); + } + if (splitHoriAfterPossible) { + splitHoriAfterAction = popupmenu->addAction(hori ? tr("split right horizontal") : tr("split below horizontal")); + } + if (splitVertAfterPossible) { + splitVertAfterAction = popupmenu->addAction(hori ? tr("split right horizontal") : tr("split below horizontal")); + } +} + +void SplitterHandle::destroyPopupMenu() +{ + if (popupmenu) { + delete popupmenu; + popupmenu = nullptr; + joinBeforeAction = nullptr; + joinAfterAction = nullptr; + splitHoriAfterAction = nullptr; + splitVertAfterAction = nullptr; + splitHoriBeforeAction = nullptr; + splitHoriAfterAction = nullptr; + } +} + +void SplitterHandle::removeFromSplitter(int toKeepIndex, int toRemoveIndex) +{ + auto* parentSplitter = splitter(); + if (!parentSplitter) { + qCritical("A BlendSplitter library error occurred. Error code: 20"); + return; + } + auto* toKeepDecorator = qobject_cast(parentSplitter->widget(toKeepIndex)); + if (!(toKeepDecorator || parentSplitter->widget(toKeepIndex)->inherits("SplitterDecorator"))) { + qCritical("A BlendSplitter library error occurred. Error code: 21"); + return; + } + //TODO: exact same code as in ExpanderCorner => isolate in sepereate method later + + QList sizes{parentSplitter->sizes()}; + sizes[toKeepIndex] += sizes[toRemoveIndex] + 1; + sizes.removeAt(toRemoveIndex); + auto* oldcontent = parentSplitter->widget(toRemoveIndex); + // we now avoid delete parentSplitter->widget(toRemoveIndex); + // we hope this also impacts the handler... + oldcontent->hide(); + oldcontent->setParent(nullptr); //removes it from splitter and makes it standalone (already hidden) window. + oldcontent->deleteLater(); + if (parentSplitter->count() == 1 && + parentSplitter->parentWidget()->inherits("SplitterDecorator")) { + auto* newParent = qobject_cast(parentSplitter->parentWidget()->parentWidget()); + if (!newParent) { + qCritical("A BlendSplitter library error occurred. Error code: 3"); + return; + } + QList sizes2{newParent->sizes()}; + newParent->insertDecoratedWidget(newParent->indexOf(parentSplitter->parentWidget()), toKeepDecorator); + delete parentSplitter->parentWidget(); + newParent->setSizes(sizes2); + } else { + parentSplitter->setSizes(sizes); + } +} diff --git a/src/blendsplitter/SplitterHandle.h b/src/blendsplitter/SplitterHandle.h new file mode 100644 index 00000000..c5d7fedf --- /dev/null +++ b/src/blendsplitter/SplitterHandle.h @@ -0,0 +1,41 @@ +#ifndef SPLITTERHANDLE_H +#define SPLITTERHANDLE_H + +#include "Global.h" + +class BlendSplitter; +class QAction; +class QMenu; + +class SplitterHandle final : public QSplitterHandle +{ + Q_OBJECT + +public: + SplitterHandle(const SplitterHandle&) = delete; + SplitterHandle& operator=(const SplitterHandle&) = delete; + SplitterHandle(Qt::Orientation orientation, QSplitter* parent); + ~SplitterHandle(); + +protected: + void createPopupMenu(); + void destroyPopupMenu(); + + void removeFromSplitter(int toKeepIndex, int toRemoveIndex); + +protected slots: + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + bool event(QEvent* event) override; + +private: + QMenu* popupmenu; + QAction* joinBeforeAction; + QAction* joinAfterAction; + QAction* splitHoriBeforeAction; + QAction* splitHoriAfterAction; + QAction* splitVertBeforeAction; + QAction* splitVertAfterAction; +}; + +#endif diff --git a/src/blendsplitter/SwitchingBar.cpp b/src/blendsplitter/SwitchingBar.cpp new file mode 100644 index 00000000..95e6e509 --- /dev/null +++ b/src/blendsplitter/SwitchingBar.cpp @@ -0,0 +1,54 @@ +#include "SwitchingBar.h" + +#include +#include "BlendSplitter.h" +#include "SwitchingCombo.h" + +void SwitchingBar::addMenu(QMenu* menu) +{ + auto* menuBar = new QMenuBar; + menuBar->setDefaultUp(true); + layout->insertWidget(layout->count() - 1, menuBar); + layout->setAlignment(menuBar, Qt::AlignVCenter); + setStyleSheet("QMenuBar{background-color: transparent;}"); + menuBar->addMenu(menu); +} + +void SwitchingBar::addWidget(QWidget* widget) +{ + layout->insertWidget(layout->count() - 1, widget); +} + +SwitchingBar::SwitchingBar(QWidget* parent) + : QWidget(parent) + , layout{new QHBoxLayout{}} + , combo{new SwitchingCombo{}} +{ + layout->setContentsMargins(BlendSplitter::expanderSize * 3 / 4, 0, BlendSplitter::expanderSize * 3 / 4, 0); + setLayout(layout); + //setMinimumHeight(BlendSplitter::switchingBarHeight); + setMaximumHeight(BlendSplitter::switchingBarHeight); + setMinimumHeight(combo->minimumHeight()); + //setMaximumHeight(combo->maximumHeight()); + //setMaximumHeight(combo->minimumHeight()); + layout->addWidget(combo); + layout->addStretch(); +} + +void SwitchingBar::reconstruct(std::function populateBar, QWidget* widget) +{ + int count{layout->count() - 1}; + for (int i = 1; i < count; i++) { + auto* it = layout->takeAt(1); + delete it->widget(); + delete it; + } + if (populateBar) { + populateBar(this, widget); + } +} + +SwitchingBar::~SwitchingBar() +{ + //delete layout; +} diff --git a/src/blendsplitter/SwitchingBar.h b/src/blendsplitter/SwitchingBar.h new file mode 100644 index 00000000..851efb51 --- /dev/null +++ b/src/blendsplitter/SwitchingBar.h @@ -0,0 +1,51 @@ +#ifndef SWITCHINGBAR_H +#define SWITCHINGBAR_H + +#include +#include +#include + +#include + +class SwitchingWidget; +class SwitchingCombo; + +/** \brief A menu bar which is always found on the bottom of SwitchingWidget + * + * This menu bar is similar to the built-in QMenuBar, but can also contain plain QWidgets. The first item on the left is always a combo box for selecting which widget should be displayed in the SwitchingWidget. + */ +class SwitchingBar : public QWidget +{ + friend SwitchingWidget; + + Q_OBJECT + +public: + SwitchingBar(const SwitchingBar&) = delete; + SwitchingBar& operator=(const SwitchingBar&) = delete; + + /** \brief Add a QMenu + * + * This function adds a QMenu to the very right of the SwitchingBar. The menu is wrapped in an invisible QMenuBar. + * \param menu A pointer to the QMenu to be added + */ + void addMenu(QMenu* menu); + + /** \brief Add a QWidget + * + * This function adds a QWidget to the very right of the SwitchingBar. The widget is placed in a QHBoxLayout. + * @param widget A pointer to the QWidget to be added + */ + void addWidget(QWidget* widget); + +private: + explicit SwitchingBar(QWidget* parent = nullptr); + void reconstruct(std::function populateBar, QWidget* widget); + ~SwitchingBar(); + +private: + QHBoxLayout* layout; + SwitchingCombo* combo; +}; + +#endif diff --git a/src/blendsplitter/SwitchingCombo.cpp b/src/blendsplitter/SwitchingCombo.cpp new file mode 100644 index 00000000..97b0e4c2 --- /dev/null +++ b/src/blendsplitter/SwitchingCombo.cpp @@ -0,0 +1,25 @@ +#include "SwitchingCombo.h" + +#include "WidgetRegistry.h" +#include "RegistryItem.h" + +SwitchingCombo::SwitchingCombo() +{ + connect(&WidgetRegistry::instance(), &WidgetRegistry::registryChanged, this, &SwitchingCombo::repopulate); + repopulate(); +} + +void SwitchingCombo::repopulate() +{ + auto& registry = WidgetRegistry::instance(); + auto* current = registry.item(currentIndex()); + clear(); + for (int i = 0; i < registry.size(); ++i) { + QComboBox::addItem(registry.item(i)->name); + } + if (current) { + setCurrentIndex(findText(current->name)); + } else { + setCurrentIndex(findText(registry.getDefault()->name)); + } +} diff --git a/src/blendsplitter/SwitchingCombo.h b/src/blendsplitter/SwitchingCombo.h new file mode 100644 index 00000000..6c8394c5 --- /dev/null +++ b/src/blendsplitter/SwitchingCombo.h @@ -0,0 +1,19 @@ +#ifndef SWITCHINGCOMBO_H +#define SWITCHINGCOMBO_H + +#include "Global.h" + +class SwitchingCombo : public QComboBox +{ + Q_OBJECT + +public: + SwitchingCombo(const SwitchingCombo&) = delete; + SwitchingCombo& operator=(const SwitchingCombo&) = delete; + SwitchingCombo(); + +public slots: + void repopulate(); +}; + +#endif diff --git a/src/blendsplitter/SwitchingWidget.cpp b/src/blendsplitter/SwitchingWidget.cpp new file mode 100644 index 00000000..d29da01e --- /dev/null +++ b/src/blendsplitter/SwitchingWidget.cpp @@ -0,0 +1,193 @@ +#include "SwitchingWidget.h" + +#include "WidgetRegistry.h" +#include "RegistryItem.h" +#include "SwitchingBar.h" +#include "SwitchingCombo.h" +#include "qscrollarea.h" +#include "SavesJsonInterface.h" + +#include +#include +#include + +SwitchingWidget::SwitchingWidget(RegistryItem* item, QWidget* parent, bool menuAtTop) + : QSplitter(Qt::Vertical, parent) + , bar{new SwitchingBar{}} + , widgetEnabled(true), isWidgetAlwaysEnabled(false) + , barAtTop(menuAtTop) +{ + setChildrenCollapsible(true); + setHandleWidth(1); + setStyleSheet("QSplitter::handle{background: grey;}"); + + auto* defaultItem = WidgetRegistry::instance().getDefault(); + if (barAtTop) { + addWidget(bar); + addWidget(wrapInScrollArea((defaultItem->widget)())); + } else { + addWidget(wrapInScrollArea((defaultItem->widget)())); + addWidget(bar); + } + bar->reconstruct(defaultItem->populateBar, widget(widgetIndex())); + connect(bar->combo, static_cast(&QComboBox::activated), this, &SwitchingWidget::changeCurrentWidget); + setCurrentWidget(item); +} + +void SwitchingWidget::setCurrentWidget(RegistryItem* item) +{ + auto& registry = WidgetRegistry::instance(); + if (item == nullptr) { + item = registry.getDefault(); + } + if (registry.indexOf(item) >= 0) { + delete widget(widgetIndex()); + insertWidget(widgetIndex(), wrapInScrollArea((item->widget)())); + bar->reconstruct(item->populateBar, widget(widgetIndex())); + bar->combo->setCurrentIndex(bar->combo->findText(item->name)); +// widget(widgetIndex())->setEnabled(widgetEnabled); + setEnableWidget(widgetEnabled); + //qDebug() << widget(widgetIndex()); + } + //hack to have manual always enabled + //qDebug() << "WidgetRegistry::getRegistry()->indexOf(item) " << registry->indexOf(item); + setWidgetAlwaysEnabled(registry.indexOf(item) == 14); +} + +bool SwitchingWidget::getEnableWidget() +{ + return widgetEnabled; +} + +void SwitchingWidget::setCurrentIndex(int index) +{ + setCurrentWidget(WidgetRegistry::instance().item(index)); +} + +int SwitchingWidget::getCurrentIndex() +{ + return bar->combo->currentIndex(); +} + +QJsonObject SwitchingWidget::save2json() +{ + QJsonObject obj; + obj["type"] = "SwitchingWidget"; + obj["item"] = bar->combo->currentIndex(); + obj["size_width"] = size().width(); + obj["size_height"] = size().height(); + QJsonObject childobj = getWidgetSettings(); + if (!childobj.isEmpty()) { + obj["childwidget"] = childobj; + } + return obj; +} + +SwitchingWidget* SwitchingWidget::createFromJson(const QJsonObject &obj) +{ + int i = obj["item"].toInt(); + auto* wdgt = new SwitchingWidget{WidgetRegistry::instance().item(i)}; + wdgt->resize(obj["size_width"].toInt(), obj["size_height"].toInt()); + + if (auto* childWdgt = dynamic_cast(wdgt->getWidget())) { + if (auto cw = obj["childwidget"]; cw != QJsonValue::Undefined) { + childWdgt->loadFromJson(cw.toObject()); + } + } + + return wdgt; +} + +QWidget *SwitchingWidget::getWidget() +{ + auto* wdgt = widget(widgetIndex()); + + if (isWrappedInScrollArea) { + auto* sa = static_cast(wdgt); + wdgt = sa->widget(); + } + return wdgt; +} + +QJsonObject SwitchingWidget::getWidgetSettings() +{ + QJsonObject obj; + if (auto* wd = dynamic_cast(getWidget())) { + obj = wd->save2json(); + } + return obj; +} + +bool SwitchingWidget::setWidgetSettings(const QJsonObject &obj) +{ + if (auto* wd = dynamic_cast(getWidget())) { + return wd->loadFromJson(obj); + } + return false; +} + +void SwitchingWidget::setEnableWidget(bool enable) +{ + widgetEnabled = enable; + + auto* wdgt = widget(widgetIndex()); + + if (isWrappedInScrollArea) { + auto* sa = static_cast(wdgt); + sa->setAutoFillBackground(true); + //sa->setBackgroundRole(QPalette::Text); //trying to force repaint of background + //sa->update(); + //sa->setBackgroundRole(enable ? QPalette::Window : QPalette::Dark); + if (auto* vp = sa->viewport()) { + vp->update(); + } + wdgt = sa->widget(); + } + + if (wdgt != nullptr) { + bool finalstatus = enable || isWidgetAlwaysEnabled; + //qDebug() << "wdgt->setEnabled(" << enable << "||" << isWidgetAlwaysEnabled << "= " << finalstatus << " ) " << wdgt->objectName(); + wdgt->setEnabled(finalstatus); + wdgt->update(); + } +} + +void SwitchingWidget::setWidgetAlwaysEnabled(bool enable) +{ + isWidgetAlwaysEnabled = enable; + //qDebug() << "SwitchingWidget::setWidgetAlwaysEnabled(bool " << enable << ")"; + setEnableWidget(widgetEnabled); +} + +void SwitchingWidget::changeCurrentWidget(int index) +{ + if (index >= 0) { + setCurrentWidget(WidgetRegistry::instance().item(index)); + } +} + +QWidget* SwitchingWidget::wrapInScrollArea(QWidget* wdgt, bool dowrap) +{ + isWrappedInScrollArea = dowrap; + if (!dowrap) { + return wdgt; + } + + auto* scrollArea = new QScrollArea; + //scrollArea->setBackgroundRole(QPalette::Dark); + scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + scrollArea->setContentsMargins(0, 0, 0, 0); + scrollArea->setWidget(wdgt); + scrollArea->setWidgetResizable(true); + return scrollArea; +} + +int SwitchingWidget::barIndex() +{ + return barAtTop ? 0 : 1; +} + +int SwitchingWidget::widgetIndex() +{ + return barAtTop ? 1 : 0; +} diff --git a/src/blendsplitter/SwitchingWidget.h b/src/blendsplitter/SwitchingWidget.h new file mode 100644 index 00000000..61fedcc8 --- /dev/null +++ b/src/blendsplitter/SwitchingWidget.h @@ -0,0 +1,69 @@ +#ifndef SWITCHINGWIDGET_H +#define SWITCHINGWIDGET_H + +#include + +class SwitchingBar; +class RegistryItem; +class QJsonObject; + +/** \brief A widget whose actual content can be selected from a combo box + * + * This widget displays a Widget with a SwitchingBar on the bottom. The widget displayed is one from WidgetRegistry and it can be selected using a combo box in the SwitchingBar. + * + * Note that constructing an object of this class when WidgetRegistry is empty will cause a default RegistryItem to be added to it. The height of the SwitchingBar can be modified by changing BlendSplitter::switchingBarHeight. + */ +class SwitchingWidget : public QSplitter +{ + Q_OBJECT + +public: + SwitchingWidget(const SwitchingWidget&) = delete; + SwitchingWidget& operator=(const SwitchingWidget&) = delete; + + /** \brief A default constructor similar to that of QWidget + * + * Creates a SwitchingWidget containing the default widget specified in WidgetRegistry + * \param item A RegistryItem to display in the widget. If nullptr, then WidgetRegistry::getDefault() is used. + * \param parent A parent widget + */ + SwitchingWidget(RegistryItem* item = nullptr, QWidget* parent = nullptr, bool menuAtTop = true); + + /** \brief Set the current Widget displayed. + * + * Sets the current widget to be the item + * \param item A RegistryItem to display in the widget. If nullptr, then WidgetRegistry::getDefault() is used. + */ + void setCurrentWidget(RegistryItem* item = nullptr); + bool getEnableWidget(); + void setCurrentIndex(int index); + int getCurrentIndex(); + + QJsonObject save2json(); + static SwitchingWidget* createFromJson(const QJsonObject& obj); + + QWidget* getWidget(); + QJsonObject getWidgetSettings(); + bool setWidgetSettings(const QJsonObject& obj); + +public slots: + void setEnableWidget(bool enable = true); + void setWidgetAlwaysEnabled(bool enable = true); + +private slots: + void changeCurrentWidget(int index); + +private: + QWidget* wrapInScrollArea(QWidget* wdgt, bool dowrap = true); + int barIndex(); + int widgetIndex(); + +private: + SwitchingBar* bar; + bool widgetEnabled; + bool isWidgetAlwaysEnabled; //some widgets need to be always enabled even if disconnect from openMSX + bool barAtTop; + bool isWrappedInScrollArea; +}; + +#endif diff --git a/src/blendsplitter/WidgetDecorator.cpp b/src/blendsplitter/WidgetDecorator.cpp new file mode 100644 index 00000000..77a92092 --- /dev/null +++ b/src/blendsplitter/WidgetDecorator.cpp @@ -0,0 +1,108 @@ +#include "WidgetDecorator.h" + +#include "BlendSplitter.h" +#include "ExpanderCorner.h" +#include +#include +#include + +WidgetDecorator::WidgetDecorator(QWidget* widget) + : widget{widget} + //, expanderBottom{new ExpanderBottom{this}} + //, expanderTop{new ExpanderTop{this}} + , expanderCorner1{new ExpanderCorner{this, Qt::TopLeftCorner}} + , expanderCorner2{new ExpanderCorner{this, Qt::BottomRightCorner}} + , expanderCorner3{new ExpanderCorner{this, Qt::TopRightCorner}} + , expanderCorner4{new ExpanderCorner{this, Qt::BottomLeftCorner}} + , dropzone(dropregions::center) +{ + auto* layout = new QHBoxLayout{}; + layout->addWidget(widget); + layout->setMargin(0); + setLayout(layout); + setMinimumSize(2 * BlendSplitter::expanderSize, 2 * BlendSplitter::expanderSize); +} + +WidgetDecorator::~WidgetDecorator() +{ + //delete layout(); +} + +void WidgetDecorator::resizeEvent(QResizeEvent*) +{ + //expanderBottom->reposition(); + //expanderTop->reposition(); + expanderCorner1->reposition(); + expanderCorner2->reposition(); + expanderCorner3->reposition(); + expanderCorner4->reposition(); +} + +//void WidgetDecorator::mouseMoveEvent(QMouseEvent* event) +//{ +// determineDropZone(event->pos()); +// update(); +//} + +void WidgetDecorator::determineDropZone(QPoint pos) +{ + int x = width() / 3; + int y = height() / 3; + if (pos.x() > x && pos.x() < (width() - x) && pos.y() > y && pos.y() < (height() - y)) { + dropzone = dropregions::center; + return; + } + // now if we are not center then use the diagonals of the rect to see in which of the 4 triangles we are + + //normal on diagonal (0, 0) -> (width, height) + x = -height(); + y = width(); + //we use the sign of the dot product to determine if we are above/below the diagonal + int side = (x * pos.x() + y * pos.y()) > 0 ? 1 : 0; + + //normal on diagonal (0, height) -> (width, 0) == (0, 0)-(width, -height) + x = height(); + y = width(); + //we use the sign of the dot product to determine if we are above/below the diagonal + side += (x * pos.x() + y * (pos.y() - height())) > 0 ? 2 : 0; + dropzone = static_cast(side); +} + +//void WidgetDecorator::paintEvent(QPaintEvent* event) +//{ +// QWidget::paintEvent(event); +// +// //qDebug() << "WidgetDecorator::paintEvent"; +// +// QPainter painter(this); +// painter.setPen(Qt::black); +// painter.setBrush(Qt::NoBrush); +// int x = width() / 3; +// int y = height() / 3; +// painter.drawLine(0, 0, x, y); +// painter.drawLine(width(), 0, width() - x, y); +// painter.drawLine(0, height(), x, height() - y); +// painter.drawLine(width(), height(), width() - x, height() - y); +// painter.drawRect(x, y, x, y); +// +// switch (dropzone) { +// case dropregions::top: +// painter.drawText(QRect(x, 0, x, y), Qt::AlignCenter, "top"); +// break; +// case dropregions::left: +// painter.drawText(QRect(0, y, x, y), Qt::AlignCenter, "left"); +// break; +// case dropregions::right: +// painter.drawText(QRect(width()-x, y, x, y), Qt::AlignCenter, "right"); +// break; +// case dropregions::bottom: +// painter.drawText(QRect(x, height()-y, x, y), Qt::AlignCenter, "bottom"); +// break; +// case dropregions::center: +// painter.drawText(QRect(x, y, x, y), Qt::AlignCenter, "center"); +// break; +// default: +// break; +// } +// setMouseTracking(true); +//} diff --git a/src/blendsplitter/WidgetDecorator.h b/src/blendsplitter/WidgetDecorator.h new file mode 100644 index 00000000..25fcd69c --- /dev/null +++ b/src/blendsplitter/WidgetDecorator.h @@ -0,0 +1,44 @@ +#ifndef WIDGETDECORATOR_H +#define WIDGETDECORATOR_H + +#include "Global.h" + +class BlendSplitter; +class ExpanderCorner; + +class WidgetDecorator final : public QWidget +{ + Q_OBJECT + +public: + WidgetDecorator(const WidgetDecorator&) = delete; + WidgetDecorator& operator=(const WidgetDecorator&) = delete; + explicit WidgetDecorator(QWidget* widget); + ~WidgetDecorator(); + +private: + void determineDropZone(QPoint pos); + +private: + QWidget* widget; + ExpanderCorner* expanderCorner1; + ExpanderCorner* expanderCorner2; + ExpanderCorner* expanderCorner3; + ExpanderCorner* expanderCorner4; + + enum class dropregions { + top = 0, + left = 1, + right = 2, + bottom = 3, + center = 4 + }; + dropregions dropzone; + +protected slots: + void resizeEvent(QResizeEvent*) override; + //void mouseMoveEvent(QMouseEvent* event) override; + //void paintEvent(QPaintEvent* event) override; +}; + +#endif diff --git a/src/blendsplitter/WidgetRegistry.cpp b/src/blendsplitter/WidgetRegistry.cpp new file mode 100644 index 00000000..de4e4d86 --- /dev/null +++ b/src/blendsplitter/WidgetRegistry.cpp @@ -0,0 +1,79 @@ +#include "WidgetRegistry.h" + +#include "RegistryItem.h" + +WidgetRegistry::WidgetRegistry() + : defaultItem{nullptr} +{ +} + +WidgetRegistry& WidgetRegistry::instance() +{ + static WidgetRegistry oneInstance; + return oneInstance; +} + +RegistryItem* WidgetRegistry::item(int i) const +{ + return list.value(i); +} + +int WidgetRegistry::indexOf(RegistryItem* item) const +{ + return list.indexOf(item); +} + +RegistryItem* WidgetRegistry::getDefault() +{ + if (defaultItem == nullptr) { + if (list.size() == 0) { + addItem(new RegistryItem{"Default", + []()->QWidget* { return new QLabel{"Default widget"}; }}); + } + defaultItem = item(0); + } + return defaultItem; +} + +void WidgetRegistry::setDefault(RegistryItem* item) +{ + if (!list.contains(item)) { + addItem(item); + } + defaultItem = item; +} + +void WidgetRegistry::setDefault(int index) +{ + if (index < size()) { + setDefault(item(index)); + } +} + +void WidgetRegistry::addItem(RegistryItem* item) +{ + list.append(item); + emit registryChanged(); +} + +void WidgetRegistry::insertItem(int index, RegistryItem* item) +{ + list.insert(index, item); + emit registryChanged(); +} + +void WidgetRegistry::removeItem(RegistryItem* item) +{ + removeItem(indexOf(item)); +} + +void WidgetRegistry::removeItem(int index) +{ + list.removeAt(index); + emit registryChanged(); +} + +int WidgetRegistry::size() const +{ + return list.size(); +} diff --git a/src/blendsplitter/WidgetRegistry.h b/src/blendsplitter/WidgetRegistry.h new file mode 100644 index 00000000..22714cd0 --- /dev/null +++ b/src/blendsplitter/WidgetRegistry.h @@ -0,0 +1,114 @@ +#ifndef WIDGETREGISTRY_H +#define WIDGETREGISTRY_H + +#include + +class RegistryItem; +class SwitchingBar; + +/** \brief A registry of all widgets that can be displayed in a SwitchingWidget + * + * This singleton-class acts as a registry of widgets that can be displayed in a SwitchingWidget by selecting from a combo box in the SwitchingBar. Each item is represented as one RegistryItem. The Registry also contains a pointer to the default RegistryItem, which is shown when a new SwitchingWidget is created. + */ +class WidgetRegistry : public QObject +{ + Q_OBJECT + +public: + WidgetRegistry(const WidgetRegistry&) = delete; + WidgetRegistry& operator=(const WidgetRegistry&) = delete; + + /** \brief Registry getter + * + * This is a singleton class, i. e. you can't construct any object yourself. To get the one-and-only instance of this class, you need to call WidgetRegistry::getRegistry(). The function will create the object if necessary (= when called for the first time) and return a pointer to it. + * \return A pointer to the one-and-only instance of WidgetRegistry + */ + static WidgetRegistry& instance(); + + /** \brief Get the item at position i. + * + * This function gives you the item at position i (counting starts at 0). + * \param i Index of the item to be returned + * \return A pointer to the RegistryItem at position i + */ + RegistryItem* item(int i) const; + + /** \brief Get the position of an item in WidgetRegistry + * + * Get the index (counting starts at 0) of an item. Often used together with item(int i) const. + * \param item A pointer to the item whose index is to be returned + * \return Index of the item + */ + int indexOf(RegistryItem* item) const; + + /** \brief Get the default RegistryItem + * + * This function gives you the default RegistryItem. Note that if no item was set as default, the currently first item is set as default by this function. If the registry is empty, a RegistryItem is added to the registry (using the default constructor) and set as default. + * \return A pointer to the default RegistryItem + */ + RegistryItem* getDefault(); + + /** \brief Set the default RegistryItem + * + * This function sets the default RegistryItem. Note that if the item is not in WidgetRegistry, it is added as the last entry. The default item is used when a new SwitchingWidget is created as the displayed widget. + * \param item A pointer to the RegistryItem to be set as default + */ + void setDefault(RegistryItem* item); + + /** \brief Set the default RegistryItem + * + * This function sets the default RegistryItem to be the RegistryItem at given index. This is equal to calling setDefault(item(index)). + * \param index Index of the RegistryItem to be set as default (counting starts at 0) + */ + void setDefault(int index = 0); + + /** \brief Add an item to WidgetRegistry + * + * Adds a given RegistryItem at the end of WidgetRegistry. + * \param item A pointer to the RegistryItem to be added + */ + void addItem(RegistryItem* item); + + /** \brief Insert an item into WidgetRegistry + * + * Inserts a given RegistryItem into WidgetRegistry at a given index. + * \param index The desired index of the inserted RegistryItem (counting starts at 0) + * \param item A pointer to the RegistryItem to be added + */ + void insertItem(int index, RegistryItem* item); + + /** \brief Remove a RegistryItem from WidgetRegistry + * + * Removes a given RegistryItem from WidgetRegistry. If this is also the default RegistryItem, the first RegistryItem is set as default if it exists. This is equal to calling removeItem(indexOf(item)). + * \param item + */ + void removeItem(RegistryItem* item); + + /** \brief Remove a RegistryItem from WidgetRegistry + * + * Removes the RegistryItem at position index (counting starts at 0) from WidgetRegistry. If this is also the default RegistryItem, the first RegistryItem is set as default if it exists. + * \param index Index of the RegistryItem to be removed + */ + void removeItem(int index); + + /** \brief Get the size of WidgetRegistry + * + * This function returns the number of RegistryItems currently in WidgetRegistry. Note that these are indexed as 0, ..., (size() - 1). + * \return Number of RegistryItems in WidgetRegistry + */ + int size() const; + +signals: + /** \brief Signal emitted when WidgetRegistry changes its contents + * + * This signal is emitted when a RegistryItem is added, inserted or removed from WidgetRegistry. It is NOT emitted when the default item is changed unless this change requires adding a RegistryItem. + */ + void registryChanged(); + +private: + QList list; + RegistryItem* defaultItem; + WidgetRegistry(); +}; + +#endif diff --git a/src/blendsplitter/resources/expander.png b/src/blendsplitter/resources/expander.png new file mode 100644 index 00000000..96047a3e Binary files /dev/null and b/src/blendsplitter/resources/expander.png differ diff --git a/src/node.mk b/src/node.mk index f6660978..b71ae2b4 100644 --- a/src/node.mk +++ b/src/node.mk @@ -4,18 +4,27 @@ SUBDIRS:= \ openmsx MOC_SRC_HDR:= \ - DockableWidget DockableWidgetArea DockableWidgetLayout \ CPURegsViewer CommClient DebuggerForm DisasmViewer FlagsViewer HexViewer \ SlotViewer StackViewer ConnectDialog OpenMSXConnection SymbolManager \ Settings PreferencesDialog BreakpointDialog DebuggableViewer \ DebugSession MainMemoryViewer BitMapViewer VramBitMappedView \ VDPDataStore VDPStatusRegViewer VDPRegViewer InteractiveLabel \ InteractiveButton VDPCommandRegViewer GotoDialog SymbolTable \ - TileViewer VramTiledView PaletteDialog VramSpriteView SpriteViewer \ - BreakpointViewer + TileViewer VramTiledView VramSpriteView SpriteViewer \ + BreakpointViewer SignalDispatcher \ + blendsplitter/BlendSplitter blendsplitter/Expander \ + blendsplitter/Overlay blendsplitter/SplitterDecorator \ + blendsplitter/SwitchingBar blendsplitter/SwitchingWidget \ + blendsplitter/WidgetRegistry blendsplitter/ExpanderCorner \ + blendsplitter/RegistryItem \ + blendsplitter/SplitterHandle blendsplitter/SwitchingCombo \ + blendsplitter/WidgetDecorator QuickGuide TabRenamerHelper \ + PaletteView PalettePatch MSXPalette + + SRC_HDR:= \ - DockManager Dasm DasmTables DebuggerData SymbolTable Convert Version \ + Dasm DasmTables DebuggerData SymbolTable Convert Version \ CPURegs SimpleHexRequest SRC_ONLY:= \ @@ -24,6 +33,7 @@ SRC_ONLY:= \ UI:= \ ConnectDialog SymbolManager PreferencesDialog BreakpointDialog \ BitMapViewer VDPStatusRegisters VDPRegistersExplained VDPCommandRegisters \ - GotoDialog TileViewer PaletteDialog SpriteViewer BreakpointViewer + GotoDialog TileViewer SpriteViewer BreakpointViewer \ + QuickGuide PaletteView include build/node-end.mk