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()
Beispiel #4
0
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()
Beispiel #5
0
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()