Skip to content

Commit

Permalink
UI dark mode independent of the OS settings.
Browse files Browse the repository at this point in the history
Fixing several visual issues.
  • Loading branch information
Bertrand256 committed Sep 14, 2021
1 parent d4751df commit d824740
Show file tree
Hide file tree
Showing 15 changed files with 157 additions and 46 deletions.
11 changes: 11 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
## [0.9.27] - 2021-09-14
**Added**
- UI dark mode option independent of the OS settings (*Config->Miscellaneous->Use UI dark mode*).
- New toolbar icons with enhanced visual consistency (kudos to **Doeke Koedijk** for creating them).
- Possibility to use keypad when entering PIN for Trezor devices.

**Fixed**
- Support for macOS dark theme.
- Fixed an issue of the "Masternode status details" option disappearing after its usage.
- Fixed several other, minor visual issues.

## [0.9.27] - 2021-05-18
###Version with screenshots: [see here](doc/v0.9.27-changes.md)
**Added**
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ more-itertools
btchip-python
ledgerwallet
python-bls
construct==2.10.60
construct==2.10.60
QDarkStyle>=3.0.2
19 changes: 13 additions & 6 deletions src/app_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def __init__(self, app_version: str):
self.save_event = threading.Event()
self.__data = {}
self.thread = None
self.changes_pending = False

def set_file_name(self, cache_file_name: str):
if cache_file_name != self.cache_file_name:
Expand All @@ -49,9 +50,11 @@ def finish(self):

def save_data(self):
try:
self.__data['app_version'] = self.app_version
json.dump(self.__data, open(self.cache_file_name, 'w'))
self.last_data_change_time = 0
if self.changes_pending:
self.__data['app_version'] = self.app_version
json.dump(self.__data, open(self.cache_file_name, 'w'))
self.last_data_change_time = 0
self.changes_pending = False
except Exception as e:
log.error('Error writing cache: ' + str(e))

Expand All @@ -65,6 +68,7 @@ def load_data(self):

def data_changed(self):
self.last_data_change_time = time.time()
self.changes_pending = True

def set_value(self, symbol, value):
if isinstance(value, (int, float, str, list, tuple, dict)):
Expand Down Expand Up @@ -94,7 +98,7 @@ def save_data_thread(self, ctrl):
self.thread = None


cache = None
cache: Optional[AppCache] = None


def init(cache_file_name, app_version):
Expand Down Expand Up @@ -129,10 +133,13 @@ def get_value(symbol, default_value, type):
return None


def save_data():
def save_data(force: bool = False):
global cache
if cache:
cache.data_changed() # it forces saving data inside a thread
if force:
cache.save_data()
else:
cache.data_changed() # it forces saving data inside a thread
else:
log.warning('AppCache not initialized')

Expand Down
40 changes: 27 additions & 13 deletions src/app_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
CURRENT_CFG_FILE_VERSION = 5
CACHE_ITEM_LOGGERS_LOGLEVEL = 'LoggersLogLevel'
CACHE_ITEM_LOG_FORMAT = 'LogFormat'

GLOBAL_SETTINGS_FILE_NAME = 'dmt_global_settings.json'

DMN_ROLE_OWNER = 0x1
DMN_ROLE_OPERATOR = 0x2
Expand Down Expand Up @@ -110,6 +110,7 @@ def __init__(self):
self.initialized = False
self.app_dir = '' # will be passed in the init method
self.app_version = ''
self.global_config = Optional[app_cache.AppCache]
QLocale.setDefault(app_utils.get_default_locale())
self.date_format = app_utils.get_default_locale().dateFormat(QLocale.ShortFormat)
self.date_time_format = app_utils.get_default_locale().dateTimeFormat(QLocale.ShortFormat)
Expand Down Expand Up @@ -189,6 +190,7 @@ def __init__(self):
self.config_file_encrypted = False
self.fetch_network_data_after_start = True
self.show_dash_value_in_fiat = True
self.ui_use_dark_mode = False # Use dark mode independently from the OS settings

# attributes related to encryption cache data with hardware wallet:
self.hw_generated_key = b"\xab\x0fs}\x8b\t\xb4\xc3\xb8\x05\xba\xd1\x96\x9bq`I\xed(8w\xbf\x95\xf0-\x1a\x14\xcb\x1c\x1d+\xcd"
Expand All @@ -209,6 +211,16 @@ def __init__(self):
self.default_rpc_connections = []
logging.exception('Exception while parsing default RPC connections.')

@staticmethod
def get_default_user_data_dir():
user_home_dir = os.path.expanduser('~')
app_user_data_dir = os.path.join(user_home_dir, APP_DATA_DIR_NAME + '-v' + str(CURRENT_CFG_FILE_VERSION))
return app_user_data_dir

@staticmethod
def get_default_global_settings_file_name():
return os.path.join(AppConfig.get_default_user_data_dir(), GLOBAL_SETTINGS_FILE_NAME)

def init(self, app_dir):
""" Initialize configuration after opening the application. """
self.app_dir = app_dir
Expand All @@ -222,6 +234,8 @@ def init(self, app_dir):
except:
pass

