From d359da146b87bd510ded734b090bd96f15d359e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 19 Sep 2024 14:20:26 -0400 Subject: [PATCH 1/3] Create dialogs.py --- qtapputils/widgets/dialogs.py | 158 ++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 qtapputils/widgets/dialogs.py diff --git a/qtapputils/widgets/dialogs.py b/qtapputils/widgets/dialogs.py new file mode 100644 index 0000000..9279990 --- /dev/null +++ b/qtapputils/widgets/dialogs.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright © QtAppUtils Project Contributors +# https://github.com/jnsebgosselin/qtapputils +# +# This file is part of QtAppUtils. +# Licensed under the terms of the MIT License. +# ----------------------------------------------------------------------------- +from __future__ import annotations +from typing import Callable + + +# ---- Third party imports +from qtpy.QtCore import Qt +from qtpy.QtWidgets import ( + QApplication, QDialog, QDialogButtonBox, QPushButton, + QWidget, QStackedWidget, QVBoxLayout, QGridLayout) +from qtapputils.icons import get_standard_icon, get_standard_iconsize +from qtapputils.qthelpers import get_default_contents_margins + +# ---- Local imports +from qtapputils.widgets.statusbar import ProcessStatusBar + + +class UserMessageDialogBase(QDialog): + """ + Basic functionalities to implement a dialog window that provide + the capability to show messages to the user. + + This class was taken from the Sardes project. + See sardes/widgets/dialogs.py at https://github.com/geo-stack/sardes + """ + + def __init__(self, parent=None, minimum_height: int = 100, + minimum_width: int = None): + super().__init__(parent) + self.setWindowFlags( + self.windowFlags() & ~Qt.WindowContextHelpButtonHint) + self.__setup__(minimum_height, minimum_width) + + def __setup__(self, minimum_height, minimum_width): + """Setup the dialog with the provided settings.""" + + self._buttons = [] + + # Setup the main widget. + self.central_widget = QWidget() + self.central_layout = QGridLayout(self.central_widget) + + # Setup the main layout. + main_layout = QVBoxLayout(self) + main_layout.setSizeConstraint(main_layout.SetDefaultConstraint) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + # Setup the stacked widget. + self._dialogs = [] + + self.stackwidget = QStackedWidget() + self.stackwidget.addWidget(self.central_widget) + self.stackwidget.setMinimumHeight(minimum_height) + if minimum_width is not None: + self.stackwidget.setMinimumWidth(minimum_width) + + main_layout.addWidget(self.stackwidget) + + # Setup the button box. + self.button_box = QDialogButtonBox() + self.button_box.layout().addStretch(1) + + main_layout.addWidget(self.button_box) + + # Note that we need to set the margins of the button box after + # adding it to the main layout or else, it has no effect. + self.button_box.layout().setContentsMargins( + *get_default_contents_margins()) + + # ---- Helpers Methods + def create_button(self, text: str, enabled: bool = True, + triggered: Callable = None, default: bool = False + ) -> QPushButton: + """Create a pushbutton to add to the button box.""" + button = QPushButton(text) + button.setDefault(default) + button.setAutoDefault(False) + if triggered is not None: + button.clicked.connect(triggered) + button.setEnabled(enabled) + return button + + def add_button(self, button): + """Add a new pushbutton to the button box.""" + self._buttons.append(button) + self.button_box.layout().addWidget(button) + + def create_msg_dialog(self, std_icon_name: str, + buttons: list[QPushButton] + ) -> ProcessStatusBar: + """Create a new message dialog.""" + dialog = ProcessStatusBar( + spacing=10, + icon_valign='top', + vsize_policy='expanding', + hsize_policy='expanding', + text_valign='top', + iconsize=get_standard_iconsize('messagebox'), + contents_margin=get_default_contents_margins()) + dialog.set_icon('failed', get_standard_icon(std_icon_name)) + dialog.setAutoFillBackground(True) + dialog._buttons = buttons + + palette = QApplication.instance().palette() + palette.setColor(dialog.backgroundRole(), palette.light().color()) + dialog.setPalette(palette) + + # Hide the buttons of the dialogs. + for btn in buttons: + btn.setVisible(False) + + return dialog + + def add_msg_dialog(self, dialog: ProcessStatusBar): + """Add a new message dialog to the stack widget.""" + self._dialogs.append(dialog) + self.stackwidget.addWidget(dialog) + for button in dialog._buttons: + self.add_button(button) + + # ---- Public Interface + def show_message_dialog( + self, dialog: ProcessStatusBar, message: str, beep: bool = True): + """Show to the user the specified dialog with the provided message.""" + self.show() + for btn in self._buttons: + btn.setVisible(btn in dialog._buttons) + dialog.show_fail_icon(message) + self.stackwidget.setCurrentWidget(dialog) + if beep is True: + QApplication.beep() + + def close_message_dialogs(self): + """Close all message dialogs and show the main interface.""" + for btn in self._buttons: + btn.setVisible(True) + for dialog in self._dialogs: + for btn in dialog._buttons: + btn.setVisible(False) + self.stackwidget.setCurrentWidget(self.central_widget) + + def show(self): + """ + Override Qt method to raise window to the top when already visible. + """ + if self.windowState() == Qt.WindowMinimized: + self.setWindowState(Qt.WindowNoState) + super().show() + self.activateWindow() + self.raise_() From 9fbcd37384beca2e78017ea4d22ed5561b28b3c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 20 Sep 2024 13:15:46 -0400 Subject: [PATCH 2/3] Create UserMessage class --- qtapputils/widgets/dialogs.py | 81 +++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 13 deletions(-) diff --git a/qtapputils/widgets/dialogs.py b/qtapputils/widgets/dialogs.py index 9279990..cd8b92c 100644 --- a/qtapputils/widgets/dialogs.py +++ b/qtapputils/widgets/dialogs.py @@ -11,17 +11,75 @@ # ---- Third party imports -from qtpy.QtCore import Qt +from qtpy.QtCore import Qt, QSize +from qtpy.QtGui import QIcon from qtpy.QtWidgets import ( QApplication, QDialog, QDialogButtonBox, QPushButton, - QWidget, QStackedWidget, QVBoxLayout, QGridLayout) -from qtapputils.icons import get_standard_icon, get_standard_iconsize -from qtapputils.qthelpers import get_default_contents_margins + QWidget, QStackedWidget, QVBoxLayout, QGridLayout, QTextBrowser, QLabel) # ---- Local imports +from qtapputils.icons import get_standard_icon, get_standard_iconsize +from qtapputils.qthelpers import get_default_contents_margins from qtapputils.widgets.statusbar import ProcessStatusBar +class UserMessage(QWidget): + + def __init__(self, parent=None, + icon: QIcon = None, iconsize: int = 24, text: str = None, + spacing: int = 5, contents_margin: list = None): + super().__init__(parent) + self._iconsize = iconsize + + # Setup the container for the text. + class LabelBrowser(QTextBrowser): + def text(self): + return self.toPlainText() + + def minimumSizeHint(self): + return QLabel().minimumSizeHint() + + def sizeHint(self): + return QLabel().sizeHint() + self._label = LabelBrowser() + self._label.setLineWrapMode(LabelBrowser.WidgetWidth) + self._label.setAlignment(Qt.AlignLeft | Qt.AlignTop) + self._label.setTextInteractionFlags( + Qt.TextSelectableByMouse | Qt.TextBrowserInteraction) + self._label.setOpenExternalLinks(True) + self._label.setFocusPolicy(Qt.NoFocus) + self._label.setFrameStyle(0) + self._label.setStyleSheet("background-color:transparent;") + + # Setup the container for the icon + self._icon = QLabel() + + # Setup layout. + layout = QGridLayout(self) + if contents_margin is None: + contents_margin = [0, 0, 0, 0] + layout.setContentsMargins(*contents_margin) + layout.setSpacing(spacing) + + layout.addWidget(self._icon, 0, 0, Qt.AlignLeft | Qt.AlignTop) + layout.addWidget(self._label, 0, 1) + + if icon is not None: + self.set_icon(icon) + if text is not None: + self.set_text() + + def set_icon(self, icon: QIcon): + """Set the icon of the user message.""" + self._icon.setPixmap( + icon.pixmap(QSize(self._iconsize, self._iconsize)) + ) + + def set_text(self, text: str): + """Set the text of the user message.""" + self._label.setText(text) + + class UserMessageDialogBase(QDialog): """ Basic functionalities to implement a dialog window that provide @@ -95,17 +153,14 @@ def add_button(self, button): def create_msg_dialog(self, std_icon_name: str, buttons: list[QPushButton] - ) -> ProcessStatusBar: + ) -> UserMessage: """Create a new message dialog.""" - dialog = ProcessStatusBar( + dialog = UserMessage( spacing=10, - icon_valign='top', - vsize_policy='expanding', - hsize_policy='expanding', - text_valign='top', iconsize=get_standard_iconsize('messagebox'), contents_margin=get_default_contents_margins()) - dialog.set_icon('failed', get_standard_icon(std_icon_name)) + dialog.set_icon(get_standard_icon(std_icon_name)) + dialog.setAutoFillBackground(True) dialog._buttons = buttons @@ -119,7 +174,7 @@ def create_msg_dialog(self, std_icon_name: str, return dialog - def add_msg_dialog(self, dialog: ProcessStatusBar): + def add_msg_dialog(self, dialog: UserMessage): """Add a new message dialog to the stack widget.""" self._dialogs.append(dialog) self.stackwidget.addWidget(dialog) @@ -133,7 +188,7 @@ def show_message_dialog( self.show() for btn in self._buttons: btn.setVisible(btn in dialog._buttons) - dialog.show_fail_icon(message) + dialog.set_text(message) self.stackwidget.setCurrentWidget(dialog) if beep is True: QApplication.beep() From 8f4d9169f524eb0e739d046d36cd47045073dac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 20 Sep 2024 13:17:34 -0400 Subject: [PATCH 3/3] Minor codestyle change --- qtapputils/widgets/dialogs.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/qtapputils/widgets/dialogs.py b/qtapputils/widgets/dialogs.py index cd8b92c..c3db595 100644 --- a/qtapputils/widgets/dialogs.py +++ b/qtapputils/widgets/dialogs.py @@ -134,9 +134,10 @@ def __setup__(self, minimum_height, minimum_width): *get_default_contents_margins()) # ---- Helpers Methods - def create_button(self, text: str, enabled: bool = True, - triggered: Callable = None, default: bool = False - ) -> QPushButton: + def create_button( + self, text: str, enabled: bool = True, + triggered: Callable = None, default: bool = False + ) -> QPushButton: """Create a pushbutton to add to the button box.""" button = QPushButton(text) button.setDefault(default) @@ -151,9 +152,9 @@ def add_button(self, button): self._buttons.append(button) self.button_box.layout().addWidget(button) - def create_msg_dialog(self, std_icon_name: str, - buttons: list[QPushButton] - ) -> UserMessage: + def create_msg_dialog( + self, std_icon_name: str, buttons: list[QPushButton] + ) -> UserMessage: """Create a new message dialog.""" dialog = UserMessage( spacing=10,