From 64ca90c92f7b0f575bc23b36a76329b8cbeb60fa Mon Sep 17 00:00:00 2001 From: Alex Pyrgiotis Date: Thu, 20 Jul 2023 15:42:07 +0300 Subject: [PATCH] Add a Qt widget for creating collapsible sections Add a Qt widget called "CollapsibleBox", in order to build sections that you can hide/show with a single click. There is no native widget for this functionality, so we borrow some code from a StackOverflow user: https://stackoverflow.com/a/52617714 --- dangerzone/gui/logic.py | 90 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/dangerzone/gui/logic.py b/dangerzone/gui/logic.py index ee9bc9b..89f48fd 100644 --- a/dangerzone/gui/logic.py +++ b/dangerzone/gui/logic.py @@ -216,3 +216,93 @@ class Alert(Dialog): message_layout.addWidget(label, stretch=1) return message_layout + + +class CollapsibleBox(QtWidgets.QWidget): + """Create a widget that can show/hide its contents when you click on it. + + The credits for this code go to eyllanesc's answer in StackOverflow: + https://stackoverflow.com/a/52617714. We have made the following improvements: + + 1. Adapt the code to PySide. + 2. Resize the window once the box uncollapses. + 3. Add type hints. + """ + + def __init__(self, title: str, parent: Optional[QtWidgets.QWidget] = None): + super(CollapsibleBox, self).__init__(parent) + self.toggle_button = QtWidgets.QToolButton( + text=title, + checkable=True, + checked=False, + ) + self.toggle_button.setStyleSheet("QToolButton { border: none; }") + self.toggle_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) + self.toggle_button.setArrowType(QtCore.Qt.RightArrow) + self.toggle_button.clicked.connect(self.on_click) + + self.toggle_animation = QtCore.QParallelAnimationGroup(self) + + self.content_area = QtWidgets.QScrollArea(maximumHeight=0, minimumHeight=0) + self.content_area.setSizePolicy( + QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed + ) + self.content_area.setFrameShape(QtWidgets.QFrame.NoFrame) + + lay = QtWidgets.QVBoxLayout(self) + lay.setSpacing(0) + lay.setContentsMargins(0, 0, 0, 0) + lay.addWidget(self.toggle_button) + lay.addWidget(self.content_area) + + self.toggle_animation.addAnimation( + QtCore.QPropertyAnimation(self, b"minimumHeight") + ) + self.toggle_animation.addAnimation( + QtCore.QPropertyAnimation(self, b"maximumHeight") + ) + self.toggle_animation.addAnimation( + QtCore.QPropertyAnimation(self.content_area, b"maximumHeight") + ) + + self.toggle_animation.finished.connect(self.on_animation_finished) + + def on_click(self) -> None: + checked = self.toggle_button.isChecked() + self.toggle_button.setArrowType( + QtCore.Qt.DownArrow if checked else QtCore.Qt.RightArrow + ) + self.toggle_animation.setDirection( + QtCore.QAbstractAnimation.Forward + if checked + else QtCore.QAbstractAnimation.Backward + ) + self.toggle_animation.start() + + def on_animation_finished(self) -> None: + if not self.toggle_button.isChecked(): + content_height = self.content_area.layout().sizeHint().height() + parent = self.parent() + assert isinstance(parent, QtWidgets.QWidget) + parent.resize(parent.width(), parent.height() - content_height) + + def setContentLayout(self, layout: QtWidgets.QBoxLayout) -> None: + lay = self.content_area.layout() + del lay + self.content_area.setLayout(layout) + collapsed_height = self.sizeHint().height() - self.content_area.maximumHeight() + content_height = layout.sizeHint().height() + for i in range(self.toggle_animation.animationCount()): + animation = self.toggle_animation.animationAt(i) + assert isinstance(animation, QtCore.QPropertyAnimation) + animation.setDuration(60) + animation.setStartValue(collapsed_height) + animation.setEndValue(collapsed_height + content_height) + + content_animation = self.toggle_animation.animationAt( + self.toggle_animation.animationCount() - 1 + ) + assert isinstance(content_animation, QtCore.QPropertyAnimation) + content_animation.setDuration(60) + content_animation.setStartValue(0) + content_animation.setEndValue(content_height)