self.global_config = app_cache.AppCache(self.app_version)

parser = argparse.ArgumentParser()
parser.add_argument('--config', help="Path to a configuration file", dest='config')
parser.add_argument('--data-dir', help="Root directory for configuration file, cache and log subdirs",
Expand Down Expand Up @@ -277,19 +291,12 @@ def init(self, app_dir):
old_user_data_dir = ''
user_home_dir = os.path.expanduser('~')
if not app_user_dir:
app_user_dir = os.path.join(user_home_dir, APP_DATA_DIR_NAME + '-v' + str(CURRENT_CFG_FILE_VERSION))
if not os.path.exists(app_user_dir):
prior_version_dirs = ['.dmt']
# look for the data dir of the previous version
for d in prior_version_dirs:
old_user_data_dir = os.path.join(user_home_dir, d)
if os.path.exists(old_user_data_dir):
migrate_config = True
break
app_user_dir = AppConfig.get_default_user_data_dir()

self.data_dir = app_user_dir
self.cache_dir = os.path.join(self.data_dir, 'cache')
cache_file_name = os.path.join(self.cache_dir, 'dmt_cache_v2.json')
global_settings_file_name = os.path.join(self.data_dir, GLOBAL_SETTINGS_FILE_NAME)

if migrate_config:
try:
Expand Down Expand Up @@ -371,6 +378,9 @@ def delayed_copy_thread(ctrl, dirs_to_copy:List[Tuple[str, str]]):
app_cache.init(cache_file_name, self.app_version)
self.app_last_version = app_cache.get_value('app_version', '', str)
self.app_config_file_name = ''
self.global_config.set_file_name(global_settings_file_name)
self.global_config.start()
self.ui_use_dark_mode = self.global_config.get_value('UI_USE_DARK_MODE', False, bool)

if args.config is not None:
# set config file name to what user passed in the 'config' argument
Expand Down Expand Up @@ -434,6 +444,11 @@ def close(self):
self.save_cache_settings()
self.save_loggers_config()
app_cache.finish()

self.global_config.set_value('UI_USE_DARK_MODE', self.ui_use_dark_mode)
app_cache.save_data(True)
self.global_config.save_data()

if self.db_intf:
self.db_intf.close()

Expand Down Expand Up @@ -490,6 +505,7 @@ def copy_from(self, src_config):
# ... otherwise just copy attribute without reconfiguring logger
self.log_level_str = src_config.log_level_str
self.encrypt_config_file = src_config.encrypt_config_file
self.ui_use_dark_mode = src_config.ui_use_dark_mode

def configure_cache(self):
if self.is_testnet:
Expand Down Expand Up @@ -638,9 +654,6 @@ def read_from_file(self, hw_session: 'HwSessionInfo', file_name: Optional[str] =
if not file_name:
file_name = self.app_config_file_name

configuration_corrected = False
errors_while_reading = False

if os.path.exists(file_name):
config = ConfigParser()
try:
Expand Down Expand Up @@ -738,6 +751,7 @@ def read_from_file(self, hw_session: 'HwSessionInfo', file_name: Optional[str] =
fallback='1'))
self.add_random_offset_to_vote_time = \
self.value_to_bool(config.get(section, 'add_random_offset_to_vote_time', fallback='1'))

self.encrypt_config_file = \
self.value_to_bool(config.get(section, 'encrypt_config_file', fallback='0'))

Expand Down
17 changes: 14 additions & 3 deletions src/config_dlg.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def __init__(self, parent, app_config: AppConfig):
# block ui controls -> cur config data copying while setting ui controls initial values
self.disable_cfg_update = False
self.is_modified = False
self.global_options_modified = False # user modified options not related to a config file
self.setupUi(self)

def setupUi(self, dialog: QDialog):
Expand Down Expand Up @@ -218,6 +219,7 @@ def setupUi(self, dialog: QDialog):
self.chbEncryptConfigFile.setChecked(self.local_config.encrypt_config_file)
self.chbFetchDataAfterStart.setChecked(self.local_config.fetch_network_data_after_start)
self.chbShowDashFIATValue.setChecked(self.local_config.show_dash_value_in_fiat)
self.chbUIDarkMode.setChecked(self.local_config.ui_use_dark_mode)

idx = {
'CRITICAL': 0,
Expand Down Expand Up @@ -253,9 +255,7 @@ def on_close(self):
app_cache.set_value('ConfigDlg_ConnectionSplitter_Sizes', self.splitter.sizes())

def on_accepted(self):
"""Executed after clicking the 'OK' button."""
if self.is_modified:
self.apply_config_changes()
self.apply_config_changes()

def display_connection_list(self):
self.lstConns.clear()
Expand Down Expand Up @@ -598,6 +598,10 @@ def on_chbRandomConn_toggled(self, checked):
self.local_config.random_dash_net_config = checked
self.set_modified()

def on_chbUIDarkMode_toggled(self, checked):
if not self.disable_cfg_update:
self.local_config.ui_use_dark_mode = checked

def update_ssh_ctrls_ui(self):
index = self.ssh_tunnel_widget.cboAuthentication.currentIndex()
pkey_visible = (index == 2)
Expand Down Expand Up @@ -854,6 +858,9 @@ def set_modified(self):
def get_is_modified(self):
return self.is_modified

def get_global_options_modified(self):
return self.global_options_modified

def apply_config_changes(self):
"""
Applies changes made by the user by moving the UI controls values to the appropriate
Expand All @@ -869,6 +876,10 @@ def apply_config_changes(self):
self.app_config.set_log_level(self.local_config.log_level_str)
self.app_config.modified = True

if self.local_config.ui_use_dark_mode != self.app_config.ui_use_dark_mode:
self.global_options_modified = True
self.app_config.ui_use_dark_mode = self.local_config.ui_use_dark_mode

def on_btnEncryptionPublicKey_clicked(self):
updated = False
key_str = self.current_network_cfg.get_rpc_encryption_pubkey_str('PEM')
Expand Down
25 changes: 24 additions & 1 deletion src/dash_masternode_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@

from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication
import qdarkstyle

import main_dlg
import traceback
import logging

from app_cache import AppCache
from app_config import AppConfig
from wnd_utils import WndUtils


Expand Down Expand Up @@ -39,8 +42,28 @@ def my_excepthook(type, value, tback):
if tail == 'src':
app_dir = path

os.environ['QT_API'] = 'pyqt5'

app = QApplication(sys.argv)
ui = main_dlg.MainWindow(app_dir)
ui_dark_mode_activated = False

try:
# check in the user configured the ui dark mode in the default global settings; if so, apply it here
# (before GUI is instantiated) to avoid flickering caused by switching from the default UI theme
config_file = AppConfig.get_default_global_settings_file_name()
if config_file and os.path.exists(config_file):
cache = AppCache('0.0.0')
cache.set_file_name(config_file)
dark_mode = cache.get_value('UI_USE_DARK_MODE', False, bool)
if dark_mode:
app.setStyleSheet(qdarkstyle.load_stylesheet())
ui_dark_mode_activated = True
del cache

except Exception:
pass

ui = main_dlg.MainWindow(app_dir, ui_dark_mode_activated)
ui.show()

try:
Expand Down
38 changes: 29 additions & 9 deletions src/main_dlg.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# Created on: 2017-03
from enum import Enum

import qdarkstyle
import simplejson
import datetime
import os
Expand Down Expand Up @@ -61,13 +62,14 @@
class MainWindow(QMainWindow, QDetectThemeChange, WndUtils, ui_main_dlg.Ui_MainWindow):
update_status_signal = QtCore.pyqtSignal(str, str) # signal for updating status text from inside thread

def __init__(self, app_dir):
def __init__(self, app_dir, ui_dark_mode_activated_externally: bool = False):
QMainWindow.__init__(self)
QDetectThemeChange.__init__(self)
WndUtils.__init__(self, None)
ui_main_dlg.Ui_MainWindow.__init__(self)

self.finishing = False
self.ui_dark_mode_activated = ui_dark_mode_activated_externally
self.app_messages: Dict[int, DispMessage] = {}
self.app_config = AppConfig()
self.app_config.init(app_dir)
Expand Down Expand Up @@ -233,10 +235,23 @@ def configuration_to_ui(self):
Show the information read from configuration file on the user interface.
:return:
"""
self.update_app_ui_theme()
self.main_view.configuration_to_ui()
self.action_open_log_file.setText('Open log file (%s)' % self.app_config.log_file)
self.update_edit_controls_state()

def update_app_ui_theme(self):
if self.app_config.ui_use_dark_mode:
if not self.ui_dark_mode_activated:
app = QApplication.instance()
app.setStyleSheet(qdarkstyle.load_stylesheet())
self.ui_dark_mode_activated = True
else:
if self.ui_dark_mode_activated:
app = QApplication.instance()
app.setStyleSheet('')
self.ui_dark_mode_activated = False

def load_configuration_from_file(self, file_name: Optional[str], ask_save_changes = True,
update_current_file_name = True) -> None:
"""
Expand Down Expand Up @@ -655,14 +670,19 @@ def on_action_open_settings_window_triggered(self):
dash_network_sav = self.app_config.dash_network
dlg = ConfigDlg(self, self.app_config)
res = dlg.exec_()
if res and dlg.get_is_modified():
self.app_config.configure_cache()
self.dashd_intf.reload_configuration()
if dash_network_sav != self.app_config.dash_network:
self.disconnect_hardware_wallet()
self.app_config.reset_network_dependent_dyn_params()
self.display_window_title()
self.update_edit_controls_state()
if res:
if dlg.get_global_options_modified():
# user modified options not related to config file - stored in cache
self.update_app_ui_theme()

if dlg.get_is_modified():
self.app_config.configure_cache()
self.dashd_intf.reload_configuration()
if dash_network_sav != self.app_config.dash_network:
self.disconnect_hardware_wallet()
self.app_config.reset_network_dependent_dyn_params()
self.display_window_title()
self.update_edit_controls_state()
del dlg
except Exception as e:
self.error_msg(str(e), True)
Expand Down
Loading

0 comments on commit d824740

Please sign in to comment.