From b11ba240d21648aaabcb13ebe87049fa2f0b9c6b Mon Sep 17 00:00:00 2001 From: karliss Date: Sun, 6 Oct 2019 20:35:44 +0300 Subject: [PATCH] Improve compatibility with cmake 3.5 and QT 5.5-5.11. (#1805) --- .travis.yml | 30 ++- docs/source/building.rst | 5 +- src/CMakeLists.txt | 23 +- src/Main.cpp | 2 + src/cmake/BundledRadare2.cmake | 2 +- src/cmake/FindGraphviz.cmake | 25 ++ src/common/ColorThemeWorker.cpp | 16 +- src/common/Helpers.cpp | 10 + src/common/Helpers.h | 2 + src/common/UpdateWorker.cpp | 5 +- src/common/UpdateWorker.h | 12 + src/core/Cutter.cpp | 3 + src/core/MainWindow.cpp | 8 + src/dialogs/AboutDialog.cpp | 7 + src/dialogs/NewFileDialog.cpp | 3 +- src/dialogs/TypesInteractionDialog.cpp | 2 + src/dialogs/WelcomeDialog.cpp | 2 +- src/dialogs/preferences/AsmOptionsWidget.cpp | 5 +- src/dialogs/preferences/PreferencesDialog.cpp | 2 +- src/menus/DisassemblyContextMenu.cpp | 45 +++- src/widgets/ColorPicker.cpp | 2 +- src/widgets/ColorThemeListView.cpp | 7 +- src/widgets/ConsoleWidget.cpp | 2 +- src/widgets/DisassemblerGraphView.cpp | 56 +++-- src/widgets/DisassemblerGraphView.h | 9 +- src/widgets/DisassemblyWidget.cpp | 7 +- src/widgets/DisassemblyWidget.h | 2 - src/widgets/GraphView.cpp | 125 +++++----- src/widgets/GraphView.h | 19 +- src/widgets/GraphWidget.cpp | 2 +- src/widgets/GraphvizLayout.cpp | 4 +- src/widgets/HexWidget.cpp | 38 +++- src/widgets/HexdumpWidget.cpp | 2 - src/widgets/HexdumpWidget.h | 2 - src/widgets/MemoryDockWidget.cpp | 20 +- src/widgets/MemoryDockWidget.h | 11 +- src/widgets/SearchWidget.cpp | 4 +- src/widgets/StackWidget.cpp | 213 ++++++++++++------ src/widgets/StackWidget.h | 41 +++- src/widgets/StringsWidget.cpp | 2 +- 40 files changed, 552 insertions(+), 225 deletions(-) create mode 100644 src/cmake/FindGraphviz.cmake diff --git a/.travis.yml b/.travis.yml index e1d2894c..c509d323 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,24 @@ matrix: - pyenv global 3.7.1 - pip install meson + - name: Linux Ubuntu 16.04 + os: linux + env: BUILD_SYSTEM=cmake_nodep PATH="/usr/bin:$PATH" + dist: xenial + addons: + apt: + packages: + - ninja-build + - libgraphviz-dev + - qt5-default + - libqt5svg5-dev + - cmake + before_install: + - pyenv install 3.5.2 + - pyenv global 3.5.2 + - pip install meson + + - name: macOS QMake + Deploy os: osx osx_image: xcode10.3 @@ -82,8 +100,10 @@ addons: brewfile: scripts/Brewfile install: - - scripts/fetch_deps.sh - - source cutter-deps/env.sh + - if [[ "$BUILD_SYSTEM" != "cmake_nodep" ]]; then + scripts/fetch_deps.sh; + source cutter-deps/env.sh; + fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export PATH=/usr/local/opt/llvm/bin:$PATH; source scripts/prepare_breakpad_macos.sh; @@ -131,6 +151,12 @@ script: -DCUTTER_USE_BUNDLED_RADARE2=ON ../src && make -j4; + elif [[ "$BUILD_SYSTEM" == "cmake_nodep" ]]; then + cmake + -DCMAKE_BUILD_TYPE=Release + -DCUTTER_USE_BUNDLED_RADARE2=ON + ../src && + make -j4; fi elif [[ "$TRAVIS_OS_NAME" == "osx" ]]; then if [[ "$BUILD_SYSTEM" == "qmake" ]]; then diff --git a/docs/source/building.rst b/docs/source/building.rst index 02bc2afb..9dc916ab 100644 --- a/docs/source/building.rst +++ b/docs/source/building.rst @@ -13,7 +13,7 @@ alternatives – cmake and meson. In any case, there are obviously some requirements: * Radare2 installed from submodule -* Qt 5.9 or above +* Qt 5.9 or above (can compile using 5.5 with some limitations) * Python3.6 * Breakpad installed using script (optional, disabled by default) @@ -30,6 +30,7 @@ Note that there are three major building options available: * ``CUTTER_ENABLE_PYTHON`` compile with Python support * ``CUTTER_ENABLE_PYTHON_BINDINGS`` automatically generate Python Bindings with Shiboken2, required for Python plugins! * ``CUTTER_ENABLE_CRASH_REPORTS`` is used to compile Cutter with crash handling system enabled (Breakpad) +* ``CUTTER_USE_BUNDLED_RADARE2`` CMake only, automatically compile Radare2 from submodule. -------------- @@ -91,7 +92,7 @@ Building with Cmake Requirements ~~~~~~~~~~~~ -- CMake >= 3.1 +- CMake >= 3.5 (recommended >= 3.6) Building on Linux ~~~~~~~~~~~~~~~~~ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c56d4f3b..fca1012b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,6 @@ +cmake_minimum_required(VERSION 3.5) -cmake_minimum_required(VERSION 3.1) - -if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.12.0") +if(POLICY CMP0074) cmake_policy(SET CMP0074 NEW) endif() @@ -103,19 +102,13 @@ else() set(KSYNTAXHIGHLIGHTING_STATUS OFF) endif() -find_package(PkgConfig REQUIRED) - if (CUTTER_ENABLE_GRAPHVIZ) if (CUTTER_ENABLE_GRAPHVIZ STREQUAL AUTO) - pkg_check_modules(GVC IMPORTED_TARGET libgvc) - if (GVC_FOUND) - set(CUTTER_ENABLE_GRAPHVIZ ON) - else() - set(CUTTER_ENABLE_GRAPHVIZ OFF) - endif() + find_package(Graphviz) else() - pkg_check_modules(GVC REQUIRED IMPORTED_TARGET libgvc) + find_package(Graphviz REQUIRED) endif() + set (CUTTER_ENABLE_GRAPHVIZ ${Graphviz_FOUND}) endif() message(STATUS "") @@ -157,7 +150,7 @@ else() endif() -if (CUTTER_ENABLE_GRAPHVIZ) +if (TARGET Graphviz::GVC) list(APPEND SOURCE_FILES ${CUTTER_PRO_GRAPHVIZ_SOURCES}) list(APPEND HEADER_FILES ${CUTTER_PRO_GRAPHVIZ_HEADERS}) endif() @@ -173,8 +166,8 @@ endif() add_executable(Cutter MACOSX_BUNDLE ${UI_FILES} ${QRC_FILES} ${SOURCE_FILES} ${HEADER_FILES} ${BINDINGS_SOURCE}) set_target_properties(Cutter PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist") -if (CUTTER_ENABLE_GRAPHVIZ) - target_link_libraries(Cutter PkgConfig::GVC) +if (TARGET Graphviz::GVC) + target_link_libraries(Cutter Graphviz::GVC) target_compile_definitions(Cutter PRIVATE CUTTER_ENABLE_GRAPHVIZ) endif() diff --git a/src/Main.cpp b/src/Main.cpp index a1f79eb4..0ed113af 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -148,6 +148,7 @@ int main(int argc, char *argv[]) migrateThemes(); if (Config()->getAutoUpdateEnabled()) { +#if CUTTER_UPDATE_WORKER_AVAILABLE UpdateWorker *updateWorker = new UpdateWorker; QObject::connect(updateWorker, &UpdateWorker::checkComplete, [=](const QVersionNumber &version, const QString & error) { @@ -157,6 +158,7 @@ int main(int argc, char *argv[]) updateWorker->deleteLater(); }); updateWorker->checkCurrentVersion(7000); +#endif } int ret = a.exec(); diff --git a/src/cmake/BundledRadare2.cmake b/src/cmake/BundledRadare2.cmake index a272bec5..1b521fdc 100644 --- a/src/cmake/BundledRadare2.cmake +++ b/src/cmake/BundledRadare2.cmake @@ -26,7 +26,7 @@ set(Radare2_INCLUDE_DIRS "${RADARE2_INSTALL_DIR}/include/libr") add_library(Radare2 INTERFACE) add_dependencies(Radare2 Radare2-Bundled) -if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.13.0") +if(NOT (${CMAKE_VERSION} VERSION_LESS "3.13.0")) target_link_directories(Radare2 INTERFACE "${RADARE2_INSTALL_DIR}/lib") else() link_directories("${RADARE2_INSTALL_DIR}/lib") diff --git a/src/cmake/FindGraphviz.cmake b/src/cmake/FindGraphviz.cmake new file mode 100644 index 00000000..cbf477c7 --- /dev/null +++ b/src/cmake/FindGraphviz.cmake @@ -0,0 +1,25 @@ +set (_module Graphviz) + +find_package(PkgConfig REQUIRED) +if (NOT (CMAKE_VERSION VERSION_LESS "3.6.0")) + pkg_check_modules(GVC IMPORTED_TARGET GLOBAL libgvc) +else() + pkg_check_modules(GVC libgvc) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(${_module} + FOUND_VAR ${_module}_FOUND + REQUIRED_VARS GVC_INCLUDE_DIRS) + +if (${GVC_FOUND}) + if (CMAKE_VERSION VERSION_LESS "3.6.0") + add_library(${_module}::GVC INTERFACE IMPORTED) + set_target_properties(${_module}::GVC PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${GVC_INCLUDE_DIRS}") + set_target_properties(${_module}::GVC PROPERTIES + INTERFACE_LINK_LIBRARIES "${GVC_LIBRARIES}") + else() + add_library(${_module}::GVC ALIAS PkgConfig::GVC) + endif() +endif() diff --git a/src/common/ColorThemeWorker.cpp b/src/common/ColorThemeWorker.cpp index b890b3a6..e2acb849 100644 --- a/src/common/ColorThemeWorker.cpp +++ b/src/common/ColorThemeWorker.cpp @@ -208,7 +208,21 @@ QJsonDocument ColorThemeWorker::getTheme(const QString& themeName) const theme.remove(key); } - return QJsonDocument(QJsonObject::fromVariantMap(theme)); + // manualy converting instead of using QJsonObject::fromVariantMap because + // Qt < 5.6 QJsonValue.fromVariant doesn't expect QVariant to already contain + // QJson values like QJsonArray. https://github.com/qt/qtbase/commit/26237f0a2d8db80024b601f676bbce54d483e672 + QJsonObject obj; + for (auto it = theme.begin(); it != theme.end(); it++) { + auto &value = it.value(); + if (value.canConvert()) { + obj[it.key()] = it.value().value(); + } else { + obj[it.key()] = QJsonValue::fromVariant(value); + } + + } + + return QJsonDocument(obj); } QString ColorThemeWorker::deleteTheme(const QString &themeName) const diff --git a/src/common/Helpers.cpp b/src/common/Helpers.cpp index 3789e10a..25339648 100644 --- a/src/common/Helpers.cpp +++ b/src/common/Helpers.cpp @@ -252,4 +252,14 @@ void prependQAction(QAction *action, QMenu *menu) } } +qreal devicePixelRatio(const QPaintDevice *p) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) + return p->devicePixelRatioF(); +#else + return p->devicePixelRatio(); +#endif +} + + } // end namespace diff --git a/src/common/Helpers.h b/src/common/Helpers.h index 0dae7925..e3d0f6fe 100644 --- a/src/common/Helpers.h +++ b/src/common/Helpers.h @@ -18,6 +18,7 @@ class QWidget; class QTreeView; class QAction; class QMenu; +class QPaintDevice; namespace qhelpers { QString formatBytecount(const long bytecount); @@ -54,6 +55,7 @@ QByteArray applyColorToSvg(const QString &filename, QColor color); void setThemeIcons(QList> supportedIconsNames, std::function setter); void prependQAction(QAction *action, QMenu *menu); +qreal devicePixelRatio(const QPaintDevice *p); } // qhelpers diff --git a/src/common/UpdateWorker.cpp b/src/common/UpdateWorker.cpp index 04ac097c..d430daf4 100644 --- a/src/common/UpdateWorker.cpp +++ b/src/common/UpdateWorker.cpp @@ -1,5 +1,6 @@ #include "UpdateWorker.h" +#if CUTTER_UPDATE_WORKER_AVAILABLE #include #include #include @@ -19,6 +20,7 @@ #include "common/Configuration.h" #include "CutterConfig.h" + UpdateWorker::UpdateWorker(QObject *parent) : QObject(parent), pending(false) { @@ -216,4 +218,5 @@ QString UpdateWorker::getRepositoryFileName() const QVersionNumber UpdateWorker::currentVersionNumber() { return QVersionNumber(CUTTER_VERSION_MAJOR, CUTTER_VERSION_MINOR, CUTTER_VERSION_PATCH); -} \ No newline at end of file +} +#endif // CUTTER_UPDATE_WORKER_AVAILABLE diff --git a/src/common/UpdateWorker.h b/src/common/UpdateWorker.h index 5814853b..ff9ec20e 100644 --- a/src/common/UpdateWorker.h +++ b/src/common/UpdateWorker.h @@ -1,10 +1,21 @@ #ifndef UPDATEWORKER_H #define UPDATEWORKER_H +#include + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) +#define CUTTER_UPDATE_WORKER_AVAILABLE 1 +#else +#define CUTTER_UPDATE_WORKER_AVAILABLE 0 +#endif + +#if CUTTER_UPDATE_WORKER_AVAILABLE + #include #include #include #include + #include class QNetworkReply; @@ -126,4 +137,5 @@ private: QNetworkReply *checkReply; }; +#endif //CUTTER_UPDATE_WORKER_AVAILABLE #endif // UPDATEWORKER_H diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 347e92ee..885979c1 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -4,6 +4,9 @@ #include #include +#include +#include + #include "common/TempConfig.h" #include "common/Configuration.h" #include "common/AsyncTask.h" diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index 712b3902..e4160a9b 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -187,6 +187,10 @@ void MainWindow::initUI() plugin->setupInterface(this); } +#if QT_VERSION < QT_VERSION_CHECK(5, 7, 0) + ui->actionGrouped_dock_dragging->setVisible(false); +#endif + initLayout(); } @@ -1402,9 +1406,13 @@ void MainWindow::on_actionExport_as_code_triggered() void MainWindow::on_actionGrouped_dock_dragging_triggered(bool checked) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) auto options = dockOptions(); options.setFlag(QMainWindow::DockOption::GroupedDragging, checked); setDockOptions(options); +#else + Q_UNUSED(checked); +#endif } void MainWindow::seekToFunctionLastInstruction() diff --git a/src/dialogs/AboutDialog.cpp b/src/dialogs/AboutDialog.cpp index db462d71..dd4a8b53 100644 --- a/src/dialogs/AboutDialog.cpp +++ b/src/dialogs/AboutDialog.cpp @@ -53,6 +53,11 @@ AboutDialog::AboutDialog(QWidget *parent) : QSignalBlocker s(ui->updatesCheckBox); ui->updatesCheckBox->setChecked(Config()->getAutoUpdateEnabled()); + + if (!CUTTER_UPDATE_WORKER_AVAILABLE) { + ui->updatesCheckBox->hide(); + ui->checkForUpdatesButton->hide(); + } } AboutDialog::~AboutDialog() {} @@ -80,6 +85,7 @@ void AboutDialog::on_showPluginsButton_clicked() void AboutDialog::on_checkForUpdatesButton_clicked() { +#if CUTTER_UPDATE_WORKER_AVAILABLE UpdateWorker updateWorker; QProgressDialog waitDialog; @@ -105,6 +111,7 @@ void AboutDialog::on_checkForUpdatesButton_clicked() updateWorker.checkCurrentVersion(7000); waitDialog.exec(); +#endif } void AboutDialog::on_updatesCheckBox_stateChanged(int) diff --git a/src/dialogs/NewFileDialog.cpp b/src/dialogs/NewFileDialog.cpp index d4dce802..78415eaf 100644 --- a/src/dialogs/NewFileDialog.cpp +++ b/src/dialogs/NewFileDialog.cpp @@ -345,7 +345,8 @@ void NewFileDialog::fillIOPluginsList() if (plugin.permissions.contains('d')) { continue; } - for (const auto &uri : qAsConst(plugin.uris)) { + const auto &uris = plugin.uris; + for (const auto &uri : uris) { if (uri == "file://") { continue; } diff --git a/src/dialogs/TypesInteractionDialog.cpp b/src/dialogs/TypesInteractionDialog.cpp index f3ffddfe..1d36f5e4 100644 --- a/src/dialogs/TypesInteractionDialog.cpp +++ b/src/dialogs/TypesInteractionDialog.cpp @@ -17,7 +17,9 @@ TypesInteractionDialog::TypesInteractionDialog(QWidget *parent, bool readOnly) : QFont font = Config()->getFont(); ui->plainTextEdit->setFont(font); ui->plainTextEdit->setPlainText(""); +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) ui->plainTextEdit->setTabStopDistance(4 * QFontMetrics(font).horizontalAdvance(' ')); +#endif syntaxHighLighter = Config()->createSyntaxHighlighter(ui->plainTextEdit->document()); ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); ui->plainTextEdit->setReadOnly(readOnly); diff --git a/src/dialogs/WelcomeDialog.cpp b/src/dialogs/WelcomeDialog.cpp index ed00bdc7..4d25ab56 100644 --- a/src/dialogs/WelcomeDialog.cpp +++ b/src/dialogs/WelcomeDialog.cpp @@ -16,7 +16,7 @@ WelcomeDialog::WelcomeDialog(QWidget *parent) : ui(new Ui::WelcomeDialog) { ui->setupUi(this); - setWindowFlag(Qt::WindowContextHelpButtonHint, false); + setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); ui->logoSvgWidget->load(Config()->getLogoFile()); ui->versionLabel->setText("" + tr("Version ") + CUTTER_VERSION_FULL + ""); ui->themeComboBox->setCurrentIndex(Config()->getInterfaceTheme()); diff --git a/src/dialogs/preferences/AsmOptionsWidget.cpp b/src/dialogs/preferences/AsmOptionsWidget.cpp index 19d25b5f..cdd5a025 100644 --- a/src/dialogs/preferences/AsmOptionsWidget.cpp +++ b/src/dialogs/preferences/AsmOptionsWidget.cpp @@ -54,9 +54,10 @@ AsmOptionsWidget::AsmOptionsWidget(PreferencesDialog *dialog) connect(confCheckbox->checkBox, &QCheckBox::stateChanged, [this, val, &cb]() { checkboxEnabler(&cb, val) ;}); } - connect(ui->commentsComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, + using indexSignalType = void (QComboBox::*)(int); + connect(ui->commentsComboBox, static_cast(&QComboBox::currentIndexChanged), this, &AsmOptionsWidget::commentsComboBoxChanged); - connect(ui->asmComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, + connect(ui->asmComboBox, static_cast(&QComboBox::currentIndexChanged), this, &AsmOptionsWidget::asmComboBoxChanged); connect(Core(), SIGNAL(asmOptionsChanged()), this, SLOT(updateAsmOptionsFromVars())); updateAsmOptionsFromVars(); diff --git a/src/dialogs/preferences/PreferencesDialog.cpp b/src/dialogs/preferences/PreferencesDialog.cpp index d12130ff..f6fc69b8 100644 --- a/src/dialogs/preferences/PreferencesDialog.cpp +++ b/src/dialogs/preferences/PreferencesDialog.cpp @@ -21,7 +21,7 @@ PreferencesDialog::PreferencesDialog(QWidget *parent) { setAttribute(Qt::WA_DeleteOnClose); ui->setupUi(this); - setWindowFlag(Qt::WindowContextHelpButtonHint, false); + setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); QList prefs { diff --git a/src/menus/DisassemblyContextMenu.cpp b/src/menus/DisassemblyContextMenu.cpp index f60d7ba5..3cf50860 100644 --- a/src/menus/DisassemblyContextMenu.cpp +++ b/src/menus/DisassemblyContextMenu.cpp @@ -22,7 +22,48 @@ DisassemblyContextMenu::DisassemblyContextMenu(QWidget *parent, MainWindow *main : QMenu(parent), offset(0), canCopy(false), - mainWindow(mainWindow) + mainWindow(mainWindow), + actionEditInstruction(this), + actionNopInstruction(this), + actionJmpReverse(this), + actionEditBytes(this), + actionCopy(this), + actionCopyAddr(this), + actionAddComment(this), + actionAddFlag(this), + actionAnalyzeFunction(this), + actionEditFunction(this), + actionRename(this), + actionRenameUsedHere(this), + actionSetFunctionVarTypes(this), + actionXRefs(this), + actionDisplayOptions(this), + actionDeleteComment(this), + actionDeleteFlag(this), + actionDeleteFunction(this), + actionLinkType(this), + actionSetBaseBinary(this), + actionSetBaseOctal(this), + actionSetBaseDecimal(this), + actionSetBaseHexadecimal(this), + actionSetBasePort(this), + actionSetBaseIPAddr(this), + actionSetBaseSyscall(this), + actionSetBaseString(this), + actionSetBits16(this), + actionSetBits32(this), + actionSetBits64(this), + actionContinueUntil(this), + actionAddBreakpoint(this), + actionSetPC(this), + actionSetToCode(this), + actionSetAsString(this), + actionSetToDataEx(this), + actionSetToDataByte(this), + actionSetToDataWord(this), + actionSetToDataDword(this), + actionSetToDataQword(this), + showInSubmenu(this) { initAction(&actionCopy, tr("Copy"), SLOT(on_actionCopy_triggered()), getCopySequence()); addAction(&actionCopy); @@ -209,7 +250,7 @@ void DisassemblyContextMenu::addSetToDataMenu() SLOT(on_actionSetToDataEx_triggered()), getSetToDataExSequence()); setToDataMenu->addAction(&actionSetToDataEx); - auto switchAction = new QAction(); + auto switchAction = new QAction(this); initAction(switchAction, "Switch Data", SLOT(on_actionSetToData_triggered()), getSetToDataSequence()); } diff --git a/src/widgets/ColorPicker.cpp b/src/widgets/ColorPicker.cpp index 4ce82998..eab813b5 100644 --- a/src/widgets/ColorPicker.cpp +++ b/src/widgets/ColorPicker.cpp @@ -327,7 +327,7 @@ QColor ColorPicker::getColorAtMouse() const QPixmap pixmap = QGuiApplication::screens().at(desktop->screenNumber()) ->grabWindow(desktop->winId(), QCursor::pos().x(), QCursor::pos().y(), 1, 1); - return pixmap.toImage().pixelColor(0, 0); + return QColor(pixmap.toImage().pixel(0, 0)); } bool ColorPicker::isPickingFromScreen() const diff --git a/src/widgets/ColorThemeListView.cpp b/src/widgets/ColorThemeListView.cpp index 45bd3f4d..c04480a5 100644 --- a/src/widgets/ColorThemeListView.cpp +++ b/src/widgets/ColorThemeListView.cpp @@ -14,6 +14,7 @@ #include "common/Configuration.h" #include "common/ColorThemeWorker.h" +#include "common/Helpers.h" #include "widgets/ColorThemeListView.h" @@ -39,7 +40,7 @@ void ColorOptionDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - int margin = this->margin * painter->device()->devicePixelRatioF(); + int margin = this->margin * qhelpers::devicePixelRatio(painter->device()); painter->save(); painter->setFont(option.font); painter->setRenderHint(QPainter::Antialiasing); @@ -138,7 +139,7 @@ void ColorOptionDelegate::paint(QPainter *painter, // Create chess-like pattern of black and white squares // and fill background of roundedColorRect with it if (currCO.color.alpha() < 255) { - const int c1 = static_cast(8 * painter->device()->devicePixelRatioF()); + const int c1 = static_cast(8 * qhelpers::devicePixelRatio(painter->device())); const int c2 = c1 / 2; QPixmap p(c1, c1); QPainter paint(&p); @@ -165,7 +166,7 @@ void ColorOptionDelegate::paint(QPainter *painter, QSize ColorOptionDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { - qreal margin = this->margin * option.widget->devicePixelRatioF(); + qreal margin = this->margin * qhelpers::devicePixelRatio(option.widget); qreal fontHeight = option.fontMetrics.height(); qreal h = QPen().width(); h += fontHeight; // option name diff --git a/src/widgets/ConsoleWidget.cpp b/src/widgets/ConsoleWidget.cpp index eba7e72a..9cf29669 100644 --- a/src/widgets/ConsoleWidget.cpp +++ b/src/widgets/ConsoleWidget.cpp @@ -176,7 +176,7 @@ void ConsoleWidget::executeCommand(const QString &command) ui->outputTextEdit->appendHtml(cmd_line + result); scrollOutputToEnd(); historyAdd(command); - commandTask = nullptr; + commandTask.clear(); ui->inputLineEdit->setEnabled(true); ui->inputLineEdit->setFocus(); if (oldOffset != Core()->getOffset()) { diff --git a/src/widgets/DisassemblerGraphView.cpp b/src/widgets/DisassemblerGraphView.cpp index bc6e4e81..aab6c2aa 100644 --- a/src/widgets/DisassemblerGraphView.cpp +++ b/src/widgets/DisassemblerGraphView.cpp @@ -9,6 +9,7 @@ #include "common/SyntaxHighlighter.h" #include "common/BasicBlockHighlighter.h" #include "dialogs/MultitypeFileSaveDialog.h" +#include "common/Helpers.h" #include #include @@ -27,16 +28,19 @@ #include #include #include +#include #include DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *seekable, - MainWindow *mainWindow) + MainWindow *mainWindow, QList additionalMenuActions) : GraphView(parent), mFontMetrics(nullptr), blockMenu(new DisassemblyContextMenu(this, mainWindow)), contextMenu(new QMenu(this)), - seekable(seekable) + seekable(seekable), + actionExportGraph(this), + actionUnhighlight(this) { highlight_token = nullptr; auto *layout = new QVBoxLayout(this); @@ -100,8 +104,6 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *se // Export Graph menu actionExportGraph.setText(tr("Export Graph")); connect(&actionExportGraph, SIGNAL(triggered(bool)), this, SLOT(on_actionExportGraph_triggered())); - actionSyncOffset.setText(tr("Sync/unsync offset")); - connect(&actionSyncOffset, &QAction::triggered, seekable, &CutterSeekable::toggleSynchronization); // Context menu that applies to everything contextMenu->addAction(&actionExportGraph); @@ -133,7 +135,7 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *se } layoutMenu->addActions(layoutGroup->actions()); contextMenu->addSeparator(); - contextMenu->addAction(&actionSyncOffset); + contextMenu->addActions(additionalMenuActions); QAction *highlightBB = new QAction(this); @@ -484,7 +486,8 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block, // Stop rendering text when it's too small auto transform = p.combinedTransform(); QRect screenChar = transform.mapRect(QRect(0, 0, charWidth, charHeight)); - if (screenChar.width() * p.device()->devicePixelRatioF() < 4) { + + if (screenChar.width() * qhelpers::devicePixelRatio(p.device()) < 4) { return; } @@ -648,7 +651,7 @@ RVA DisassemblerGraphView::getAddrForMouseEvent(GraphBlock &block, QPoint *point } DisassemblerGraphView::Instr *DisassemblerGraphView::getInstrForMouseEvent( - GraphView::GraphBlock &block, QPoint *point) + GraphView::GraphBlock &block, QPoint *point, bool force) { DisassemblyBlock &db = disassembly_blocks[block.entry]; @@ -667,6 +670,13 @@ DisassemblerGraphView::Instr *DisassemblerGraphView::getInstrForMouseEvent( } cur_row += instr.text.lines.size(); } + if (force && !db.instrs.empty()) { + if (mouse_row <= 0) { + return &db.instrs.front(); + } else { + return &db.instrs.back(); + } + } return nullptr; } @@ -935,7 +945,7 @@ QPoint DisassemblerGraphView::getInstructionOffset(const DisassemblyBlock &block void DisassemblerGraphView::blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos) { - Instr *instr = getInstrForMouseEvent(block, &pos); + Instr *instr = getInstrForMouseEvent(block, &pos, event->button() == Qt::RightButton); if (!instr) { return; } @@ -952,14 +962,27 @@ void DisassemblerGraphView::blockClicked(GraphView::GraphBlock &block, QMouseEve if (highlight_token) { blockMenu->setCurHighlightedWord(highlight_token->content); } - if (event->button() == Qt::RightButton) { - actionUnhighlight.setVisible(Core()->getBBHighlighter()->getBasicBlock(block.entry)); - event->accept(); - blockMenu->exec(event->globalPos()); - } viewport()->update(); } +void DisassemblerGraphView::blockContextMenuRequested(GraphView::GraphBlock &block, + QContextMenuEvent *event, QPoint pos) +{ + actionUnhighlight.setVisible(Core()->getBBHighlighter()->getBasicBlock(block.entry)); + event->accept(); + blockMenu->exec(event->globalPos()); +} + +void DisassemblerGraphView::contextMenuEvent(QContextMenuEvent *event) +{ + GraphView::contextMenuEvent(event); + if (!event->isAccepted()) { + //TODO: handle opening block menu using keyboard + contextMenu->exec(event->globalPos()); + event->accept(); + } +} + void DisassemblerGraphView::blockDoubleClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos) { @@ -1112,16 +1135,13 @@ void DisassemblerGraphView::exportR2GraphvizGraph(QString filePath, QString type { TempConfig tempConfig; tempConfig.set("graph.gv.format", type); - qWarning() << Core()->cmdRaw(QString("agfw \"%1\" @ 0x%2").arg(filePath).arg(currentFcnAddr, 0, 16)); + qWarning() << Core()->cmdRaw(QString("agfw \"%1\" @ 0x%2") + .arg(filePath).arg(currentFcnAddr, 0, 16)); } void DisassemblerGraphView::mousePressEvent(QMouseEvent *event) { GraphView::mousePressEvent(event); - if (!event->isAccepted() && event->button() == Qt::RightButton) { - contextMenu->exec(event->globalPos()); - event->accept(); - } emit graphMoved(); } diff --git a/src/widgets/DisassemblerGraphView.h b/src/widgets/DisassemblerGraphView.h index 5d44b7a6..2be8fd6f 100644 --- a/src/widgets/DisassemblerGraphView.h +++ b/src/widgets/DisassemblerGraphView.h @@ -87,7 +87,8 @@ class DisassemblerGraphView : public GraphView }; public: - DisassemblerGraphView(QWidget *parent, CutterSeekable *seekable, MainWindow *mainWindow); + DisassemblerGraphView(QWidget *parent, CutterSeekable *seekable, MainWindow *mainWindow, + QList additionalMenuAction); ~DisassemblerGraphView() override; std::unordered_map disassembly_blocks; virtual void drawBlock(QPainter &p, GraphView::GraphBlock &block, bool interactive) override; @@ -146,6 +147,9 @@ protected: void resizeEvent(QResizeEvent *event) override; void paintEvent(QPaintEvent *event) override; + void blockContextMenuRequested(GraphView::GraphBlock &block, QContextMenuEvent *event, + QPoint pos) override; + void contextMenuEvent(QContextMenuEvent *event) override; private slots: void on_actionExportGraph_triggered(); @@ -175,7 +179,7 @@ private: QPoint getTextOffset(int line) const; QPoint getInstructionOffset(const DisassemblyBlock &block, int line) const; RVA getAddrForMouseEvent(GraphBlock &block, QPoint *point); - Instr *getInstrForMouseEvent(GraphBlock &block, QPoint *point); + Instr *getInstrForMouseEvent(GraphBlock &block, QPoint *point, bool force = false); /** * @brief Get instructions placement and size relative to block. * Inefficient don't use this function when iterating over all instructions. @@ -216,7 +220,6 @@ private: QAction actionExportGraph; QAction actionUnhighlight; - QAction actionSyncOffset; QLabel *emptyText = nullptr; signals: diff --git a/src/widgets/DisassemblyWidget.cpp b/src/widgets/DisassemblyWidget.cpp index e0c882f7..80ee98e0 100644 --- a/src/widgets/DisassemblyWidget.cpp +++ b/src/widgets/DisassemblyWidget.cpp @@ -169,9 +169,7 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main, QAction *action) connect(mCtxMenu, SIGNAL(copy()), mDisasTextEdit, SLOT(copy())); mCtxMenu->addSeparator(); - syncIt.setText(tr("Sync/unsync offset")); - mCtxMenu->addAction(&syncIt); - connect(&syncIt, &QAction::triggered, seekable, &CutterSeekable::toggleSynchronization); + mCtxMenu->addAction(&syncAction); connect(seekable, &CutterSeekable::seekableSeekChanged, this, &DisassemblyWidget::on_seekChanged); addActions(mCtxMenu->actions()); @@ -907,10 +905,11 @@ void DisassemblyLeftPanel::paintEvent(QPaintEvent *event) } const RVA currOffset = disas->getSeekable()->getOffset(); + qreal pixelRatio = qhelpers::devicePixelRatio(p.device()); // Draw the lines for (const auto& l : lines) { int lineOffset = int((distanceBetweenLines * arrowInfo[l.offset].second + distanceBetweenLines) * - p.device()->devicePixelRatioF()); + pixelRatio); // Skip until we reach a line that jumps to a destination if (l.arrow == RVA_INVALID) { continue; diff --git a/src/widgets/DisassemblyWidget.h b/src/widgets/DisassemblyWidget.h index b3621254..504d6efc 100644 --- a/src/widgets/DisassemblyWidget.h +++ b/src/widgets/DisassemblyWidget.h @@ -88,8 +88,6 @@ private: void connectCursorPositionChanged(bool disconnect); void moveCursorRelative(bool up, bool page); - - QAction syncIt; }; class DisassemblyScrollArea : public QAbstractScrollArea diff --git a/src/widgets/GraphView.cpp b/src/widgets/GraphView.cpp index 8f3805d7..77825e0a 100644 --- a/src/widgets/GraphView.cpp +++ b/src/widgets/GraphView.cpp @@ -4,6 +4,7 @@ #ifdef CUTTER_ENABLE_GRAPHVIZ #include "GraphvizLayout.h" #endif +#include "Helpers.h" #include #include @@ -12,7 +13,7 @@ #include #include -#ifndef QT_NO_OPENGL +#ifndef CUTTER_NO_OPENGL_GRAPH #include #include #include @@ -22,12 +23,12 @@ GraphView::GraphView(QWidget *parent) : QAbstractScrollArea(parent) , useGL(false) -#ifndef QT_NO_OPENGL +#ifndef CUTTER_NO_OPENGL_GRAPH , cacheTexture(0) , cacheFBO(0) #endif { -#ifndef QT_NO_OPENGL +#ifndef CUTTER_NO_OPENGL_GRAPH if (useGL) { glWidget = new QOpenGLWidget(this); setViewport(glWidget); @@ -76,20 +77,11 @@ void GraphView::blockHelpEvent(GraphView::GraphBlock &block, QHelpEvent *event, bool GraphView::helpEvent(QHelpEvent *event) { - int x = event->pos().x() + offset.x(); - int y = event->pos().y() - offset.y(); - - for (auto &blockIt : blocks) { - GraphBlock &block = blockIt.second; - - if ((block.x <= x) && (block.y <= y) && - (x <= block.x + block.width) & (y <= block.y + block.height)) { - QPoint pos = QPoint(x - block.x, y - block.y); - blockHelpEvent(block, event, pos); - return true; - } + auto p = viewToLogicalCoordinates(event->pos()); + if (auto block = getBlockContaining(p)) { + blockHelpEvent(*block, event, p - QPoint(block->x, block->y)); + return true; } - return false; } @@ -111,6 +103,10 @@ GraphView::EdgeConfiguration GraphView::edgeConfiguration(GraphView::GraphBlock return ec; } +void GraphView::blockContextMenuRequested(GraphView::GraphBlock &, QContextMenuEvent *, QPoint) +{ +} + bool GraphView::event(QEvent *event) { if (event->type() == QEvent::ToolTip) { @@ -122,6 +118,17 @@ bool GraphView::event(QEvent *event) return QAbstractScrollArea::event(event); } +void GraphView::contextMenuEvent(QContextMenuEvent *event) +{ + event->ignore(); + if (event->reason() == QContextMenuEvent::Mouse) { + QPoint p = viewToLogicalCoordinates(event->pos()); + if (auto block = getBlockContaining(p)) { + blockContextMenuRequested(*block, event, p); + } + } +} + // This calculates the full graph starting at block entry. void GraphView::computeGraph(ut64 entry) { @@ -154,7 +161,7 @@ void GraphView::setViewScale(qreal scale) QSize GraphView::getCacheSize() { return -#ifndef QT_NO_OPENGL +#ifndef CUTTER_NO_OPENGL_GRAPH useGL ? cacheSize : #endif pixmap.size(); @@ -163,33 +170,29 @@ QSize GraphView::getCacheSize() qreal GraphView::getCacheDevicePixelRatioF() { return -#ifndef QT_NO_OPENGL +#ifndef CUTTER_NO_OPENGL_GRAPH useGL ? 1.0 : #endif - pixmap.devicePixelRatioF(); + qhelpers::devicePixelRatio(&pixmap); } QSize GraphView::getRequiredCacheSize() { - return -#ifndef QT_NO_OPENGL - useGL ? viewport()->size() : -#endif - viewport()->size() * devicePixelRatioF(); + return viewport()->size() * getRequiredCacheDevicePixelRatioF(); } qreal GraphView::getRequiredCacheDevicePixelRatioF() { return -#ifndef QT_NO_OPENGL +#ifndef CUTTER_NO_OPENGL_GRAPH useGL ? 1.0f : #endif - devicePixelRatioF(); + qhelpers::devicePixelRatio(this); } void GraphView::paintEvent(QPaintEvent *) { -#ifndef QT_NO_OPENGL +#ifndef CUTTER_NO_OPENGL_GRAPH if (useGL) { glWidget->makeCurrent(); } @@ -206,7 +209,7 @@ void GraphView::paintEvent(QPaintEvent *) } if (useGL) { -#ifndef QT_NO_OPENGL +#ifndef CUTTER_NO_OPENGL_GRAPH auto gl = glWidget->context()->extraFunctions(); gl->glBindFramebuffer(GL_READ_FRAMEBUFFER, cacheFBO); gl->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, glWidget->defaultFramebufferObject()); @@ -249,12 +252,12 @@ void GraphView::addViewOffset(QPoint move, bool emitSignal) void GraphView::paintGraphCache() { -#ifndef QT_NO_OPENGL +#ifndef CUTTER_NO_OPENGL_GRAPH QOpenGLPaintDevice *paintDevice = nullptr; #endif QPainter p; if (useGL) { -#ifndef QT_NO_OPENGL +#ifndef CUTTER_NO_OPENGL_GRAPH auto gl = QOpenGLContext::currentContext()->functions(); bool resizeTex = false; @@ -289,7 +292,7 @@ void GraphView::paintGraphCache() p.begin(paintDevice); #endif } else { - auto dpr = devicePixelRatioF(); + auto dpr = qhelpers::devicePixelRatio(this); pixmap = QPixmap(getRequiredCacheSize()); pixmap.setDevicePixelRatio(dpr); p.begin(&pixmap); @@ -299,7 +302,7 @@ void GraphView::paintGraphCache() paint(p, offset, this->viewport()->rect(), current_scale); p.end(); -#ifndef QT_NO_OPENGL +#ifndef CUTTER_NO_OPENGL_GRAPH delete paintDevice; #endif } @@ -488,6 +491,25 @@ void GraphView::showRectangle(const QRect &block, bool anywhere) viewport()->update(); } +GraphView::GraphBlock *GraphView::getBlockContaining(QPoint p) +{ + // Check if a block was clicked + for (auto &blockIt : blocks) { + GraphBlock &block = blockIt.second; + + QRect rec(block.x, block.y, block.width, block.height); + if (rec.contains(p)) { + return █ + } + } + return nullptr; +} + +QPoint GraphView::viewToLogicalCoordinates(QPoint p) +{ + return p / current_scale + offset; +} + void GraphView::setGraphLayout(GraphView::Layout layout) { graphLayout = layout; @@ -550,21 +572,14 @@ void GraphView::mousePressEvent(QMouseEvent *event) return; } - int x = event->pos().x() / current_scale + offset.x(); - int y = event->pos().y() / current_scale + offset.y(); + QPoint pos = viewToLogicalCoordinates(event->pos()); // Check if a block was clicked - for (auto &blockIt : blocks) { - GraphBlock &block = blockIt.second; - - if ((block.x <= x) && (block.y <= y) && - (x <= block.x + block.width) & (y <= block.y + block.height)) { - QPoint pos = QPoint(x - block.x, y - block.y); - blockClicked(block, event, pos); - // Don't do anything else here! blockClicked might seek and - // all our data is invalid then. - return; - } + if (auto block = getBlockContaining(pos)) { + blockClicked(*block, event, pos - QPoint(block->x, block->y)); + // Don't do anything else here! blockClicked might seek and + // all our data is invalid then. + return; } // Check if a line beginning/end was clicked @@ -577,13 +592,13 @@ void GraphView::mousePressEvent(QMouseEvent *event) } QPointF start = edge.polyline.first(); QPointF end = edge.polyline.last(); - if (checkPointClicked(start, x, y)) { + if (checkPointClicked(start, pos.x(), pos.y())) { showBlock(blocks[edge.target]); // TODO: Callback to child return; break; } - if (checkPointClicked(end, x, y, true)) { + if (checkPointClicked(end, pos.x(), pos.y(), true)) { showBlock(block); // TODO: Callback to child return; @@ -615,19 +630,9 @@ void GraphView::mouseMoveEvent(QMouseEvent *event) void GraphView::mouseDoubleClickEvent(QMouseEvent *event) { - int x = event->pos().x() / current_scale + offset.x(); - int y = event->pos().y() / current_scale + offset.y(); - - // Check if a block was clicked - for (auto &blockIt : blocks) { - GraphBlock &block = blockIt.second; - - if ((block.x <= x) && (block.y <= y) && - (x <= block.x + block.width) & (y <= block.y + block.height)) { - QPoint pos = QPoint(x - block.x, y - block.y); - blockDoubleClicked(block, event, pos); - return; - } + auto p = viewToLogicalCoordinates(event->pos()); + if (auto block = getBlockContaining(p)) { + blockDoubleClicked(*block, event, p - QPoint(block->x, block->y)); } } diff --git a/src/widgets/GraphView.h b/src/widgets/GraphView.h index 2984f368..3b2475ae 100644 --- a/src/widgets/GraphView.h +++ b/src/widgets/GraphView.h @@ -17,7 +17,12 @@ #include "core/Cutter.h" #include "widgets/GraphLayout.h" -#ifndef QT_NO_OPENGL +#if defined(QT_NO_OPENGL) || QT_VERSION < QT_VERSION_CHECK(5, 6, 0) +// QOpenGLExtraFunctions were introduced in 5.6 +#define CUTTER_NO_OPENGL_GRAPH +#endif + +#ifndef CUTTER_NO_OPENGL_GRAPH class QOpenGLWidget; #endif @@ -64,6 +69,13 @@ public: * @param anywhere - set to true for minimizing movement */ void showRectangle(const QRect &rect, bool anywhere = false); + /** + * @brief Get block containing specified point logical coordinates. + * @param p positionin graph logical coordinates + * @return Block or nullptr if position is outside all blocks. + */ + GraphView::GraphBlock *getBlockContaining(QPoint p); + QPoint viewToLogicalCoordinates(QPoint p); void setGraphLayout(Layout layout); Layout getGraphLayout() const { return graphLayout; } @@ -101,8 +113,11 @@ protected: virtual void wheelEvent(QWheelEvent *event) override; virtual EdgeConfiguration edgeConfiguration(GraphView::GraphBlock &from, GraphView::GraphBlock *to, bool interactive = true); + virtual void blockContextMenuRequested(GraphView::GraphBlock &block, QContextMenuEvent *event, + QPoint pos); bool event(QEvent *event) override; + void contextMenuEvent(QContextMenuEvent *event) override; // Mouse events void mousePressEvent(QMouseEvent *event) override; @@ -153,7 +168,7 @@ private: */ QPixmap pixmap; -#ifndef QT_NO_OPENGL +#ifndef CUTTER_NO_OPENGL_GRAPH uint32_t cacheTexture; uint32_t cacheFBO; QSize cacheSize; diff --git a/src/widgets/GraphWidget.cpp b/src/widgets/GraphWidget.cpp index 0c87c052..98083ee3 100644 --- a/src/widgets/GraphWidget.cpp +++ b/src/widgets/GraphWidget.cpp @@ -23,7 +23,7 @@ GraphWidget::GraphWidget(MainWindow *main, QAction *action) : header->setReadOnly(true); layout->addWidget(header); - graphView = new DisassemblerGraphView(layoutWidget, seekable, main); + graphView = new DisassemblerGraphView(layoutWidget, seekable, main, {&syncAction}); layout->addWidget(graphView); // Title needs to get set after graphView is defined diff --git a/src/widgets/GraphvizLayout.cpp b/src/widgets/GraphvizLayout.cpp index 1c6cbf13..d19a6907 100644 --- a/src/widgets/GraphvizLayout.cpp +++ b/src/widgets/GraphvizLayout.cpp @@ -194,9 +194,9 @@ void GraphvizLayout::CalculateLayout(std::unordered_map &block if (edge.target == block.entry && edge.polyline.first().y() < edge.polyline.last().y()) { std::reverse(edge.polyline.begin(), edge.polyline.end()); } - auto it = edge.polyline.rbegin(); + auto it = std::prev(edge.polyline.end()); QPointF direction = *it; - direction -= *(++it); + direction -= *(--it); edge.arrow = getArrowDirection(direction, lineType == LineType::Polyline); } else { diff --git a/src/widgets/HexWidget.cpp b/src/widgets/HexWidget.cpp index dbbf5a7e..349fbbc9 100644 --- a/src/widgets/HexWidget.cpp +++ b/src/widgets/HexWidget.cpp @@ -817,7 +817,7 @@ void HexWidget::fillSelectionBackground(QPainter &painter, bool ascii) return; } const auto parts = rangePolygons(selection.start(), selection.end(), ascii); - for (const auto &shape : qAsConst(parts)) { + for (const auto &shape : parts) { QColor highlightColor = palette().color(QPalette::Highlight); if (ascii == cursorOnAscii) { painter.setBrush(highlightColor); @@ -1060,6 +1060,30 @@ const QColor HexWidget::itemColor(uint8_t byte) return color; } +template +static T fromBigEndian(const void * src) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) + return qFromBigEndian(src); +#else + T result; + memcpy(&result, src, sizeof(T)); + return qFromBigEndian(result); +#endif +} + +template +static T fromLittleEndian(const void * src) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) + return qFromLittleEndian(src); +#else + T result; + memcpy(&result, src, sizeof(T)); + return qFromLittleEndian(result); +#endif +} + QVariant HexWidget::readItem(int offset, QColor *color) { quint8 byte; @@ -1082,9 +1106,9 @@ QVariant HexWidget::readItem(int offset, QColor *color) return QVariant(static_cast(static_cast(byte))); case 2: if (itemBigEndian) - word = qFromBigEndian(dataPtr); + word = fromBigEndian(dataPtr); else - word = qFromLittleEndian(dataPtr); + word = fromLittleEndian(dataPtr); if (color) *color = defColor; if (!signedItem) @@ -1092,9 +1116,9 @@ QVariant HexWidget::readItem(int offset, QColor *color) return QVariant(static_cast(static_cast(word))); case 4: if (itemBigEndian) - dword = qFromBigEndian(dataPtr); + dword = fromBigEndian(dataPtr); else - dword = qFromLittleEndian(dataPtr); + dword = fromLittleEndian(dataPtr); if (color) *color = defColor; if (itemFormat == ItemFormatFloat) { @@ -1106,9 +1130,9 @@ QVariant HexWidget::readItem(int offset, QColor *color) return QVariant(static_cast(static_cast(dword))); case 8: if (itemBigEndian) - qword = qFromBigEndian(dataPtr); + qword = fromBigEndian(dataPtr); else - qword = qFromLittleEndian(dataPtr); + qword = fromLittleEndian(dataPtr); if (color) *color = defColor; if (itemFormat == ItemFormatFloat) { diff --git a/src/widgets/HexdumpWidget.cpp b/src/widgets/HexdumpWidget.cpp index ec6ff84d..96cdc779 100644 --- a/src/widgets/HexdumpWidget.cpp +++ b/src/widgets/HexdumpWidget.cpp @@ -73,8 +73,6 @@ HexdumpWidget::HexdumpWidget(MainWindow *main, QAction *action) : refresh(offset ? *offset : RVA_INVALID); }); - connect(&syncAction, &QAction::triggered, seekable, &CutterSeekable::toggleSynchronization); - syncAction.setText(tr("Sync/unsync offset")); this->ui->hexTextView->addAction(&syncAction); connect(Config(), SIGNAL(fontsUpdated()), this, SLOT(fontsUpdated())); diff --git a/src/widgets/HexdumpWidget.h b/src/widgets/HexdumpWidget.h index b1e54d3b..2716f19d 100644 --- a/src/widgets/HexdumpWidget.h +++ b/src/widgets/HexdumpWidget.h @@ -62,8 +62,6 @@ private: QString getWindowTitle() const override; - QAction syncAction; - private slots: void onSeekChanged(RVA addr); diff --git a/src/widgets/MemoryDockWidget.cpp b/src/widgets/MemoryDockWidget.cpp index f3e149f0..d4dc7a44 100644 --- a/src/widgets/MemoryDockWidget.cpp +++ b/src/widgets/MemoryDockWidget.cpp @@ -3,15 +3,25 @@ #include "MainWindow.h" #include #include +#include +#include MemoryDockWidget::MemoryDockWidget(MemoryWidgetType type, MainWindow *parent, QAction *action) : CutterDockWidget(parent, action) - , mType(type), seekable(new CutterSeekable(this)) + , mType(type) + , seekable(new CutterSeekable(this)) + , syncAction(tr("Sync/unsync offset"), this) { if (parent) { parent->addMemoryDockWidget(this); } connect(seekable, &CutterSeekable::syncChanged, this, &MemoryDockWidget::updateWindowTitle); + connect(&syncAction, &QAction::triggered, seekable, &CutterSeekable::toggleSynchronization); + + dockMenu = new QMenu(this); + dockMenu->addAction(&syncAction); + + setContextMenuPolicy(Qt::ContextMenuPolicy::DefaultContextMenu); } bool MemoryDockWidget::tryRaiseMemoryWidget() @@ -61,7 +71,13 @@ void MemoryDockWidget::updateWindowTitle() setWindowTitle(name); } -CutterSeekable* MemoryDockWidget::getSeekable() const +void MemoryDockWidget::contextMenuEvent(QContextMenuEvent *event) +{ + event->accept(); + dockMenu->exec(mapToGlobal(event->pos())); +} + +CutterSeekable *MemoryDockWidget::getSeekable() const { return seekable; } diff --git a/src/widgets/MemoryDockWidget.h b/src/widgets/MemoryDockWidget.h index 55dc9481..7147ffa8 100644 --- a/src/widgets/MemoryDockWidget.h +++ b/src/widgets/MemoryDockWidget.h @@ -4,6 +4,8 @@ #include "CutterDockWidget.h" #include "core/Cutter.h" +#include + class CutterSeekable; /* Disassembly/Graph/Hexdump/Decompiler view priority */ @@ -14,9 +16,9 @@ class MemoryDockWidget : public CutterDockWidget Q_OBJECT public: MemoryDockWidget(MemoryWidgetType type, MainWindow *parent, QAction *action = nullptr); - ~MemoryDockWidget() {} + ~MemoryDockWidget() override {} - CutterSeekable* getSeekable() const; + CutterSeekable *getSeekable() const; bool tryRaiseMemoryWidget(); void raiseMemoryWidget(); @@ -24,7 +26,7 @@ public: { return mType; } - bool eventFilter(QObject *object, QEvent *event); + bool eventFilter(QObject *object, QEvent *event) override; private: MemoryWidgetType mType; @@ -34,8 +36,11 @@ public slots: protected: CutterSeekable *seekable = nullptr; + QAction syncAction; + QMenu *dockMenu = nullptr; virtual QString getWindowTitle() const = 0; + void contextMenuEvent(QContextMenuEvent *event) override; }; #endif // MEMORYDOCKWIDGET_H diff --git a/src/widgets/SearchWidget.cpp b/src/widgets/SearchWidget.cpp index d05798fd..028fe5d5 100644 --- a/src/widgets/SearchWidget.cpp +++ b/src/widgets/SearchWidget.cpp @@ -196,8 +196,8 @@ SearchWidget::SearchWidget(MainWindow *main, QAction *action) : refreshSearch(); }); - connect(ui->searchspaceCombo, QOverload::of(&QComboBox::currentIndexChanged), - [ = ](int index) { updatePlaceholderText(index);}); + connect(ui->searchspaceCombo, static_cast(&QComboBox::currentIndexChanged), + this, [this](int index) { updatePlaceholderText(index);}); QString currentSearchBoundary = Core()->getConfig("search.in"); ui->searchInCombo->setCurrentIndex(ui->searchInCombo->findData(currentSearchBoundary)); diff --git a/src/widgets/StackWidget.cpp b/src/widgets/StackWidget.cpp index e69dae19..6d955d8d 100644 --- a/src/widgets/StackWidget.cpp +++ b/src/widgets/StackWidget.cpp @@ -8,23 +8,15 @@ #include "QHeaderView" #include "QMenu" -enum ColumnIndex { - COLUMN_OFFSET = 0, - COLUMN_VALUE, - COLUMN_DESCRIPTION -}; - StackWidget::StackWidget(MainWindow *main, QAction *action) : CutterDockWidget(main, action), ui(new Ui::StackWidget), + menuText(this), addressableItemContextMenu(this, main) { ui->setupUi(this); // Setup stack model - modelStack->setHorizontalHeaderItem(COLUMN_OFFSET, new QStandardItem(tr("Offset"))); - modelStack->setHorizontalHeaderItem(COLUMN_VALUE, new QStandardItem(tr("Value"))); - modelStack->setHorizontalHeaderItem(COLUMN_DESCRIPTION, new QStandardItem(tr("Reference"))); viewStack->setFont(Config()->getFont()); viewStack->setModel(modelStack); viewStack->verticalHeader()->hide(); @@ -51,7 +43,6 @@ StackWidget::StackWidget(MainWindow *main, QAction *action) : connect(editAction, &QAction::triggered, this, &StackWidget::editStack); connect(viewStack->selectionModel(), &QItemSelectionModel::currentChanged, this, &StackWidget::onCurrentChanged); - connect(modelStack, &QStandardItemModel::itemChanged, this, &StackWidget::onItemChanged); addressableItemContextMenu.addAction(editAction); addActions(addressableItemContextMenu.actions()); @@ -73,47 +64,8 @@ void StackWidget::updateContents() void StackWidget::setStackGrid() { - updatingData = true; - QJsonArray stackValues = Core()->getStack().array(); - int i = 0; - for (const QJsonValue &value : stackValues) { - QJsonObject stackItem = value.toObject(); - QString addr = RAddressString(stackItem["addr"].toVariant().toULongLong()); - QString valueStack = RAddressString(stackItem["value"].toVariant().toULongLong()); - QStandardItem *rowOffset = new QStandardItem(addr); - rowOffset->setEditable(false); - QStandardItem *rowValue = new QStandardItem(valueStack); - modelStack->setItem(i, COLUMN_OFFSET, rowOffset); - modelStack->setItem(i, COLUMN_VALUE, rowValue); - QJsonValue refObject = stackItem["ref"]; - if (!refObject.isUndefined()) { // check that the key exists - QString ref = refObject.toString(); - if (ref.contains("ascii") && ref.count("-->") == 1) { - ref = Core()->cmd("psz @ [" + addr + "]"); - } - QStandardItem *rowRef = new QStandardItem(ref); - rowRef->setEditable(false); - QModelIndex cell = modelStack->index(i, COLUMN_DESCRIPTION); - modelStack->setItem(i, COLUMN_DESCRIPTION, rowRef); - if (refObject.toString().contains("ascii") && refObject.toString().count("-->") == 1) { - modelStack->setData(cell, QVariant(QColor(243, 156, 17)), - Qt::ForegroundRole); // orange - } else if (ref.contains("program R X") && ref.count("-->") == 0) { - modelStack->setData(cell, QVariant(QColor(Qt::red)), - Qt::ForegroundRole); - } else if (ref.contains("stack") && ref.count("-->") == 0) { - modelStack->setData(cell, QVariant(QColor(Qt::cyan)), - Qt::ForegroundRole); - } else if (ref.contains("library") && ref.count("-->") == 0) { - modelStack->setData(cell, QVariant(QColor(Qt::green)), - Qt::ForegroundRole); - } - } - i++; - } - viewStack->setModel(modelStack); + modelStack->reload(); viewStack->resizeColumnsToContents(); - updatingData = false; } void StackWidget::fontsUpdatedSlot() @@ -127,10 +79,10 @@ void StackWidget::onDoubleClicked(const QModelIndex &index) return; // Check if we are clicking on the offset or value columns and seek if it is the case int column = index.column(); - if (column <= COLUMN_VALUE) { + if (column <= StackModel::ValueColumn) { QString item = index.data().toString(); Core()->seek(item); - if (column == COLUMN_OFFSET) { + if (column == StackModel::OffsetColumn) { mainWindow->showMemoryWidget(MemoryWidgetType::Hexdump); } else { Core()->showMemoryWidget(); @@ -148,11 +100,11 @@ void StackWidget::editStack() bool ok; int row = viewStack->selectionModel()->currentIndex().row(); auto model = viewStack->model(); - QString offset = model->index(row, COLUMN_OFFSET).data().toString(); + QString offset = model->index(row, StackModel::OffsetColumn).data().toString(); EditInstructionDialog e(EDIT_NONE, this); e.setWindowTitle(tr("Edit stack at %1").arg(offset)); - QString oldBytes = model->index(row, COLUMN_VALUE).data().toString(); + QString oldBytes = model->index(row, StackModel::ValueColumn).data().toString(); e.setInstruction(oldBytes); if (e.exec()) { @@ -169,38 +121,149 @@ void StackWidget::onCurrentChanged(const QModelIndex ¤t, const QModelIndex Q_UNUSED(previous) auto currentIndex = viewStack->selectionModel()->currentIndex(); QString offsetString; - if (currentIndex.column() != COLUMN_DESCRIPTION) { + if (currentIndex.column() != StackModel::DescriptionColumn) { offsetString = currentIndex.data().toString(); } else { - offsetString = currentIndex.sibling(currentIndex.row(), COLUMN_VALUE).data().toString(); + offsetString = currentIndex.sibling(currentIndex.row(), StackModel::ValueColumn).data().toString(); } RVA offset = Core()->math(offsetString); addressableItemContextMenu.setTarget(offset); - if (currentIndex.column() == COLUMN_OFFSET) { + if (currentIndex.column() == StackModel::OffsetColumn) { menuText.setText(tr("Stack position")); } else { menuText.setText(tr("Pointed memory")); } } -void StackWidget::onItemChanged(QStandardItem *item) +StackModel::StackModel(QObject *parent) + : QAbstractTableModel(parent) { - if (updatingData || item->column() != COLUMN_VALUE) { - return; - } - QModelIndex index = item->index(); - int row = item->row(); - QString text = item->text(); - // Queue the update instead of performing immediately. Editing will trigger reload. - // Performing reload while itemChanged signal is on stack would result - // in itemView getting stuck in EditingState and preventing further edits. - QMetaObject::invokeMethod(this, [this, index, row, text]() { - QString offsetString = index.sibling(row, COLUMN_OFFSET).data().toString(); - bool ok = false; - auto offset = offsetString.toULongLong(&ok, 16); - if (ok) { - Core()->editBytesEndian(offset, text); - } - }, Qt::QueuedConnection); +} + +void StackModel::reload() +{ + QJsonArray stackValues = Core()->getStack().array(); + + beginResetModel(); + values.clear(); + for (const QJsonValue &value : stackValues) { + QJsonObject stackItem = value.toObject(); + Item item; + + item.offset = stackItem["addr"].toVariant().toULongLong(); + item.value = RAddressString(stackItem["value"].toVariant().toULongLong()); + + + QJsonValue refObject = stackItem["ref"]; + if (!refObject.isUndefined()) { // check that the key exists + QString ref = refObject.toString(); + if (ref.contains("ascii") && ref.count("-->") == 1) { + ref = Core()->cmd(QString("psz @ [%1]").arg(item.offset)); + } + item.description = ref; + + + if (refObject.toString().contains("ascii") && refObject.toString().count("-->") == 1) { + item.descriptionColor = QVariant(QColor(243, 156, 17)); + } else if (ref.contains("program R X") && ref.count("-->") == 0) { + item.descriptionColor = QVariant(QColor(Qt::red)); + } else if (ref.contains("stack") && ref.count("-->") == 0) { + item.descriptionColor = QVariant(QColor(Qt::cyan)); + } else if (ref.contains("library") && ref.count("-->") == 0) { + item.descriptionColor = QVariant(QColor(Qt::green)); + } + } + values.push_back(item); + } + endResetModel(); +} + +int StackModel::rowCount(const QModelIndex &) const +{ + return this->values.size(); +} + +int StackModel::columnCount(const QModelIndex &) const +{ + return ColumnCount; +} + +QVariant StackModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= values.count()) + return QVariant(); + + const auto &item = values.at(index.row()); + + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case OffsetColumn: + return RAddressString(item.offset); + case ValueColumn: + return item.value; + case DescriptionColumn: + return item.description; + default: + return QVariant(); + } + case Qt::ForegroundRole: + switch (index.column()) { + case DescriptionColumn: + return item.descriptionColor; + default: + return QVariant(); + } + case StackDescriptionRole: + return QVariant::fromValue(item); + default: + return QVariant(); + } +} + +QVariant StackModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_UNUSED(orientation); + switch (role) { + case Qt::DisplayRole: + switch (section) { + case OffsetColumn: + return tr("Offset"); + case ValueColumn: + return tr("Value"); + case DescriptionColumn: + return tr("Reference"); + default: + return QVariant(); + } + default: + return QVariant(); + } +} + +bool StackModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (role != Qt::EditRole || index.column() != ValueColumn) { + return false; + } + + auto currentData = data(index, StackDescriptionRole); + if (!currentData.canConvert()) { + return false; + } + auto currentItem = currentData.value(); + + Core()->editBytesEndian(currentItem.offset, value.toString()); + return true; +} + +Qt::ItemFlags StackModel::flags(const QModelIndex &index) const +{ + switch (index.column()) { + case ValueColumn: + return QAbstractTableModel::flags(index) | Qt::ItemIsEditable; + default: + return QAbstractTableModel::flags(index); + } } diff --git a/src/widgets/StackWidget.h b/src/widgets/StackWidget.h index 0faf6edb..42f3a768 100644 --- a/src/widgets/StackWidget.h +++ b/src/widgets/StackWidget.h @@ -15,6 +15,39 @@ namespace Ui { class StackWidget; } +class StackModel : public QAbstractTableModel +{ + Q_OBJECT +public: + struct Item { + RVA offset; + QString value; + QString description; + QVariant descriptionColor; + }; + + enum Column { OffsetColumn = 0, ValueColumn, DescriptionColumn, ColumnCount}; + enum Role { StackDescriptionRole = Qt::UserRole }; + + StackModel(QObject *parent = nullptr); + + void reload(); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + +private: + QVector values; + +}; +Q_DECLARE_METATYPE(StackModel::Item) + class StackWidget : public CutterDockWidget { Q_OBJECT @@ -31,15 +64,13 @@ private slots: void customMenuRequested(QPoint pos); void editStack(); void onCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous); - void onItemChanged(QStandardItem *item); - private: std::unique_ptr ui; - QTableView *viewStack = new QTableView; - QStandardItemModel *modelStack = new QStandardItemModel(1, 3, this); + QTableView *viewStack = new QTableView(this); + StackModel *modelStack = new StackModel(this); QAction *editAction; QAction menuText; RefreshDeferrer *refreshDeferrer; AddressableItemContextMenu addressableItemContextMenu; - bool updatingData = false; + }; diff --git a/src/widgets/StringsWidget.cpp b/src/widgets/StringsWidget.cpp index ee7dae8b..62017454 100644 --- a/src/widgets/StringsWidget.cpp +++ b/src/widgets/StringsWidget.cpp @@ -240,7 +240,7 @@ void StringsWidget::stringSearchFinished(const QList &strings tree->showItemsNumber(proxyModel->rowCount()); - task = nullptr; + task.clear(); } void StringsWidget::on_actionCopy()