diff --git a/src/BreakpointViewer.cpp b/src/BreakpointViewer.cpp index 70e4d2b7..03dbf5fd 100644 --- a/src/BreakpointViewer.cpp +++ b/src/BreakpointViewer.cpp @@ -19,7 +19,7 @@ enum TableColumns { T_CONDITION = 3, SLOT = 4, SEGMENT = 5, - INDEX = 6 + ID = 6 }; BreakpointViewer::BreakpointViewer(QWidget* parent) @@ -30,14 +30,18 @@ BreakpointViewer::BreakpointViewer(QWidget* parent) bpTableWidget->sortByColumn(BP_ADDRESS, Qt::AscendingOrder); bpTableWidget->setColumnHidden(WP_TYPE, true); - bpTableWidget->setColumnHidden(INDEX, true); + bpTableWidget->setColumnHidden(ID, true); bpTableWidget->resizeColumnsToContents(); - connect(bpTableWidget, &QTableWidget::itemChanged, this, - &BreakpointViewer::changeBpTableItem); + bpTableWidget->setSortingEnabled(true); + connect(bpTableWidget, &QTableWidget::itemPressed, this, &BreakpointViewer::on_itemPressed); + connect(bpTableWidget, &QTableWidget::itemChanged, this, &BreakpointViewer::changeBpTableItem); + connect(bpTableWidget->horizontalHeader(), &QHeaderView::sectionClicked, this, + &BreakpointViewer::on_headerClicked); - wpTableWidget->setColumnHidden(INDEX, true); + wpTableWidget->setColumnHidden(ID, true); wpTableWidget->sortByColumn(WP_REGION, Qt::AscendingOrder); wpTableWidget->resizeColumnsToContents(); + wpTableWidget->setSortingEnabled(true); connect(wpTableWidget, &QTableWidget::itemChanged, this, &BreakpointViewer::changeWpTableItem); @@ -45,109 +49,161 @@ BreakpointViewer::BreakpointViewer(QWidget* parent) cnTableWidget->setColumnHidden(LOCATION, true); cnTableWidget->setColumnHidden(SLOT, true); cnTableWidget->setColumnHidden(SEGMENT, true); - cnTableWidget->setColumnHidden(INDEX, true); + cnTableWidget->setColumnHidden(ID, true); + cnTableWidget->sortByColumn(T_CONDITION, Qt::AscendingOrder); cnTableWidget->resizeColumnsToContents(); + cnTableWidget->setSortingEnabled(true); connect(cnTableWidget, &QTableWidget::itemChanged, this, &BreakpointViewer::changeCnTableItem); - selector[BREAKPOINT] = bpTableWidget; - selector[WATCHPOINT] = wpTableWidget; - selector[CONDITION] = cnTableWidget; - - userMode = true; + tables[BreakpointData::BREAKPOINT] = bpTableWidget; + tables[BreakpointData::WATCHPOINT] = wpTableWidget; + tables[BreakpointData::CONDITION] = cnTableWidget; } // TODO: move the createSetCommand to a session manager -void BreakpointViewer::createBreakpoint(Type type, int row) +void BreakpointViewer::createBreakpoint(BreakpointData::Type type, int row) { - if (type == CONDITION) _createCondition(row); + if (type == BreakpointData::CONDITION) _createCondition(row); else _createBreakpoint(type, row); } -void BreakpointViewer::_createBreakpoint(Type type, int row) +void BreakpointViewer::_handleSyncError(const QString& error) { - assert(type != CONDITION); - auto table = selector[type]; - auto model = table->model(); - auto combo = (QComboBox*) table->indexWidget(model->index(row, WP_TYPE)); - Breakpoint::Type type2; + int choice = QMessageBox::warning(nullptr, tr("Synchronization error"), + tr("Error message from openMSX:\n\"%1\"\n\nClick on Reset to resynchronize with openMSX") + .arg(error.trimmed()), QMessageBox::Reset | QMessageBox::Cancel); + if (choice == QMessageBox::Reset) { + auto sa = ScopedAssign(selfUpdating, false); + emit contentsUpdated(false); + } +} - if (type == WATCHPOINT) { - type2 = static_cast(combo->currentIndex() + 1); - } else { - type2 = Breakpoint::BREAKPOINT; +void BreakpointViewer::_handleKeyAlreadyExists() +{ + int choice = QMessageBox::warning(nullptr, tr("Reference error"), + tr("A breakpoint with the same name already exists locally. This is probably " + "a synchronization problem.\n\nClick on Reset to resynchronize with openMSX"), + QMessageBox::Reset | QMessageBox::Cancel); + if (choice == QMessageBox::Reset) { + auto sa = ScopedAssign(selfUpdating, false); + emit contentsUpdated(false); + } +} + +void BreakpointViewer::_handleKeyNotFound() +{ + int choice = QMessageBox::warning(nullptr, tr("Reference error"), + tr("Breakpoint was not found locally. This is probably a synchronization problem.\n\n" + "Click on Reset to resynchronize with openMSX"), + QMessageBox::Reset | QMessageBox::Cancel); + if (choice == QMessageBox::Reset) { + auto sa = ScopedAssign(selfUpdating, false); + emit contentsUpdated(false); } +} + +void BreakpointViewer::_createBreakpoint(BreakpointData::Type type, int row) +{ + assert(type != BreakpointData::CONDITION); + auto* table = tables[type]; + auto* model = table->model(); + auto* combo = (QComboBox*) table->indexWidget(model->index(row, WP_TYPE)); + Breakpoint::Type wtype = type == BreakpointData::WATCHPOINT ? readComboBox(row) : Breakpoint::BREAKPOINT; int address, endRange; QString location = table->item(row, LOCATION)->text(); - bool ok1 = processLocationField(-1, type, location, address, endRange, - combo ? combo->currentText() : ""); + bool ok1 = parseLocationField(-1, type, location, address, endRange, combo ? combo->currentText() : ""); if (!ok1) { - auto sa = ScopedAssign(userMode, false); - setBreakpointStatus(type, row, false); + setBreakpointChecked(type, row, Qt::Unchecked); return; + } else { + setBreakpointChecked(type, row, Qt::Checked); } QString condition = table->item(row, T_CONDITION)->text(); qint8 ps, ss; - bool ok2 = processSlotField(-1, table->item(row, SLOT)->text(), ps, ss); + bool ok2 = parseSlotField(-1, table->item(row, SLOT)->text(), ps, ss); assert(ok2); qint16 segment; - bool ok3 = processSegmentField(-1, table->item(row, SEGMENT)->text(), segment); + bool ok3 = parseSegmentField(-1, table->item(row, SEGMENT)->text(), segment); assert(ok3); - const QString cmdStr = Breakpoints::createSetCommand(type2, address, ps, ss, segment, - endRange, condition); + const QString cmdStr = Breakpoints::createSetCommand(wtype, address, ps, ss, segment, endRange, condition); - auto command = new Command(cmdStr, - [this] (const QString& result) { + auto* command = new Command(cmdStr, + [this, type, row] (const QString& id) { + setTextField(type, row, ID, id); + + BreakpointData data { type, id, row, -1 }; + if (!connectBreakpointID(id, data)) { + setBreakpointChecked(type, row, Qt::Unchecked); + _handleKeyAlreadyExists(); + return; + } + + auto sa = ScopedAssign(selfUpdating, true); emit contentsUpdated(false); - } + }, + [this] (const QString& error) { _handleSyncError(error); } ); + CommClient::instance().sendCommand(command); } // TODO: move the createRemoveCommand to a session manager -void BreakpointViewer::replaceBreakpoint(Type type, int row) +void BreakpointViewer::replaceBreakpoint(BreakpointData::Type type, int row) { - auto table = selector[type]; - auto item = table->item(row, INDEX); - - bool ok; - int index = item->text().toInt(&ok); - assert(ok); - - const QString& id = breakpoints->getBreakpoint(index).id; - const QString cmdStr = breakpoints->createRemoveCommand(id); + auto* table = tables[type]; + auto* item = table->item(row, ID); + QString id = item->text(); - auto command = new Command(cmdStr, + const QString cmdStr = breakpoints->createRemoveCommand(id); + auto* command = new Command(cmdStr, [this, type, row] (const QString& result) { createBreakpoint(type, row); - } + }, + [this](const QString& error) { _handleSyncError(error); } ); + CommClient::instance().sendCommand(command); } // TODO: move the createRemoveCommand to a session manager -void BreakpointViewer::removeBreakpoint(Type type, int row) +void BreakpointViewer::removeBreakpoint(BreakpointData::Type type, int row, bool logical) { - auto table = selector[type]; - auto item = table->item(row, INDEX); + auto* table = tables[type]; + + auto* item = table->item(row, ID); + assert(!item->text().isEmpty()); + QString id = item->text(); - bool ok; - int index = item->text().toInt(&ok); - assert(ok); - item->setText(""); + const QString cmdStr = Breakpoints::createRemoveCommand(id); - const QString& id = breakpoints->getBreakpoint(index).id; - const QString cmdStr = Breakpoints::createRemoveCommand(id); + auto* command = new Command(cmdStr, + [this, type, row, id, logical] (const QString& result) { + size_t erased = maps[type].erase(id); + + if (erased != 1) { + setBreakpointChecked(type, row, Qt::Unchecked); + _handleKeyNotFound(); + return; + } - auto command = new Command(cmdStr, - [this] (const QString& result) { + if (!logical) { + auto* table = tables[type]; + auto sa = ScopedAssign(userMode, false); + table->removeRow(row); + } + + auto sa = ScopedAssign(selfUpdating, true); emit contentsUpdated(false); - }); + }, + [this] (const QString& error) { _handleSyncError(error); } + ); + CommClient::instance().sendCommand(command); } @@ -156,30 +212,61 @@ void BreakpointViewer::_createCondition(int row) { QString condition = cnTableWidget->item(row, T_CONDITION)->text(); if (condition.isEmpty()) { - setBreakpointStatus(CONDITION, row, false); + setBreakpointChecked(BreakpointData::CONDITION, row, Qt::Unchecked); return; + } else { + setBreakpointChecked(BreakpointData::CONDITION, row, Qt::Checked); } const QString cmdStr = Breakpoints::createSetCommand(Breakpoint::CONDITION, -1, -1, -1, -1, -1, condition); - auto command = new Command(cmdStr, - [this] (const QString& result) { + auto* command = new Command(cmdStr, + [this, row] (const QString& id) { + setTextField(BreakpointData::CONDITION, row, ID, id); + + BreakpointData data { BreakpointData::CONDITION, id, row, -1 }; + if (!connectBreakpointID(id, data)) { + _handleKeyAlreadyExists(); + return; + } + + auto sa = ScopedAssign(selfUpdating, true); emit contentsUpdated(false); - } + }, + [this] (const QString& error) { _handleSyncError(error); } ); + CommClient::instance().sendCommand(command); } -void BreakpointViewer::setBreakpointStatus(Type type, int row, int status) +void BreakpointViewer::setBreakpointChecked(BreakpointData::Type type, int row, Qt::CheckState state) +{ + auto sa = ScopedAssign(userMode, false); + + auto* table = tables[type]; + bool oldValue = table->isSortingEnabled(); + auto* item = table->item(row, ENABLED); + assert(item); + + table->setSortingEnabled(false); + item->setCheckState(state); + table->setSortingEnabled(oldValue); +} + +void BreakpointViewer::setTextField(BreakpointData::Type type, int row, int column, const QString& value) { - auto table = selector[type]; - auto item = table->item(row, ENABLED); - item->setFlags(status - ? item->flags() | Qt::ItemIsEnabled - : item->flags() & ~Qt::ItemIsEnabled); + auto sa = ScopedAssign(userMode, false); + + auto* table = tables[type]; + bool oldValue = table->isSortingEnabled(); + auto* item = table->item(row, column); + + table->setSortingEnabled(false); + item->setText(value); + table->setSortingEnabled(oldValue); } -bool BreakpointViewer::processSlotField(int index, const QString& field, qint8& ps, qint8& ss) +bool BreakpointViewer::parseSlotField(int index, const QString& field, qint8& ps, qint8& ss) { QStringList s = field.simplified().split("/", Qt::SplitBehaviorFlags::SkipEmptyParts); @@ -217,7 +304,7 @@ bool BreakpointViewer::processSlotField(int index, const QString& field, qint8& return true; } -bool BreakpointViewer::processSegmentField(int index, const QString& field, qint16& segment) +bool BreakpointViewer::parseSegmentField(int index, const QString& field, qint16& segment) { QString s = field.simplified(); @@ -241,10 +328,10 @@ bool BreakpointViewer::processSegmentField(int index, const QString& field, qint static const char* ComboTypeNames[] = { "read_mem", "write_mem", "read_io", "write_io" }; -bool BreakpointViewer::processLocationField(int index, BreakpointViewer::Type type, - const QString& field, int& begin, int& end, const QString comboTxt) +bool BreakpointViewer::parseLocationField(int index, BreakpointData::Type type, + const QString& field, int& begin, int& end, const QString& comboTxt) { - if (type == BreakpointViewer::BREAKPOINT) { + if (type == BreakpointData::BREAKPOINT) { if (int value = stringToValue(field.simplified()); value > -1 && value < 0x10000) { begin = value; end = -1; @@ -261,37 +348,35 @@ bool BreakpointViewer::processLocationField(int index, BreakpointViewer::Type ty begin = (s.size() >= 1) ? stringToValue(s[0]) : -1; end = (s.size() == 2) ? stringToValue(s[1]) : (s.size() == 1 ? begin : -1); - auto iter = std::find(std::begin(ComboTypeNames), std::end(ComboTypeNames), comboTxt); - auto type2 = static_cast(std::distance(ComboTypeNames, iter) + 1); - if ((type2 == Breakpoint::WATCHPOINT_IOREAD || type2 == Breakpoint::WATCHPOINT_IOWRITE) + auto iter = std::find(std::begin(ComboTypeNames), std::end(ComboTypeNames), comboTxt); + auto wtype = static_cast(std::distance(ComboTypeNames, iter) + 1); + if ((wtype == Breakpoint::WATCHPOINT_IOREAD || wtype == Breakpoint::WATCHPOINT_IOWRITE) && (begin > 0xFF || end > 0xFF)) { return false; } if (begin != -1 && end != -1 && end >= begin) return true; if (index == -1) return false; - auto bp = breakpoints->getBreakpoint(index); - begin = bp.address; - end = bp.regionEnd; + const auto& bp = breakpoints->getBreakpoint(index); + begin = bp.address; + end = bp.regionEnd; return true; } -void BreakpointViewer::changeTableItem(Type type, QTableWidgetItem* item) +void BreakpointViewer::changeTableItem(BreakpointData::Type type, QTableWidgetItem* item) { - auto table = selector[type]; - int row = table->row(item); - auto indexItem = table->item(row, INDEX); + auto* table = tables[type]; + int row = table->row(item); // trying to modify a bp instead of creating a new one - bool createBp = false; - - bool ok; - int index = indexItem != nullptr ? indexItem->text().toInt(&ok) : false; - if (!ok) index = -1; + bool createBp = false; + auto* idItem = table->item(row, ID); + auto it = findBreakpointData(type, idItem->text()); + int index = it != maps[type].end() ? std::distance(maps[type].begin(), it) : -1; // check if breakpoint is enabled - QTableWidgetItem* enabledItem = table->item(row, ENABLED); - bool enabled = enabledItem->checkState() == Qt::Checked; + auto* enabledItem = table->item(row, ENABLED); + bool enabled = enabledItem->checkState() == Qt::Checked; switch (table->column(item)) { case ENABLED: @@ -299,17 +384,17 @@ void BreakpointViewer::changeTableItem(Type type, QTableWidgetItem* item) break; case WP_TYPE: assert(table == wpTableWidget); - if (!enabled) return; - break; + item = table->item(row, LOCATION); + [[fallthrough]]; case LOCATION: { - auto model = table->model(); - auto combo = (QComboBox*) table->indexWidget(model->index(row, WP_TYPE)); - int adrLen; + auto* model = table->model(); + auto* combo = (QComboBox*) table->indexWidget(model->index(row, WP_TYPE)); + int adrLen; - if (type == CONDITION) { + if (type == BreakpointData::CONDITION) { return; } - else if (type == WATCHPOINT) { + else if (type == BreakpointData::WATCHPOINT) { Breakpoint::Type wtype = static_cast(combo->currentIndex() + 1); adrLen = (wtype == Breakpoint::WATCHPOINT_IOREAD || wtype == Breakpoint::WATCHPOINT_IOWRITE) ? 2 : 4; @@ -318,59 +403,50 @@ void BreakpointViewer::changeTableItem(Type type, QTableWidgetItem* item) } int begin, end; - if (processLocationField(index, type, item->text(), begin, end, combo ? combo->currentText() : "")) { - auto sa = ScopedAssign(userMode, false); - item->setText(QString("%1%2%3").arg(hexValue(begin, adrLen)) + if (parseLocationField(index, type, item->text(), begin, end, combo ? combo->currentText() : "")) { + setTextField(type, row, LOCATION, QString("%1%2%3").arg(hexValue(begin, adrLen)) .arg(end == begin || end == -1 ? "" : ":") .arg(end == begin || end == -1 ? "" : hexValue(end, adrLen))); - setBreakpointStatus(type, row, true); } else { - auto sa = ScopedAssign(userMode, false); enabled = false; - item->setText(""); - setBreakpointStatus(type, row, false); + setTextField(type, row, LOCATION, ""); + setBreakpointChecked(type, row, Qt::Unchecked); } if (!enabled) return; break; } case SLOT: { qint8 ps, ss; - if (processSlotField(index, item->text(), ps, ss)) { - auto sa = ScopedAssign(userMode, false); - item->setText(QString("%1/%2") + if (parseSlotField(index, item->text(), ps, ss)) { + setTextField(type, row, SLOT, QString("%1/%2") .arg(ps == -1 ? QChar('X') : QChar('0' + ps)) .arg(ss == -1 ? QChar('X') : QChar('0' + ss))); } else { - auto sa = ScopedAssign(userMode, false); - item->setText("X/X"); + setTextField(type, row, SLOT, "X/X"); } if (!enabled) return; break; } case SEGMENT: { qint16 segment; - if (processSegmentField(index, item->text(), segment)) { - auto sa = ScopedAssign(userMode, false); - item->setText(QString("%1").arg(segment == -1 ? "X" : QString::number(segment))); + if (parseSegmentField(index, item->text(), segment)) { + setTextField(type, row, SEGMENT, QString("%1").arg(segment == -1 ? "X" : QString::number(segment))); } else { - auto sa = ScopedAssign(userMode, false); - item->setText("X"); + setTextField(type, row, SEGMENT, "X"); } if (!enabled) return; break; } case T_CONDITION: { - auto sa = ScopedAssign(userMode, false); - item->setText(item->text().simplified()); + setTextField(type, row, T_CONDITION, item->text().simplified()); - if (type == CONDITION && !item->text().isEmpty()) { - setBreakpointStatus(type, row, true); + if (type == BreakpointData::CONDITION) { + setBreakpointChecked(type, row, item->text().isEmpty() ? Qt::Unchecked : item->checkState()); } - if (!enabled) return; break; } - case INDEX: + case ID: return; default: qWarning() << "Unknown table column" << table->column(item); @@ -383,27 +459,45 @@ void BreakpointViewer::changeTableItem(Type type, QTableWidgetItem* item) } else { replaceBreakpoint(type, row); } - } else if (index != -1) { - removeBreakpoint(type, row); + } else { + if (index != -1) removeBreakpoint(type, row, true); + } +} + +void BreakpointViewer::disableSorting(BreakpointData::Type type) +{ + auto unsort = [](auto* table) { + table->sortByColumn(-1, Qt::AscendingOrder); + }; + + if (type == BreakpointData::ALL) { + for (auto* table : tables) { + unsort(table); + } + } else { + unsort(tables[type]); } } void BreakpointViewer::changeBpTableItem(QTableWidgetItem* item) { if (!userMode) return; - changeTableItem(BREAKPOINT, item); + disableSorting(BreakpointData::BREAKPOINT); + changeTableItem(BreakpointData::BREAKPOINT, item); } void BreakpointViewer::changeWpTableItem(QTableWidgetItem* item) { if (!userMode) return; - changeTableItem(WATCHPOINT, item); + disableSorting(BreakpointData::WATCHPOINT); + changeTableItem(BreakpointData::WATCHPOINT, item); } void BreakpointViewer::changeCnTableItem(QTableWidgetItem* item) { if (!userMode) return; - changeTableItem(CONDITION, item); + disableSorting(BreakpointData::CONDITION); + changeTableItem(BreakpointData::CONDITION, item); } void BreakpointViewer::setBreakpoints(Breakpoints* bps) @@ -411,23 +505,6 @@ void BreakpointViewer::setBreakpoints(Breakpoints* bps) breakpoints = bps; } -void BreakpointViewer::keepUnusedItems() -{ - for (auto table: selector) { - int row = 0; - - while (row < table->rowCount()) { - QTableWidgetItem* enabledItem = table->item(row, ENABLED); - bool enabled = enabledItem->checkState() == Qt::Checked; - if (enabled) { - table->removeRow(row); - } else { - row++; - } - } - } -} - void BreakpointViewer::setRunState() { runState = true; @@ -438,151 +515,166 @@ void BreakpointViewer::setBreakState() runState = false; } -void BreakpointViewer::sync() +void BreakpointViewer::stretchTable(BreakpointData::Type type) { - auto sa = ScopedAssign(userMode, false); - keepUnusedItems(); - - int bpRow = bpTableWidget->rowCount(); - int wpRow = wpTableWidget->rowCount(); - int cnRow = cnTableWidget->rowCount(); + auto stretch = [](auto* table) { + // stretching will not work without sorting + table->setSortingEnabled(true); + table->resizeColumnsToContents(); + }; + + if (type == BreakpointData::ALL) { + for (auto* table : tables) { + stretch(table); + } + } else { + stretch(tables[type]); + } +} - if (breakpoints->breakpointCount() != -1) { - for (int index = 0; index < breakpoints->breakpointCount(); ++index) { - const Breakpoint& bp = breakpoints->getBreakpoint(index); +std::map::iterator +BreakpointViewer::scanBreakpointData(const Breakpoint& bp) +{ + BreakpointData::Type type = bp.type == Breakpoint::BREAKPOINT ? BreakpointData::BREAKPOINT + : (bp.type == Breakpoint::CONDITION ? BreakpointData::CONDITION : BreakpointData::WATCHPOINT); + if (maps[type].empty()) return maps[type].end(); + + // assumed premise: a new bp will never repeat same name again + if (auto it = maps[type].find(bp.id); it != maps[type].end() && it->second.index == -1) { + // assumed premise 2: if name remains the same, bp is exactly the same. + return it; + } - switch (bp.type) { - case Breakpoint::BREAKPOINT: { - bpRow = createTableRow(BREAKPOINT); - setBreakpointStatus(BREAKPOINT, bpRow, true); + return maps[type].end(); +} - bool currentSlot = breakpoints->inCurrentSlot(bp); - QBrush color = palette().brush(runState | currentSlot ? - QPalette::ColorGroup::Normal : QPalette::ColorGroup::Disabled, - QPalette::WindowText); +bool BreakpointViewer::connectBreakpointID(const QString& id, BreakpointData& data) +{ + auto [_, result] = maps[data.type].try_emplace(id, data); + return result; +} - QTableWidgetItem* item1 = bpTableWidget->item(bpRow, ENABLED); - item1->setCheckState(Qt::Checked); +void BreakpointViewer::sync() +{ + // don't reload if self-inflicted update + if (selfUpdating) return; - QTableWidgetItem* item2 = bpTableWidget->item(bpRow, BP_ADDRESS); - item2->setForeground(color); - item2->setText(hexValue(bp.address, 4)); + bool empty[BreakpointData::ALL] = {false, false, false}; - QTableWidgetItem* item3 = bpTableWidget->item(bpRow, T_CONDITION); - item3->setForeground(color); - item3->setText(bp.condition.trimmed()); + for (int type = 0; type < BreakpointData::ALL; ++type) { + if (maps[type].empty()) empty[type] = true; - QTableWidgetItem* item4 = bpTableWidget->item(bpRow, SLOT); - item4->setForeground(color); - item4->setText(QString("%1/%2") - .arg(bp.ps == -1 ? QChar('X') : QChar('0' + bp.ps)) - .arg(bp.ss == -1 ? QChar('X') : QChar('0' + bp.ss))); + for (auto& [_, data] : maps[type]) { + data.index = -1; + data.row = -1; + } + } - QTableWidgetItem* item5 = bpTableWidget->item(bpRow, SEGMENT); - item5->setForeground(color); - item5->setText(bp.segment == -1 ? "X" : QString("%1").arg(bp.segment)); + for (int index = 0; index < breakpoints->breakpointCount(); ++index) { + const Breakpoint& bp = breakpoints->getBreakpoint(index); - QTableWidgetItem* item6 = bpTableWidget->item(bpRow, INDEX); - item6->setText(QString::number(index)); + BreakpointData::Type type = bp.type == Breakpoint::BREAKPOINT ? BreakpointData::BREAKPOINT + : (bp.type == Breakpoint::CONDITION ? BreakpointData::CONDITION : BreakpointData::WATCHPOINT); - break; - } - case Breakpoint::WATCHPOINT_MEMREAD: - case Breakpoint::WATCHPOINT_MEMWRITE: - case Breakpoint::WATCHPOINT_IOREAD: - case Breakpoint::WATCHPOINT_IOWRITE: { - wpRow = createTableRow(WATCHPOINT); - setBreakpointStatus(WATCHPOINT, wpRow, true); - - bool currentSlot = (bp.type == Breakpoint::WATCHPOINT_MEMREAD - || bp.type == Breakpoint::WATCHPOINT_MEMWRITE) - ? breakpoints->inCurrentSlot(bp) : true; - QBrush color = palette().brush(runState | currentSlot ? - QPalette::ColorGroup::Normal : QPalette::ColorGroup::Disabled, - QPalette::WindowText); - - QTableWidgetItem* item1 = wpTableWidget->item(wpRow, ENABLED); - item1->setCheckState(Qt::Checked); - - auto model = wpTableWidget->model(); - auto combo = (QComboBox*) wpTableWidget->indexWidget(model->index(wpRow, WP_TYPE)); - combo->setCurrentIndex(static_cast(bp.type) - 1); - int adrLen = (bp.type == Breakpoint::WATCHPOINT_IOREAD - || bp.type == Breakpoint::WATCHPOINT_IOWRITE) ? 2 : 4; - - QString address = hexValue(bp.address, adrLen); - QString regionEnd = hexValue(bp.regionEnd, adrLen); - QTableWidgetItem* item2 = wpTableWidget->item(wpRow, WP_REGION); - item2->setForeground(color); - item2->setText(QString("%1%2%3").arg(address) - .arg(regionEnd == -1 || regionEnd == address ? "" : ":") - .arg(regionEnd == -1 || regionEnd == address ? "" : regionEnd)); - - QTableWidgetItem* item3 = wpTableWidget->item(wpRow, T_CONDITION); - item3->setForeground(color); - item3->setText(bp.condition.trimmed()); - - QTableWidgetItem* item4 = wpTableWidget->item(wpRow, SLOT); - item4->setForeground(color); - item4->setText(QString("%1/%2") - .arg(bp.ps == -1 ? QChar('X') : QChar('0' + bp.ps)) - .arg(bp.ss == -1 ? QChar('X') : QChar('0' + bp.ss))); - - QTableWidgetItem* item5 = wpTableWidget->item(wpRow, SEGMENT); - item5->setForeground(color); - item5->setText(bp.segment == -1 ? "X" : QString("%1").arg(bp.segment)); - - QTableWidgetItem* item6 = wpTableWidget->item(wpRow, INDEX); - item6->setText(QString::number(index)); - - break; + if (!empty[type]) { + if (auto it = scanBreakpointData(bp); it != maps[type].end()) { + it->second.index = index; + continue; } - case Breakpoint::CONDITION: { - cnRow = createTableRow(CONDITION); - setBreakpointStatus(CONDITION, cnRow, true); - - QTableWidgetItem* item1 = cnTableWidget->item(cnRow, ENABLED); - item1->setCheckState(Qt::Checked); + } - QTableWidgetItem* item2 = cnTableWidget->item(cnRow, T_CONDITION); - item2->setText(bp.condition.trimmed()); + // create new BreakpointDatas + BreakpointData data { type, bp.id, -1, index }; + bool result = connectBreakpointID(bp.id, data); + assert(result); + } - QTableWidgetItem* item3 = cnTableWidget->item(cnRow, INDEX); - item3->setText(QString::number(index)); + // remove missing BreakpointData + for (auto& map : maps) { + auto it = map.begin(); - break; + while (it != map.end()) { + if (it->second.index == -1) { + auto oldit = it++; + map.erase(oldit->second.id); + } else { + it++; } - default: - assert(false); + } + } + + populate(); +} + +void BreakpointViewer::populate() +{ + // store unused items position by disabling ordering + disableSorting(); + + // remember selected rows + int bpFocus = bpTableWidget->currentRow(); + int wpFocus = wpTableWidget->currentRow(); + int cnFocus = cnTableWidget->currentRow(); + + // reuse current table rows + for (int index = 0; index < BreakpointData::ALL; ++index) { + BreakpointData::Type type = static_cast(index); + auto* table = tables[index]; + + for (int row = 0; row < table->rowCount(); ++row) { + auto* idItem = table->item(row, ID); + assert(idItem); + auto it = findBreakpointData(type, idItem->text()); + + // update reused breakpoint + if (it != maps[type].end()) { + it->second.row = row; + } else { + auto* enabledItem = table->item(row, ENABLED); + enabledItem->setCheckState(Qt::Unchecked); } } } - bpTableWidget->resizeColumnsToContents(); - wpTableWidget->resizeColumnsToContents(); - cnTableWidget->resizeColumnsToContents(); + // add only new breakpoints + for (auto& map : maps) { + for (auto& [_, data] : map) { + if (data.row != -1) continue; + data.row = createTableRow(data.type); + fillTableRow(data.index, data.type, data.row); + } + } - bpTableWidget->setSortingEnabled(true); - bpTableWidget->setSortingEnabled(false); - wpTableWidget->setSortingEnabled(true); - wpTableWidget->setSortingEnabled(false); + stretchTable(BreakpointData::ALL); + + bpTableWidget->selectRow(bpFocus); + wpTableWidget->selectRow(wpFocus); + cnTableWidget->selectRow(cnFocus); +} + +std::map::iterator +BreakpointViewer::findBreakpointData(BreakpointData::Type type, const QString& id) +{ + return maps[type].find(id); } void BreakpointViewer::changeCurrentWpType(int row, int selected) { if (!userMode) return; - auto item = wpTableWidget->item(row, WP_TYPE); - changeTableItem(WATCHPOINT, item); + auto* item = wpTableWidget->item(row, WP_TYPE); + changeTableItem(BreakpointData::WATCHPOINT, item); } void BreakpointViewer::createComboBox(int row) { - auto combo = new QComboBox(); + auto sa = ScopedAssign(userMode, false); + + auto* combo = new QComboBox(); combo->setEditable(false); combo->insertItems(0, QStringList(std::begin(ComboTypeNames), std::end(ComboTypeNames))); - auto model = wpTableWidget->model(); - auto index = model->index(row, WP_TYPE); + auto* model = wpTableWidget->model(); + auto index = model->index(row, WP_TYPE); wpTableWidget->setIndexWidget(index, combo); @@ -590,126 +682,197 @@ void BreakpointViewer::createComboBox(int row) [this, row](int index){ changeCurrentWpType(row, index); }); } -int BreakpointViewer::createTableRow(Type type) +Breakpoint::Type BreakpointViewer::readComboBox(int row) { - auto sa = ScopedAssign(userMode, false); + auto* model = wpTableWidget->model(); + auto* combo = (QComboBox*) wpTableWidget->indexWidget(model->index(row, WP_TYPE)); + return static_cast(combo->currentIndex() + 1); +} + +int BreakpointViewer::createTableRow(BreakpointData::Type type, int row) +{ + auto sa = ScopedAssign(userMode, false); + auto& table = tables[type]; - auto table = selector[type]; - int row = table->rowCount(); - table->setRowCount(row + 1); - table->selectRow(row); + if (row == -1) { + row = table->rowCount(); + table->setRowCount(row + 1); + table->selectRow(row); + } else { + table->insertRow(row); + table->selectRow(row); + } // enabled - QTableWidgetItem* item1 = new QTableWidgetItem(); - item1->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsSelectable); - item1->setCheckState(Qt::Unchecked); - table->setItem(row, ENABLED, item1); + auto* item0 = new QTableWidgetItem(); + item0->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); + item0->setTextAlignment(Qt::AlignCenter); + table->setItem(row, ENABLED, item0); - // type - QTableWidgetItem* item2 = new QTableWidgetItem(); - item2->setTextAlignment(Qt::AlignCenter); - table->setItem(row, WP_TYPE, item2); + // watchpoint type + auto* item1 = new QTableWidgetItem(); + item1->setTextAlignment(Qt::AlignCenter); + table->setItem(row, WP_TYPE, item1); - if (type == WATCHPOINT) { + if (type == BreakpointData::WATCHPOINT) { createComboBox(row); } // address - QTableWidgetItem* item3 = new QTableWidgetItem(); - item3->setTextAlignment(Qt::AlignCenter); - table->setItem(row, LOCATION, item3); + auto* item2 = new QTableWidgetItem(); + item2->setTextAlignment(Qt::AlignCenter); + table->setItem(row, LOCATION, item2); - if (type == CONDITION) { - item3->setFlags(Qt::NoItemFlags); - item3->setText("0"); + if (type == BreakpointData::CONDITION) { + item2->setFlags(Qt::NoItemFlags); + item2->setText("0"); + } else { + item2->setText(""); } + // condition + auto* item3 = new QTableWidgetItem(); + item3->setTextAlignment(Qt::AlignCenter); + item3->setText(""); + table->setItem(row, T_CONDITION, item3); + // ps/ss - QTableWidgetItem* item4 = new QTableWidgetItem(); + auto* item4 = new QTableWidgetItem(); item4->setTextAlignment(Qt::AlignCenter); item4->setText("X/X"); table->setItem(row, SLOT, item4); // segment - QTableWidgetItem* item5 = new QTableWidgetItem(); + auto* item5 = new QTableWidgetItem(); item5->setTextAlignment(Qt::AlignCenter); item5->setText("X"); table->setItem(row, SEGMENT, item5); - // condition - QTableWidgetItem* item6 = new QTableWidgetItem(); - item6->setTextAlignment(Qt::AlignCenter); - table->setItem(row, T_CONDITION, item6); - // index - QTableWidgetItem* item7 = new QTableWidgetItem(); - item7->setFlags(Qt::NoItemFlags); - table->setItem(row, INDEX, item7); - - table->resizeColumnsToContents(); + auto* item6 = new QTableWidgetItem(); + item6->setFlags(Qt::NoItemFlags); + item6->setText(""); + table->setItem(row, ID, item6); return row; } -void BreakpointViewer::on_btnAddBp_clicked() +void BreakpointViewer::fillTableRow(int index, BreakpointData::Type type, int row) { auto sa = ScopedAssign(userMode, false); - int row = createTableRow(BREAKPOINT); - setBreakpointStatus(BREAKPOINT, row, false); + + const auto& bp = breakpoints->getBreakpoint(index); + auto& table = tables[type]; + + // enabled status + setBreakpointChecked(type, row, Qt::Checked); + + // watchpoint type + if (type == BreakpointData::WATCHPOINT) { + auto* model = table->model(); + auto* combo = (QComboBox*) table->indexWidget(model->index(row, WP_TYPE)); + combo->setCurrentIndex(static_cast(bp.type) - 1); + } + + // location + int locLen = (bp.type == Breakpoint::WATCHPOINT_IOREAD + || bp.type == Breakpoint::WATCHPOINT_IOWRITE) ? 2 : 4; + QString address = hexValue(bp.address, locLen); + QString regionEnd = hexValue(bp.regionEnd, locLen); + QString location = QString("%1%2%3").arg(address) + .arg(regionEnd == -1 || regionEnd == address ? "" : ":") + .arg(regionEnd == -1 || regionEnd == address ? "" : regionEnd); + + auto* item2 = table->item(row, LOCATION); + item2->setText(location); + + auto* item3 = table->item(row, T_CONDITION); + item3->setText(bp.condition); + + // slot + QString slot = QString("%1/%2") + .arg(bp.ps == -1 ? QChar('X') : QChar('0' + bp.ps)) + .arg(bp.ss == -1 ? QChar('X') : QChar('0' + bp.ss)); + + auto* item4 = table->item(row, SLOT); + item4->setText(slot); + + // segment + QString segment = (bp.segment == -1 ? "X" : QString("%1").arg(bp.segment)); + + auto* item5 = table->item(row, SEGMENT); + item5->setText(segment); + + auto* item6 = table->item(row, ID); + item6->setText(bp.id); } -void BreakpointViewer::on_btnRemoveBp_clicked() +void BreakpointViewer::onAddBtnClicked(BreakpointData::Type type) { - if (bpTableWidget->currentRow() == -1) + auto sa = ScopedAssign(userMode, false); + + auto* table = tables[type]; + table->sortByColumn(-1, Qt::AscendingOrder); + table->setSortingEnabled(false); + + int row = createTableRow(type, 0); + setBreakpointChecked(type, row, Qt::Unchecked); + stretchTable(type); + table->setSortingEnabled(true); +} + +void BreakpointViewer::onRemoveBtnClicked(BreakpointData::Type type) +{ + auto* table = tables[type]; + + if (table->currentRow() == -1) return; auto sa = ScopedAssign(userMode, false); - QTableWidgetItem* item = bpTableWidget->item(bpTableWidget->currentRow(), ENABLED); + QTableWidgetItem* item = table->item(table->currentRow(), ENABLED); if (item->checkState() == Qt::Checked) { - removeBreakpoint(BREAKPOINT, bpTableWidget->currentRow()); + removeBreakpoint(type, table->currentRow(), false); + } else { + table->removeRow(table->currentRow()); } - bpTableWidget->removeRow(bpTableWidget->currentRow()); - bpTableWidget->resizeColumnsToContents(); +} + +void BreakpointViewer::on_btnAddBp_clicked() +{ + onAddBtnClicked(BreakpointData::BREAKPOINT); +} + +void BreakpointViewer::on_btnRemoveBp_clicked() +{ + onRemoveBtnClicked(BreakpointData::BREAKPOINT); } void BreakpointViewer::on_btnAddWp_clicked() { - auto sa = ScopedAssign(userMode, false); - int row = createTableRow(WATCHPOINT); - setBreakpointStatus(WATCHPOINT, row, false); + onAddBtnClicked(BreakpointData::WATCHPOINT); } void BreakpointViewer::on_btnRemoveWp_clicked() { - if (wpTableWidget->currentRow() == -1) - return; - - auto sa = ScopedAssign(userMode, false); - QTableWidgetItem* item = wpTableWidget->item(wpTableWidget->currentRow(), ENABLED); - if (item->checkState() == Qt::Checked) { - removeBreakpoint(WATCHPOINT, wpTableWidget->currentRow()); - } - wpTableWidget->removeRow(wpTableWidget->currentRow()); - wpTableWidget->resizeColumnsToContents(); + onRemoveBtnClicked(BreakpointData::WATCHPOINT); } void BreakpointViewer::on_btnAddCn_clicked() { - auto sa = ScopedAssign(userMode, false); - int row = createTableRow(CONDITION); - setBreakpointStatus(CONDITION, row, false); + onAddBtnClicked(BreakpointData::CONDITION); } void BreakpointViewer::on_btnRemoveCn_clicked() { - if (cnTableWidget->currentRow() == -1) - return; + onRemoveBtnClicked(BreakpointData::CONDITION); +} - auto sa = ScopedAssign(userMode, false); - QTableWidgetItem* item = cnTableWidget->item(cnTableWidget->currentRow(), ENABLED); - if (item->checkState() == Qt::Checked) { - removeBreakpoint(CONDITION, cnTableWidget->currentRow()); - } - cnTableWidget->removeRow(cnTableWidget->currentRow()); - cnTableWidget->resizeColumnsToContents(); +void BreakpointViewer::on_itemPressed(QTableWidgetItem* item) +{ + bpTableWidget->setSortingEnabled(false); +} + +void BreakpointViewer::on_headerClicked(int index) +{ + bpTableWidget->setSortingEnabled(true); } diff --git a/src/BreakpointViewer.h b/src/BreakpointViewer.h index f9c999ed..74f582c2 100644 --- a/src/BreakpointViewer.h +++ b/src/BreakpointViewer.h @@ -9,6 +9,15 @@ class QPaintEvent; class Breakpoints; +struct BreakpointData { + enum Type { BREAKPOINT, WATCHPOINT, CONDITION, ALL } type; + + QString id; + + int row = -1; + int index = -1; +}; + class BreakpointViewer : public QTabWidget, private Ui::BreakpointViewer { Q_OBJECT @@ -16,44 +25,55 @@ class BreakpointViewer : public QTabWidget, private Ui::BreakpointViewer BreakpointViewer(QWidget* parent = nullptr); void setBreakpoints(Breakpoints* bps); - enum Type { BREAKPOINT, WATCHPOINT, CONDITION }; - private: Ui::BreakpointViewer* ui; - QTableWidget* selector[3]; - - // layout - int frameL, frameR, frameT, frameB; - int leftRegPos, leftValuePos, rightRegPos, rightValuePos; - int rowHeight; - int cursorLoc; - bool userMode; + QTableWidget* tables[BreakpointData::ALL]; + std::map maps[BreakpointData::ALL]; + + bool selfUpdating = false; + bool userMode = true; bool runState; bool conditionsMsg = false; Breakpoints* breakpoints; - bool processLocationField(int index, BreakpointViewer::Type type, const QString& field, - int& begin, int& end, const QString combo = QString()); - bool processSlotField(int index, const QString& field, qint8& ps, qint8& ss); - bool processSegmentField(int index, const QString& field, qint16& segment); - void changeTableItem(Type type, QTableWidgetItem* item); + void setTextField(BreakpointData::Type type, int row, int column, const QString& value); + bool parseLocationField(int index, BreakpointData::Type type, const QString& field, + int& begin, int& end, const QString& combo = {}); + bool parseSlotField(int index, const QString& field, qint8& ps, qint8& ss); + bool parseSegmentField(int index, const QString& field, qint16& segment); + void changeTableItem(BreakpointData::Type type, QTableWidgetItem* item); void createComboBox(int row); - int createTableRow(Type type); + Breakpoint::Type readComboBox(int row); + int createTableRow(BreakpointData::Type type, int row = -1); + void fillTableRow(int index, BreakpointData::Type type, int row); + bool connectBreakpointID(const QString& id, BreakpointData& data); + void populate(); - void createBreakpoint(Type type, int row); - void _createBreakpoint(Type type, int row); + void createBreakpoint(BreakpointData::Type type, int row); + void _handleSyncError(const QString& error); + void _handleKeyAlreadyExists(); + void _handleKeyNotFound(); + void _createBreakpoint(BreakpointData::Type type, int row); void _createCondition(int row); - void replaceBreakpoint(Type type, int row); - void removeBreakpoint(Type type, int row); - void setBreakpointStatus(Type type, int row, int status); - void keepUnusedItems(); + void replaceBreakpoint(BreakpointData::Type type, int row); + void removeBreakpoint(BreakpointData::Type type, int row, bool logical = false); + void setBreakpointChecked(BreakpointData::Type type, int row, Qt::CheckState state); + void onAddBtnClicked(BreakpointData::Type type); + void onRemoveBtnClicked(BreakpointData::Type type); + void stretchTable(BreakpointData::Type type = BreakpointData::ALL); + + std::map::iterator scanBreakpointData(const Breakpoint& bp); + std::map::iterator findBreakpointData(BreakpointData::Type type, const QString& id); private slots: void changeCurrentWpType(int row, int index); + void disableSorting(BreakpointData::Type type = BreakpointData::ALL); void changeBpTableItem(QTableWidgetItem* item); void changeWpTableItem(QTableWidgetItem* item); void changeCnTableItem(QTableWidgetItem* item); + void on_itemPressed(QTableWidgetItem* item); + void on_headerClicked(int index); public slots: void on_btnAddBp_clicked(); diff --git a/src/BreakpointViewer.ui b/src/BreakpointViewer.ui index fc2b50f6..41a4ce42 100644 --- a/src/BreakpointViewer.ui +++ b/src/BreakpointViewer.ui @@ -75,7 +75,7 @@ 120 - false + true false @@ -197,7 +197,7 @@ 120 - false + true false @@ -319,7 +319,7 @@ 120 - false + true false diff --git a/src/DebuggerData.cpp b/src/DebuggerData.cpp index 3446aa4f..b8b7a15a 100644 --- a/src/DebuggerData.cpp +++ b/src/DebuggerData.cpp @@ -127,6 +127,7 @@ QString Breakpoints::createRemoveCommand(const QString& id) void Breakpoints::setBreakpoints(const QString& str) { breakpoints.clear(); + QStringList bps = str.split('\n'); for (auto& bp : bps) { if (bp.trimmed().isEmpty()) continue; @@ -269,9 +270,9 @@ bool Breakpoints::inCurrentSlot(const Breakpoint& bp) void Breakpoints::insertBreakpoint(Breakpoint& bp) { auto it = std::upper_bound(breakpoints.begin(), breakpoints.end(), bp, - [](const Breakpoint& bp1, const Breakpoint& bp2) { - return bp1.address < bp2.address; - } + [](const Breakpoint& bp1, const Breakpoint& bp2) { + return bp1.address < bp2.address; + } ); breakpoints.insert(it, bp); } diff --git a/src/DebuggerForm.cpp b/src/DebuggerForm.cpp index 6beac9a6..813fad7a 100644 --- a/src/DebuggerForm.cpp +++ b/src/DebuggerForm.cpp @@ -580,6 +580,7 @@ void DebuggerForm::createForm() bpView = new BreakpointViewer(this); connect(bpView, &BreakpointViewer::contentsUpdated, this, &DebuggerForm::reloadBreakpoints); connect(this, &DebuggerForm::breakpointsUpdated, bpView, &BreakpointViewer::sync); + dw = new DockableWidget(dockMan); dw->setWidget(bpView); dw->setTitle(tr("Debug list")); @@ -592,8 +593,6 @@ void DebuggerForm::createForm() &DebuggerForm::dockWidgetVisibilityChanged); connect(this, &DebuggerForm::runStateEntered, bpView, &BreakpointViewer::setRunState); connect(this, &DebuggerForm::breakStateEntered, bpView, &BreakpointViewer::setBreakState); - // TODO replace sync by new function that only repaint bp/wp row - connect(slotView, &SlotViewer::slotsUpdated, bpView, &BreakpointViewer::sync); // restore layout restoreGeometry(Settings::get().value("Layout/WindowGeometry", saveGeometry()).toByteArray()); @@ -674,7 +673,6 @@ void DebuggerForm::createForm() session.breakpoints().setMemoryLayout(&memLayout); mainMemory = new unsigned char[65536 + 4]; memset(mainMemory, 0, 65536 + 4); - bpView->setBreakpoints(&session.breakpoints()); disasmView->setMemory(mainMemory); disasmView->setBreakpoints(&session.breakpoints()); disasmView->setMemoryLayout(&memLayout); @@ -682,6 +680,7 @@ void DebuggerForm::createForm() mainMemoryView->setDebuggable("memory", 65536); stackView->setData(mainMemory, 65536); slotView->setMemoryLayout(&memLayout); + bpView->setBreakpoints(&session.breakpoints()); } DebuggerForm::~DebuggerForm()