From bcd24cc035d9944b8339a4fc9d36072d83cc963e Mon Sep 17 00:00:00 2001 From: Alessandro Date: Fri, 29 Nov 2019 20:17:07 +0100 Subject: [PATCH] Close #14 Make font customizable. Also manage user settings via a settings.json file. Also improve 'dark' and 'elegant_dark' themes. Finally improve 'Signal's wiki' button behaviour. Also fix a bug in forecast/now view which caused a crash if solar activity was inactive --- .gitignore | 1 + CHANGELOG.md | 13 ++- src/artemis.py | 102 +++++++++++++++++++---- src/artemis.ui | 53 ++++++++++-- src/constants.py | 4 +- src/settings.py | 44 ++++++++++ src/spaceweathermanager.py | 2 +- src/themes/dark/dark.qss | 2 - src/themes/elegant_dark/elegant_dark.qss | 32 ++++--- src/themesmanager.py | 64 +++++++------- src/urlbutton.py | 80 ++++++++++++++++++ src/utilities.py | 22 ++++- 12 files changed, 339 insertions(+), 80 deletions(-) create mode 100644 src/settings.py create mode 100644 src/urlbutton.py diff --git a/.gitignore b/.gitignore index 49116b5..bda3468 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ launch.bat .code-workspace spec_files/**/output *.txt +*.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 459026b..281c8a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,16 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm The first release is [3.0.0] because this is actually the third major version (completely rewritten) of the software. ## [Unreleased] -... +## [3.2.0] - 2019-12-14 + +### Added +- The default font can be changed ([#14](https://github.com/AresValley/Artemis/pull/14)) +- Move `Themes` into `Settings`. +- Better settings management in `settings.json`. + +### Fixed +- Fix a bug in the space weather. An inactive k-index caused a crash. ## [3.1.0] - 2019-10-21 ### Added @@ -36,7 +44,8 @@ First release. -[Unreleased]: https://github.com/AresValley/Artemis/compare/v3.1.0...HEAD +[Unreleased]: https://github.com/AresValley/Artemis/compare/v3.2.0...HEAD +[3.2.0]: https://github.com/AresValley/Artemis/compare/v3.1.0...v3.2.0 [3.1.0]: https://github.com/AresValley/Artemis/compare/v3.0.1...v3.1.0 [3.0.1]: https://github.com/AresValley/Artemis/compare/v3.0.0...v3.0.1 [3.0.0]: https://github.com/AresValley/Artemis/releases/tag/v3.0.0 \ No newline at end of file diff --git a/src/artemis.py b/src/artemis.py index 61ac4f2..97e4259 100644 --- a/src/artemis.py +++ b/src/artemis.py @@ -1,4 +1,5 @@ from collections import namedtuple +from itertools import chain from functools import partial import webbrowser import os @@ -15,8 +16,10 @@ from PyQt5.QtWidgets import ( QListWidgetItem, QMessageBox, QSplashScreen, + QFontDialog, + QWidget, ) -from PyQt5.QtGui import QPixmap +from PyQt5.QtGui import QPixmap, QFont from PyQt5 import uic from PyQt5.QtCore import ( QFileInfo, @@ -46,17 +49,20 @@ from utilities import ( is_undef_band, format_numbers, safe_cast, + UniqueMessageBox, ) from executable_utilities import IS_BINARY, resource_path from os_utilities import IS_MAC from web_utilities import get_db_hash_code from downloadtargetfactory import get_download_target +from settings import Settings from updatescontroller import UpdatesController +from urlbutton import UrlButton # import default_imgs_rc -__LATEST_VERSION__ = "3.1.0" +__LATEST_VERSION__ = "3.2.0" if IS_BINARY: __VERSION__ = __LATEST_VERSION__ @@ -101,6 +107,7 @@ class Artemis(QMainWindow, Ui_MainWindow): self.action_github.triggered.connect( lambda: webbrowser.open(Constants.GITHUB_REPO) ) + self.action_font.triggered.connect(self.start_font_selection) self.db = None self.current_signal_name = '' self.signal_names = [] @@ -124,8 +131,6 @@ class Artemis(QMainWindow, Ui_MainWindow): # ####################################################################################### - UrlColors = namedtuple("UrlColors", ["inactive", "active", "clicked"]) - self.url_button.colors = UrlColors("#9f9f9f", "#4c75ff", "#942ccc") self.category_labels = [ self.cat_mil, self.cat_rad, @@ -205,10 +210,80 @@ class Artemis(QMainWindow, Ui_MainWindow): ) # Final operations. + self.settings = Settings() + self.settings.load() self.theme_manager.start() + self.load_font() self.load_db() self.display_signals() + def apply_font(self, font): + """Apply a given QFont object to all the widgets.""" + UniqueMessageBox.set_font(font) + # This is the smaller-text label. Not the most general strategy, but whatever.. + smaller_point_size = self.forecast_today_0_lbl.font().pointSize() + min_reference_font = 4 + for w in set(chain( + self.findChildren(QWidget), + self.download_window.findChildren(QWidget) + )): + old_font = w.font() + point_size = old_font.pointSize() + new_font = QFont(font) + new_font.setUnderline(old_font.underline()) + new_font.setBold(old_font.bold()) + new_font.setItalic(old_font.italic()) + new_size = font.pointSize() + (point_size - smaller_point_size) + if new_size < min_reference_font: + new_size = min_reference_font + new_font.setPointSize(new_size) + w.setFont(new_font) + + def load_font(self): + """Apply a QFont object if present.""" + if self.settings.font is None: + return + try: + font = QFont( + self.settings.font['family'], + self.settings.font['point_size'], + self.settings.font['weight'], + self.settings.font['italic'] + ) + font.setStyle(self.settings.font['style']) + font.setPointSize(self.settings.font['point_size']) + font.setStrikeOut(self.settings.font['strikeout']) + font.setUnderline(self.settings.font['underline']) + self.apply_font(font) + except Exception: # Invalid font + pass + + @pyqtSlot() + def start_font_selection(self): + """Open a font selection widget and apply the selected font.""" + initial_font = self.description_text.font() + dialog = QFontDialog() + dialog.setCurrentFont(initial_font) + font, ok = dialog.getFont( + initial_font, + self, + "Choose a font", + options=QFontDialog.DontUseNativeDialog + ) + if ok: + self.apply_font(font) + self.settings.save( + font={ + 'family': font.family(), + 'style': font.style(), + 'point_size': font.pointSize(), + 'weight': font.weight(), + 'italic': font.italic(), + 'strikeout': font.strikeOut(), + 'underline': font.underline(), + } + ) + def action_after_download(self): """Decide what to do after a successful download. @@ -544,15 +619,11 @@ class Artemis(QMainWindow, Ui_MainWindow): self.name_lab.setText(self.current_signal_name) self.name_lab.setAlignment(Qt.AlignHCenter) current_signal = self.db.loc[self.current_signal_name] - self.url_button.setEnabled(True) if not current_signal.at[Signal.WIKI_CLICKED]: - self.url_button.setStyleSheet( - f"color: {self.url_button.colors.active};" - ) + state = UrlButton.State.ACTIVE else: - self.url_button.setStyleSheet( - f"color: {self.url_button.colors.clicked};" - ) + state = UrlButton.State.CLICKED + self.url_button.set_enabled(state) category_code = current_signal.at[Signal.CATEGORY_CODE] undef_freq = is_undef_freq(current_signal) undef_band = is_undef_band(current_signal) @@ -590,10 +661,7 @@ class Artemis(QMainWindow, Ui_MainWindow): self.set_band_range(current_signal) self.audio_widget.set_audio_player(self.current_signal_name) else: - self.url_button.setEnabled(False) - self.url_button.setStyleSheet( - f"color: {self.url_button.colors.inactive};" - ) + self.url_button.set_disabled() self.current_signal_name = '' self.name_lab.setText("No Signal") self.name_lab.setAlignment(Qt.AlignHCenter) @@ -665,9 +733,7 @@ class Artemis(QMainWindow, Ui_MainWindow): Do nothing if no signal is selected. """ if self.current_signal_name: - self.url_button.setStyleSheet( - f"color: {self.url_button.colors.clicked}" - ) + self.url_button.set_clicked() webbrowser.open(self.db.at[self.current_signal_name, Signal.URL]) self.db.at[self.current_signal_name, Signal.WIKI_CLICKED] = True diff --git a/src/artemis.ui b/src/artemis.ui index 8269123..2e47e3d 100644 --- a/src/artemis.ui +++ b/src/artemis.ui @@ -1255,7 +1255,7 @@ - + false @@ -6949,7 +6949,9 @@ STORM 13 + 75 false + true @@ -6977,7 +6979,9 @@ STORM 13 + 75 false + true @@ -7005,7 +7009,9 @@ STORM 13 + 75 false + true @@ -7033,7 +7039,9 @@ STORM 13 + 75 false + true @@ -7061,7 +7069,9 @@ STORM 13 + 75 false + true @@ -7092,7 +7102,9 @@ STORM 13 + 75 false + true @@ -7120,7 +7132,9 @@ STORM 13 + 75 false + true @@ -7148,7 +7162,9 @@ STORM 13 + 75 false + true @@ -7176,7 +7192,9 @@ STORM 13 + 75 false + true @@ -9516,11 +9534,6 @@ QSlider::handle:horizontal { - - - Themes - - Sigidwiki @@ -9537,10 +9550,16 @@ QSlider::handle:horizontal { + + + Settings + + + - + @@ -9598,6 +9617,21 @@ QSlider::handle:horizontal { Check software version + + + Themes + + + + + Font... + + + + + Themes + + @@ -9636,6 +9670,11 @@ QSlider::handle:horizontal { QLabel
switchable_label.h
+ + UrlButton + QPushButton +
urlbutton.h
+
diff --git a/src/constants.py b/src/constants.py index ec9f940..cbc5b8c 100644 --- a/src/constants.py +++ b/src/constants.py @@ -181,6 +181,8 @@ class Constants: DEFAULT_IMGS_FOLDER = os.path.join(":", "pics", "default_pics") DEFAULT_NOT_SELECTED = os.path.join(DEFAULT_IMGS_FOLDER, NOT_SELECTED) DEFAULT_NOT_AVAILABLE = os.path.join(DEFAULT_IMGS_FOLDER, NOT_AVAILABLE) + FONT_FILE = os.path.join(__BASE_FOLDER__, 'font.json') + SETTINGS_FILE = os.path.join(__BASE_FOLDER__, "settings.json") class Messages: @@ -215,7 +217,6 @@ class ThemeConstants: EXTENSION = ".qss" ICONS_FOLDER = "icons" DEFAULT = "dark" - CURRENT = "__current_theme" COLORS = "colors.txt" COLOR_SEPARATOR = "=" DEFAULT_ACTIVE_COLOR = "#000000" @@ -231,5 +232,4 @@ class ThemeConstants: DEFAULT_ICONS_PATH = os.path.join(FOLDER, DEFAULT, ICONS_FOLDER) DEFAULT_SEARCH_LABEL_PATH = os.path.join(DEFAULT_ICONS_PATH, Constants.SEARCH_LABEL_IMG) DEFAULT_VOLUME_LABEL_PATH = os.path.join(DEFAULT_ICONS_PATH, Constants.VOLUME_LABEL_IMG) - CURRENT_THEME_FILE = os.path.join(FOLDER, CURRENT) DEFAULT_THEME_PATH = os.path.join(FOLDER, DEFAULT) diff --git a/src/settings.py b/src/settings.py new file mode 100644 index 0000000..0bd2ec1 --- /dev/null +++ b/src/settings.py @@ -0,0 +1,44 @@ +import os.path +from constants import Constants +import json + + +class Settings: + """Dynamically save and load the settings of the application.""" + + def __init__(self): + self._dct = {} + + def load(self): + """Load the setiings.json file.""" + if not os.path.exists(Constants.SETTINGS_FILE): + return + try: + with open(Constants.SETTINGS_FILE, 'r') as settings_file: + self._dct = json.load(settings_file) + except Exception: + pass # Invalid file. + + def save(self, **kwargs): + """Save the settings.json file. + + Also update the current settings specified in kwargs. + New settings can be dynamically added via this method.""" + for k, v in kwargs.items(): + self._dct[k] = v + with open(Constants.SETTINGS_FILE, mode='w') as settings_file: + json.dump( + self._dct, + settings_file, + sort_keys=True, + indent=4 + ) + + def __getattr__(self, attr): + """Return the corresponding setting. + + Return None if there is no such setting yet.""" + try: + return self._dct[attr] + except Exception: + return None diff --git a/src/spaceweathermanager.py b/src/spaceweathermanager.py index 22c3756..dc5f5f9 100644 --- a/src/spaceweathermanager.py +++ b/src/spaceweathermanager.py @@ -198,7 +198,7 @@ class SpaceWeatherManager(QObject): if k_index == 0: self._switchable_g_now_labels.switch_on(self._owner.g0_now_lbl) - self._k_storm_labels.switch_on(self.k_inactive_lbl) + self._k_storm_labels.switch_on(self._owner.k_inactive_lbl) self._owner.expected_noise_lbl.setText(" S0 - S1 (<-120 dBm) ") elif k_index == 1: self._switchable_g_now_labels.switch_on(self._owner.g0_now_lbl) diff --git a/src/themes/dark/dark.qss b/src/themes/dark/dark.qss index 7b33aac..dc42680 100644 --- a/src/themes/dark/dark.qss +++ b/src/themes/dark/dark.qss @@ -260,8 +260,6 @@ QPushButton { } QPushButton:hover { - border: 2px dashed #4545e5; - border-radius: 13px; color: #FFFFFF; } diff --git a/src/themes/elegant_dark/elegant_dark.qss b/src/themes/elegant_dark/elegant_dark.qss index d25807a..894bc44 100644 --- a/src/themes/elegant_dark/elegant_dark.qss +++ b/src/themes/elegant_dark/elegant_dark.qss @@ -113,58 +113,60 @@ QSpinBox::down-button:disabled { } QPushButton{ - border-style: outset; + /*border-style: outset; border-width: 2px; border-top-color: qlineargradient(spread:pad, x1:0.5, y1:0.6, x2:0.5, y2:0.4, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255)); border-right-color: qlineargradient(spread:pad, x1:0.4, y1:0.5, x2:0.6, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255)); border-left-color: qlineargradient(spread:pad, x1:0.6, y1:0.5, x2:0.4, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255)); border-bottom-color: rgb(58, 58, 58); border-bottom-width: 1px; - border-style: solid; + border-style: solid;*/ + border: 0px; color: rgb(255, 255, 255); padding: 2px; - background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(77, 77, 77, 255), stop:1 rgba(97, 97, 97, 255)); + background-color: transparent; } QPushButton:hover{ - border-style: outset; + /*border-style: outset; border-width: 2px; border-top-color: qlineargradient(spread:pad, x1:0.5, y1:0.6, x2:0.5, y2:0.4, stop:0 rgba(180, 180, 180, 255), stop:1 rgba(110, 110, 110, 255)); border-right-color: qlineargradient(spread:pad, x1:0.4, y1:0.5, x2:0.6, y2:0.5, stop:0 rgba(180, 180, 180, 255), stop:1 rgba(110, 110, 110, 255)); border-left-color: qlineargradient(spread:pad, x1:0.6, y1:0.5, x2:0.4, y2:0.5, stop:0 rgba(180, 180, 180, 255), stop:1 rgba(110, 110, 110, 255)); border-bottom-color: rgb(115, 115, 115); border-bottom-width: 1px; - border-style: solid; - color: rgb(255, 255, 255); + border-style: solid;*/ + border: 0px; + color: #AFAFAF; padding: 2px; - background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(107, 107, 107, 255), stop:1 rgba(157, 157, 157, 255)); } QPushButton:pressed{ - border-style: outset; + /*border-style: outset; border-width: 2px; border-top-color: qlineargradient(spread:pad, x1:0.5, y1:0.6, x2:0.5, y2:0.4, stop:0 rgba(62, 62, 62, 255), stop:1 rgba(22, 22, 22, 255)); border-right-color: qlineargradient(spread:pad, x1:0.4, y1:0.5, x2:0.6, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255)); border-left-color: qlineargradient(spread:pad, x1:0.6, y1:0.5, x2:0.4, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255)); border-bottom-color: rgb(58, 58, 58); border-bottom-width: 1px; - border-style: solid; + border-style: solid;*/ + border: 0px; color: rgb(255, 255, 255); padding: 2px; - background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(77, 77, 77, 255), stop:1 rgba(97, 97, 97, 255)); } QPushButton:disabled{ - border-style: outset; + /*border-style: outset; border-width: 2px; border-top-color: qlineargradient(spread:pad, x1:0.5, y1:0.6, x2:0.5, y2:0.4, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255)); border-right-color: qlineargradient(spread:pad, x1:0.4, y1:0.5, x2:0.6, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255)); border-left-color: qlineargradient(spread:pad, x1:0.6, y1:0.5, x2:0.4, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255)); border-bottom-color: rgb(58, 58, 58); border-bottom-width: 1px; - border-style: solid; + border-style: solid;*/ + border: 0px; color: rgb(0, 0, 0); padding: 2px; - background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(57, 57, 57, 255), stop:1 rgba(77, 77, 77, 255)); } QPushButton:checked { + border: 0px; color: #00ff00; } @@ -187,6 +189,10 @@ QRadioButton{ color: #FFFFFF; } +QRadioButton:hover{ + color: #AFAFAF; +} + QComboBox { border: 0px solid transparent; border-radius: 2px; diff --git a/src/themesmanager.py b/src/themesmanager.py index 13f44ca..60329cb 100644 --- a/src/themesmanager.py +++ b/src/themesmanager.py @@ -102,7 +102,7 @@ class ThemeManager: self._theme_names = {} @pyqtSlot() - def _apply(self, theme_path): + def _apply(self, theme_path, save=True): """Apply the selected theme. Refresh all relevant widgets. @@ -110,7 +110,7 @@ class ThemeManager: self._theme_path = theme_path if os.path.exists(theme_path): if self._theme_path != self._current_theme: - self._change() + self._change(save) self._owner.display_specs( item=self._owner.signals_list.currentItem(), previous_item=None @@ -141,29 +141,30 @@ class ThemeManager: Display a QMessageBox if the theme folder is not found.""" themes = [] ag = QActionGroup(self._owner, exclusive=True) - if os.path.exists(ThemeConstants.FOLDER): - for theme_folder in sorted(os.listdir(ThemeConstants.FOLDER)): - relative_folder = os.path.join(ThemeConstants.FOLDER, theme_folder) - if os.path.isdir(os.path.abspath(relative_folder)): - relative_folder = os.path.join(ThemeConstants.FOLDER, theme_folder) - themes.append(relative_folder) - for theme_path in themes: - theme_name = '&' + self._pretty_name(os.path.basename(theme_path)) - new_theme = ag.addAction( - QAction( - theme_name, - self._owner, - checkable=True - ) - ) - self._owner.menu_themes.addAction(new_theme) - self._theme_names[theme_name.lstrip('&')] = new_theme - new_theme.triggered.connect(partial(self._apply, theme_path)) - else: + themes_menu = self._owner.settings_menu.addMenu("Themes") + if not os.path.exists(ThemeConstants.FOLDER): pop_up(self._owner, title=ThemeConstants.THEME_FOLDER_NOT_FOUND, text=ThemeConstants.MISSING_THEME_FOLDER).show() + return + for theme_folder in sorted(os.listdir(ThemeConstants.FOLDER)): + relative_folder = os.path.join(ThemeConstants.FOLDER, theme_folder) + if os.path.isdir(os.path.abspath(relative_folder)): + relative_folder = os.path.join(ThemeConstants.FOLDER, theme_folder) + themes.append(relative_folder) + for theme_path in themes: + theme_name = '&' + self._pretty_name(os.path.basename(theme_path)) + new_theme = ag.addAction( + QAction( + theme_name, + self._owner, + checkable=True + ) + ) + themes_menu.addAction(new_theme) + self._theme_names[theme_name.lstrip('&')] = new_theme + new_theme.triggered.connect(partial(self._apply, theme_path)) - def _change(self): + def _change(self, save=True): """Change the current theme. Apply the stylesheet and set active and inactive colors. @@ -173,8 +174,8 @@ class ThemeManager: try: with open(os.path.join(self._theme_path, theme_name), "r") as stylesheet: style = stylesheet.read() - self._owner.setStyleSheet(style) - self._owner.download_window.setStyleSheet(style) + self._owner.setStyleSheet(style) + self._owner.download_window.setStyleSheet(style) except FileNotFoundError: pop_up(self._owner, title=ThemeConstants.THEME_NOT_FOUND, text=ThemeConstants.MISSING_THEME).show() @@ -266,12 +267,8 @@ class ThemeManager: ThemeConstants.DEFAULT_TEXT_COLOR ) self._current_theme = self._theme_path - - try: - with open(ThemeConstants.CURRENT_THEME_FILE, "w") as current_theme: - current_theme.write(os.path.basename(self._theme_path)) - except Exception: - pass + if save: + self._owner.settings.save(theme=os.path.basename(self._theme_path)) def apply_default_theme(self): """Apply the default theme if no theme is set or the theme name is invalid.""" @@ -291,15 +288,14 @@ class ThemeManager: def start(self): """Start the theme manager.""" self._detect_themes() - if os.path.exists(ThemeConstants.CURRENT_THEME_FILE): - with open(ThemeConstants.CURRENT_THEME_FILE, "r") as current_theme_name: - theme_path = os.path.join(ThemeConstants.FOLDER, current_theme_name.read()) + if self._owner.settings.theme is not None: + theme_path = os.path.join(ThemeConstants.FOLDER, self._owner.settings.theme) theme_name = self._pretty_name(os.path.basename(theme_path)) try: self._theme_names[theme_name].setChecked(True) except Exception: self.apply_default_theme() else: - self._apply(theme_path) + self._apply(theme_path, save=False) else: self.apply_default_theme() diff --git a/src/urlbutton.py b/src/urlbutton.py new file mode 100644 index 0000000..8cc3451 --- /dev/null +++ b/src/urlbutton.py @@ -0,0 +1,80 @@ +from PyQt5.QtWidgets import QPushButton +from collections import namedtuple +from enum import Enum, auto + + +class UrlButton(QPushButton): + """Define the behaviour of the wiki button.""" + + class State(Enum): + """Possible states of the button.""" + ACTIVE = auto() + INACTIVE = auto() + CLICKED = auto() + + _UrlColors = namedtuple( + "UrlColors", + [ + "INACTIVE", + "ACTIVE", + "CLICKED", + "ACTIVE_HOVER", + "CLICKED_HOVER", + ] + ) + _COLORS = _UrlColors( + "#9f9f9f", + "#4c75ff", + "#942ccc", + "#808FFF", + "#DE78FF", + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def set_enabled(self, state): + """Enable the button and set the stylesheet.""" + super().setEnabled(True) + if state is self.State.ACTIVE: + color = self._COLORS.ACTIVE + else: + color = self._COLORS.CLICKED + self.setStyleSheet(f""" + QPushButton {{ + border: 0px; + background-color: transparent; + color: {color}; + }} + QPushButton::hover {{ + border: 0px; + background-color: transparent; + color: {self._COLORS.ACTIVE_HOVER}; + }} + """) + + def set_disabled(self): + """Disable the button and set the stylesheet.""" + super().setEnabled(False) + self.setStyleSheet(f""" + QPushButton:disabled {{ + border: 0px; + background-color: transparent; + color: {self._COLORS.INACTIVE}; + }} + """) + + def set_clicked(self): + """Apply the stylesheet for the clicked state.""" + self.setStyleSheet(f""" + QPushButton {{ + border: 0px; + background-color: transparent; + color: {self._COLORS.CLICKED}; + }} + QPushButton::hover {{ + border: 0px; + background-color: transparent; + color: {self._COLORS.CLICKED_HOVER}; + }} + """) diff --git a/src/utilities.py b/src/utilities.py index 64cc075..6565f11 100644 --- a/src/utilities.py +++ b/src/utilities.py @@ -11,20 +11,40 @@ class UniqueMessageBox(QMessageBox): If another instance is the the exec loop, calling exec simply return None.""" _open_message = False + _font = None + + @classmethod + def set_font(cls, font): + """Store the font for all UniqueMessageBox(es).""" + cls._font = font def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + def setFont(self, font): + """Extends QMessageBox.setFont. Apply the font only if it is not None.""" + if font is not None: + super().setFont(font) + def exec(self): """Overrides QMessageBox.exec. Call the parent method if there are no - other instances executing exec. Otherwise return None,""" + other instances executing exec; also set the current font. + Otherwise return None,""" if UniqueMessageBox._open_message: return None + self.setFont(self._font) UniqueMessageBox._open_message = True answer = super().exec() UniqueMessageBox._open_message = False return answer + def show(self): + """Extends QMessageBox.show(). + + Set the font before showing the message.""" + self.setFont(self._font) + super().show() + def uncheck_and_emit(button): """Set the button to the unchecked state and emit the clicked signal."""