From b4867cadefd2c5e9a2e4d6504704e2a069c895e1 Mon Sep 17 00:00:00 2001 From: xarkes Date: Wed, 11 Oct 2017 23:07:32 +0200 Subject: [PATCH] Moved disassembly view to its own class --- src/MainWindow.cpp | 38 +-- src/MainWindow.h | 2 + src/cutter.pro | 6 +- src/widgets/DisassemblyView.cpp | 408 +++++++++++++++++++++++++++++ src/widgets/DisassemblyView.h | 35 +++ src/widgets/MemoryWidget.cpp | 439 +------------------------------- src/widgets/MemoryWidget.h | 20 -- src/widgets/MemoryWidget.ui | 58 +---- 8 files changed, 481 insertions(+), 525 deletions(-) create mode 100644 src/widgets/DisassemblyView.cpp create mode 100644 src/widgets/DisassemblyView.h diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index f6419920..194e5f52 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -181,14 +181,7 @@ void MainWindow::initUI() /* * Dock Widgets */ - dockWidgets.reserve(12); - - // Add graph view as dockable - graphDock = new QDockWidget(tr("Graph"), this); - graphDock->setAllowedAreas(Qt::AllDockWidgetAreas); - DisassemblerGraphView *gv = new DisassemblerGraphView(graphDock); - graphDock->setWidget(gv); - dockWidgets.push_back(graphDock); + dockWidgets.reserve(14); // Add Memory DockWidget this->memoryDock = new MemoryWidget(); @@ -197,6 +190,17 @@ void MainWindow::initUI() // this->memoryDock->setAttribute(Qt::WA_DeleteOnClose); // this->add_debug_output( QString::number(this->dockList.length()) ); + // Add disassembly view (dockable) + this->disassemblyDock = new DisassemblyView(tr("Disassembly"), this); + dockWidgets.push_back(disassemblyDock); + + // Add graph view as dockable + graphDock = new QDockWidget(tr("Graph"), this); + graphDock->setAllowedAreas(Qt::AllDockWidgetAreas); + DisassemblerGraphView *gv = new DisassemblerGraphView(graphDock); + graphDock->setWidget(gv); + dockWidgets.push_back(graphDock); + // Add Sections dock panel this->sectionsDock = new SectionsDock(this); dockWidgets.push_back(sectionsDock); @@ -367,7 +371,6 @@ void MainWindow::finalizeOpen() start_web_server(); showMaximized(); // Initialize syntax highlighters - memoryDock->highlightDisasms(); notepadDock->highlightPreview(); } @@ -510,6 +513,9 @@ void MainWindow::updateFrames() static bool first_time = true; + //TODO Send signal rather than that + disassemblyDock->refreshDisasm(); + if (first_time) { for (auto W : dockWidgets) @@ -619,7 +625,7 @@ void MainWindow::on_actionMem_triggered() this->dockWidgets << newMemDock; newMemDock->setAttribute(Qt::WA_DeleteOnClose); this->tabifyDockWidget(this->memoryDock, newMemDock); - newMemDock->refreshDisasm(); + //newMemDock->refreshDisasm(); newMemDock->refreshHexdump(); } @@ -752,13 +758,14 @@ void MainWindow::on_actionDisasAdd_comment_triggered() void MainWindow::restoreDocks() { - addDockWidget(Qt::RightDockWidgetArea, sectionsDock); + addDockWidget(Qt::LeftDockWidgetArea, this->functionsDock); + addDockWidget(Qt::RightDockWidgetArea, this->sectionsDock); addDockWidget(Qt::TopDockWidgetArea, this->dashboardDock); - this->tabifyDockWidget(sectionsDock, this->commentsDock); + this->tabifyDockWidget(this->sectionsDock, this->commentsDock); + this->tabifyDockWidget(this->dashboardDock, this->disassemblyDock); this->tabifyDockWidget(this->dashboardDock, this->graphDock); this->tabifyDockWidget(this->dashboardDock, this->memoryDock); this->tabifyDockWidget(this->dashboardDock, this->entrypointDock); - this->tabifyDockWidget(this->dashboardDock, this->functionsDock); this->tabifyDockWidget(this->dashboardDock, this->flagsDock); this->tabifyDockWidget(this->dashboardDock, this->stringsDock); this->tabifyDockWidget(this->dashboardDock, this->relocsDock); @@ -767,7 +774,7 @@ void MainWindow::restoreDocks() this->tabifyDockWidget(this->dashboardDock, this->symbolsDock); this->tabifyDockWidget(this->dashboardDock, this->notepadDock); this->dashboardDock->raise(); - sectionsDock->raise(); + this->sectionsDock->raise(); this->functionsDock->raise(); } @@ -790,7 +797,6 @@ void MainWindow::hideAllDocks() void MainWindow::showDefaultDocks() { const QList defaultDocks = { sectionsDock, - graphDock, entrypointDock, functionsDock, memoryDock, @@ -799,6 +805,8 @@ void MainWindow::showDefaultDocks() importsDock, symbolsDock, notepadDock, + graphDock, + disassemblyDock, dashboardDock }; diff --git a/src/MainWindow.h b/src/MainWindow.h index 7eb77573..ddbe98c8 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -5,6 +5,7 @@ #include #include #include "RadareWebServer.h" +#include "widgets/DisassemblyView.h" #include "cutter.h" // only needed for ut64 class CutterCore; @@ -169,6 +170,7 @@ private slots: private: CutterCore *core; + DisassemblyView *disassemblyDock; QDockWidget *graphDock; QDockWidget *asmDock; QDockWidget *calcDock; diff --git a/src/cutter.pro b/src/cutter.pro index aeba96d7..686f888f 100644 --- a/src/cutter.pro +++ b/src/cutter.pro @@ -73,7 +73,8 @@ SOURCES += \ widgets/Sidebar.cpp \ widgets/StringsWidget.cpp \ widgets/SymbolsWidget.cpp \ - menus/DisassemblyContextMenu.cpp + menus/DisassemblyContextMenu.cpp \ + widgets/DisassemblyView.cpp HEADERS += \ cutter.h \ @@ -119,7 +120,8 @@ HEADERS += \ widgets/Sidebar.h \ widgets/StringsWidget.h \ widgets/SymbolsWidget.h \ - menus/DisassemblyContextMenu.h + menus/DisassemblyContextMenu.h \ + widgets/DisassemblyView.h FORMS += \ widgets/MemoryWidget.ui \ diff --git a/src/widgets/DisassemblyView.cpp b/src/widgets/DisassemblyView.cpp new file mode 100644 index 00000000..d63f6fbf --- /dev/null +++ b/src/widgets/DisassemblyView.cpp @@ -0,0 +1,408 @@ +#include "DisassemblyView.h" +#include "menus/DisassemblyContextMenu.h" +#include "dialogs/XrefsDialog.h" +#include "utils/HexAsciiHighlighter.h" +#include "utils/HexHighlighter.h" +#include +#include + +DisassemblyView::DisassemblyView(QWidget *parent) : + QDockWidget(parent), + mDisasTextEdit(new QTextEdit(this)) +{ + // Configure Dock + this->setWidget(mDisasTextEdit); + this->setAllowedAreas(Qt::AllDockWidgetAreas); + + // TODO Use Settings + mDisasTextEdit->setFont(QFont("Monospace", 10)); + + // Increase asm text edit margin + QTextDocument *asm_docu = mDisasTextEdit->document(); + asm_docu->setDocumentMargin(10); + + // Setup disasm highlight + connect(mDisasTextEdit, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); + highlightCurrentLine(); + + // Event filter to intercept double clicks in the textbox + mDisasTextEdit->viewport()->installEventFilter(this); + + // Set Disas context menu + mDisasTextEdit->setContextMenuPolicy(Qt::CustomContextMenu); + connect(mDisasTextEdit, SIGNAL(customContextMenuRequested(const QPoint &)), + this, SLOT(showDisasContextMenu(const QPoint &))); + + // x or X to show XRefs + connect(new QShortcut(QKeySequence(Qt::Key_X), mDisasTextEdit), + SIGNAL(activated()), this, SLOT(showXrefsDialog())); + connect(new QShortcut(Qt::SHIFT + Qt::Key_X, mDisasTextEdit), + SIGNAL(activated()), this, SLOT(showXrefsDialog())); + + // Scrollbar + connect(mDisasTextEdit->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(disasmScrolled())); + + // TODO Shortcuts + // Semicolon to add comment + //QShortcut *comment_shortcut = new QShortcut(QKeySequence(Qt::Key_Semicolon), mDisasTextEdit); + //connect(comment_shortcut, SIGNAL(activated()), this, SLOT(on_actionDisasAdd_comment_triggered())); + //comment_shortcut->setContext(Qt::WidgetShortcut); + + // N to rename function + //QShortcut *rename_shortcut = new QShortcut(QKeySequence(Qt::Key_N), mDisasTextEdit); + //connect(rename_shortcut, SIGNAL(activated()), this, SLOT(on_actionFunctionsRename_triggered())); + //rename_shortcut->setContext(Qt::WidgetShortcut); + + // Esc to seek back + //QShortcut *back_shortcut = new QShortcut(QKeySequence(Qt::Key_Escape), mDisasTextEdit); + //connect(back_shortcut, SIGNAL(activated()), this, SLOT(seek_back())); + //back_shortcut->setContext(Qt::WidgetShortcut); + + // CTRL + R to refresh the disasm + //QShortcut *refresh_shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_R), mDisasTextEdit); + //connect(refresh_shortcut, SIGNAL(activated()), this, SLOT(refreshDisasm())); + //refresh_shortcut->setContext(Qt::WidgetShortcut); + + // Seek signal + connect(CutterCore::getInstance(), SIGNAL(seekChanged(RVA)), this, SLOT(on_seekChanged(RVA))); +} + +DisassemblyView::DisassemblyView(const QString &title, QWidget *parent) : + DisassemblyView(parent) +{ + this->setWindowTitle(title); +} +void DisassemblyView::highlightCurrentLine() +{ + QList extraSelections; + + // Highlight the current line in yellow + if (mDisasTextEdit->isReadOnly()) + { + QTextEdit::ExtraSelection selection; + + QColor lineColor = QColor(190, 144, 212); + + selection.format.setBackground(lineColor); + selection.format.setProperty(QTextFormat::FullWidthSelection, true); + selection.cursor = mDisasTextEdit->textCursor(); + selection.cursor.clearSelection(); + extraSelections.append(selection); + } + + // Highlight the current word + QTextCursor cursor = mDisasTextEdit->textCursor(); + cursor.select(QTextCursor::WordUnderCursor); + + QTextEdit::ExtraSelection currentWord; + + QColor blueColor = QColor(Qt::blue).lighter(160); + currentWord.format.setBackground(blueColor); + + currentWord.cursor = cursor; + extraSelections.append(currentWord); + currentWord.cursor.clearSelection(); + + // Highlight all the words in the document same as the actual one + QString searchString = cursor.selectedText(); + QTextDocument *document = mDisasTextEdit->document(); + + //QTextCursor highlightCursor(document); + QTextEdit::ExtraSelection highlightSelection; + highlightSelection.cursor = cursor; + highlightSelection.format.setBackground(blueColor); + QTextCursor cursor2(document); + + cursor2.beginEditBlock(); + + highlightSelection.cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor); + while (!highlightSelection.cursor.isNull() && !highlightSelection.cursor.atEnd()) + { + highlightSelection.cursor = document->find(searchString, highlightSelection.cursor, QTextDocument::FindWholeWords); + + if (!highlightSelection.cursor.isNull()) + { + highlightSelection.cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + extraSelections.append(highlightSelection); + } + } + cursor2.endEditBlock(); + + mDisasTextEdit->setExtraSelections(extraSelections); +} + +void DisassemblyView::showDisasContextMenu(const QPoint &pt) +{ + DisassemblyContextMenu menu(this->readCurrentDisassemblyOffset(), mDisasTextEdit); + menu.exec(mDisasTextEdit->mapToGlobal(pt)); +} + +void DisassemblyView::showXrefsDialog() +{ + // Get current offset + QTextCursor tc = mDisasTextEdit->textCursor(); + tc.select(QTextCursor::LineUnderCursor); + QString lastline = tc.selectedText(); + QString ele = lastline.split(" ", QString::SkipEmptyParts)[0]; + if (ele.contains("0x")) + { + RVA addr = ele.toLongLong(0, 16); + XrefsDialog *dialog = new XrefsDialog(this); + dialog->fillRefsForAddress(addr, RAddressString(addr), false); + dialog->exec(); + } +} + +RVA DisassemblyView::readCurrentDisassemblyOffset() +{ + // TODO: do this in a different way without parsing the disassembly text + QTextCursor tc = mDisasTextEdit->textCursor(); + tc.select(QTextCursor::LineUnderCursor); + QString lastline = tc.selectedText(); + QStringList parts = lastline.split(" ", QString::SkipEmptyParts); + + if (parts.isEmpty()) + return RVA_INVALID; + + QString ele = parts[0]; + if (!ele.contains("0x")) + return RVA_INVALID; + + return ele.toULongLong(0, 16); +} + +bool DisassemblyView::loadMoreDisassembly() +{ + /* + * Add more disasm as the user scrolls + * Not working properly when scrolling upwards + * r2 doesn't handle properly 'pd-' for archs with variable instruction size + */ + // Disconnect scroll signals to add more content + disconnect(mDisasTextEdit->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(disasmScrolled())); + + QScrollBar *sb = mDisasTextEdit->verticalScrollBar(); + + bool loaded = false; + + if (sb->value() > sb->maximum() - 10) + { + //this->main->add_debug_output("End is coming"); + + QTextCursor tc = mDisasTextEdit->textCursor(); + tc.movePosition(QTextCursor::End); + RVA offset = readCurrentDisassemblyOffset(); + + if (offset != RVA_INVALID) + { + CutterCore::getInstance()->seek(offset); + QString raw = CutterCore::getInstance()->cmd("pd 200"); + QString txt = raw.section("\n", 1, -1); + //this->disasTextEdit->appendPlainText(" ;\n ; New content here\n ;\n " + txt.trimmed()); + mDisasTextEdit->append(txt.trimmed()); + } + else + { + tc.movePosition(QTextCursor::End); + tc.select(QTextCursor::LineUnderCursor); + QString lastline = tc.selectedText(); + //this->main->addDebugOutput("Last line: " + lastline); + } + + loaded = true; + + // Code below will be used to append more disasm upwards, one day + } /* else if (sb->value() < sb->minimum() + 10) { + //this->main->add_debug_output("Begining is coming"); + + QTextCursor tc = this->disasTextEdit->textCursor(); + tc.movePosition( QTextCursor::Start ); + tc.select( QTextCursor::LineUnderCursor ); + QString firstline = tc.selectedText(); + //this->main->add_debug_output("First Line: " + firstline); + QString ele = firstline.split(" ", QString::SkipEmptyParts)[0]; + //this->main->add_debug_output("First Offset: " + ele); + if (ele.contains("0x")) { + int b = this->disasTextEdit->verticalScrollBar()->maximum(); + this->core->cmd("ss " + ele); + this->core->cmd("so -50"); + QString raw = this->core->cmd("pd 50"); + //this->main->add_debug_output(raw); + //QString txt = raw.section("\n", 1, -1); + //this->main->add_debug_output(txt); + tc.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor); + //tc.insertText(raw.trimmed() + "\n ;\n ; New content prepended here\n ;\n"); + int c = this->disasTextEdit->verticalScrollBar()->maximum(); + int z = c -b; + int a = this->disasTextEdit->verticalScrollBar()->sliderPosition(); + this->disasTextEdit->verticalScrollBar()->setValue(a + z); + } else { + tc.movePosition( QTextCursor::Start ); + tc.select( QTextCursor::LineUnderCursor ); + QString lastline = tc.selectedText(); + this->main->add_debug_output("Last line: " + lastline); + } + } */ + + // Reconnect scroll signals + connect(mDisasTextEdit->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(disasmScrolled())); + + return loaded; +} + + +void DisassemblyView::disasmScrolled() +{ + loadMoreDisassembly(); +} + +void DisassemblyView::refreshDisasm() +{ + // TODO Very slow mostly because of the highlight + // Prevent further scroll + disconnect(mDisasTextEdit->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(disasmScrolled())); + disconnect(mDisasTextEdit, SIGNAL(cursorPositionChanged()), this, SLOT(on_mDisasTextEdit_cursorPositionChanged())); + + QString disas = CutterCore::getInstance()->cmd("pd 200"); + + mDisasTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + + mDisasTextEdit->setPlainText(disas.trimmed()); + + auto cursor = mDisasTextEdit->textCursor(); + cursor.setPosition(0); + mDisasTextEdit->setTextCursor(cursor); + mDisasTextEdit->verticalScrollBar()->setValue(0); + + // load more disassembly if necessary + /*static const int load_more_limit = 10; // limit passes, so it can't take forever + for (int load_more_i = 0; load_more_i < load_more_limit; load_more_i++) + { + if (!loadMoreDisassembly()) + break; + mDisasTextEdit->verticalScrollBar()->setValue(0); + }*/ + + connect(mDisasTextEdit->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(disasmScrolled())); + connect(mDisasTextEdit, SIGNAL(cursorPositionChanged()), this, SLOT(on_mDisasTextEdit_cursorPositionChanged())); + //this->on_mDisasTextEdit_cursorPositionChanged(); + this->highlightDisasms(); +} + +void DisassemblyView::on_mDisasTextEdit_cursorPositionChanged() +{ + // Get current offset + QTextCursor tc = mDisasTextEdit->textCursor(); + tc.select(QTextCursor::LineUnderCursor); + QString lastline = tc.selectedText().trimmed(); + QList words = lastline.split(" ", QString::SkipEmptyParts); + if (words.length() == 0) + { + return; + } + QString ele = words[0]; + // TODO + /*if (ele.contains("0x")) + { + this->fillOffsetInfo(ele); + QString at = this->core->cmdFunctionAt(ele); + QString deco = this->core->getDecompiledCode(at); + + + RVA addr = ele.midRef(2).toULongLong(0, 16); + // FIXME per widget CursorAddress no? + // this->main->setCursorAddress(addr); + + if (deco != "") + { + ui->decoTextEdit->setPlainText(deco); + } + else + { + ui->decoTextEdit->setPlainText(""); + } + // Get jump information to fill the preview + QString jump = this->core->getOffsetJump(ele); + if (!jump.isEmpty()) + { + // Fill the preview + QString jump_code = this->core->cmd("pdf @ " + jump); + ui->previewTextEdit->setPlainText(jump_code.trimmed()); + ui->previewTextEdit->moveCursor(QTextCursor::End); + ui->previewTextEdit->find(jump.trimmed(), QTextDocument::FindBackward); + ui->previewTextEdit->moveCursor(QTextCursor::StartOfWord, QTextCursor::MoveAnchor); + } + else + { + ui->previewTextEdit->setPlainText(""); + } + //this->main->add_debug_output("Fcn at: '" + at + "'"); + if (this->last_fcn != at) + { + this->last_fcn = at; + //this->main->add_debug_output("New Fcn: '" + this->last_fcn + "'"); + // Refresh function information at sidebar + ui->fcnNameEdit->setText(at); + // FIXME TITLE? + // this->main->memoryDock->setWindowTitle(at); + //this->main->memoryDock->create_graph(ele); + this->setMiniGraph(at); + } + } + */ +} + +bool DisassemblyView::eventFilter(QObject *obj, QEvent *event) +{ + if ((obj == mDisasTextEdit || obj == mDisasTextEdit->viewport()) && event->type() == QEvent::MouseButtonDblClick) + { + QMouseEvent *mouseEvent = static_cast(event); + //qDebug()<x()).arg(mouseEvent->y()); + QTextCursor cursor = mDisasTextEdit->cursorForPosition(QPoint(mouseEvent->x(), mouseEvent->y())); + cursor.select(QTextCursor::LineUnderCursor); + QString lastline = cursor.selectedText(); + auto eles = lastline.split(" ", QString::SkipEmptyParts); + QString ele = eles.isEmpty() ? "" : eles[0]; + if (ele.contains("0x")) + { + QString jump = CutterCore::getInstance()->getOffsetJump(ele); + if (!jump.isEmpty()) + { + if (jump.contains("0x")) + { + QString fcn = CutterCore::getInstance()->cmdFunctionAt(jump); + if (!fcn.isEmpty()) + { + RVA addr = jump.trimmed().toULongLong(0, 16); + CutterCore::getInstance()->seek(addr); + } + } + else + { + RVA addr = CutterCore::getInstance()->cmd("?v " + jump).toULongLong(0, 16); + CutterCore::getInstance()->seek(addr); + } + } + } + } + return QDockWidget::eventFilter(obj, event); +} + +void DisassemblyView::on_seekChanged(RVA offset) +{ + Q_UNUSED(offset); + refreshDisasm(); +} + +void DisassemblyView::highlightDisasms() +{ + // Syntax Highliting + // TODO doing new all the time + // TODO Seems very very heavy -- merge it with Graph one + Highlighter *highlighter = new Highlighter(mDisasTextEdit->document()); + Highlighter *highlighter_5 = new Highlighter(mDisasTextEdit->document()); + AsciiHighlighter *ascii_highlighter = new AsciiHighlighter(mDisasTextEdit->document()); + HexHighlighter *hex_highlighter = new HexHighlighter(mDisasTextEdit->document()); + Highlighter *preview_highlighter = new Highlighter(mDisasTextEdit->document()); + Highlighter *deco_highlighter = new Highlighter(mDisasTextEdit->document()); +} diff --git a/src/widgets/DisassemblyView.h b/src/widgets/DisassemblyView.h new file mode 100644 index 00000000..d7c1188e --- /dev/null +++ b/src/widgets/DisassemblyView.h @@ -0,0 +1,35 @@ +#ifndef DISASSEMBLYVIEW_H +#define DISASSEMBLYVIEW_H + +#include +#include +#include "cutter.h" + +class DisassemblyView : public QDockWidget +{ + Q_OBJECT +public: + explicit DisassemblyView(QWidget *parent = nullptr); + explicit DisassemblyView(const QString &title, QWidget *parent = nullptr); + +signals: + +public slots: + void highlightCurrentLine(); + void disasmScrolled(); + void showDisasContextMenu(const QPoint &pt); + void showXrefsDialog(); + void on_mDisasTextEdit_cursorPositionChanged(); + void on_seekChanged(RVA offset); + void refreshDisasm(); + +private: + QTextEdit *mDisasTextEdit; + + RVA readCurrentDisassemblyOffset(); + bool loadMoreDisassembly(); + void highlightDisasms(); + bool eventFilter(QObject *obj, QEvent *event); +}; + +#endif // DISASSEMBLYVIEW_H diff --git a/src/widgets/MemoryWidget.cpp b/src/widgets/MemoryWidget.cpp index 2f04bdea..51189ebd 100644 --- a/src/widgets/MemoryWidget.cpp +++ b/src/widgets/MemoryWidget.cpp @@ -4,8 +4,6 @@ #include "MainWindow.h" #include "utils/Helpers.h" -#include "dialogs/XrefsDialog.h" -#include "menus/DisassemblyContextMenu.h" #include #include @@ -27,8 +25,6 @@ MemoryWidget::MemoryWidget() : core(CutterCore::getInstance()) { ui->setupUi(this); - - this->disasTextEdit = ui->disasTextEdit_2; this->hexOffsetText = ui->hexOffsetText_2; this->hexHexText = ui->hexHexText_2; this->hexDisasTextEdit = ui->hexDisasTextEdit_2; @@ -44,13 +40,6 @@ MemoryWidget::MemoryWidget() : disasm_top_offset = 0; next_disasm_top_offset = 0; - // Increase asm text edit margin - QTextDocument *asm_docu = this->disasTextEdit->document(); - asm_docu->setDocumentMargin(10); - - // Setup disasm highlight - connect(ui->disasTextEdit_2, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); - highlightCurrentLine(); //this->on_actionSettings_menu_1_triggered(); // Setup hex highlight @@ -81,7 +70,6 @@ MemoryWidget::MemoryWidget() : ui->fcnNameEdit->setTextMargins(5, 0, 0, 0); // Normalize fonts for other OS - qhelpers::normalizeFont(this->disasTextEdit); qhelpers::normalizeEditFont(this->hexOffsetText); qhelpers::normalizeEditFont(this->hexHexText); qhelpers::normalizeEditFont(this->hexASCIIText); @@ -92,18 +80,10 @@ MemoryWidget::MemoryWidget() : memMenu->addAction(ui->actionSettings_menu_1); ui->memSettingsButton_2->setMenu(memMenu); - // Event filter to intercept double clicks in the textbox - ui->disasTextEdit_2->viewport()->installEventFilter(this); - // Set Splitter stretch factor ui->splitter->setStretchFactor(0, 10); ui->splitter->setStretchFactor(1, 1); - // Set Disas context menu - ui->disasTextEdit_2->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->disasTextEdit_2, SIGNAL(customContextMenuRequested(const QPoint &)), - this, SLOT(showDisasContextMenu(const QPoint &))); - // Set hexdump context menu ui->hexHexText_2->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->hexHexText_2, SIGNAL(customContextMenuRequested(const QPoint &)), @@ -128,39 +108,12 @@ MemoryWidget::MemoryWidget() : connect(ui->hexASCIIText_2->verticalScrollBar(), SIGNAL(valueChanged(int)), ui->hexHexText_2->verticalScrollBar(), SLOT(setValue(int))); - // x or X to show XRefs - connect(new QShortcut(QKeySequence(Qt::Key_X), ui->disasTextEdit_2), - SIGNAL(activated()), this, SLOT(showXrefsDialog())); - connect(new QShortcut(Qt::SHIFT + Qt::Key_X, ui->disasTextEdit_2), - SIGNAL(activated()), this, SLOT(showXrefsDialog())); - // Space to switch between disassembly and graph QShortcut *graph_shortcut = new QShortcut(QKeySequence(Qt::Key_Space), this); connect(graph_shortcut, SIGNAL(activated()), this, SLOT(cycleViews())); //graph_shortcut->setContext(Qt::WidgetShortcut); - // Semicolon to add comment - QShortcut *comment_shortcut = new QShortcut(QKeySequence(Qt::Key_Semicolon), ui->disasTextEdit_2); - connect(comment_shortcut, SIGNAL(activated()), this, SLOT(on_actionDisasAdd_comment_triggered())); - comment_shortcut->setContext(Qt::WidgetShortcut); - - // N to rename function - QShortcut *rename_shortcut = new QShortcut(QKeySequence(Qt::Key_N), ui->disasTextEdit_2); - connect(rename_shortcut, SIGNAL(activated()), this, SLOT(on_actionFunctionsRename_triggered())); - rename_shortcut->setContext(Qt::WidgetShortcut); - - // Esc to seek back - QShortcut *back_shortcut = new QShortcut(QKeySequence(Qt::Key_Escape), ui->disasTextEdit_2); - connect(back_shortcut, SIGNAL(activated()), this, SLOT(seek_back())); - back_shortcut->setContext(Qt::WidgetShortcut); - - // CTRL + R to refresh the disasm - QShortcut *refresh_shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_R), ui->disasTextEdit_2); - connect(refresh_shortcut, SIGNAL(activated()), this, SLOT(refreshDisasm())); - refresh_shortcut->setContext(Qt::WidgetShortcut); - // Control Disasm and Hex scroll to add more contents - connect(this->disasTextEdit->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(disasmScrolled())); connect(this->hexASCIIText->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(hexScrolled())); connect(core, SIGNAL(seekChanged(RVA)), this, SLOT(on_seekChanged(RVA))); @@ -187,78 +140,6 @@ void MemoryWidget::on_cursorAddressChanged(RVA addr) /* * Text highlight functions */ - -void MemoryWidget::highlightDisasms() -{ - // Syntax Highliting - highlighter = new Highlighter(ui->disasTextEdit_2->document()); - highlighter_5 = new Highlighter(ui->hexDisasTextEdit_2->document()); - ascii_highlighter = new AsciiHighlighter(ui->hexASCIIText_2->document()); - hex_highlighter = new HexHighlighter(ui->hexHexText_2->document()); - preview_highlighter = new Highlighter(ui->previewTextEdit->document()); - deco_highlighter = new Highlighter(ui->decoTextEdit->document()); - -} - -void MemoryWidget::highlightCurrentLine() -{ - QList extraSelections; - - // Highlight the current line in yellow - if (ui->disasTextEdit_2->isReadOnly()) - { - QTextEdit::ExtraSelection selection; - - QColor lineColor = QColor(190, 144, 212); - - selection.format.setBackground(lineColor); - selection.format.setProperty(QTextFormat::FullWidthSelection, true); - selection.cursor = ui->disasTextEdit_2->textCursor(); - selection.cursor.clearSelection(); - extraSelections.append(selection); - } - - // Highlight the current word - QTextCursor cursor = ui->disasTextEdit_2->textCursor(); - cursor.select(QTextCursor::WordUnderCursor); - - QTextEdit::ExtraSelection currentWord; - - QColor blueColor = QColor(Qt::blue).lighter(160); - currentWord.format.setBackground(blueColor); - - currentWord.cursor = cursor; - extraSelections.append(currentWord); - currentWord.cursor.clearSelection(); - - // Highlight all the words in the document same as the actual one - QString searchString = cursor.selectedText(); - QTextDocument *document = ui->disasTextEdit_2->document(); - - //QTextCursor highlightCursor(document); - QTextEdit::ExtraSelection highlightSelection; - highlightSelection.cursor = cursor; - highlightSelection.format.setBackground(blueColor); - QTextCursor cursor2(document); - - cursor2.beginEditBlock(); - - highlightSelection.cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor); - while (!highlightSelection.cursor.isNull() && !highlightSelection.cursor.atEnd()) - { - highlightSelection.cursor = document->find(searchString, highlightSelection.cursor, QTextDocument::FindWholeWords); - - if (!highlightSelection.cursor.isNull()) - { - highlightSelection.cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); - extraSelections.append(highlightSelection); - } - } - cursor2.endEditBlock(); - - ui->disasTextEdit_2->setExtraSelections(extraSelections); -} - void MemoryWidget::highlightHexCurrentLine() { QList extraSelections; @@ -369,24 +250,6 @@ void MemoryWidget::highlightDecoCurrentLine() ui->decoTextEdit->setExtraSelections(extraSelections); } -RVA MemoryWidget::readCurrentDisassemblyOffset() -{ - // TODO: do this in a different way without parsing the disassembly text - QTextCursor tc = this->disasTextEdit->textCursor(); - tc.select(QTextCursor::LineUnderCursor); - QString lastline = tc.selectedText(); - QStringList parts = lastline.split(" ", QString::SkipEmptyParts); - - if (parts.isEmpty()) - return RVA_INVALID; - - QString ele = parts[0]; - if (!ele.contains("0x")) - return RVA_INVALID; - - return ele.toULongLong(0, 16); -} - MemoryWidget::~MemoryWidget() {} void MemoryWidget::setup() @@ -422,159 +285,6 @@ void MemoryWidget::fillPlugins() ui->hexArchComboBox_2->insertItems(0, core->getAsmPluginNames()); } -void MemoryWidget::addTextDisasm(QString txt) -{ - //QTextDocument *document = ui->disasTextEdit_2->document(); - //document->undo(); - ui->disasTextEdit_2->appendPlainText(txt); -} - -void MemoryWidget::replaceTextDisasm(QString txt) -{ - //QTextDocument *document = ui->disasTextEdit_2->document(); - ui->disasTextEdit_2->clear(); - //document->undo(); - ui->disasTextEdit_2->setPlainText(txt); -} - -bool MemoryWidget::loadMoreDisassembly() -{ - /* - * Add more disasm as the user scrolls - * Not working properly when scrolling upwards - * r2 doesn't handle properly 'pd-' for archs with variable instruction size - */ - // Disconnect scroll signals to add more content - disconnect(this->disasTextEdit->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(disasmScrolled())); - - QScrollBar *sb = this->disasTextEdit->verticalScrollBar(); - - bool loaded = false; - - if (sb->value() > sb->maximum() - 10) - { - //this->main->add_debug_output("End is coming"); - - QTextCursor tc = this->disasTextEdit->textCursor(); - tc.movePosition(QTextCursor::End); - RVA offset = readCurrentDisassemblyOffset(); - - if (offset != RVA_INVALID) - { - core->seek(offset); - QString raw = this->core->cmd("pd 200"); - QString txt = raw.section("\n", 1, -1); - //this->disasTextEdit->appendPlainText(" ;\n ; New content here\n ;\n " + txt.trimmed()); - this->disasTextEdit->appendPlainText(txt.trimmed()); - } - else - { - tc.movePosition(QTextCursor::End); - tc.select(QTextCursor::LineUnderCursor); - QString lastline = tc.selectedText(); - //this->main->addDebugOutput("Last line: " + lastline); - } - - loaded = true; - - // Code below will be used to append more disasm upwards, one day - } /* else if (sb->value() < sb->minimum() + 10) { - //this->main->add_debug_output("Begining is coming"); - - QTextCursor tc = this->disasTextEdit->textCursor(); - tc.movePosition( QTextCursor::Start ); - tc.select( QTextCursor::LineUnderCursor ); - QString firstline = tc.selectedText(); - //this->main->add_debug_output("First Line: " + firstline); - QString ele = firstline.split(" ", QString::SkipEmptyParts)[0]; - //this->main->add_debug_output("First Offset: " + ele); - if (ele.contains("0x")) { - int b = this->disasTextEdit->verticalScrollBar()->maximum(); - this->core->cmd("ss " + ele); - this->core->cmd("so -50"); - QString raw = this->core->cmd("pd 50"); - //this->main->add_debug_output(raw); - //QString txt = raw.section("\n", 1, -1); - //this->main->add_debug_output(txt); - tc.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor); - //tc.insertText(raw.trimmed() + "\n ;\n ; New content prepended here\n ;\n"); - int c = this->disasTextEdit->verticalScrollBar()->maximum(); - int z = c -b; - int a = this->disasTextEdit->verticalScrollBar()->sliderPosition(); - this->disasTextEdit->verticalScrollBar()->setValue(a + z); - } else { - tc.movePosition( QTextCursor::Start ); - tc.select( QTextCursor::LineUnderCursor ); - QString lastline = tc.selectedText(); - this->main->add_debug_output("Last line: " + lastline); - } - } */ - - // Reconnect scroll signals - connect(this->disasTextEdit->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(disasmScrolled())); - - return loaded; -} - - -void MemoryWidget::disasmScrolled() -{ - loadMoreDisassembly(); -} - -void MemoryWidget::refreshDisasm() -{ - RCoreLocked lcore = this->core->core(); - - // Prevent further scroll - disconnect(this->disasTextEdit->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(disasmScrolled())); - disconnect(this->disasTextEdit, SIGNAL(cursorPositionChanged()), this, SLOT(on_disasTextEdit_2_cursorPositionChanged())); - - RVA offset = next_disasm_top_offset; - next_disasm_top_offset = RVA_INVALID; - bool offset_changed = offset != RVA_INVALID; - - if (offset_changed) // new offset (seek) - { - disasm_top_offset = offset; - this->core->cmd(QString("s %1").arg(offset)); - } - else // simple refresh - { - core->cmd(QString("s %1").arg(disasm_top_offset)); - } - - QString txt2 = this->core->cmd("pd 200"); - - disasTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); - - // if the offset changed, jump to the top - // otherwise try to retain the position - int cursor_pos = offset_changed ? 0 : disasTextEdit->textCursor().position(); - int scroll_pos = offset_changed ? 0 : disasTextEdit->verticalScrollBar()->value(); - - this->disasTextEdit->setPlainText(txt2.trimmed()); - - auto cursor = disasTextEdit->textCursor(); - cursor.setPosition(cursor_pos); - disasTextEdit->setTextCursor(cursor); - - disasTextEdit->verticalScrollBar()->setValue(scroll_pos); - - // load more disassembly if necessary - static const int load_more_limit = 10; // limit passes, so it can't take forever - for (int load_more_i = 0; load_more_i < load_more_limit; load_more_i++) - { - if (!loadMoreDisassembly()) - break; - disasTextEdit->verticalScrollBar()->setValue(scroll_pos); - } - - connect(this->disasTextEdit->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(disasmScrolled())); - connect(this->disasTextEdit, SIGNAL(cursorPositionChanged()), this, SLOT(on_disasTextEdit_2_cursorPositionChanged())); - this->on_disasTextEdit_2_cursorPositionChanged(); -} - void MemoryWidget::refreshHexdump(const QString &where) { RCoreLocked lcore = this->core->core(); @@ -714,14 +424,6 @@ QList MemoryWidget::get_hexdump(const QString &offset) return ret; } - -void MemoryWidget::seek_to(const QString &offset) -{ - this->disasTextEdit->moveCursor(QTextCursor::End); - this->disasTextEdit->find(offset, QTextDocument::FindBackward); - this->disasTextEdit->moveCursor(QTextCursor::StartOfWord, QTextCursor::MoveAnchor); -} - void MemoryWidget::resizeHexdump() { this->hexOffsetText->setMinimumWidth(this->hexOffsetText->document()->size().width()); @@ -975,12 +677,6 @@ void MemoryWidget::showHexASCIIContextMenu(const QPoint &pt) delete menu; } -void MemoryWidget::showDisasContextMenu(const QPoint &pt) -{ - DisassemblyContextMenu menu(this->readCurrentDisassemblyOffset(), ui->disasTextEdit_2); - menu.exec(ui->disasTextEdit_2->mapToGlobal(pt)); -} - void MemoryWidget::on_showInfoButton_2_clicked() { if (ui->showInfoButton_2->isChecked()) @@ -1009,23 +705,6 @@ void MemoryWidget::on_offsetToolButton_clicked() } } - -void MemoryWidget::showXrefsDialog() -{ - // Get current offset - QTextCursor tc = this->disasTextEdit->textCursor(); - tc.select(QTextCursor::LineUnderCursor); - QString lastline = tc.selectedText(); - QString ele = lastline.split(" ", QString::SkipEmptyParts)[0]; - if (ele.contains("0x")) - { - RVA addr = ele.toLongLong(0, 16); - XrefsDialog *x = new XrefsDialog(this); - x->fillRefsForAddress(addr, RAddressString(addr), false); - x->exec(); - } -} - /* * Show widgets */ @@ -1055,9 +734,9 @@ void MemoryWidget::on_actionSettings_menu_1_triggered() { bool ok = true; - // QFont font = QFont("Monospace", 8); + QFont font = QFont("Monospace", 8); // TODO Use global configuration - QFont font = QFontDialog::getFont(&ok, ui->disasTextEdit_2->font(), this); + //QFont font = QFontDialog::getFont(&ok, ui->disasTextEdit_2->font(), this); if (ok) { @@ -1066,11 +745,12 @@ void MemoryWidget::on_actionSettings_menu_1_triggered() emit fontChanged(font); } } + void MemoryWidget::setFonts(QFont font) { - ui->disasTextEdit_2->setFont(font); + //ui->disasTextEdit_2->setFont(font); // the user clicked OK and font is set to the font the user selected - ui->disasTextEdit_2->setFont(font); + //ui->disasTextEdit_2->setFont(font); ui->hexOffsetText_2->setFont(font); ui->hexHexText_2->setFont(font); ui->hexASCIIText_2->setFont(font); @@ -1130,13 +810,13 @@ void MemoryWidget::on_hexButton_clicked() ui->memSideTabWidget_2->setCurrentIndex(1); } -void MemoryWidget::on_actionSend_to_Notepad_triggered() +/*void MemoryWidget::on_actionSend_to_Notepad_triggered() { QTextCursor cursor = ui->disasTextEdit_2->textCursor(); QString text = cursor.selectedText(); // TODO // this->main->sendToNotepad(text); -} +}*/ void MemoryWidget::on_action8columns_triggered() { @@ -1396,67 +1076,6 @@ void MemoryWidget::setFcnName(RVA addr) ui->fcnNameEdit->setText(addr_string); } -void MemoryWidget::on_disasTextEdit_2_cursorPositionChanged() -{ - // Get current offset - QTextCursor tc = this->disasTextEdit->textCursor(); - tc.select(QTextCursor::LineUnderCursor); - QString lastline = tc.selectedText().trimmed(); - QList words = lastline.split(" ", QString::SkipEmptyParts); - if (words.length() == 0) - { - return; - } - QString ele = words[0]; - if (ele.contains("0x")) - { - this->fillOffsetInfo(ele); - QString at = this->core->cmdFunctionAt(ele); - QString deco = this->core->getDecompiledCode(at); - - - RVA addr = ele.midRef(2).toULongLong(0, 16); - // FIXME per widget CursorAddress no? - // this->main->setCursorAddress(addr); - - if (deco != "") - { - ui->decoTextEdit->setPlainText(deco); - } - else - { - ui->decoTextEdit->setPlainText(""); - } - // Get jump information to fill the preview - QString jump = this->core->getOffsetJump(ele); - if (!jump.isEmpty()) - { - // Fill the preview - QString jump_code = this->core->cmd("pdf @ " + jump); - ui->previewTextEdit->setPlainText(jump_code.trimmed()); - ui->previewTextEdit->moveCursor(QTextCursor::End); - ui->previewTextEdit->find(jump.trimmed(), QTextDocument::FindBackward); - ui->previewTextEdit->moveCursor(QTextCursor::StartOfWord, QTextCursor::MoveAnchor); - } - else - { - ui->previewTextEdit->setPlainText(""); - } - //this->main->add_debug_output("Fcn at: '" + at + "'"); - if (this->last_fcn != at) - { - this->last_fcn = at; - //this->main->add_debug_output("New Fcn: '" + this->last_fcn + "'"); - // Refresh function information at sidebar - ui->fcnNameEdit->setText(at); - // FIXME TITLE? - // this->main->memoryDock->setWindowTitle(at); - //this->main->memoryDock->create_graph(ele); - this->setMiniGraph(at); - } - } -} - QString MemoryWidget::normalizeAddr(QString addr) { QString base = addr.split("0x")[1].trimmed(); @@ -1603,49 +1222,12 @@ void MemoryWidget::resizeEvent(QResizeEvent *event) QDockWidget::resizeEvent(event); } -bool MemoryWidget::eventFilter(QObject *obj, QEvent *event) -{ - if ((obj == ui->disasTextEdit_2 || obj == ui->disasTextEdit_2->viewport()) && event->type() == QEvent::MouseButtonDblClick) - { - QMouseEvent *mouseEvent = static_cast(event); - //qDebug()<x()).arg(mouseEvent->y()); - QTextCursor cursor = ui->disasTextEdit_2->cursorForPosition(QPoint(mouseEvent->x(), mouseEvent->y())); - cursor.select(QTextCursor::LineUnderCursor); - QString lastline = cursor.selectedText(); - auto eles = lastline.split(" ", QString::SkipEmptyParts); - QString ele = eles.isEmpty() ? "" : eles[0]; - if (ele.contains("0x")) - { - QString jump = this->core->getOffsetJump(ele); - if (!jump.isEmpty()) - { - if (jump.contains("0x")) - { - QString fcn = this->core->cmdFunctionAt(jump); - if (!fcn.isEmpty()) - { - RVA addr = jump.trimmed().toULongLong(0, 16); - this->core->seek(addr); - } - } - else - { - RVA addr = this->core->cmd("?v " + jump).toULongLong(0, 16); - this->core->seek(addr); - } - } - } - } - return QDockWidget::eventFilter(obj, event); -} - void MemoryWidget::setScrollMode() { qhelpers::setVerticalScrollMode(ui->xreFromTreeWidget_2); qhelpers::setVerticalScrollMode(ui->xrefToTreeWidget_2); } - void MemoryWidget::on_copyMD5_clicked() { QString md5 = ui->bytesMD5->text(); @@ -1727,12 +1309,7 @@ void MemoryWidget::updateViews(RVA offset) if (offset != RVA_INVALID) next_disasm_top_offset = offset; - if (index == 0) - { - // Disasm - this->refreshDisasm(); - } - else if (index == 1) + if (index == 1) { // Hex if (this->last_hexdump_fcn != cursor_addr) diff --git a/src/widgets/MemoryWidget.h b/src/widgets/MemoryWidget.h index 43efa77a..8a6153aa 100644 --- a/src/widgets/MemoryWidget.h +++ b/src/widgets/MemoryWidget.h @@ -35,7 +35,6 @@ public: void refresh() override; - QPlainTextEdit *disasTextEdit; QTextEdit *hexOffsetText; QPlainTextEdit *hexDisasTextEdit; QTextEdit *hexASCIIText; @@ -58,20 +57,12 @@ signals: public slots: void fillPlugins(); - void addTextDisasm(QString txt); - - void replaceTextDisasm(QString txt); - - void refreshDisasm(); - void refreshHexdump(const QString &where = QString()); void fill_refs(QList refs, QList xrefs, QList graph_data); void fillOffsetInfo(QString off); - void seek_to(const QString &offset); - QString normalize_addr(QString addr); QString normalizeAddr(QString addr); @@ -80,15 +71,12 @@ public slots: void switchTheme(bool dark); - void highlightDisasms(); - void selectHexPreview(); void showOffsets(bool show); protected: void resizeEvent(QResizeEvent *event) override; - bool eventFilter(QObject *obj, QEvent *event) override; private: std::unique_ptr ui; @@ -115,12 +103,9 @@ private slots: void on_cursorAddressChanged(RVA addr); void on_seekChanged(RVA addr); - void highlightCurrentLine(); - void highlightHexCurrentLine(); void highlightPreviewCurrentLine(); void highlightDecoCurrentLine(); - RVA readCurrentDisassemblyOffset(); void setFonts(QFont font); void highlightHexWords(const QString &str); @@ -132,10 +117,8 @@ private slots: void on_disasButton_clicked(); void on_hexButton_clicked(); - void showDisasContextMenu(const QPoint &pt); void showHexdumpContextMenu(const QPoint &pt); void showHexASCIIContextMenu(const QPoint &pt); - void on_actionSend_to_Notepad_triggered(); void on_hexHexText_2_selectionChanged(); void on_hexArchComboBox_2_currentTextChanged(const QString &arg1); @@ -149,12 +132,10 @@ private slots: void on_action32columns_triggered(); void on_action64columns_triggered(); - void disasmScrolled(); void resizeHexdump(); void hexScrolled(); QList get_hexdump(const QString &offset); - void showXrefsDialog(); void updateViews(RVA offset = RVA_INVALID); void cycleViews(); void on_xreFromTreeWidget_2_itemDoubleClicked(QTreeWidgetItem *item, int column); @@ -162,7 +143,6 @@ private slots: void on_xrefFromToolButton_2_clicked(); void on_xrefToToolButton_2_clicked(); void on_codeCombo_2_currentTextChanged(const QString &arg1); - void on_disasTextEdit_2_cursorPositionChanged(); void on_offsetToolButton_clicked(); void on_polarToolButton_clicked(); void on_radarToolButton_clicked(); diff --git a/src/widgets/MemoryWidget.ui b/src/widgets/MemoryWidget.ui index 00cdd067..7a78c4cd 100644 --- a/src/widgets/MemoryWidget.ui +++ b/src/widgets/MemoryWidget.ui @@ -438,62 +438,6 @@ border-top: 0px; 2 - - - - 0 - 0 - - - - - 0 - 0 - - - - - Anonymous Pro - 13 - - - - - - - QFrame::NoFrame - - - 0 - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAlwaysOff - - - true - - - QPlainTextEdit::NoWrap - - - true - - - - - - false - - - 80 - - - Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - QFrame::NoFrame @@ -2883,8 +2827,8 @@ QToolTip { + -