diff --git a/setoolsgui/widgets/tab.py b/setoolsgui/widgets/tab.py index 9241abe2..52b7ab93 100644 --- a/setoolsgui/widgets/tab.py +++ b/setoolsgui/widgets/tab.py @@ -108,7 +108,7 @@ class BaseAnalysisTabWidget(QtWidgets.QScrollArea, metaclass=TabRegistry): perm_map: setools.PermissionMap def __init__(self, _, __, /, *, - enable_criteria: bool = True, + enable_criteria: bool = True, enable_browser: bool = False, parent: QtWidgets.QWidget | None = None) -> None: super().__init__(parent) @@ -124,27 +124,45 @@ def __init__(self, _, __, /, *, # # Create top-level widget for the scroll area # - self.top_widget = QtWidgets.QWidget(self) - self.top_widget.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose) - # size policy for tab contents sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) + # Create splitter + self.top_widget = QtWidgets.QSplitter(self) + self.top_widget.setOrientation(QtCore.Qt.Orientation.Horizontal) self.top_widget.setSizePolicy(sizePolicy) self.setWidget(self.top_widget) # - # Create top level layout + # Build browser + # + if enable_browser: + # create browser + self.browser = views.SEToolsListView(self.top_widget) + self.browser.setSizePolicy(sizePolicy) + self.top_widget.addWidget(self.browser) + self.top_widget.setCollapsible(self.top_widget.indexOf(self.browser), True) + + # + # Build analysis widget # - self.top_layout = QtWidgets.QGridLayout(self.top_widget) - self.top_layout.setContentsMargins(6, 6, 6, 6) - self.top_layout.setSpacing(3) + self.analysis_widget = QtWidgets.QWidget(self.top_widget) + self.analysis_widget.setSizePolicy(sizePolicy) + self.top_widget.addWidget(self.analysis_widget) + self.top_widget.setCollapsible(self.top_widget.indexOf(self.analysis_widget), False) + + # + # Create analysis layout + # + self.analysis_layout = QtWidgets.QGridLayout(self.analysis_widget) + self.analysis_layout.setContentsMargins(6, 6, 6, 6) + self.analysis_layout.setSpacing(3) # title and "show" checkboxes - title = QtWidgets.QLabel(self.top_widget) + title = QtWidgets.QLabel(self.analysis_widget) title.setText(self.tab_title) title.setObjectName("title") sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, @@ -153,15 +171,15 @@ def __init__(self, _, __, /, *, sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(title.sizePolicy().hasHeightForWidth()) title.setSizePolicy(sizePolicy) - self.top_layout.addWidget(title, 0, 0) + self.analysis_layout.addWidget(title, 0, 0) # spacer between title and "show:" spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.top_layout.addItem(spacerItem, 0, 1) + self.analysis_layout.addItem(spacerItem, 0, 1) # "show" label - label_2 = QtWidgets.QLabel(self.top_widget) + label_2 = QtWidgets.QLabel(self.analysis_widget) label_2.setText("Show:") sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Fixed) @@ -169,11 +187,11 @@ def __init__(self, _, __, /, *, sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(label_2.sizePolicy().hasHeightForWidth()) label_2.setSizePolicy(sizePolicy) - self.top_layout.addWidget(label_2, 0, 2) + self.analysis_layout.addWidget(label_2, 0, 2) if enable_criteria: # criteria expander checkbox - self.criteria_expander = QtWidgets.QCheckBox(self.top_widget) + self.criteria_expander = QtWidgets.QCheckBox(self.analysis_widget) self.criteria_expander.setChecked(CRITERIA_DEFAULT_CHECKED) self.criteria_expander.setToolTip( "Show or hide the search criteria (no settings are lost)") @@ -184,10 +202,10 @@ def __init__(self, _, __, /, *,
No settings are lost if the criteria is hidden.
""") self.criteria_expander.setText("Criteria") - self.top_layout.addWidget(self.criteria_expander, 0, 3) + self.analysis_layout.addWidget(self.criteria_expander, 0, 3) # notes expander checkbox - self.notes_expander = QtWidgets.QCheckBox(self.top_widget) + self.notes_expander = QtWidgets.QCheckBox(self.analysis_widget) self.notes_expander.setSizePolicy(sizePolicy) self.notes_expander.setToolTip("Show or hide the notes.") self.notes_expander.setWhatsThis( @@ -203,11 +221,11 @@ def __init__(self, _, __, /, *, sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.notes_expander.sizePolicy().hasHeightForWidth()) - self.top_layout.addWidget(self.notes_expander, 0, 4) + self.analysis_layout.addWidget(self.notes_expander, 0, 4) if enable_criteria: # criteria frame - self.criteria_frame = QtWidgets.QFrame(self.top_widget) + self.criteria_frame = QtWidgets.QFrame(self.analysis_widget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(0) @@ -222,7 +240,7 @@ def __init__(self, _, __, /, *, self.criteria_frame_layout = QtWidgets.QGridLayout(self.criteria_frame) self.criteria_frame_layout.setContentsMargins(6, 6, 6, 6) self.criteria_frame_layout.setSpacing(3) - self.top_layout.addWidget(self.criteria_frame, 1, 0, 1, 5) + self.analysis_layout.addWidget(self.criteria_frame, 1, 0, 1, 5) # Button box at the bottom of the criteria frame. This must be # added to self.criteria_frame_layout by the subclasses, as the @@ -237,7 +255,7 @@ def __init__(self, _, __, /, *, QtWidgets.QDialogButtonBox.ButtonRole.AcceptRole) # notes pane - self.notes = QtWidgets.QTextEdit(self.top_widget) + self.notes = QtWidgets.QTextEdit(self.analysis_widget) self.notes.setToolTip("Optionally enter notes here.") self.notes.setWhatsThis( """ @@ -255,10 +273,10 @@ def __init__(self, _, __, /, *, sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.notes.sizePolicy().hasHeightForWidth()) self.notes.setSizePolicy(sizePolicy) - self.top_layout.addWidget(self.notes, 3, 0, 1, 5) + self.analysis_layout.addWidget(self.notes, 3, 0, 1, 5) self.notes_expander.toggled.connect(self.notes.setVisible) - QtCore.QMetaObject.connectSlotsByName(self.top_widget) + QtCore.QMetaObject.connectSlotsByName(self.analysis_widget) QtCore.QMetaObject.connectSlotsByName(self) @property @@ -267,7 +285,7 @@ def results(self) -> QtWidgets.QWidget: @results.setter def results(self, widget: QtWidgets.QWidget) -> None: - self.top_layout.addWidget(widget, 2, 0, 1, 5) + self.analysis_layout.addWidget(widget, 2, 0, 1, 5) self._results_widget = widget def run(self) -> None: @@ -353,13 +371,15 @@ class ResultTab(enum.IntEnum): Text = 1 def __init__(self, query: setools.PolicyQuery, _, /, *, - enable_criteria: bool = True, parent: QtWidgets.QWidget | None = None) -> None: + enable_criteria: bool = True, enable_browser: bool = False, + parent: QtWidgets.QWidget | None = None) -> None: - super().__init__(query, None, enable_criteria=enable_criteria, parent=parent) + super().__init__(query, None, enable_criteria=enable_criteria, + enable_browser=enable_browser, parent=parent) self.query: typing.Final = query # results as 2 tab - self.results = QtWidgets.QTabWidget(self.top_widget) + self.results = QtWidgets.QTabWidget(self.analysis_widget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.MinimumExpanding) sizePolicy.setHorizontalStretch(0) @@ -412,10 +432,10 @@ def __init__(self, query: setools.PolicyQuery, _, /, *, self.results.setCurrentIndex(TableResultTabWidget.ResultTab.Table) # set up processing thread - self.processing_thread = QtCore.QThread(self.top_widget) + self.processing_thread = QtCore.QThread(self.analysis_widget) # create a "busy, please wait" dialog - self.busy = QtWidgets.QProgressDialog(self.top_widget) + self.busy = QtWidgets.QProgressDialog(self.analysis_widget) self.busy.setModal(True) self.busy.setRange(0, 0) self.busy.setMinimumDuration(0) @@ -530,11 +550,12 @@ def __init__(self, query: DGA, _, /, *, enable_criteria: bool = True, parent: QtWidgets.QWidget | None = None) -> None: - super().__init__(query, None, enable_criteria=enable_criteria, parent=parent) + super().__init__(query, None, enable_criteria=enable_criteria, enable_browser=False, + parent=parent) self.query: typing.Final = query # Create tab widget - self.results = QtWidgets.QTabWidget(self.top_widget) + self.results = QtWidgets.QTabWidget(self.analysis_widget) tw_sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.MinimumExpanding) tw_sizePolicy.setHorizontalStretch(0) @@ -599,10 +620,10 @@ def __init__(self, query: DGA, _, /, *, self.results.setCurrentIndex(DirectedGraphResultTab.ResultTab.Tree) # set up processing thread - self.processing_thread = QtCore.QThread(self.top_widget) + self.processing_thread = QtCore.QThread(self.analysis_widget) # create a "busy, please wait" dialog - self.busy = QtWidgets.QProgressDialog(self.top_widget) + self.busy = QtWidgets.QProgressDialog(self.analysis_widget) self.busy.setModal(True) self.busy.setRange(0, 0) self.busy.setMinimumDuration(0) diff --git a/tests-gui/widgets/test_tab.py b/tests-gui/widgets/test_tab.py index b785c14f..c3e725c5 100644 --- a/tests-gui/widgets/test_tab.py +++ b/tests-gui/widgets/test_tab.py @@ -14,13 +14,13 @@ def test_basetab_layout(qtbot: QtBot) -> None: widget = tab.BaseAnalysisTabWidget(None, None, enable_criteria=True) qtbot.addWidget(widget) - assert widget.top_layout.columnCount() == 5 - assert widget.top_layout.rowCount() == 4 - assert widget.top_layout.itemAtPosition(0, 3).widget() == widget.criteria_expander - assert widget.top_layout.itemAtPosition(0, 4).widget() == widget.notes_expander - assert widget.top_layout.itemAtPosition(1, 0).widget() == widget.criteria_frame - assert not widget.top_layout.itemAtPosition(2, 0) # result widget set by subclasses - assert widget.top_layout.itemAtPosition(3, 0).widget() == widget.notes + assert widget.analysis_layout.columnCount() == 5 + assert widget.analysis_layout.rowCount() == 4 + assert widget.analysis_layout.itemAtPosition(0, 3).widget() == widget.criteria_expander + assert widget.analysis_layout.itemAtPosition(0, 4).widget() == widget.notes_expander + assert widget.analysis_layout.itemAtPosition(1, 0).widget() == widget.criteria_frame + assert not widget.analysis_layout.itemAtPosition(2, 0) # result widget set by subclasses + assert widget.analysis_layout.itemAtPosition(3, 0).widget() == widget.notes def test_basetab_layout_nocriteria(qtbot: QtBot) -> None: @@ -28,13 +28,13 @@ def test_basetab_layout_nocriteria(qtbot: QtBot) -> None: widget = tab.BaseAnalysisTabWidget(None, None, enable_criteria=False) qtbot.addWidget(widget) - assert widget.top_layout.columnCount() == 5 - assert widget.top_layout.rowCount() == 4 - assert not widget.top_layout.itemAtPosition(0, 3) # no criteria expander - assert widget.top_layout.itemAtPosition(0, 4).widget() == widget.notes_expander - assert not widget.top_layout.itemAtPosition(1, 0) # no criteria pane - assert not widget.top_layout.itemAtPosition(2, 0) # result widget set by subclasses - assert widget.top_layout.itemAtPosition(3, 0).widget() == widget.notes + assert widget.analysis_layout.columnCount() == 5 + assert widget.analysis_layout.rowCount() == 4 + assert not widget.analysis_layout.itemAtPosition(0, 3) # no criteria expander + assert widget.analysis_layout.itemAtPosition(0, 4).widget() == widget.notes_expander + assert not widget.analysis_layout.itemAtPosition(1, 0) # no criteria pane + assert not widget.analysis_layout.itemAtPosition(2, 0) # result widget set by subclasses + assert widget.analysis_layout.itemAtPosition(3, 0).widget() == widget.notes def test_basetab_criteria_expander(qtbot: QtBot) -> None: @@ -84,13 +84,13 @@ def test_tableresulttab_layout(qtbot: QtBot) -> None: qtbot.addWidget(widget) results_widget = cast(QtWidgets.QTabWidget, widget.results) - assert widget.top_layout.columnCount() == 5 - assert widget.top_layout.rowCount() == 4 - assert widget.top_layout.itemAtPosition(0, 3).widget() == widget.criteria_expander - assert widget.top_layout.itemAtPosition(0, 4).widget() == widget.notes_expander - assert widget.top_layout.itemAtPosition(1, 0).widget() == widget.criteria_frame - assert widget.top_layout.itemAtPosition(2, 0).widget() == results_widget - assert widget.top_layout.itemAtPosition(3, 0).widget() == widget.notes + assert widget.analysis_layout.columnCount() == 5 + assert widget.analysis_layout.rowCount() == 4 + assert widget.analysis_layout.itemAtPosition(0, 3).widget() == widget.criteria_expander + assert widget.analysis_layout.itemAtPosition(0, 4).widget() == widget.notes_expander + assert widget.analysis_layout.itemAtPosition(1, 0).widget() == widget.criteria_frame + assert widget.analysis_layout.itemAtPosition(2, 0).widget() == results_widget + assert widget.analysis_layout.itemAtPosition(3, 0).widget() == widget.notes assert results_widget.count() == 2 assert results_widget.widget(0) == widget.table_results