From f8a258f98c1d9a3202cc98ed0a7c00e850fd9479 Mon Sep 17 00:00:00 2001 From: yasmin Date: Mon, 25 Mar 2024 06:08:04 -0400 Subject: [PATCH 1/2] [WIP] Add multiline selection with Shift + Left Click --- src/widgets/DisassemblyWidget.cpp | 101 ++++++++++++++++++++++++++++-- src/widgets/DisassemblyWidget.h | 13 +++- 2 files changed, 108 insertions(+), 6 deletions(-) diff --git a/src/widgets/DisassemblyWidget.cpp b/src/widgets/DisassemblyWidget.cpp index 946a2d357..bbeef00e9 100644 --- a/src/widgets/DisassemblyWidget.cpp +++ b/src/widgets/DisassemblyWidget.cpp @@ -439,6 +439,29 @@ void DisassemblyWidget::highlightPCLine() mDisasTextEdit->setExtraSelections(currentSelections); } +void DisassemblyWidget::highlightMultiLineSelections() +{ + QList extraSelections; + QTextEdit::ExtraSelection highlightSelection; + QColor highlightColor = ConfigColor("lineHighlight"); + + // Highlight each selection in the multilineSelections list + for (const QTextEdit::ExtraSelection &selection : mDisasTextEdit->getMultiLineSelections()) { + highlightSelection.format.setBackground(highlightColor); + highlightSelection.format.setProperty(QTextFormat::FullWidthSelection, true); + highlightSelection.cursor = mDisasTextEdit->textCursor(); + highlightSelection.cursor.setPosition(selection.cursor.selectionStart()); + highlightSelection.cursor.setPosition(selection.cursor.selectionEnd(), + QTextCursor::KeepAnchor); + extraSelections.append(highlightSelection); + } + + // Don't override any extraSelections already set + QList currentSelections = mDisasTextEdit->extraSelections(); + currentSelections.append(extraSelections); + mDisasTextEdit->setExtraSelections(currentSelections); +} + void DisassemblyWidget::showDisasContextMenu(const QPoint &pt) { mCtxMenu->exec(mDisasTextEdit->mapToGlobal(pt)); @@ -511,7 +534,7 @@ void DisassemblyWidget::updateCursorPosition() } highlightPCLine(); - + highlightMultiLineSelections(); connectCursorPositionChanged(false); } @@ -546,6 +569,7 @@ void DisassemblyWidget::cursorPositionChanged() seekFromCursor = false; highlightCurrentLine(); highlightPCLine(); + highlightMultiLineSelections(); mCtxMenu->setCanCopy(mDisasTextEdit->textCursor().hasSelection()); if (mDisasTextEdit->textCursor().hasSelection()) { // A word is selected so use it @@ -615,6 +639,7 @@ void DisassemblyWidget::moveCursorRelative(bool up, bool page) seekable->seek(offset); highlightCurrentLine(); highlightPCLine(); + highlightMultiLineSelections(); } } } @@ -661,7 +686,10 @@ bool DisassemblyWidget::eventFilter(QObject *obj, QEvent *event) void DisassemblyWidget::keyPressEvent(QKeyEvent *event) { - if (event->key() == Qt::Key_Return) { + if (event->key() == Qt::Key_Shift) { + qDebug() << "shift key pressed"; + mDisasTextEdit->setMultiLineSelection(true); + } else if (event->key() == Qt::Key_Return) { const QTextCursor cursor = mDisasTextEdit->textCursor(); jumpToOffsetUnderCursor(cursor); } @@ -669,6 +697,16 @@ void DisassemblyWidget::keyPressEvent(QKeyEvent *event) MemoryDockWidget::keyPressEvent(event); } +void DisassemblyWidget::keyReleaseEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Shift) { + qDebug() << "shift key released"; + mDisasTextEdit->setMultiLineSelection(false); + } + + MemoryDockWidget::keyReleaseEvent(event); +} + QString DisassemblyWidget::getWindowTitle() const { return tr("Disassembly"); @@ -788,9 +826,64 @@ void DisassemblyTextEdit::keyPressEvent(QKeyEvent *event) Q_UNUSED(event) // QPlainTextEdit::keyPressEvent(event); } - void DisassemblyTextEdit::mousePressEvent(QMouseEvent *event) { + if (event->button() == Qt::LeftButton && !multiLineSelection) { + multilineSelections.clear(); + } + + // Check for left button click and multiline individual selection mode (shift key pressed) + if (event->button() == Qt::LeftButton && multiLineSelection) { + // Use cursorForPosition to set the cursor to the exact click position + QTextCursor cursor = cursorForPosition(event->pos()); + setTextCursor(cursor); // Update the text cursor to this position + + int startPos = cursor.selectionStart(); + int endPos = cursor.selectionEnd(); + + // Format selection + QTextEdit::ExtraSelection selection; + selection.format.setBackground(QColor("green")); + selection.format.setProperty(QTextFormat::FullWidthSelection, true); + + // Move cursor to start of the line + cursor.movePosition(QTextCursor::StartOfLine); + startPos = cursor.position(); + + // Extend cursor to the end of the line + cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); + endPos = cursor.position(); + + // Debug log + qDebug() << "Offset of line added: " << startPos << "-" << endPos; + + // Check if line is already selected + auto it = std::find_if(multilineSelections.begin(), multilineSelections.end(), + [startPos, endPos](const QTextEdit::ExtraSelection &s) { + return s.cursor.selectionStart() == startPos + && s.cursor.selectionEnd() == endPos; + }); + + if (it != multilineSelections.end()) { + // Deselect if already selected + multilineSelections.erase(it); + } else { + // Add this line to the selections if not already selected + selection.cursor = cursor; + multilineSelections.append(selection); + } + + // Apply the selections + setExtraSelections(multilineSelections); + + // Debug log + qDebug() << "Multiline selections:"; + for (const auto &selection : multilineSelections) { + qDebug() << "Start:" << selection.cursor.selectionStart() + << "End:" << selection.cursor.selectionEnd(); + } + } + QPlainTextEdit::mousePressEvent(event); if (event->button() == Qt::RightButton && !textCursor().hasSelection()) { @@ -1027,4 +1120,4 @@ void DisassemblyLeftPanel::clearArrowFrom(RVA offset) if (it != arrows.end()) { arrows.erase(it); } -} +} \ No newline at end of file diff --git a/src/widgets/DisassemblyWidget.h b/src/widgets/DisassemblyWidget.h index 8dadd4d4b..7074323c7 100644 --- a/src/widgets/DisassemblyWidget.h +++ b/src/widgets/DisassemblyWidget.h @@ -41,6 +41,7 @@ public slots: * overrides all previous highlighting. */ void highlightPCLine(); + void highlightMultiLineSelections(); void showDisasContextMenu(const QPoint &pt); void fontsUpdatedSlot(); void colorsUpdatedSlot(); @@ -86,6 +87,7 @@ protected slots: RVA readCurrentDisassemblyOffset(); bool eventFilter(QObject *obj, QEvent *event) override; void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; QString getWindowTitle() const override; int topOffsetHistoryPos = 0; @@ -129,11 +131,16 @@ class DisassemblyTextEdit : public QPlainTextEdit public: explicit DisassemblyTextEdit(QWidget *parent = nullptr) - : QPlainTextEdit(parent), lockScroll(false) + : QPlainTextEdit(parent), lockScroll(false), multiLineSelection(false) { } void setLockScroll(bool lock) { this->lockScroll = lock; } + void setMultiLineSelection(bool multiLineSelection) { this->multiLineSelection = multiLineSelection; } + void setMultiLineSelections(const QList &selections) { multilineSelections = selections; } + + bool getMultiLineSelection() const { return multiLineSelection; } + QList getMultiLineSelections() const { return multilineSelections; } qreal textOffset() const; @@ -145,6 +152,8 @@ class DisassemblyTextEdit : public QPlainTextEdit private: bool lockScroll; + bool multiLineSelection; + QList multilineSelections; }; /** @@ -197,4 +206,4 @@ class DisassemblyLeftPanel : public QFrame std::vector arrows; }; -#endif // DISASSEMBLYWIDGET_H +#endif // DISASSEMBLYWIDGET_H \ No newline at end of file From 13d2c5ef025a7119b9d84dba2f13b7e1f13b754f Mon Sep 17 00:00:00 2001 From: Rohan-Sagar Date: Fri, 3 May 2024 20:56:50 -0400 Subject: [PATCH 2/2] Added context menu with Set as option --- src/core/Cutter.cpp | 3 + src/menus/DisassemblyContextMenu.cpp | 112 ++++++++++++++++++++++++--- src/menus/DisassemblyContextMenu.h | 12 ++- src/widgets/DisassemblyWidget.cpp | 11 ++- 4 files changed, 126 insertions(+), 12 deletions(-) diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 0f646b810..a5eeed994 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -772,6 +773,7 @@ void CutterCore::editBytesEndian(RVA addr, const QString &bytes) void CutterCore::setToCode(RVA addr) { + qDebug() << "setToCode" << addr << "\n"; CORE_LOCK(); rz_meta_del(core->analysis, RZ_META_TYPE_STRING, core->offset, 1); rz_meta_del(core->analysis, RZ_META_TYPE_DATA, core->offset, 1); @@ -842,6 +844,7 @@ QString CutterCore::getMetaString(RVA addr) void CutterCore::setToData(RVA addr, int size, int repeat) { + qDebug() << "setToData" << addr << size << repeat << "\n"; if (size <= 0 || repeat <= 0) { return; } diff --git a/src/menus/DisassemblyContextMenu.cpp b/src/menus/DisassemblyContextMenu.cpp index fbadfecac..13afce8ad 100644 --- a/src/menus/DisassemblyContextMenu.cpp +++ b/src/menus/DisassemblyContextMenu.cpp @@ -1,4 +1,5 @@ #include "DisassemblyContextMenu.h" +#include "common/DisassemblyPreview.h" #include "dialogs/preferences/PreferencesDialog.h" #include "dialogs/EditInstructionDialog.h" #include "dialogs/CommentsDialog.h" @@ -15,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -242,7 +245,7 @@ void DisassemblyContextMenu::addSetAsMenu() { setAsMenu = addMenu(tr("Set as...")); - initAction(&actionSetToCode, tr("Code"), SLOT(on_actionSetToCode_triggered()), + initAction(&actionSetToCode, tr("Code"), SLOT(applySetToCode()), getSetToCodeSequence()); setAsMenu->addAction(&actionSetToCode); @@ -262,35 +265,83 @@ void DisassemblyContextMenu::addSetAsMenu() addSetToDataMenu(); } +void DisassemblyContextMenu::applySetToCode() { + qDebug() << "Applying set to code" << "\n"; + if (selectedLines.size() > 1) { + QVector> offsets; + for (const auto &selection : selectedLines) { + int startPos = selection.cursor.selectionStart(); + RVA lineOffset = DisassemblyPreview::readDisassemblyOffset(selection.cursor); + offsets.append(qMakePair(startPos, lineOffset)); + } + + // Sorting by start position in descending order + std::sort(offsets.begin(), offsets.end(), + [](const QPair &a, const QPair &b) { + return a.first > b.first; + }); + + for (const auto &offset : offsets) { + qDebug() << "Offset:" << offset.second; + on_actionSetToCode_triggered(offset.second); + } + } else if (selectedLines.size() <= 1) { + on_actionSetToCode_triggered(); + } + selectedLines.clear(); +} + void DisassemblyContextMenu::addSetToDataMenu() { setToDataMenu = setAsMenu->addMenu(tr("Data...")); initAction(&actionSetToDataByte, tr("Byte")); setToDataMenu->addAction(&actionSetToDataByte); - connect(&actionSetToDataByte, &QAction::triggered, this, [this] { setToData(1); }); + connect(&actionSetToDataByte, &QAction::triggered, this, [this] { applySetToData(1); }); initAction(&actionSetToDataWord, tr("Word")); setToDataMenu->addAction(&actionSetToDataWord); - connect(&actionSetToDataWord, &QAction::triggered, this, [this] { setToData(2); }); + connect(&actionSetToDataWord, &QAction::triggered, this, [this] { applySetToData(2); }); initAction(&actionSetToDataDword, tr("Dword")); setToDataMenu->addAction(&actionSetToDataDword); - connect(&actionSetToDataDword, &QAction::triggered, this, [this] { setToData(4); }); + connect(&actionSetToDataDword, &QAction::triggered, this, [this] { applySetToData(4); }); initAction(&actionSetToDataQword, tr("Qword")); setToDataMenu->addAction(&actionSetToDataQword); - connect(&actionSetToDataQword, &QAction::triggered, this, [this] { setToData(8); }); + connect(&actionSetToDataQword, &QAction::triggered, this, [this] { applySetToData(8); }); - initAction(&actionSetToDataEx, "...", SLOT(on_actionSetToDataEx_triggered()), - getSetToDataExSequence()); + initAction(&actionSetToDataEx, "...", SLOT(on_actionSetToDataEx_triggered()), getSetToDataExSequence()); setToDataMenu->addAction(&actionSetToDataEx); +} + +void DisassemblyContextMenu::applySetToData(int dataSize) { + qDebug() << "Applying set to data with size:" << dataSize << "\n"; + if (selectedLines.size() > 1) { + QVector> offsets; + for (const auto &selection : selectedLines) { + int startPos = selection.cursor.selectionStart(); + RVA lineOffset = DisassemblyPreview::readDisassemblyOffset(selection.cursor); + offsets.append(qMakePair(startPos, lineOffset)); + } - auto switchAction = new QAction(this); - initAction(switchAction, "Switch Data", SLOT(on_actionSetToData_triggered()), - getSetToDataSequence()); + // Sorting by start position in descending order + std::sort(offsets.begin(), offsets.end(), + [](const QPair &a, const QPair &b) { + return a.first > b.first; + }); + + for (const auto &offset : offsets) { + qDebug() << "Offset:" << offset.second; + setToData(offset.second, dataSize); + } + } else if (selectedLines.size() <= 1) { + setToData(dataSize); + } + selectedLines.clear(); } + void DisassemblyContextMenu::addEditMenu() { editMenu = addMenu(tr("Edit")); @@ -365,6 +416,27 @@ QVector DisassemblyContextMenu::getThingU return result; } + +void DisassemblyContextMenu::prepareMenu(const QList& selectedLines) { + this->clear(); + this->selectedLines = selectedLines; + + qDebug() << "Number of selected lines:" << selectedLines.size(); + for (const auto& selection : selectedLines) { + int cursorPosition = selection.cursor.position(); + int anchorPosition = selection.cursor.anchor(); + auto offset = DisassemblyPreview::readDisassemblyOffset(selection.cursor); + + qDebug() << "Cursor position:" << cursorPosition << "Anchor position:" << anchorPosition << "Offset:" << offset << "\n"; + } + + addAction(&actionCopy); + + if (selectedLines.size() > 1) { + addSetAsMenu(); + } +} + void DisassemblyContextMenu::setOffset(RVA offset) { this->offset = offset; @@ -964,16 +1036,31 @@ void DisassemblyContextMenu::on_actionSetToCode_triggered() Core()->setToCode(offset); } +void DisassemblyContextMenu::on_actionSetToCode_triggered(RVA offset) +{ + Core()->setToCode(offset); +} + void DisassemblyContextMenu::on_actionSetAsString_triggered() { Core()->setAsString(offset); } +void DisassemblyContextMenu::on_actionSetAsString_triggered(RVA offset) +{ + Core()->setAsString(offset); +} + void DisassemblyContextMenu::on_actionSetAsStringRemove_triggered() { Core()->removeString(offset); } +void DisassemblyContextMenu::on_actionSetAsStringRemove_triggered(RVA offset) +{ + Core()->removeString(offset); +} + void DisassemblyContextMenu::on_actionSetAsStringAdvanced_triggered() { EditStringDialog dialog(parentForDialog()); @@ -1116,6 +1203,11 @@ void DisassemblyContextMenu::setToData(int size, int repeat) Core()->setToData(offset, size, repeat); } +void DisassemblyContextMenu::setToData(RVA offset, int size, int repeat) +{ + Core()->setToData(offset, size, repeat); +} + QAction *DisassemblyContextMenu::addAnonymousAction(QString name, const char *slot, QKeySequence keySequence) { diff --git a/src/menus/DisassemblyContextMenu.h b/src/menus/DisassemblyContextMenu.h index 0cf915561..0842eda9f 100644 --- a/src/menus/DisassemblyContextMenu.h +++ b/src/menus/DisassemblyContextMenu.h @@ -4,6 +4,8 @@ #include "core/Cutter.h" #include "common/IOModesController.h" #include +#include +#include #include class MainWindow; @@ -22,6 +24,8 @@ class CUTTER_EXPORT DisassemblyContextMenu : public QMenu public slots: void setOffset(RVA offset); void setCanCopy(bool enabled); + void prepareMenu(const QList& selectedLines); + /** * @brief Sets the value of curHighlightedWord @@ -61,9 +65,13 @@ private slots: void on_actionContinueUntil_triggered(); void on_actionSetPC_triggered(); + void applySetToCode(); void on_actionSetToCode_triggered(); + void on_actionSetToCode_triggered(RVA offset); void on_actionSetAsString_triggered(); + void on_actionSetAsString_triggered(RVA offset); void on_actionSetAsStringRemove_triggered(); + void on_actionSetAsStringRemove_triggered(RVA offset); void on_actionSetAsStringAdvanced_triggered(); void on_actionSetToData_triggered(); void on_actionSetToDataEx_triggered(); @@ -100,6 +108,7 @@ private slots: bool canCopy; QString curHighlightedWord; // The current highlighted word MainWindow *mainWindow; + QList selectedLines; IOModesController ioModesController; QList anonymousActions; @@ -189,12 +198,13 @@ private slots: void setBase(QString base); void setToData(int size, int repeat = 1); + void setToData(RVA offset, int size, int repeat = 1); void setBits(int bits); - void addSetBaseMenu(); void addSetBitsMenu(); void addSetAsMenu(); void addSetToDataMenu(); + void applySetToData(int datasize); void addEditMenu(); void addAddAtMenu(); void addBreakpointMenu(); diff --git a/src/widgets/DisassemblyWidget.cpp b/src/widgets/DisassemblyWidget.cpp index bbeef00e9..c281aa316 100644 --- a/src/widgets/DisassemblyWidget.cpp +++ b/src/widgets/DisassemblyWidget.cpp @@ -464,6 +464,12 @@ void DisassemblyWidget::highlightMultiLineSelections() void DisassemblyWidget::showDisasContextMenu(const QPoint &pt) { + qDebug() << "showDisasContextMenu()" << "\n"; + auto selectedLines = mDisasTextEdit->getMultiLineSelections(); + if (selectedLines.size() > 1) { + mCtxMenu->prepareMenu(selectedLines); + } + mCtxMenu->exec(mDisasTextEdit->mapToGlobal(pt)); } @@ -843,7 +849,7 @@ void DisassemblyTextEdit::mousePressEvent(QMouseEvent *event) // Format selection QTextEdit::ExtraSelection selection; - selection.format.setBackground(QColor("green")); + selection.format.setBackground(QColor("darkBlue")); selection.format.setProperty(QTextFormat::FullWidthSelection, true); // Move cursor to start of the line @@ -881,6 +887,9 @@ void DisassemblyTextEdit::mousePressEvent(QMouseEvent *event) for (const auto &selection : multilineSelections) { qDebug() << "Start:" << selection.cursor.selectionStart() << "End:" << selection.cursor.selectionEnd(); + qDebug() << selection.cursor.block().text(); + auto offset = DisassemblyPreview::readDisassemblyOffset(selection.cursor); + qDebug() << "Offset:" << offset << "\n"; } }