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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ launch.bat
|
||||
.code-workspace
|
||||
spec_files/**/output
|
||||
*.txt
|
||||
*.json
|
||||
|
||||
13
CHANGELOG.md
13
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.
|
||||
|
||||
|
||||
<!-- 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.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
|
||||
102
src/artemis.py
102
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
|
||||
|
||||
|
||||
@@ -1255,7 +1255,7 @@
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_17">
|
||||
<item>
|
||||
<widget class="QPushButton" name="url_button">
|
||||
<widget class="UrlButton" name="url_button">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
@@ -6949,7 +6949,9 @@ STORM</string>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>13</pointsize>
|
||||
<weight>75</weight>
|
||||
<italic>false</italic>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
@@ -6977,7 +6979,9 @@ STORM</string>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>13</pointsize>
|
||||
<weight>75</weight>
|
||||
<italic>false</italic>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
@@ -7005,7 +7009,9 @@ STORM</string>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>13</pointsize>
|
||||
<weight>75</weight>
|
||||
<italic>false</italic>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
@@ -7033,7 +7039,9 @@ STORM</string>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>13</pointsize>
|
||||
<weight>75</weight>
|
||||
<italic>false</italic>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
@@ -7061,7 +7069,9 @@ STORM</string>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>13</pointsize>
|
||||
<weight>75</weight>
|
||||
<italic>false</italic>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
@@ -7092,7 +7102,9 @@ STORM</string>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>13</pointsize>
|
||||
<weight>75</weight>
|
||||
<italic>false</italic>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
@@ -7120,7 +7132,9 @@ STORM</string>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>13</pointsize>
|
||||
<weight>75</weight>
|
||||
<italic>false</italic>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
@@ -7148,7 +7162,9 @@ STORM</string>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>13</pointsize>
|
||||
<weight>75</weight>
|
||||
<italic>false</italic>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
@@ -7176,7 +7192,9 @@ STORM</string>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>13</pointsize>
|
||||
<weight>75</weight>
|
||||
<italic>false</italic>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
@@ -9516,11 +9534,6 @@ QSlider::handle:horizontal {
|
||||
<addaction name="action_update_database"/>
|
||||
<addaction name="action_check_software_version"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_themes">
|
||||
<property name="title">
|
||||
<string>Themes</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuSigidwiki">
|
||||
<property name="title">
|
||||
<string>Sigidwiki</string>
|
||||
@@ -9537,10 +9550,16 @@ QSlider::handle:horizontal {
|
||||
<addaction name="action_rtl_sdr_com"/>
|
||||
<addaction name="action_github"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="settings_menu">
|
||||
<property name="title">
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<addaction name="action_font"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuUpdates"/>
|
||||
<addaction name="menu_themes"/>
|
||||
<addaction name="menuSigidwiki"/>
|
||||
<addaction name="settings_menu"/>
|
||||
<addaction name="menuAbout"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar">
|
||||
@@ -9598,6 +9617,21 @@ QSlider::handle:horizontal {
|
||||
<string>Check software version</string>
|
||||
</property>
|
||||
</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>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
@@ -9636,6 +9670,11 @@ QSlider::handle:horizontal {
|
||||
<extends>QLabel</extends>
|
||||
<header>switchable_label.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>UrlButton</class>
|
||||
<extends>QPushButton</extends>
|
||||
<header>urlbutton.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="default_imgs.qrc"/>
|
||||
|
||||
@@ -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)
|
||||
|
||||
44
src/settings.py
Normal file
44
src/settings.py
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -260,8 +260,6 @@ QPushButton {
|
||||
}
|
||||
|
||||
QPushButton:hover {
|
||||
border: 2px dashed #4545e5;
|
||||
border-radius: 13px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
80
src/urlbutton.py
Normal file
80
src/urlbutton.py
Normal 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};
|
||||
}}
|
||||
""")
|
||||
@@ -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."""
|
||||
|
||||
Reference in New Issue
Block a user