diff --git a/radare2 b/radare2 index 521ac7c2..cb60b5e8 160000 --- a/radare2 +++ b/radare2 @@ -1 +1 @@ -Subproject commit 521ac7c28f42a24586bc0b02f10feb75900a066c +Subproject commit cb60b5e8fd8dc76d847d9935d2ded4df2e05b63e diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index 6616436d..c6b4cf79 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -87,6 +87,7 @@ #include #include #include +#include #include #include @@ -188,6 +189,8 @@ void MainWindow::initUI() ui->actionBackward->setShortcut(QKeySequence::Back); ui->actionForward->setShortcut(QKeySequence::Forward); + initBackForwardMenu(); + /* Setup plugins interfaces */ for (auto plugin : Plugins()->getPlugins()) { plugin->setupInterface(this); @@ -985,6 +988,106 @@ void MainWindow::initCorners() setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); } +void MainWindow::initBackForwardMenu() +{ + auto prepareButtonMenu = [this](QAction *action) -> QMenu* { + QToolButton *button = qobject_cast(ui->mainToolBar->widgetForAction(action)); + if (!button) { + return nullptr; + } + QMenu *menu = new QMenu(button); + button->setMenu(menu); + button->setPopupMode(QToolButton::DelayedPopup); + button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(button, &QWidget::customContextMenuRequested, button, + [menu, button] (const QPoint &pos) { + menu->exec(button->mapToGlobal(pos)); + }); + + QFontMetrics metrics(fontMetrics()); + // Roughly 10-16 lines depending on padding size, no need to calculate more precisely + menu->setMaximumHeight(metrics.lineSpacing() * 20); + + menu->setToolTipsVisible(true); + return menu; + }; + + if (auto menu = prepareButtonMenu(ui->actionBackward)) { + menu->setObjectName("historyMenu"); + connect(menu, &QMenu::aboutToShow, menu, [this, menu]() { + updateHistoryMenu(menu, false); + }); + } + if (auto menu = prepareButtonMenu(ui->actionForward)) { + menu->setObjectName("forwardHistoryMenu"); + connect(menu, &QMenu::aboutToShow, menu, [this, menu]() { + updateHistoryMenu(menu, true); + }); + } +} + +void MainWindow::updateHistoryMenu(QMenu *menu, bool redo) +{ + // Not too long so that whole screen doesn't get covered, + // not too short so that reasonable length c++ names can be seen most of the time + const int MAX_NAME_LENGTH = 64; + + auto hist = Core()->cmdj("sj"); + bool history = true; + QList actions; + for (auto item : Core()->cmdj("sj").array()) { + QJsonObject obj = item.toObject(); + QString name = obj["name"].toString(); + RVA offset = obj["offset"].toVariant().toULongLong(); + bool current = obj["current"].toBool(false); + if (current) { + history = false; + } + if (history != redo || current) { // Include current in both directions + QString addressString = RAddressString(offset); + + QString toolTip = QString("%1 %2").arg(addressString, name); // show non truncated name in tooltip + + name.truncate(MAX_NAME_LENGTH); // TODO:#1904 use common name shortening function + QString label = QString("%1 (%2)").arg(name, addressString); + if (current) { + label = QString("current position (%1)").arg(addressString); + } + QAction *action = new QAction(label, menu); + action->setToolTip(toolTip); + actions.push_back(action); + if (current) { + QFont font; + font.setBold(true); + action->setFont(font); + } + } + } + if (!redo) { + std::reverse(actions.begin(), actions.end()); + } + menu->clear(); + menu->addActions(actions); + int steps = 0; + for (QAction *item : menu->actions()) { + if (redo) { + connect(item, &QAction::triggered, item, [steps]() { + for (int i = 0; i < steps; i++) { + Core()->seekNext(); + } + }); + } else { + connect(item, &QAction::triggered, item, [steps]() { + for (int i = 0; i < steps; i++) { + Core()->seekPrev(); + } + }); + } + ++steps; + } + +} + void MainWindow::addWidget(QDockWidget* widget) { dockWidgets.push_back(widget); diff --git a/src/core/MainWindow.h b/src/core/MainWindow.h index acae77ef..ab5a11bb 100644 --- a/src/core/MainWindow.h +++ b/src/core/MainWindow.h @@ -261,6 +261,7 @@ private: void initDocks(); void initLayout(); void initCorners(); + void initBackForwardMenu(); void displayInitialOptionsDialog(const InitialOptions &options = InitialOptions(), bool skipOptionsDialog = false); void resetToDefaultLayout(); @@ -274,6 +275,12 @@ private: void showZenDocks(); void showDebugDocks(); void enableDebugWidgetsMenu(bool enable); + /** + * @brief Fill menu with seek history entries. + * @param menu + * @param redo set to false for undo history, true for redo. + */ + void updateHistoryMenu(QMenu *menu, bool redo = false); void toggleDockWidget(QDockWidget *dock_widget, bool show); diff --git a/src/themes/lightstyle/light.qss b/src/themes/lightstyle/light.qss index c8453975..dc4fde11 100644 --- a/src/themes/lightstyle/light.qss +++ b/src/themes/lightstyle/light.qss @@ -397,7 +397,7 @@ QToolButton::menu-button { QToolButton::menu-indicator { image:url(:/qss_icons/rc/down_arrow.png); - top:-7px; + top:-2px; left:-2px; } @@ -755,3 +755,6 @@ QLineEdit, QLineEdit[text=""] { color: #00304d; } +QMenu#historyMenu, QMenu#forwardHistoryMenu { + menu-scrollable: true; +} diff --git a/src/themes/native/native.qss b/src/themes/native/native.qss index 22e35a14..2ecca0a1 100644 --- a/src/themes/native/native.qss +++ b/src/themes/native/native.qss @@ -24,3 +24,7 @@ CutterTreeView::item padding-top: 1px; padding-bottom: 1px; } + +QMenu#historyMenu, QMenu#forwardHistoryMenu { + menu-scrollable: true; +} diff --git a/src/themes/qdarkstyle/style.qss b/src/themes/qdarkstyle/style.qss index 31f64b37..2097523e 100644 --- a/src/themes/qdarkstyle/style.qss +++ b/src/themes/qdarkstyle/style.qss @@ -1010,7 +1010,7 @@ QToolButton::menu-button:pressed { QToolButton::menu-indicator { image: url(:/qss_icons/rc/down_arrow.png); - top: -7px; + top: -2px; left: -2px; /* shift it a bit */ } @@ -1222,4 +1222,8 @@ QDateEdit::down-arrow:on, QDateEdit::down-arrow:hover, QDateEdit::down-arrow:focus { image: url(:/qss_icons/rc/down_arrow.png); -} \ No newline at end of file +} + +QMenu#historyMenu, QMenu#forwardHistoryMenu { + menu-scrollable: true; +}