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
This commit is contained in:
Alessandro
2019-11-29 20:17:07 +01:00
parent 5908110a43
commit 9fd03760aa
12 changed files with 339 additions and 80 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ launch.bat
.code-workspace .code-workspace
spec_files/**/output spec_files/**/output
*.txt *.txt
*.json

View File

@@ -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. The first release is [3.0.0] because this is actually the third major version (completely rewritten) of the software.
## [Unreleased] ## [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 ## [3.1.0] - 2019-10-21
### Added ### Added
@@ -36,7 +44,8 @@ First release.
<!-- Links definitions --> <!-- Links definitions -->
[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.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.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 [3.0.0]: https://github.com/AresValley/Artemis/releases/tag/v3.0.0

View File

@@ -1,4 +1,5 @@
from collections import namedtuple from collections import namedtuple
from itertools import chain
from functools import partial from functools import partial
import webbrowser import webbrowser
import os import os
@@ -15,8 +16,10 @@ from PyQt5.QtWidgets import (
QListWidgetItem, QListWidgetItem,
QMessageBox, QMessageBox,
QSplashScreen, QSplashScreen,
QFontDialog,
QWidget,
) )
from PyQt5.QtGui import QPixmap from PyQt5.QtGui import QPixmap, QFont
from PyQt5 import uic from PyQt5 import uic
from PyQt5.QtCore import ( from PyQt5.QtCore import (
QFileInfo, QFileInfo,
@@ -46,17 +49,20 @@ from utilities import (
is_undef_band, is_undef_band,
format_numbers, format_numbers,
safe_cast, safe_cast,
UniqueMessageBox,
) )
from executable_utilities import IS_BINARY, resource_path from executable_utilities import IS_BINARY, resource_path
from os_utilities import IS_MAC from os_utilities import IS_MAC
from web_utilities import get_db_hash_code from web_utilities import get_db_hash_code
from downloadtargetfactory import get_download_target from downloadtargetfactory import get_download_target
from settings import Settings
from updatescontroller import UpdatesController from updatescontroller import UpdatesController
from urlbutton import UrlButton
# import default_imgs_rc # import default_imgs_rc
__LATEST_VERSION__ = "3.1.0" __LATEST_VERSION__ = "3.2.0"
if IS_BINARY: if IS_BINARY:
__VERSION__ = __LATEST_VERSION__ __VERSION__ = __LATEST_VERSION__
@@ -101,6 +107,7 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.action_github.triggered.connect( self.action_github.triggered.connect(
lambda: webbrowser.open(Constants.GITHUB_REPO) lambda: webbrowser.open(Constants.GITHUB_REPO)
) )
self.action_font.triggered.connect(self.start_font_selection)
self.db = None self.db = None
self.current_signal_name = '' self.current_signal_name = ''
self.signal_names = [] 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.category_labels = [
self.cat_mil, self.cat_mil,
self.cat_rad, self.cat_rad,
@@ -205,10 +210,80 @@ class Artemis(QMainWindow, Ui_MainWindow):
) )
# Final operations. # Final operations.
self.settings = Settings()
self.settings.load()
self.theme_manager.start() self.theme_manager.start()
self.load_font()
self.load_db() self.load_db()
self.display_signals() 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): def action_after_download(self):
"""Decide what to do after a successful download. """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.setText(self.current_signal_name)
self.name_lab.setAlignment(Qt.AlignHCenter) self.name_lab.setAlignment(Qt.AlignHCenter)
current_signal = self.db.loc[self.current_signal_name] current_signal = self.db.loc[self.current_signal_name]
self.url_button.setEnabled(True)
if not current_signal.at[Signal.WIKI_CLICKED]: if not current_signal.at[Signal.WIKI_CLICKED]:
self.url_button.setStyleSheet( state = UrlButton.State.ACTIVE
f"color: {self.url_button.colors.active};"
)
else: else:
self.url_button.setStyleSheet( state = UrlButton.State.CLICKED
f"color: {self.url_button.colors.clicked};" self.url_button.set_enabled(state)
)
category_code = current_signal.at[Signal.CATEGORY_CODE] category_code = current_signal.at[Signal.CATEGORY_CODE]
undef_freq = is_undef_freq(current_signal) undef_freq = is_undef_freq(current_signal)
undef_band = is_undef_band(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.set_band_range(current_signal)
self.audio_widget.set_audio_player(self.current_signal_name) self.audio_widget.set_audio_player(self.current_signal_name)
else: else:
self.url_button.setEnabled(False) self.url_button.set_disabled()
self.url_button.setStyleSheet(
f"color: {self.url_button.colors.inactive};"
)
self.current_signal_name = '' self.current_signal_name = ''
self.name_lab.setText("No Signal") self.name_lab.setText("No Signal")
self.name_lab.setAlignment(Qt.AlignHCenter) self.name_lab.setAlignment(Qt.AlignHCenter)
@@ -665,9 +733,7 @@ class Artemis(QMainWindow, Ui_MainWindow):
Do nothing if no signal is selected. Do nothing if no signal is selected.
""" """
if self.current_signal_name: if self.current_signal_name:
self.url_button.setStyleSheet( self.url_button.set_clicked()
f"color: {self.url_button.colors.clicked}"
)
webbrowser.open(self.db.at[self.current_signal_name, Signal.URL]) webbrowser.open(self.db.at[self.current_signal_name, Signal.URL])
self.db.at[self.current_signal_name, Signal.WIKI_CLICKED] = True self.db.at[self.current_signal_name, Signal.WIKI_CLICKED] = True

View File

@@ -1255,7 +1255,7 @@
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout_17"> <layout class="QHBoxLayout" name="horizontalLayout_17">
<item> <item>
<widget class="QPushButton" name="url_button"> <widget class="UrlButton" name="url_button">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
</property> </property>
@@ -6949,7 +6949,9 @@ STORM</string>
<property name="font"> <property name="font">
<font> <font>
<pointsize>13</pointsize> <pointsize>13</pointsize>
<weight>75</weight>
<italic>false</italic> <italic>false</italic>
<bold>true</bold>
</font> </font>
</property> </property>
<property name="layoutDirection"> <property name="layoutDirection">
@@ -6977,7 +6979,9 @@ STORM</string>
<property name="font"> <property name="font">
<font> <font>
<pointsize>13</pointsize> <pointsize>13</pointsize>
<weight>75</weight>
<italic>false</italic> <italic>false</italic>
<bold>true</bold>
</font> </font>
</property> </property>
<property name="layoutDirection"> <property name="layoutDirection">
@@ -7005,7 +7009,9 @@ STORM</string>
<property name="font"> <property name="font">
<font> <font>
<pointsize>13</pointsize> <pointsize>13</pointsize>
<weight>75</weight>
<italic>false</italic> <italic>false</italic>
<bold>true</bold>
</font> </font>
</property> </property>
<property name="layoutDirection"> <property name="layoutDirection">
@@ -7033,7 +7039,9 @@ STORM</string>
<property name="font"> <property name="font">
<font> <font>
<pointsize>13</pointsize> <pointsize>13</pointsize>
<weight>75</weight>
<italic>false</italic> <italic>false</italic>
<bold>true</bold>
</font> </font>
</property> </property>
<property name="layoutDirection"> <property name="layoutDirection">
@@ -7061,7 +7069,9 @@ STORM</string>
<property name="font"> <property name="font">
<font> <font>
<pointsize>13</pointsize> <pointsize>13</pointsize>
<weight>75</weight>
<italic>false</italic> <italic>false</italic>
<bold>true</bold>
</font> </font>
</property> </property>
<property name="layoutDirection"> <property name="layoutDirection">
@@ -7092,7 +7102,9 @@ STORM</string>
<property name="font"> <property name="font">
<font> <font>
<pointsize>13</pointsize> <pointsize>13</pointsize>
<weight>75</weight>
<italic>false</italic> <italic>false</italic>
<bold>true</bold>
</font> </font>
</property> </property>
<property name="layoutDirection"> <property name="layoutDirection">
@@ -7120,7 +7132,9 @@ STORM</string>
<property name="font"> <property name="font">
<font> <font>
<pointsize>13</pointsize> <pointsize>13</pointsize>
<weight>75</weight>
<italic>false</italic> <italic>false</italic>
<bold>true</bold>
</font> </font>
</property> </property>
<property name="layoutDirection"> <property name="layoutDirection">
@@ -7148,7 +7162,9 @@ STORM</string>
<property name="font"> <property name="font">
<font> <font>
<pointsize>13</pointsize> <pointsize>13</pointsize>
<weight>75</weight>
<italic>false</italic> <italic>false</italic>
<bold>true</bold>
</font> </font>
</property> </property>
<property name="layoutDirection"> <property name="layoutDirection">
@@ -7176,7 +7192,9 @@ STORM</string>
<property name="font"> <property name="font">
<font> <font>
<pointsize>13</pointsize> <pointsize>13</pointsize>
<weight>75</weight>
<italic>false</italic> <italic>false</italic>
<bold>true</bold>
</font> </font>
</property> </property>
<property name="layoutDirection"> <property name="layoutDirection">
@@ -9516,11 +9534,6 @@ QSlider::handle:horizontal {
<addaction name="action_update_database"/> <addaction name="action_update_database"/>
<addaction name="action_check_software_version"/> <addaction name="action_check_software_version"/>
</widget> </widget>
<widget class="QMenu" name="menu_themes">
<property name="title">
<string>Themes</string>
</property>
</widget>
<widget class="QMenu" name="menuSigidwiki"> <widget class="QMenu" name="menuSigidwiki">
<property name="title"> <property name="title">
<string>Sigidwiki</string> <string>Sigidwiki</string>
@@ -9537,10 +9550,16 @@ QSlider::handle:horizontal {
<addaction name="action_rtl_sdr_com"/> <addaction name="action_rtl_sdr_com"/>
<addaction name="action_github"/> <addaction name="action_github"/>
</widget> </widget>
<widget class="QMenu" name="settings_menu">
<property name="title">
<string>Settings</string>
</property>
<addaction name="action_font"/>
</widget>
<addaction name="menuFile"/> <addaction name="menuFile"/>
<addaction name="menuUpdates"/> <addaction name="menuUpdates"/>
<addaction name="menu_themes"/>
<addaction name="menuSigidwiki"/> <addaction name="menuSigidwiki"/>
<addaction name="settings_menu"/>
<addaction name="menuAbout"/> <addaction name="menuAbout"/>
</widget> </widget>
<widget class="QStatusBar" name="statusbar"> <widget class="QStatusBar" name="statusbar">
@@ -9598,6 +9617,21 @@ QSlider::handle:horizontal {
<string>Check software version</string> <string>Check software version</string>
</property> </property>
</action> </action>
<action name="action_themes">
<property name="text">
<string>Themes</string>
</property>
</action>
<action name="action_font">
<property name="text">
<string>Font...</string>
</property>
</action>
<action name="actionThemes">
<property name="text">
<string>Themes</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
@@ -9636,6 +9670,11 @@ QSlider::handle:horizontal {
<extends>QLabel</extends> <extends>QLabel</extends>
<header>switchable_label.h</header> <header>switchable_label.h</header>
</customwidget> </customwidget>
<customwidget>
<class>UrlButton</class>
<extends>QPushButton</extends>
<header>urlbutton.h</header>
</customwidget>
</customwidgets> </customwidgets>
<resources> <resources>
<include location="default_imgs.qrc"/> <include location="default_imgs.qrc"/>

View File

@@ -181,6 +181,8 @@ class Constants:
DEFAULT_IMGS_FOLDER = os.path.join(":", "pics", "default_pics") DEFAULT_IMGS_FOLDER = os.path.join(":", "pics", "default_pics")
DEFAULT_NOT_SELECTED = os.path.join(DEFAULT_IMGS_FOLDER, NOT_SELECTED) DEFAULT_NOT_SELECTED = os.path.join(DEFAULT_IMGS_FOLDER, NOT_SELECTED)
DEFAULT_NOT_AVAILABLE = os.path.join(DEFAULT_IMGS_FOLDER, NOT_AVAILABLE) 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: class Messages:
@@ -215,7 +217,6 @@ class ThemeConstants:
EXTENSION = ".qss" EXTENSION = ".qss"
ICONS_FOLDER = "icons" ICONS_FOLDER = "icons"
DEFAULT = "dark" DEFAULT = "dark"
CURRENT = "__current_theme"
COLORS = "colors.txt" COLORS = "colors.txt"
COLOR_SEPARATOR = "=" COLOR_SEPARATOR = "="
DEFAULT_ACTIVE_COLOR = "#000000" DEFAULT_ACTIVE_COLOR = "#000000"
@@ -231,5 +232,4 @@ class ThemeConstants:
DEFAULT_ICONS_PATH = os.path.join(FOLDER, DEFAULT, ICONS_FOLDER) 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_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) 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) DEFAULT_THEME_PATH = os.path.join(FOLDER, DEFAULT)

44
src/settings.py Normal file
View File

@@ -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

View File

@@ -198,7 +198,7 @@ class SpaceWeatherManager(QObject):
if k_index == 0: if k_index == 0:
self._switchable_g_now_labels.switch_on(self._owner.g0_now_lbl) 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) ") self._owner.expected_noise_lbl.setText(" S0 - S1 (<-120 dBm) ")
elif k_index == 1: elif k_index == 1:
self._switchable_g_now_labels.switch_on(self._owner.g0_now_lbl) self._switchable_g_now_labels.switch_on(self._owner.g0_now_lbl)

View File

@@ -260,8 +260,6 @@ QPushButton {
} }
QPushButton:hover { QPushButton:hover {
border: 2px dashed #4545e5;
border-radius: 13px;
color: #FFFFFF; color: #FFFFFF;
} }

View File

@@ -113,58 +113,60 @@ QSpinBox::down-button:disabled {
} }
QPushButton{ QPushButton{
border-style: outset; /*border-style: outset;
border-width: 2px; 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-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-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-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-color: rgb(58, 58, 58);
border-bottom-width: 1px; border-bottom-width: 1px;
border-style: solid; border-style: solid;*/
border: 0px;
color: rgb(255, 255, 255); color: rgb(255, 255, 255);
padding: 2px; 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{ QPushButton:hover{
border-style: outset; /*border-style: outset;
border-width: 2px; 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-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-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-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-color: rgb(115, 115, 115);
border-bottom-width: 1px; border-bottom-width: 1px;
border-style: solid; border-style: solid;*/
color: rgb(255, 255, 255); border: 0px;
color: #AFAFAF;
padding: 2px; 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{ QPushButton:pressed{
border-style: outset; /*border-style: outset;
border-width: 2px; 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-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-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-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-color: rgb(58, 58, 58);
border-bottom-width: 1px; border-bottom-width: 1px;
border-style: solid; border-style: solid;*/
border: 0px;
color: rgb(255, 255, 255); color: rgb(255, 255, 255);
padding: 2px; 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{ QPushButton:disabled{
border-style: outset; /*border-style: outset;
border-width: 2px; 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-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-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-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-color: rgb(58, 58, 58);
border-bottom-width: 1px; border-bottom-width: 1px;
border-style: solid; border-style: solid;*/
border: 0px;
color: rgb(0, 0, 0); color: rgb(0, 0, 0);
padding: 2px; 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 { QPushButton:checked {
border: 0px;
color: #00ff00; color: #00ff00;
} }
@@ -187,6 +189,10 @@ QRadioButton{
color: #FFFFFF; color: #FFFFFF;
} }
QRadioButton:hover{
color: #AFAFAF;
}
QComboBox { QComboBox {
border: 0px solid transparent; border: 0px solid transparent;
border-radius: 2px; border-radius: 2px;

View File

@@ -102,7 +102,7 @@ class ThemeManager:
self._theme_names = {} self._theme_names = {}
@pyqtSlot() @pyqtSlot()
def _apply(self, theme_path): def _apply(self, theme_path, save=True):
"""Apply the selected theme. """Apply the selected theme.
Refresh all relevant widgets. Refresh all relevant widgets.
@@ -110,7 +110,7 @@ class ThemeManager:
self._theme_path = theme_path self._theme_path = theme_path
if os.path.exists(theme_path): if os.path.exists(theme_path):
if self._theme_path != self._current_theme: if self._theme_path != self._current_theme:
self._change() self._change(save)
self._owner.display_specs( self._owner.display_specs(
item=self._owner.signals_list.currentItem(), item=self._owner.signals_list.currentItem(),
previous_item=None previous_item=None
@@ -141,29 +141,30 @@ class ThemeManager:
Display a QMessageBox if the theme folder is not found.""" Display a QMessageBox if the theme folder is not found."""
themes = [] themes = []
ag = QActionGroup(self._owner, exclusive=True) ag = QActionGroup(self._owner, exclusive=True)
if os.path.exists(ThemeConstants.FOLDER): themes_menu = self._owner.settings_menu.addMenu("Themes")
for theme_folder in sorted(os.listdir(ThemeConstants.FOLDER)): if not os.path.exists(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:
pop_up(self._owner, title=ThemeConstants.THEME_FOLDER_NOT_FOUND, pop_up(self._owner, title=ThemeConstants.THEME_FOLDER_NOT_FOUND,
text=ThemeConstants.MISSING_THEME_FOLDER).show() 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. """Change the current theme.
Apply the stylesheet and set active and inactive colors. Apply the stylesheet and set active and inactive colors.
@@ -173,8 +174,8 @@ class ThemeManager:
try: try:
with open(os.path.join(self._theme_path, theme_name), "r") as stylesheet: with open(os.path.join(self._theme_path, theme_name), "r") as stylesheet:
style = stylesheet.read() style = stylesheet.read()
self._owner.setStyleSheet(style) self._owner.setStyleSheet(style)
self._owner.download_window.setStyleSheet(style) self._owner.download_window.setStyleSheet(style)
except FileNotFoundError: except FileNotFoundError:
pop_up(self._owner, title=ThemeConstants.THEME_NOT_FOUND, pop_up(self._owner, title=ThemeConstants.THEME_NOT_FOUND,
text=ThemeConstants.MISSING_THEME).show() text=ThemeConstants.MISSING_THEME).show()
@@ -266,12 +267,8 @@ class ThemeManager:
ThemeConstants.DEFAULT_TEXT_COLOR ThemeConstants.DEFAULT_TEXT_COLOR
) )
self._current_theme = self._theme_path self._current_theme = self._theme_path
if save:
try: self._owner.settings.save(theme=os.path.basename(self._theme_path))
with open(ThemeConstants.CURRENT_THEME_FILE, "w") as current_theme:
current_theme.write(os.path.basename(self._theme_path))
except Exception:
pass
def apply_default_theme(self): def apply_default_theme(self):
"""Apply the default theme if no theme is set or the theme name is invalid.""" """Apply the default theme if no theme is set or the theme name is invalid."""
@@ -291,15 +288,14 @@ class ThemeManager:
def start(self): def start(self):
"""Start the theme manager.""" """Start the theme manager."""
self._detect_themes() self._detect_themes()
if os.path.exists(ThemeConstants.CURRENT_THEME_FILE): if self._owner.settings.theme is not None:
with open(ThemeConstants.CURRENT_THEME_FILE, "r") as current_theme_name: theme_path = os.path.join(ThemeConstants.FOLDER, self._owner.settings.theme)
theme_path = os.path.join(ThemeConstants.FOLDER, current_theme_name.read())
theme_name = self._pretty_name(os.path.basename(theme_path)) theme_name = self._pretty_name(os.path.basename(theme_path))
try: try:
self._theme_names[theme_name].setChecked(True) self._theme_names[theme_name].setChecked(True)
except Exception: except Exception:
self.apply_default_theme() self.apply_default_theme()
else: else:
self._apply(theme_path) self._apply(theme_path, save=False)
else: else:
self.apply_default_theme() self.apply_default_theme()

80
src/urlbutton.py Normal file
View File

@@ -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};
}}
""")

View File

@@ -11,20 +11,40 @@ class UniqueMessageBox(QMessageBox):
If another instance is the the exec loop, calling exec simply return None.""" If another instance is the the exec loop, calling exec simply return None."""
_open_message = False _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): def __init__(self, *args, **kwargs):
super().__init__(*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): def exec(self):
"""Overrides QMessageBox.exec. Call the parent method if there are no """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: if UniqueMessageBox._open_message:
return None return None
self.setFont(self._font)
UniqueMessageBox._open_message = True UniqueMessageBox._open_message = True
answer = super().exec() answer = super().exec()
UniqueMessageBox._open_message = False UniqueMessageBox._open_message = False
return answer 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): def uncheck_and_emit(button):
"""Set the button to the unchecked state and emit the clicked signal.""" """Set the button to the unchecked state and emit the clicked signal."""