def _ui_init_toolbar_elements(self): """ Initialize the coverage toolbar UI elements. """ # the composing shell self._shell = ComposingShell(self.lctx, weakref.proxy(self._table_model), weakref.proxy(self._table_view)) # the coverage combobox self._combobox = CoverageComboBox(self.director) # the splitter to make the shell / combobox resizable self._shell_elements = QtWidgets.QSplitter(QtCore.Qt.Horizontal) self._shell_elements.setStyleSheet(""" QSplitter { border: none; } QSplitter::handle { background-color: #909090; width: 2px; height: 2px; margin: 0 0.5em 0 0.5em } QSplitter::handle:horizontal:hover { background-color: #3399FF; } """) # add the child items we wish to put the 'splitter' between # [ composing shell ] [SPLITTER] [ combobox ] self._shell_elements.addWidget(self._shell) self._shell_elements.addWidget(self._combobox) # make the splitter responsive (animate) when hovered self._shell_elements.handle(1).setAttribute(QtCore.Qt.WA_Hover) # give the shell expansion preference over the combobox self._shell_elements.setStretchFactor(0, 1)
def _ui_init_toolbar_elements(self, director): """ Initialize the coverage toolbar UI elements. """ # the composing shell self._shell = ComposingShell(director, weakref.proxy(self._model), self._table) # the coverage combobox self._combobox = CoverageComboBox(director) # the checkbox to hide 0% coverage entries self._hide_zero_label = QtWidgets.QLabel("Hide 0% Coverage: ") self._hide_zero_label.setFont(self._font) self._hide_zero_checkbox = QtWidgets.QCheckBox() # the splitter to make the shell / combobox resizable self._splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal) self._splitter.setStyleSheet(""" QSplitter::handle { background-color: #909090; width: 2px; height: 2px; margin: 0 0.5em 0 0.5em } QSplitter::handle:horizontal:hover { background-color: #3399FF; } """) # add the child items we wish to put the 'splitter' between self._splitter.addWidget(self._shell) self._splitter.addWidget(self._combobox) # this makes the splitter responsive to hover events self._splitter.handle(1).setAttribute(QtCore.Qt.WA_Hover) # give the shell expansion preference over the combobox self._splitter.setStretchFactor(0, 1)
class CoverageOverview(DockableShim): """ The Coverage Overview Widget. """ def __init__(self, director): super(CoverageOverview, self).__init__( "Coverage Overview", plugin_resource(os.path.join("icons", "overview.png"))) # internal self._model = CoverageModel(director, self._widget) # pseudo widget science self._visible = False self._events = EventProxy(self) self._widget.installEventFilter(self._events) # initialize the plugin UI self._ui_init(director) # refresh the data UI such that it reflects the most recent data self.refresh() #-------------------------------------------------------------------------- # Pseudo Widget Functions #-------------------------------------------------------------------------- def show(self): """ Show the CoverageOverview UI / widget. """ self.refresh() super(CoverageOverview, self).show() self._visible = True def terminate(self): """ The CoverageOverview is being hidden / deleted. """ self._visible = False self._model = None self._widget = None def isVisible(self): return self._visible #-------------------------------------------------------------------------- # Initialization - UI #-------------------------------------------------------------------------- def _ui_init(self, director): """ Initialize UI elements. """ # initialize a monospace font to use with our widget(s) self._font = MonospaceFont() self._font_metrics = QtGui.QFontMetricsF(self._font) # initialize our ui elements self._ui_init_table(director) self._ui_init_toolbar(director) self._ui_init_signals() # layout the populated ui just before showing it self._ui_layout() def _ui_init_table(self, director): """ Initialize the coverage table. """ self._table = QtWidgets.QTableView() self._table.setFocusPolicy(QtCore.Qt.NoFocus) self._table.setStyleSheet( "QTableView { gridline-color: black; } " + "QTableView::item:selected { color: white; background-color: %s; } " % director._palette.selection.name()) # set these properties so the user can arbitrarily shrink the table self._table.setMinimumHeight(0) self._table.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored) # install the underlying data source for the table self._table.setModel(self._model) # set the initial column widths for the table for i in xrange(len(SAMPLE_CONTENTS)): rect = self._font_metrics.boundingRect(SAMPLE_CONTENTS[i]) self._table.setColumnWidth(i, rect.width()) # table selection should be by row, not by cell self._table.setSelectionBehavior( QtWidgets.QAbstractItemView.SelectRows) # more code-friendly, readable aliases vh = self._table.verticalHeader() hh = self._table.horizontalHeader() # NOTE/COMPAT: set the row heights as fixed if using_pyqt5: vh.setSectionResizeMode(QtWidgets.QHeaderView.Fixed) else: vh.setResizeMode(QtWidgets.QHeaderView.Fixed) # specify the fixed row height in pixels vh.setDefaultSectionSize(int(self._font_metrics.height())) # hide the vertical header themselves as we don't need them vh.hide() # stretch the last column (which is blank) hh.setStretchLastSection(True) # disable bolding of table column headers when table is selected hh.setHighlightSections(False) # allow sorting of the table, and initialize the sort indicator self._table.setSortingEnabled(True) hh.setSortIndicator(FUNC_ADDR, QtCore.Qt.AscendingOrder) def _ui_init_toolbar(self, director): """ Initialize the coverage toolbar. """ # initialize toolbar elements self._ui_init_toolbar_elements(director) # populate the toolbar self._toolbar = QtWidgets.QToolBar() # # customize the style of the bottom toolbar specifically, we are # interested in tweaking the seperator and item padding. # self._toolbar.setStyleSheet(""" QToolBar::separator { background-color: #909090; width: 2px; margin: 0 0.5em 0 0.5em } """) # populate the toolbar with all our subordinates self._toolbar.addWidget(self._splitter) self._toolbar.addSeparator() self._toolbar.addWidget(self._hide_zero_label) self._toolbar.addWidget(self._hide_zero_checkbox) def _ui_init_toolbar_elements(self, director): """ Initialize the coverage toolbar UI elements. """ # the composing shell self._shell = ComposingShell(director, weakref.proxy(self._model), self._table) # the coverage combobox self._combobox = CoverageComboBox(director) # the checkbox to hide 0% coverage entries self._hide_zero_label = QtWidgets.QLabel("Hide 0% Coverage: ") self._hide_zero_label.setFont(self._font) self._hide_zero_checkbox = QtWidgets.QCheckBox() # the splitter to make the shell / combobox resizable self._splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal) self._splitter.setStyleSheet(""" QSplitter::handle { background-color: #909090; width: 2px; height: 2px; margin: 0 0.5em 0 0.5em } QSplitter::handle:horizontal:hover { background-color: #3399FF; } """) # add the child items we wish to put the 'splitter' between self._splitter.addWidget(self._shell) self._splitter.addWidget(self._combobox) # this makes the splitter responsive to hover events self._splitter.handle(1).setAttribute(QtCore.Qt.WA_Hover) # give the shell expansion preference over the combobox self._splitter.setStretchFactor(0, 1) def _ui_init_signals(self): """ Connect UI signals. """ # jump to disassembly on table row double click self._table.doubleClicked.connect(self._ui_entry_double_click) # right click popup menu #self._table.setContextMenuPolicy(Qt.CustomContextMenu) #self._table.customContextMenuRequested.connect(...) # toggle 0% coverage checkbox self._hide_zero_checkbox.stateChanged.connect( self._ui_hide_zero_toggle) def _ui_layout(self): """ Layout the major UI elements of the widget. """ # layout the major elements of our widget layout = QtWidgets.QGridLayout() layout.addWidget(self._table) layout.addWidget(self._toolbar) # apply the layout to the containing form self._widget.setLayout(layout) #-------------------------------------------------------------------------- # Signal Handlers #-------------------------------------------------------------------------- def _ui_entry_double_click(self, index): """ Handle double click event on the coverage table view. A double click on the coverage table view will jump the user to the corresponding function in the IDA disassembly view. """ idaapi.jumpto(self._model.row2func[index.row()]) def _ui_hide_zero_toggle(self, checked): """ Handle state change of 'Hide 0% Coverage' checkbox. """ self._model.filter_zero_coverage(checked) #-------------------------------------------------------------------------- # Refresh #-------------------------------------------------------------------------- @idafast def refresh(self): """ Refresh the Coverage Overview. """ self._model.refresh() self._shell.refresh() self._combobox.refresh()
class CoverageOverview(DockableWindow): """ The Coverage Overview Widget. """ def __init__(self, core): super(CoverageOverview, self).__init__( "Coverage Overview", plugin_resource(os.path.join("icons", "overview.png"))) self._core = core self._visible = False # see the EventProxy class below for more details self._events = EventProxy(self) self._widget.installEventFilter(self._events) # initialize the plugin UI self._ui_init() # refresh the data UI such that it reflects the most recent data self.refresh() #-------------------------------------------------------------------------- # Pseudo Widget Functions #-------------------------------------------------------------------------- def show(self): """ Show the CoverageOverview UI / widget. """ self.refresh() super(CoverageOverview, self).show() self._visible = True def terminate(self): """ The CoverageOverview is being hidden / deleted. """ self._visible = False self._combobox = None self._shell = None self._table_view = None self._table_controller = None self._table_model = None self._widget = None def isVisible(self): return self._visible #-------------------------------------------------------------------------- # Initialization - UI #-------------------------------------------------------------------------- def _ui_init(self): """ Initialize UI elements. """ # initialize our ui elements self._ui_init_table() self._ui_init_toolbar() self._ui_init_signals() # layout the populated ui just before showing it self._ui_layout() def _ui_init_table(self): """ Initialize the coverage table. """ self._table_model = CoverageTableModel(self._core.director, self._widget) self._table_controller = CoverageTableController(self._table_model) self._table_view = CoverageTableView(self._table_controller, self._table_model, self._widget) def _ui_init_toolbar(self): """ Initialize the coverage toolbar. """ # initialize child elements to go on the toolbar self._ui_init_toolbar_elements() self._ui_init_settings() # # create the 'toolbar', and customize its style. specifically, we are # interested in tweaking the separator and padding between elements. # self._toolbar = QtWidgets.QToolBar() self._toolbar.setStyle(QtWidgets.QStyleFactory.create("Windows")) self._toolbar.setStyleSheet('QToolBar{padding:0;margin:0;}') # populate the toolbar with all our subordinates self._toolbar.addWidget(self._shell_elements) self._toolbar.addWidget(self._settings_button) def _ui_init_toolbar_elements(self): """ Initialize the coverage toolbar UI elements. """ # the composing shell self._shell = ComposingShell(self._core.director, weakref.proxy(self._table_model), weakref.proxy(self._table_view)) # the coverage combobox self._combobox = CoverageComboBox(self._core.director) # the splitter to make the shell / combobox resizable self._shell_elements = QtWidgets.QSplitter(QtCore.Qt.Horizontal) self._shell_elements.setStyleSheet(""" QSplitter::handle { background-color: #909090; width: 2px; height: 2px; margin: 0 0.5em 0 0.5em } QSplitter::handle:horizontal:hover { background-color: #3399FF; } """) # add the child items we wish to put the 'splitter' between # [ composing shell ] [SPLITTER] [ combobox ] self._shell_elements.addWidget(self._shell) self._shell_elements.addWidget(self._combobox) # make the splitter responsive (animate) when hovered self._shell_elements.handle(1).setAttribute(QtCore.Qt.WA_Hover) # give the shell expansion preference over the combobox self._shell_elements.setStretchFactor(0, 1) def _ui_init_settings(self): """ Initialize the overview settings popup. """ # settings button self._settings_button = QtWidgets.QToolButton() self._settings_button.setIcon(get_qt_icon("SP_DialogResetButton")) self._settings_button.setStyleSheet( "QToolButton::menu-indicator{image: none;}") # settings menu self._settings_menu = TableSettingsMenu(self._widget) def _ui_init_signals(self): """ Connect UI signals. """ self._settings_menu.connect_signals(self._table_controller, self._core) self._settings_button.clicked.connect(self._ui_show_settings) def _ui_layout(self): """ Layout the major UI elements of the widget. """ # layout the major elements of our widget layout = QtWidgets.QGridLayout() layout.setSpacing(get_dpi_scale()) layout.addWidget(self._table_view) layout.addWidget(self._toolbar) # apply the layout to the containing form self._widget.setLayout(layout) #-------------------------------------------------------------------------- # Signal Handlers #-------------------------------------------------------------------------- def _ui_show_settings(self): """ Handle a click of the settings button. """ delta = QtCore.QPoint(-1 * self._settings_menu.sizeHint().width(), -1 * self._settings_menu.sizeHint().height()) center = QtCore.QPoint(self._settings_button.sizeHint().width() / 2, self._settings_button.sizeHint().height() / 2) where = self._settings_button.mapToGlobal(center + delta) self._settings_menu.popup(where) #-------------------------------------------------------------------------- # Refresh #-------------------------------------------------------------------------- @disassembler.execute_ui def refresh(self): """ Refresh the Coverage Overview. """ self._table_model.refresh() self._shell.refresh() self._combobox.refresh()
class CoverageOverview(idaapi.PluginForm): """ The Coverage Overview Widget. """ def __init__(self, director): super(CoverageOverview, self).__init__() self._title = "Coverage Overview" self._director = director self._model = CoverageModel(director) #-------------------------------------------------------------------------- # PluginForm Overloads #-------------------------------------------------------------------------- def Show(self): """ Show the dialog. """ return super(CoverageOverview, self).Show(self._title, options=idaapi.PluginForm.FORM_PERSIST) def OnCreate(self, form): """ Called when the view is created. """ # NOTE/COMPAT if using_pyqt5(): self.parent = self.FormToPyQtWidget(form) else: self.parent = self.FormToPySideWidget(form) # initialize the plugin UI self._ui_init() # refresh the data UI such that it reflects the most recent data self.refresh() def OnClose(self, Form): """ Called when the view is being closed. """ self.parent = None #-------------------------------------------------------------------------- # Initialization - UI #-------------------------------------------------------------------------- def _ui_init(self): """ Initialize UI elements. """ # set window icon to the coverage overview icon self.parent.setWindowIcon( QtGui.QIcon(plugin_resource("icons\overview.png"))) # initialize a monospace font to use with our widget(s) self._font = MonospaceFont() self._font_metrics = QtGui.QFontMetricsF(self._font) # initialize our ui elements self._ui_init_table() self._ui_init_toolbar() self._ui_init_signals() # layout the populated ui just before showing it self._ui_layout() def _ui_init_table(self): """ Initialize the coverage table. """ self._table = QtWidgets.QTableView() self._table.setStyleSheet("QTableView { gridline-color: black; }") # set these properties so the user can arbitrarily shrink the table self._table.setMinimumHeight(0) self._table.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored) # allow sorting of the table, and initialize the sort indicator self._table.setSortingEnabled(True) self._table.horizontalHeader().setSortIndicator( FUNC_ADDR, QtCore.Qt.AscendingOrder) # install the underlying data source for the table self._table.setModel(self._model) # set the initial column widths for the table for i in xrange(len(SAMPLE_CONTENTS)): rect = self._font_metrics.boundingRect(SAMPLE_CONTENTS[i]) self._table.setColumnWidth(i, rect.width()) # table selection should be by row, not by cell self._table.setSelectionBehavior( QtWidgets.QAbstractItemView.SelectRows) # more code-friendly, readable aliases vh = self._table.verticalHeader() hh = self._table.horizontalHeader() # NOTE/COMPAT: set the row heights as fixed if using_pyqt5(): vh.setSectionResizeMode(QtWidgets.QHeaderView.Fixed) else: vh.setResizeMode(QtWidgets.QHeaderView.Fixed) # specify the fixed row height in pixels vh.setDefaultSectionSize(int(self._font_metrics.height())) # stretch the last column (which is blank) hh.setStretchLastSection(True) def _ui_init_toolbar(self): """ Initialize the coverage toolbar. """ # initialize toolbar elements self._ui_init_toolbar_elements() # populate the toolbar self._toolbar = QtWidgets.QToolBar() # # customize the style of the bottom toolbar specifically, we are # interested in tweaking the seperator and item padding. # self._toolbar.setStyleSheet(""" QToolBar::separator { background-color: #909090; width: 2px; margin: 0 0.5em 0 0.5em } """) # populate the toolbar with all our subordinates self._toolbar.addWidget(self._splitter) self._toolbar.addSeparator() self._toolbar.addWidget(self._hide_zero_label) self._toolbar.addWidget(self._hide_zero_checkbox) def _ui_init_toolbar_elements(self): """ Initialize the coverage toolbar UI elements. """ # the composing shell self._shell = ComposingShell(self._director) # the coverage combobox self._combobox = CoverageComboBox(self._director) # the checkbox to hide 0% coverage entries self._hide_zero_label = QtWidgets.QLabel("Hide 0% Coverage: ") self._hide_zero_label.setFont(self._font) self._hide_zero_checkbox = QtWidgets.QCheckBox() # the splitter to make the shell / combobox resizable self._splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal) self._splitter.setStyleSheet(""" QSplitter::handle { background-color: #909090; width: 2px; height: 2px; margin: 0 0.5em 0 0.5em } QSplitter::handle:horizontal:hover { background-color: #3399FF; } """) # add the child items we wish to put the 'splitter' between self._splitter.addWidget(self._shell) self._splitter.addWidget(self._combobox) # this makes the splitter responsive to hover events self._splitter.handle(1).setAttribute(QtCore.Qt.WA_Hover) # give the shell expansion preference over the combobox self._splitter.setStretchFactor(0, 1) def _ui_init_signals(self): """ Connect UI signals. """ # jump to disassembly on table row double click self._table.doubleClicked.connect(self._ui_entry_double_click) # right click popup menu #self._table.setContextMenuPolicy(Qt.CustomContextMenu) #self._table.customContextMenuRequested.connect(...) # toggle 0% coverage checkbox self._hide_zero_checkbox.stateChanged.connect( self._ui_hide_zero_toggle) # register for cues from the director self._director.coverage_switched( self._coverage_changed) # TODO: too heavy self._director.coverage_modified(self._coverage_changed) def _ui_layout(self): """ Layout the major UI elements of the widget. """ assert self.parent # layout the major elements of our widget layout = QtWidgets.QGridLayout() layout.addWidget(self._table) layout.addWidget(self._toolbar) # apply the widget layout self.parent.setLayout(layout) #-------------------------------------------------------------------------- # Signal Handlers #-------------------------------------------------------------------------- def _ui_entry_double_click(self, index): """ Handle double click event on the coverage table view. """ # # a double click on the coverage table view will jump the user to # the corresponding function in the IDA disassembly view # idaapi.jumpto(self._model.row2func[index.row()]) def _ui_hide_zero_toggle(self, checked): """ Handle state change of 'Hide 0% Coverage' checkbox. """ self._model.hide_zero_coverage(checked) def _coverage_changed(self): """ Handle a coverage (switched | modified) event from the director. """ # # we only bother to act on an incoming director signal if the # coverage overview is actually visible. return now if hidden # if not self.parent.isVisible(): return # refresh the coverage overview self.refresh() #-------------------------------------------------------------------------- # Refresh #-------------------------------------------------------------------------- @idafast def refresh(self): """ Refresh the Coverage Overview. """ assert self.parent self._model.refresh() self._shell.refresh() self._combobox.refresh()
class CoverageOverview(DockableShim): """ The Coverage Overview Widget. """ def __init__(self, director): super(CoverageOverview, self).__init__( "Coverage Overview", plugin_resource(os.path.join("icons", "overview.png"))) # local reference to the director self._director = director # underlying data model for the coverage overview self._model = CoverageModel(director, self._widget) # pseudo widget science self._visible = False self._events = EventProxy(self) self._widget.installEventFilter(self._events) # initialize the plugin UI self._ui_init() # refresh the data UI such that it reflects the most recent data self.refresh() #-------------------------------------------------------------------------- # Pseudo Widget Functions #-------------------------------------------------------------------------- def show(self): """ Show the CoverageOverview UI / widget. """ self.refresh() super(CoverageOverview, self).show() self._visible = True def terminate(self): """ The CoverageOverview is being hidden / deleted. """ self._visible = False self._model = None self._widget = None def isVisible(self): return self._visible #-------------------------------------------------------------------------- # Initialization - UI #-------------------------------------------------------------------------- def _ui_init(self): """ Initialize UI elements. """ # initialize a monospace font to use with our widget(s) self._font = MonospaceFont() self._font_metrics = QtGui.QFontMetricsF(self._font) # initialize our ui elements self._ui_init_table() self._ui_init_toolbar() self._ui_init_ctx_menu_actions() self._ui_init_signals() # layout the populated ui just before showing it self._ui_layout() def _ui_init_table(self): """ Initialize the coverage table. """ palette = self._director._palette self._table = QtWidgets.QTableView() self._table.setFocusPolicy(QtCore.Qt.NoFocus) self._table.setStyleSheet( "QTableView { gridline-color: black; background-color: %s } " % palette.overview_bg.name() + "QTableView::item:selected { color: white; background-color: %s; } " % palette.selection.name()) # set these properties so the user can arbitrarily shrink the table self._table.setMinimumHeight(0) self._table.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored) # install the underlying data source for the table self._table.setModel(self._model) # set the initial column widths for the table for i in xrange(len(SAMPLE_CONTENTS)): rect = self._font_metrics.boundingRect(SAMPLE_CONTENTS[i]) self._table.setColumnWidth(i, rect.width()) # table selection should be by row, not by cell self._table.setSelectionBehavior( QtWidgets.QAbstractItemView.SelectRows) # more code-friendly, readable aliases vh = self._table.verticalHeader() hh = self._table.horizontalHeader() # NOTE/COMPAT: set the row heights as fixed if using_pyqt5: vh.setSectionResizeMode(QtWidgets.QHeaderView.Fixed) else: vh.setResizeMode(QtWidgets.QHeaderView.Fixed) # specify the fixed row height in pixels vh.setDefaultSectionSize(int(self._font_metrics.height())) # hide the vertical header themselves as we don't need them vh.hide() # stretch the last column (which is blank) hh.setStretchLastSection(True) # disable bolding of table column headers when table is selected hh.setHighlightSections(False) # allow sorting of the table, and initialize the sort indicator self._table.setSortingEnabled(True) hh.setSortIndicator(FUNC_ADDR, QtCore.Qt.AscendingOrder) def _ui_init_toolbar(self): """ Initialize the coverage toolbar. """ # initialize toolbar elements self._ui_init_toolbar_elements() # populate the toolbar self._toolbar = QtWidgets.QToolBar() # # customize the style of the bottom toolbar specifically, we are # interested in tweaking the seperator and item padding. # self._toolbar.setStyleSheet(""" QToolBar::separator { background-color: #909090; width: 2px; margin: 0 0.5em 0 0.5em } """) # populate the toolbar with all our subordinates self._toolbar.addWidget(self._splitter) self._toolbar.addSeparator() self._toolbar.addWidget(self._hide_zero_label) self._toolbar.addWidget(self._hide_zero_checkbox) def _ui_init_toolbar_elements(self): """ Initialize the coverage toolbar UI elements. """ # the composing shell self._shell = ComposingShell(self._director, weakref.proxy(self._model), self._table) # the coverage combobox self._combobox = CoverageComboBox(self._director) # the checkbox to hide 0% coverage entries self._hide_zero_label = QtWidgets.QLabel("Hide 0% Coverage: ") self._hide_zero_label.setFont(self._font) self._hide_zero_checkbox = QtWidgets.QCheckBox() # the splitter to make the shell / combobox resizable self._splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal) self._splitter.setStyleSheet(""" QSplitter::handle { background-color: #909090; width: 2px; height: 2px; margin: 0 0.5em 0 0.5em } QSplitter::handle:horizontal:hover { background-color: #3399FF; } """) # add the child items we wish to put the 'splitter' between self._splitter.addWidget(self._shell) self._splitter.addWidget(self._combobox) # this makes the splitter responsive to hover events self._splitter.handle(1).setAttribute(QtCore.Qt.WA_Hover) # give the shell expansion preference over the combobox self._splitter.setStretchFactor(0, 1) def _ui_init_ctx_menu_actions(self): """ Initialize the right click context menu actions. """ # function actions self._action_rename = QtWidgets.QAction("Rename", None) self._action_copy_name = QtWidgets.QAction("Copy name", None) self._action_copy_address = QtWidgets.QAction("Copy address", None) # function prefixing actions self._action_prefix = QtWidgets.QAction("Prefix selected functions", None) self._action_clear_prefix = QtWidgets.QAction("Clear prefixes", None) # misc actions self._action_refresh_metadata = QtWidgets.QAction( "Full refresh (slow)", None) def _ui_init_signals(self): """ Connect UI signals. """ # jump to disassembly on table row double click self._table.doubleClicked.connect(self._ui_entry_double_click) # right click popup menu self._table.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self._table.customContextMenuRequested.connect( self._ui_ctx_menu_handler) # toggle 0% coverage checkbox self._hide_zero_checkbox.stateChanged.connect( self._ui_hide_zero_toggle) def _ui_layout(self): """ Layout the major UI elements of the widget. """ # layout the major elements of our widget layout = QtWidgets.QGridLayout() layout.addWidget(self._table) layout.addWidget(self._toolbar) # apply the layout to the containing form self._widget.setLayout(layout) #-------------------------------------------------------------------------- # Signal Handlers #-------------------------------------------------------------------------- def _ui_entry_double_click(self, index): """ Handle double click event on the coverage table. A double click on the coverage table view will jump the user to the corresponding function in the IDA disassembly view. """ idaapi.jumpto(self._model.row2func[index.row()]) def _ui_ctx_menu_handler(self, position): """ Handle right click context menu event on the coverage table. """ # create a right click menu based on the state and context ctx_menu = self._populate_ctx_menu() if not ctx_menu: return # show the popup menu to the user, and wait for their selection action = ctx_menu.exec_(self._table.viewport().mapToGlobal(position)) # process the user action self._process_ctx_menu_action(action) def _ui_hide_zero_toggle(self, checked): """ Handle state change of 'Hide 0% Coverage' checkbox. """ self._model.filter_zero_coverage(checked) #-------------------------------------------------------------------------- # Context Menu #-------------------------------------------------------------------------- def _populate_ctx_menu(self): """ Populate a context menu for the table view based on selection. Returns a populated QMenu, or None. """ # get the list rows currently selected in the coverage table selected_rows = self._table.selectionModel().selectedRows() if len(selected_rows) == 0: return None # the context menu we will dynamically populate ctx_menu = QtWidgets.QMenu() # # if there is only one table entry (a function) selected, then # show the menu actions available for a single function such as # copy function name, address, or renaming the function. # if len(selected_rows) == 1: ctx_menu.addAction(self._action_rename) ctx_menu.addAction(self._action_copy_name) ctx_menu.addAction(self._action_copy_address) ctx_menu.addSeparator() # function prefixing actions ctx_menu.addAction(self._action_prefix) ctx_menu.addAction(self._action_clear_prefix) ctx_menu.addSeparator() # misc actions ctx_menu.addAction(self._action_refresh_metadata) # return the completed context menu return ctx_menu def _process_ctx_menu_action(self, action): """ Process the given (user selected) context menu action. """ # a right click menu action was not clicked. nothing else to do if not action: return # get the list rows currently selected in the coverage table selected_rows = self._table.selectionModel().selectedRows() if len(selected_rows) == 0: return # # extract the function addresses for the list of selected rows # as they will probably come in handy later. # function_addresses = [] for index in selected_rows: address = self._model.row2func[index.row()] function_addresses.append(address) # # check the universal actions first # # handle the 'Prefix functions' action if action == self._action_prefix: gui_prefix_functions(function_addresses) # handle the 'Clear prefix' action elif action == self._action_clear_prefix: clear_prefixes(function_addresses) # handle the 'Refresh metadata' action elif action == self._action_refresh_metadata: idaapi.show_wait_box("Building database metadata...") self._director.refresh() # ensure the table's model gets refreshed idaapi.replace_wait_box("Refreshing Coverage Overview...") self.refresh() # all done idaapi.hide_wait_box() # # the following actions are only applicable if there is only one # row/function selected in the coverage overview table. don't # bother to check multi-function selections against these # if len(selected_rows) != 1: return # unpack the single QModelIndex index = selected_rows[0] function_address = function_addresses[0] # handle the 'Rename' action if action == self._action_rename: gui_rename_function(function_address) # handle the 'Copy name' action elif action == self._action_copy_name: name_index = self._model.index(index.row(), FUNC_NAME) function_name = self._model.data(name_index, QtCore.Qt.DisplayRole) copy_to_clipboard(function_name) # handle the 'Copy address' action elif action == self._action_copy_address: address_string = "0x%X" % function_address copy_to_clipboard(address_string) #-------------------------------------------------------------------------- # Refresh #-------------------------------------------------------------------------- @idafast def refresh(self): """ Refresh the Coverage Overview. """ self._model.refresh() self._shell.refresh() self._combobox.refresh()