diff options
| author | Rusty Wagner <rusty.wagner@gmail.com> | 2021-10-07 12:57:07 -0600 |
|---|---|---|
| committer | Rusty Wagner <rusty.wagner@gmail.com> | 2021-10-26 16:16:50 -0600 |
| commit | 644f9709668eca8c206d662bf40361180ed1cb8c (patch) | |
| tree | bc75058cfc894de891aa8419def6b9063bff2943 | |
| parent | cb46d56c6c95a945bd2835d042ec1c79d16a16ef (diff) | |
Replace log/scripting console dock widgets with global area concept
| -rw-r--r-- | python/examples/helloglobalarea.py | 83 | ||||
| -rw-r--r-- | ui/globalarea.h | 121 | ||||
| -rw-r--r-- | ui/logview.h | 17 | ||||
| -rw-r--r-- | ui/scriptingconsole.h | 9 | ||||
| -rw-r--r-- | ui/tabwidget.h | 163 | ||||
| -rw-r--r-- | ui/uicontext.h | 2 |
6 files changed, 369 insertions, 26 deletions
diff --git a/python/examples/helloglobalarea.py b/python/examples/helloglobalarea.py new file mode 100644 index 00000000..565cedb4 --- /dev/null +++ b/python/examples/helloglobalarea.py @@ -0,0 +1,83 @@ +# Copyright (c) 2015-2021 Vector 35 Inc +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +# This is an example UI plugin which demonstrates how to add global area widgets to Binary Ninja. +# See .../api/ui/globalarea.h for interface details. + +from binaryninjaui import GlobalAreaWidget, GlobalArea, UIActionHandler +from PySide6 import QtCore +from PySide6.QtCore import Qt, QRectF +from PySide6.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QLabel, QWidget +from PySide6.QtGui import QImage, QPixmap, QPainter, QFont, QColor + +instance_id = 0 + +# Global area widgets must derive from GlobalAreaWidget, not QWidget. GlobalAreaWidget is a QWidget but +# provides callbacks for global area events, and must be created with a title. +class HelloGlobalAreaWidget(GlobalAreaWidget): + def __init__(self, name): + global instance_id + GlobalAreaWidget.__init__(self, name) + self.actionHandler = UIActionHandler() + self.actionHandler.setupActionHandler(self) + offset_layout = QHBoxLayout() + offset_layout.addWidget(QLabel("Offset: ")) + self.offset = QLabel(hex(0)) + offset_layout.addWidget(self.offset) + offset_layout.setAlignment(QtCore.Qt.AlignCenter) + datatype_layout = QHBoxLayout() + datatype_layout.addWidget(QLabel("Data Type: ")) + self.datatype = QLabel("") + datatype_layout.addWidget(self.datatype) + datatype_layout.setAlignment(QtCore.Qt.AlignCenter) + layout = QVBoxLayout() + title = QLabel(name, self) + title.setAlignment(QtCore.Qt.AlignCenter) + instance = QLabel("Instance: " + str(instance_id), self) + instance.setAlignment(QtCore.Qt.AlignCenter) + layout.addStretch() + layout.addWidget(title) + layout.addWidget(instance) + layout.addLayout(datatype_layout) + layout.addLayout(offset_layout) + layout.addStretch() + self.setLayout(layout) + instance_id += 1 + self.data = None + + def notifyOffsetChanged(self, offset): + self.offset.setText(hex(offset)) + + def notifyViewChanged(self, view_frame): + if view_frame is None: + self.datatype.setText("None") + self.data = None + else: + self.datatype.setText(view_frame.getCurrentView()) + view = view_frame.getCurrentViewInterface() + self.data = view.getData() + + def contextMenuEvent(self, event): + self.m_contextMenuManager.show(self.m_menu, self.actionHandler) + +# Register the global area widget constructor with Binary Ninja. This will create a new +# global area widget for each window. The callback function receives a `UIContext` object +# for identifying the window. +GlobalArea.addWidget(lambda context: HelloGlobalAreaWidget("Hello")) diff --git a/ui/globalarea.h b/ui/globalarea.h new file mode 100644 index 00000000..c49679a1 --- /dev/null +++ b/ui/globalarea.h @@ -0,0 +1,121 @@ +#pragma once + +#include <QtWidgets/QWidget> +#include "theme.h" +#include "viewframe.h" +#include "tabwidget.h" + +class BINARYNINJAUIAPI GlobalAreaWidget: public QWidget +{ + Q_OBJECT + +protected: + QString m_title; + UIActionHandler m_actionHandler; + ContextMenuManager* m_contextMenuManager = nullptr; + Menu* m_menu = nullptr; + +public: + GlobalAreaWidget(const QString& title); + + const QString& title() const { return m_title; } + + virtual void notifyFontChanged() { } + virtual void notifyOffsetChanged(uint64_t /*offset*/) { } + virtual void notifyThemeChanged() { } + virtual void notifyViewChanged(ViewFrame* /*frame*/) { } + virtual void notifyViewLocationChanged(View* /*view*/, const ViewLocation& /*viewLocation*/) { } + virtual void focus(); +}; + +class BINARYNINJAUIAPI GlobalAreaTabStyle: public DockableTabStyle +{ + int closeButtonSize(const QWidget* widget) const; + +public: + virtual QSize sizeForTab(const QWidget* widget, const DockableTabInfo& info, int idx, + int count, int active) const override; + virtual QRect closeButtonRect(const QWidget* widget, const DockableTabInfo& info, int idx, + int count, int active) const override; + virtual QRect closeIconRect(const QWidget* widget, const DockableTabInfo& info, int idx, + int count, int active) const override; + virtual void paintTab(const QWidget* widget, QStylePainter& p, const DockableTabInfo& info, int idx, + int count, int active, DockableTabInteractionState state, const QRect& rect) const override; + virtual DockableTabStyle* duplicate() override; +}; + +class BINARYNINJAUIAPI GlobalAreaHideButton: public QWidget +{ + Q_OBJECT + + bool m_mouseInside = false; + bool m_buttonDown = false; + QTimer* m_timer; + +public: + GlobalAreaHideButton(); + virtual QSize sizeHint() const override; + +protected: + virtual void paintEvent(QPaintEvent* event) override; + virtual void enterEvent(QEnterEvent* event) override; + virtual void leaveEvent(QEvent* event) override; + virtual void mouseMoveEvent(QMouseEvent* event) override; + virtual void mousePressEvent(QMouseEvent* event) override; + virtual void mouseReleaseEvent(QMouseEvent* event) override; + +private Q_SLOTS: + void underMouseTimerEvent(); + +Q_SIGNALS: + void clicked(); +}; + +class BINARYNINJAUIAPI GlobalArea: public QWidget +{ + Q_OBJECT + + SplitTabWidget* m_tabs; + DockableTabCollection* m_collection; + QSplitter* m_parentSplitter = nullptr; + std::optional<QList<int>> m_pendingParentSplitterSizes, m_savedParentSplitterSizes; + + static std::vector<std::function<GlobalAreaWidget*(UIContext*)>> m_widgetFactories; + + QString actionNameForWidget(const QString &title); + static QVariant sizesToVariant(const QList<int>& sizes); + static std::optional<QList<int>> variantToSizes(const QVariant& variant); + +public: + GlobalArea(); + void setSplitter(QSplitter* splitter); + + void addWidget(GlobalAreaWidget* widget, bool canClose = false); + static void addWidget(const std::function<GlobalAreaWidget*(UIContext*)>& createWidget); + + void initRegisteredWidgets(UIContext* context); + + void updateFonts(); + void updateTheme(); + void updateViewLocation(View* view, const ViewLocation& viewLocation); + void viewChanged(ViewFrame* frame); + + bool isWidgetVisible(const QString& title); + + bool toggleVisible(); + bool toggleWidgetVisible(const QString& title); + void focusWidget(const QString& title); + + void saveSizes(const QSettings& settings, const QString& windowStateName); + void saveState(const QSettings& settings, const QString& windowStateName); + void restoreSizes(const QSettings& settings, const QString& windowStateName); + void restoreState(const QSettings& settings, const QString& windowStateName); + +Q_SIGNALS: + void widgetClosed(GlobalAreaWidget* widget); + +private Q_SLOTS: + void currentChanged(QWidget* widget); + void tabClosed(QWidget* widget); + void hideButtonClicked(); +}; diff --git a/ui/logview.h b/ui/logview.h index c0ff7768..bf9d4627 100644 --- a/ui/logview.h +++ b/ui/logview.h @@ -14,7 +14,7 @@ #include "binaryninjaapi.h" #include "action.h" -#include "dockhandler.h" +#include "globalarea.h" #define LOG_UPDATE_INTERVAL 100 @@ -102,13 +102,13 @@ public Q_SLOTS: }; -class BINARYNINJAUIAPI LogView: public QListView, public DockContextHandler +class BINARYNINJAUIAPI LogView: public GlobalAreaWidget { Q_OBJECT - Q_INTERFACES(DockContextHandler) QPointer<LogStatus> m_logStatus; std::vector<std::pair<QAction*, bool>> m_actionEnableList; + QListView* m_list; LogListModel* m_listModel; LogItemDelegate* m_itemDelegate; QTimer* m_updateTimer; @@ -118,7 +118,7 @@ class BINARYNINJAUIAPI LogView: public QListView, public DockContextHandler bool m_hasSelection = false; public: - LogView(QWidget* parent, LogStatus* logStatus); + LogView(LogStatus* logStatus); virtual void copy(); virtual bool canCopy(); @@ -128,12 +128,15 @@ public: static bool IsHexString(const QString& str, std::pair<int, int> offsetLen); static bool StartsWith0x(const QString& str, std::pair<int, int> offsetLen); -protected: - void contextMenuEvent(QContextMenuEvent* event) override; void notifyFontChanged() override; void notifyThemeChanged() override; void notifyViewChanged(ViewFrame* frame) override; - void notifyVisibilityChanged(bool visible) override; + void focus() override; + + LogListModel* model() { return m_listModel; } + +protected: + void contextMenuEvent(QContextMenuEvent* event) override; Q_SIGNALS: void notifyUiStatus(); diff --git a/ui/scriptingconsole.h b/ui/scriptingconsole.h index b2b3d09e..b0e20253 100644 --- a/ui/scriptingconsole.h +++ b/ui/scriptingconsole.h @@ -17,7 +17,7 @@ #include <functional> #include "binaryninjaapi.h" #include "action.h" -#include "dockhandler.h" +#include "globalarea.h" #include "uitypes.h" #define SCRIPT_OUTPUT_UPDATE_INTERVAL 100 @@ -123,10 +123,9 @@ public Q_SLOTS: class ScriptingConsoleWidget; -class BINARYNINJAUIAPI ScriptingConsole: public QWidget, public DockContextHandler, BinaryNinja::ScriptingOutputListener +class BINARYNINJAUIAPI ScriptingConsole: public GlobalAreaWidget, BinaryNinja::ScriptingOutputListener { Q_OBJECT - Q_INTERFACES(DockContextHandler) struct ScriptOutput { @@ -166,8 +165,6 @@ Q_SIGNALS: protected: void customEvent(QEvent* event) override; - void notifyFontChanged() override; - void notifyVisibilityChanged(bool visible) override; public: ScriptingConsole(QWidget* parent, const QString& providerName, const QString& instanceName, ScriptingInstanceRef instance); @@ -186,6 +183,8 @@ public: virtual void NotifyError(const std::string& text) override; virtual void NotifyInputReadyStateChanged(BNScriptingProviderInputReadyState state) override; virtual void notifyViewChanged(ViewFrame* frame) override; + virtual void notifyFontChanged() override; + virtual void focus() override; void moveUpInHistory(); void moveDownInHistory(); diff --git a/ui/tabwidget.h b/ui/tabwidget.h index fd982b9d..08b84182 100644 --- a/ui/tabwidget.h +++ b/ui/tabwidget.h @@ -7,8 +7,10 @@ #include <QtWidgets/QAbstractScrollArea> #include <QtWidgets/QStylePainter> #include <QtWidgets/QRubberBand> +#include <QtWidgets/QSplitter> #include <QtGui/QMouseEvent> #include "uitypes.h" +#include "json/json.h" class DockableTabWidget; @@ -48,23 +50,74 @@ protected: virtual void paintEvent(QPaintEvent* event) override; }; +enum DockableTabInteractionState +{ + NoTabInteraction, + MouseOverTab, + MouseOverCloseButton, + CloseButtonPressActive, + CloseButtonPressInactive +}; + +struct BINARYNINJAUIAPI DockableTabInfo +{ + QString title; + QString toolTip; + QRect tabRect, closeButtonRect, closeIconRect; + bool modifiedIndicator; + bool canClose; +}; + +class BINARYNINJAUIAPI DockableTabStyle +{ +public: + virtual ~DockableTabStyle() {} + virtual QSize sizeForTab(const QWidget* widget, const DockableTabInfo& info, int idx, + int count, int active) const; + virtual QRect closeButtonRect(const QWidget* widget, const DockableTabInfo& info, int idx, + int count, int active) const; + virtual QRect closeIconRect(const QWidget* widget, const DockableTabInfo& info, int idx, + int count, int active) const; + virtual void paintTab(const QWidget* widget, QStylePainter& p, const DockableTabInfo& info, int idx, + int count, int active, DockableTabInteractionState state, const QRect& rect) const; + virtual void paintBase(const QWidget* widget, QStylePainter& p, const QRect& rect, + const QRect& activeRect) const; + virtual DockableTabStyle* duplicate(); +}; + +class BINARYNINJAUIAPI DefaultDockableTabStyle: public DockableTabStyle +{ + QStyleOptionTab styleForTab(const QWidget* widget, const DockableTabInfo& info, int idx, + int count, int active) const; + int closeButtonSize(const QWidget* widget) const; + +public: + virtual QSize sizeForTab(const QWidget* widget, const DockableTabInfo& info, int idx, + int count, int active) const override; + virtual QRect closeButtonRect(const QWidget* widget, const DockableTabInfo& info, int idx, + int count, int active) const override; + virtual QRect closeIconRect(const QWidget* widget, const DockableTabInfo& info, int idx, + int count, int active) const override; + virtual void paintTab(const QWidget* widget, QStylePainter& p, const DockableTabInfo& info, int idx, + int count, int active, DockableTabInteractionState state, const QRect& rect) const override; + virtual void paintBase(const QWidget* widget, QStylePainter& p, const QRect& rect, + const QRect& activeRect) const override; + virtual DockableTabStyle* duplicate() override; +}; + class BINARYNINJAUIAPI DockableTabBar: public QAbstractScrollArea { Q_OBJECT - struct TabInfo - { - QString title; - QString toolTip; - QRect tabRect, closeButtonRect, closeIconRect; - bool modifiedIndicator; - }; - DockableTabCollection* m_collection; - std::vector<TabInfo> m_tabs; + DockableTabStyle* m_style; + std::vector<DockableTabInfo> m_tabs; int m_active = -1; + bool m_canCreateNewWindow = false; + bool m_canSplit = false; bool m_mouseInside = false; + int m_tabHover = -1; int m_closeButtonHover = -1; int m_closeButtonDown = -1; @@ -73,6 +126,7 @@ class BINARYNINJAUIAPI DockableTabBar: public QAbstractScrollArea DockableTabBar* m_tabDragTarget = nullptr; int m_tabDragWidth; int m_tabDragTargetIndex = -1; + std::optional<Qt::Edge> m_tabDragSplitEdge; bool m_tabDragNewWindow = false; QRubberBand* m_tabDropIndicator = nullptr; @@ -82,11 +136,7 @@ class BINARYNINJAUIAPI DockableTabBar: public QAbstractScrollArea QTimer* m_timer; - QStyleOptionTab styleForTab(int idx, const TabInfo& info) const; - QSize sizeForTab(const QStyleOptionTab& tabStyle, const TabInfo& info) const; - int closeButtonSize() const; void updateLayout(); - void paintTab(QStylePainter& p, const TabInfo& info, int i, const QRect& rect); public: DockableTabBar(DockableTabCollection* collection); @@ -99,6 +149,9 @@ public: void setTabText(int idx, const QString& title); void setTabToolTip(int idx, const QString& toolTip); void setTabModifiedIndicator(int idx, bool indicator); + void setCanCloseTab(int idx, bool canClose); + void setCanCreateNewWindow(bool canCreate); + void setCanSplit(bool canSplit); int count() const; int currentIndex() const; @@ -107,15 +160,22 @@ public: QString tabText(int idx); QString tabToolTip(int idx); bool tabModifiedIndicator(int idx); + bool canCloseTab(int idx); + bool canCreateNewWindow(); + bool canSplit(); void ensureCurrentTabVisible(); + DockableTabStyle* tabStyle() const { return m_style; } + void setTabStyle(DockableTabStyle* style); + Q_SIGNALS: void currentChanged(int idx); void tabCloseRequested(int idx); void tabMoved(int oldIdx, int newIdx); void newWindowForTab(int idx, QRect rectHint); void reparentTab(int oldIdx, DockableTabWidget* target, int newIdx); + void splitTab(int idx, Qt::Edge edge); private Q_SLOTS: void underMouseTimerEvent(); @@ -137,6 +197,9 @@ class BINARYNINJAUIAPI DockableTabBarWithCornerWidget: public QWidget { DockableTabBar* m_bar; QHBoxLayout* m_barLayout; + Qt::Corner m_corner = Qt::TopRightCorner; + QWidget* m_cornerWidget = nullptr; + QWidget* m_cornerWidgetContainer = nullptr; protected: virtual void paintEvent(QPaintEvent* event) override; @@ -145,6 +208,8 @@ public: DockableTabBarWithCornerWidget(DockableTabBar* bar); DockableTabBar* tabBar() const { return m_bar; } void setCornerWidget(QWidget* widget, Qt::Corner corner = Qt::TopRightCorner); + Qt::Corner corner() const { return m_corner; } + QWidget* cornerWidget() const { return m_cornerWidget; } }; class BINARYNINJAUIAPI DockableTabWidget: public QWidget @@ -157,7 +222,7 @@ class BINARYNINJAUIAPI DockableTabWidget: public QWidget QStackedWidget* m_widgets; void addReparentedTab(DockableTabWidget* source, int idx, QWidget* widget, - const QString& title, const QString& toolTip); + const QString& title, const QString& toolTip, bool canClose); public: DockableTabWidget(DockableTabCollection* collection, DockableTabBar* bar = nullptr); @@ -171,15 +236,28 @@ public: QWidget* currentWidget(); QWidget* widget(int idx); DockableTabBar* tabBar() const { return m_bar; } + QStackedWidget* container() const { return m_widgets; } + DockableTabCollection* collection() const { return m_collection; } QString tabText(int idx); bool tabModifiedIndicator(int idx); + bool canCloseTab(int idx); + bool canCreateNewWindow(); + bool canSplit(); int indexOf(QWidget* widget); void setCurrentIndex(int idx); void setTabText(int idx, const QString& title); void setTabModifiedIndicator(int idx, bool indicator); + void setCanCloseTab(int idx, bool canClose); + void setCanCreateNewWindow(bool canCreate); + void setCanSplit(bool canSplit); void setCornerWidget(QWidget* widget, Qt::Corner corner = Qt::TopRightCorner); + Qt::Corner corner() const; + QWidget* cornerWidget() const; + + DockableTabStyle* tabStyle() const { return m_bar->tabStyle(); } + void setTabStyle(DockableTabStyle* style); virtual QSize sizeHint() const override; @@ -190,11 +268,68 @@ Q_SIGNALS: void newWindowForTab(int idx, QRect rectHint); void tabRemovedForReparent(int oldIdx, QWidget* widget, DockableTabWidget* target, int newIdx); void tabAddedForReparent(int idx, DockableTabWidget* source); + void splitTab(int idx, Qt::Edge edge); private Q_SLOTS: void tabBarCurrentChanged(int idx); void tabBarCloseRequested(int idx); void tabBarTabMoved(int oldIdx, int newIdx); void tabBarNewWindowForTab(int idx, QRect rectHint); + void tabBarSplitTab(int idx, Qt::Edge edge); void reparentTab(int oldIdx, DockableTabWidget* target, int newIdx); }; + +class BINARYNINJAUIAPI SplitTabWidget: public QWidget +{ + Q_OBJECT + + DockableTabWidget* m_tabs = nullptr; + QSplitter* m_splitter = nullptr; + SplitTabWidget* m_first = nullptr; + SplitTabWidget* m_second = nullptr; + QVBoxLayout* m_layout; + + SplitTabWidget(DockableTabWidget* tabs); + SplitTabWidget(SplitTabWidget* first, SplitTabWidget* second, Qt::Orientation orientation); + + void splitTabInternal(QWidget* widget, QString title, bool canClose, Qt::Edge edge); + void promoteChild(SplitTabWidget* child, SplitTabWidget* other); + + void enumerateTabTree(const std::function<void(SplitTabWidget*, QWidget*, QString)>& func); + Json::Value savedLayoutObject() const; + void restoreLayoutObject(const Json::Value& layout); + void restoreLayoutObjectWithTabs(const Json::Value& layout, + std::map<QString, std::pair<DockableTabWidget*, QWidget*>>& tabWidgets); + DockableTabWidget* findFirstTabWidget(); + void collapseEmptyTabs(); + +public: + SplitTabWidget(DockableTabCollection* collection); + + void addTab(QWidget* widget, const QString& title); + void setCanCloseTab(QWidget* widget, bool canClose); + void enumerateTabs(const std::function<void(QWidget*)>& func); + void selectWidget(QWidget* widget); + bool isWidgetVisible(QWidget* widget); + + void setTabStyle(DockableTabStyle* style); + + void setCornerWidget(QWidget* widget, Qt::Corner corner = Qt::TopRightCorner); + + QString savedLayoutString() const; + void restoreLayoutString(const QString& layout); + +Q_SIGNALS: + void tabClosed(QWidget* widget); + void currentChanged(QWidget* widget); + void layoutChanged(); + +private Q_SLOTS: + void tabCloseRequested(int idx); + void currentTabChanged(int idx); + void splitTab(int idx, Qt::Edge edge); + void tabRemovedForReparent(int oldIdx, QWidget* widget, DockableTabWidget* target, int newIdx); + void childTabClosed(QWidget* widget); + void childCurrentChanged(QWidget* widget); + void childLayoutChanged(); +}; diff --git a/ui/uicontext.h b/ui/uicontext.h index b0e2adde..de58a74b 100644 --- a/ui/uicontext.h +++ b/ui/uicontext.h @@ -20,6 +20,7 @@ class FileContext; class ViewLocation; class Sidebar; class SidebarWidgetContainer; +class GlobalArea; struct SelectionInfoForXref; /*! @@ -271,6 +272,7 @@ public: virtual UIActionHandler* contentActionHandler() = 0; virtual Sidebar* sidebar() = 0; + virtual GlobalArea* globalArea() = 0; void updateCrossReferences(ViewFrame* frame, View* view, const SelectionInfoForXref& selection); |
