Merge branch 'theme-selector' into 'master'

Better Dark Mode and UI Theme Support

See merge request openlp/openlp!335
This commit is contained in:
Tim Bentley 2021-09-01 18:34:07 +00:00
commit 9c7b140960
7 changed files with 519 additions and 95 deletions

View File

@ -55,7 +55,7 @@ from openlp.core.ui.firsttimeform import FirstTimeForm
from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm
from openlp.core.ui.mainwindow import MainWindow
from openlp.core.ui.splashscreen import SplashScreen
from openlp.core.ui.style import get_application_stylesheet, set_windows_darkmode
from openlp.core.ui.style import get_application_stylesheet, set_default_theme
from openlp.core.version import check_for_update, get_version
@ -117,9 +117,8 @@ class OpenLP(QtCore.QObject, LogMixin):
self.backup_on_upgrade(has_run_wizard, can_show_splash)
# start the main app window
loader()
# Set the darkmode for windows is enabled
if is_win():
set_windows_darkmode(app)
# Set the darkmode based on theme
set_default_theme(app)
self.main_window = MainWindow()
self.main_window.installEventFilter(self.main_window)
# Correct stylesheet bugs

View File

@ -24,6 +24,7 @@ This class contains the core default settings.
import datetime
import json
import logging
from openlp.core.ui.style import UiThemes
import os
from enum import IntEnum
from pathlib import Path
@ -104,6 +105,16 @@ def upgrade_screens(number, x_position, y_position, height, width, can_override,
}
def upgrade_dark_theme_to_ui_theme(value):
"""
Upgrade the dark theme setting to use the new UiThemes setting.
:param bool value: The old use_dark_style setting
:returns UiThemes: New UiThemes value
"""
return UiThemes.QDarkStyle if value else UiThemes.Automatic
class Settings(QtCore.QSettings):
"""
Class to wrap QSettings.
@ -174,7 +185,7 @@ class Settings(QtCore.QSettings):
'advanced/single click service preview': False,
'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,
'advanced/search as type': True,
'advanced/use_dark_style': False,
'advanced/ui_theme_name': UiThemes.Automatic,
'alerts/font face': QtGui.QFont().family(),
'alerts/font size': 40,
'alerts/db type': 'sqlite',
@ -446,7 +457,8 @@ class Settings(QtCore.QSettings):
('media/override player', '', []),
('core/audio start paused', '', []),
('core/audio repeat list', '', []),
('core/save prompt', '', [])
('core/save prompt', '', []),
('advanced/use_dark_style', 'advanced/ui_theme_name', [(upgrade_dark_theme_to_ui_theme, [False])])
]
@staticmethod

View File

@ -26,10 +26,10 @@ from pathlib import Path
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import get_images_filter, is_win
from openlp.core.common import get_images_filter
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.lib.settingstab import SettingsTab
from openlp.core.ui.style import HAS_DARK_STYLE
from openlp.core.ui.style import UiThemes, has_ui_theme
from openlp.core.widgets.buttons import ColorButton
from openlp.core.widgets.edits import PathEdit
@ -169,10 +169,16 @@ class GeneralTab(SettingsTab):
self.new_service_message_check_box = QtWidgets.QCheckBox(self.ui_group_box)
self.new_service_message_check_box.setObjectName('new_service_message_check_box')
self.ui_layout.addRow(self.new_service_message_check_box)
if not is_win() and HAS_DARK_STYLE:
self.use_dark_style_checkbox = QtWidgets.QCheckBox(self.ui_group_box)
self.use_dark_style_checkbox.setObjectName('use_dark_style_checkbox')
self.ui_layout.addRow(self.use_dark_style_checkbox)
self.ui_theme_style_label = QtWidgets.QLabel(self.ui_group_box)
self.ui_theme_style_label.setObjectName('theme_style_label')
self.ui_theme_style_combo_box = QtWidgets.QComboBox(self.ui_group_box)
if has_ui_theme(UiThemes.QDarkStyle):
self.ui_theme_style_combo_box.addItems(['', '', '', ''])
else:
self.ui_theme_style_combo_box.addItems(['', '', ''])
self.ui_theme_style_combo_box.setObjectName('theme_style_combo_box')
self.ui_layout.addRow(self.ui_theme_style_label)
self.ui_layout.addRow(self.ui_theme_style_combo_box)
self.right_layout.addWidget(self.ui_group_box)
# Push everything in both columns to the top
self.left_layout.addStretch()
@ -220,8 +226,7 @@ class GeneralTab(SettingsTab):
'Max height for non-text slides\nin slide controller:'))
self.slide_max_height_combo_box.setItemText(0, translate('OpenLP.AdvancedTab', 'Disabled'))
self.slide_max_height_combo_box.setItemText(1, translate('OpenLP.AdvancedTab', 'Automatic'))
self.autoscroll_label.setText(translate('OpenLP.AdvancedTab',
'When changing slides:'))
self.autoscroll_label.setText(translate('OpenLP.AdvancedTab', 'When changing slides:'))
self.autoscroll_combo_box.setItemText(0, translate('OpenLP.AdvancedTab', 'Do not auto-scroll'))
self.autoscroll_combo_box.setItemText(1, translate('OpenLP.AdvancedTab',
'Auto-scroll the previous slide into view'))
@ -251,8 +256,12 @@ class GeneralTab(SettingsTab):
self.new_service_message_check_box.setText(translate('OpenLP.AdvancedTab',
'Alert if New clicked on blank service'))
self.search_as_type_check_box.setText(translate('SongsPlugin.GeneralTab', 'Enable search as you type'))
if not is_win() and HAS_DARK_STYLE:
self.use_dark_style_checkbox.setText(translate('OpenLP.AdvancedTab', 'Use dark style (needs restart)'))
self.ui_theme_style_label.setText(translate('OpenLP.AdvancedTab', 'Interface Theme (needs restart):'))
self.ui_theme_style_combo_box.setItemText(0, translate('OpenLP.AdvancedTab', 'Use system theme'))
self.ui_theme_style_combo_box.setItemText(1, translate('OpenLP.AdvancedTab', 'Default Light'))
self.ui_theme_style_combo_box.setItemText(2, translate('OpenLP.AdvancedTab', 'Default Dark'))
if has_ui_theme(UiThemes.QDarkStyle):
self.ui_theme_style_combo_box.setItemText(3, translate('OpenLP.AdvancedTab', 'QDarkStyle'))
self.hide_mouse_check_box.setText(translate('OpenLP.AdvancedTab', 'Hide mouse cursor when over display window'))
def load(self):
@ -289,14 +298,55 @@ class GeneralTab(SettingsTab):
if self.autoscroll_map[i] == autoscroll_value and i < self.autoscroll_combo_box.count():
self.autoscroll_combo_box.setCurrentIndex(i)
self.enable_auto_close_check_box.setChecked(self.settings.value('advanced/enable exit confirmation'))
ui_theme_index = GeneralTab.get_ui_theme_index(self.settings.value('advanced/ui_theme_name'))
self.ui_theme_style_combo_box.setCurrentIndex(ui_theme_index)
self.slide_no_in_footer_checkbox.setChecked(self.settings.value('advanced/slide numbers in footer'))
self.new_service_message_check_box.setChecked(self.settings.value('advanced/new service message'))
if not is_win() and HAS_DARK_STYLE:
self.use_dark_style_checkbox.setChecked(self.settings.value('advanced/use_dark_style'))
self.hide_mouse_check_box.setChecked(self.settings.value('advanced/hide mouse'))
self.is_search_as_you_type_enabled = self.settings.value('advanced/search as type')
self.search_as_type_check_box.setChecked(self.is_search_as_you_type_enabled)
@staticmethod
def get_ui_theme_index(ui_theme):
"""
Converts :class:`~openlp.core.ui.dark.UiThemes` item to Interface Theme ComboBox
:param ui_theme UIThemes enum item
:return ComboBox index
"""
if ui_theme == UiThemes.Automatic:
return 0
if ui_theme == UiThemes.DefaultLight:
return 1
if ui_theme == UiThemes.DefaultDark:
return 2
if ui_theme == UiThemes.QDarkStyle:
return 3 if has_ui_theme(UiThemes.QDarkStyle) else 2
return 0
@staticmethod
def get_ui_theme_name(index):
"""
Converts "Interface Theme" ComboBox index to :class:`~openlp.core.ui.dark.UiThemes` item
:param index "Interface Theme" ComboBox current index
:return UiThemes enum item
"""
if not has_ui_theme(UiThemes.QDarkStyle) and index == 3:
index = 2
if index == 0:
return UiThemes.Automatic
if index == 1:
return UiThemes.DefaultLight
if index == 2:
return UiThemes.DefaultDark
if index == 3:
return UiThemes.QDarkStyle
return UiThemes.Automatic
def save(self):
"""
Save the settings from the form
@ -326,8 +376,8 @@ class GeneralTab(SettingsTab):
self.settings.setValue('advanced/new service message', self.new_service_message_check_box.isChecked())
self.settings.setValue('advanced/hide mouse', self.hide_mouse_check_box.isChecked())
self.settings.setValue('advanced/search as type', self.is_search_as_you_type_enabled)
if not is_win() and HAS_DARK_STYLE:
self.settings.setValue('advanced/use_dark_style', self.use_dark_style_checkbox.isChecked())
theme_name = GeneralTab.get_ui_theme_name(self.ui_theme_style_combo_box.currentIndex())
self.settings.setValue('advanced/ui_theme_name', theme_name)
self.post_set_up()
def post_set_up(self):

View File

@ -28,9 +28,8 @@ from PyQt5 import QtGui, QtWidgets
from openlp.core.common import Singleton
from openlp.core.common.applocation import AppLocation
from openlp.core.common.registry import Registry
from openlp.core.lib import build_icon
from openlp.core.ui.style import HAS_DARK_STYLE
from openlp.core.ui.style import is_ui_theme_dark
log = logging.getLogger(__name__)
@ -180,7 +179,7 @@ class UiIcons(metaclass=Singleton):
"""
Load the list of icons to be processed
"""
is_dark = (HAS_DARK_STYLE and Registry().get('settings').value('advanced/use_dark_style'))
is_dark = is_ui_theme_dark()
for key in icon_list:
try:
icon = icon_list[key]['icon']

View File

@ -21,17 +21,18 @@
"""
The :mod:`~openlp.core.ui.dark` module looks for and loads a dark theme
"""
from subprocess import Popen, PIPE
from enum import Enum
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import is_win
from openlp.core.common import is_macosx, is_win
from openlp.core.common.registry import Registry
try:
import qdarkstyle
HAS_DARK_STYLE = True
HAS_DARK_THEME = True
except ImportError:
HAS_DARK_STYLE = False
HAS_DARK_THEME = False
WIN_REPAIR_STYLESHEET = """
QMainWindow::separator
@ -76,21 +77,133 @@ QProgressBar{
"""
def set_windows_darkmode(app):
class UiThemes(Enum):
"""
Setup darkmode on the application if enabled in the OS (windows) settings
Source: https://github.com/worstje/manuskript/blob/develop/manuskript/main.py (GPL3)
An enumeration for themes.
"""
Automatic = 'automatic'
DefaultLight = 'light:default'
DefaultDark = 'dark:default'
QDarkStyle = 'dark:qdarkstyle'
def is_ui_theme_dark():
ui_theme_name = Registry().get('settings').value('advanced/ui_theme_name')
if ui_theme_name == UiThemes.Automatic:
return is_system_darkmode()
else:
return ui_theme_name.value.startswith('dark:')
def is_ui_theme(ui_theme: UiThemes):
ui_theme_name = Registry().get('settings').value('advanced/ui_theme_name')
return ui_theme_name == ui_theme
def init_ui_theme_if_needed(ui_theme_name):
return not isinstance(ui_theme_name, UiThemes)
def has_ui_theme(ui_theme: UiThemes):
if ui_theme == UiThemes.QDarkStyle:
return HAS_DARK_THEME
return True
IS_SYSTEM_DARKMODE = None
def is_system_darkmode():
global IS_SYSTEM_DARKMODE
if IS_SYSTEM_DARKMODE is None:
try:
if is_win():
IS_SYSTEM_DARKMODE = is_windows_darkmode()
elif is_macosx():
IS_SYSTEM_DARKMODE = is_macosx_darkmode()
else:
IS_SYSTEM_DARKMODE = False
except Exception:
IS_SYSTEM_DARKMODE = False
return IS_SYSTEM_DARKMODE
def is_windows_darkmode():
"""
Detects if Windows is using dark mode system theme.
Source: https://github.com/olivierkes/manuskript/blob/731e017e9e0dd7e4062f1af419705c11b2825515/manuskript/main.py
(GPL3)
Changes:
* Allowed palette to be set on any operating system;
* Split Windows Dark Mode detection to another function.
"""
theme_settings = QtCore.QSettings('HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes'
'\\Personalize',
QtCore.QSettings.NativeFormat)
if theme_settings.value('AppsUseLightTheme') == 0:
return theme_settings.value('AppsUseLightTheme') == 0
def is_macosx_darkmode():
"""
Detects if Mac OS X is using dark mode system theme.
Source: https://stackoverflow.com/a/65357166 (CC BY-SA 4.0)
Changes:
* Using OpenLP formatting rules
* Handling exceptions
"""
try:
command = 'defaults read -g AppleInterfaceStyle'
process = Popen(command, stdout=PIPE, stderr=PIPE, shell=True)
stdin = process.communicate()[0]
return bool(stdin)
except Exception:
return False
def set_default_theme(app):
"""
Setup theme
"""
if is_ui_theme(UiThemes.DefaultDark) or (is_ui_theme(UiThemes.Automatic) and is_ui_theme_dark()):
set_default_darkmode(app)
elif is_ui_theme(UiThemes.DefaultLight):
set_default_lightmode(app)
def set_default_lightmode(app):
"""
Setup lightmode on the application if Default Lightt theme is enabled in the OpenLP Settings.
"""
app.setStyle('Fusion')
app.setPalette(app.style().standardPalette())
def set_default_darkmode(app):
"""
Setup darkmode on the application if enabled in the OpenLP Settings or using a dark mode system theme.
Source:
https://github.com/olivierkes/manuskript/blob/731e017e9e0dd7e4062f1af419705c11b2825515/manuskript/main.py
(GPL3)
Changes:
* Allowed palette to be set on any operating system;
* Split Windows Dark Mode detection to another function.
"""
app.setStyle('Fusion')
dark_palette = QtGui.QPalette()
dark_color = QtGui.QColor(45, 45, 45)
disabled_color = QtGui.QColor(127, 127, 127)
dark_palette.setColor(QtGui.QPalette.Window, dark_color)
dark_palette.setColor(QtGui.QPalette.WindowText, QtCore.Qt.white)
dark_palette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, disabled_color)
dark_palette.setColor(QtGui.QPalette.Base, QtGui.QColor(18, 18, 18))
dark_palette.setColor(QtGui.QPalette.AlternateBase, dark_color)
dark_palette.setColor(QtGui.QPalette.ToolTipBase, QtCore.Qt.white)
@ -108,6 +221,8 @@ def set_windows_darkmode(app):
# Fixes ugly (not to mention hard to read) disabled menu items.
# Source: https://bugreports.qt.io/browse/QTBUG-10322?focusedCommentId=371060#comment-371060
dark_palette.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Light, QtCore.Qt.transparent)
# Fixes ugly media manager headers.
dark_palette.setColor(QtGui.QPalette.Mid, QtGui.QColor(64, 64, 64))
app.setPalette(dark_palette)
@ -118,7 +233,7 @@ def get_application_stylesheet():
:return str: The correct stylesheet as a string
"""
stylesheet = ''
if not is_win() and HAS_DARK_STYLE and Registry().get('settings').value('advanced/use_dark_style'):
if is_ui_theme(UiThemes.QDarkStyle):
stylesheet = qdarkstyle.load_stylesheet_pyqt5()
else:
if not Registry().get('settings').value('advanced/alternate rows'):
@ -137,7 +252,7 @@ def get_library_stylesheet():
:return str: The correct stylesheet as a string
"""
if not HAS_DARK_STYLE or not Registry().get('settings').value('advanced/use_dark_style'):
if not is_ui_theme(UiThemes.QDarkStyle):
return MEDIA_MANAGER_STYLE
else:
return ''

View File

@ -102,6 +102,7 @@ using a computer and a data projector.""",
'chardet',
'dbus-python; platform_system=="Linux"',
'distro; platform_system=="Linux"',
'darkdetect',
'flask',
'flask-cors',
'lxml',

View File

@ -22,88 +22,336 @@
Package to test the :mod:`~openlp.core.ui.style` module.
"""
from unittest import skipIf
from unittest.mock import MagicMock, patch
from unittest.mock import MagicMock, patch, call
from openlp.core.ui.style import MEDIA_MANAGER_STYLE, UiThemes, WIN_REPAIR_STYLESHEET, get_application_stylesheet, \
get_library_stylesheet, has_ui_theme, is_ui_theme_dark, set_default_theme
import openlp.core.ui.style
from openlp.core.ui.style import MEDIA_MANAGER_STYLE, WIN_REPAIR_STYLESHEET, get_application_stylesheet, \
get_library_stylesheet
@skipIf(not hasattr(openlp.core.ui.style, 'qdarkstyle'), 'qdarkstyle is not installed')
@patch('openlp.core.ui.style.HAS_DARK_STYLE', True)
@patch('openlp.core.ui.style.is_win')
@patch('openlp.core.ui.style.HAS_DARK_THEME', True)
@patch('openlp.core.ui.style.qdarkstyle')
def test_get_application_stylesheet_dark(mocked_qdarkstyle, mocked_is_win, mock_settings):
"""Test that the dark stylesheet is returned when available and enabled"""
# GIVEN: We're not on Windows and dark style is set
mocked_is_win.return_value = False
mocked_settings = MagicMock()
mocked_settings.value.return_value = True
mock_settings.return_value = mocked_settings
def test_get_application_stylesheet_qdarkstyle(mocked_qdarkstyle, mock_settings):
"""Test that the QDarkStyle stylesheet is returned when available and enabled"""
# GIVEN: Theme is QDarkStyle
mock_settings.value.return_value = UiThemes.QDarkStyle
mocked_qdarkstyle.load_stylesheet_pyqt5.return_value = 'dark_style'
# WHEN: can_show_icon() is called
# WHEN: get_application_stylesheet() is called
result = get_application_stylesheet()
# THEN: the result should be false
# THEN: the result should be QDarkStyle stylesheet
assert result == 'dark_style'
@patch('openlp.core.ui.style.HAS_DARK_STYLE', False)
@skipIf(not hasattr(openlp.core.ui.style, 'qdarkstyle'), 'qdarkstyle is not installed')
@patch('openlp.core.ui.style.HAS_DARK_THEME', True)
def test_has_ui_theme_qdarkstyle_true_when_available(mock_settings):
"""Test that the QDarkStyle UI theme exists when qdarkstyle is available """
# GIVEN: Theme is QDarkStyle
mock_settings.value.return_value = UiThemes.QDarkStyle
# WHEN: has_ui_theme() is called
result = has_ui_theme(UiThemes.QDarkStyle)
# THEN: the result should be true
assert result is True
@patch('openlp.core.ui.style.HAS_DARK_THEME', False)
def test_has_ui_theme_qdarkstyle_false_when_unavailable(mock_settings):
"""Test that the QDarkStyle UI theme not exists when qdarkstyle is not available """
# GIVEN: Theme is QDarkStyle
mock_settings.value.return_value = UiThemes.QDarkStyle
# WHEN: has_ui_theme() is called
result = has_ui_theme(UiThemes.QDarkStyle)
# THEN: the result should be false
assert result is False
@patch('openlp.core.ui.style.HAS_DARK_THEME', False)
@patch('openlp.core.ui.style.is_win')
@patch('openlp.core.app.QtWidgets.QApplication.palette')
def test_get_application_stylesheet_not_alternate_rows(mocked_palette, mocked_is_win, mock_settings):
"""Test that the alternate rows stylesheet is returned when enabled in settings"""
# GIVEN: We're not on Windows and no dark style is set
def settings_values(key):
if key == 'advanced/ui_theme_name':
return UiThemes.DefaultLight
else:
return False
# GIVEN: We're not on Windows and UI theme is not QDarkStyle
mocked_is_win.return_value = False
mock_settings.value.return_value = False
mock_settings.value = MagicMock(side_effect=settings_values)
mocked_palette.return_value.color.return_value.name.return_value = 'color'
# WHEN: can_show_icon() is called
# WHEN: get_application_stylesheet() is called
result = get_application_stylesheet()
# THEN: the result should be false
mock_settings.value.assert_called_once_with('advanced/alternate rows')
# THEN: result should match non-alternate-rows
mock_settings.value.assert_has_calls([call('advanced/ui_theme_name'), call('advanced/alternate rows')])
assert result == 'QTableWidget, QListWidget, QTreeWidget {alternate-background-color: color;}\n', result
@patch('openlp.core.ui.style.HAS_DARK_STYLE', False)
@patch('openlp.core.ui.style.HAS_DARK_THEME', False)
@patch('openlp.core.ui.style.is_win')
def test_get_application_stylesheet_win_repair(mocked_is_win, mock_settings):
"""Test that the Windows repair stylesheet is returned when on Windows"""
# GIVEN: We're on Windows and no dark style is set
mocked_is_win.return_value = True
mock_settings.value.return_value = True
def settings_values(key):
if key == 'advanced/ui_theme_name':
return UiThemes.DefaultLight
else:
return True
# WHEN: can_show_icon() is called
# GIVEN: We're on Windows and Theme is not QDarkStyle
mocked_is_win.return_value = True
mock_settings.value = MagicMock(side_effect=settings_values)
# WHEN: get_application_stylesheet() is called
result = get_application_stylesheet()
# THEN: the result should be false
mock_settings.value.assert_called_once_with('advanced/alternate rows')
# THEN: result should return Windows repair stylesheet
mock_settings.value.assert_has_calls([call('advanced/ui_theme_name'), call('advanced/alternate rows')])
assert result == WIN_REPAIR_STYLESHEET
@patch('openlp.core.ui.style.HAS_DARK_STYLE', False)
def test_get_library_stylesheet_no_dark_style(mock_settings):
"""Test that the media manager stylesheet is returned when there's no dark theme available"""
# GIVEN: No dark style
mock_settings.value.return_value = False
@patch('openlp.core.ui.style.HAS_DARK_THEME', False)
@patch('openlp.core.ui.style.is_win')
def test_get_application_stylesheet_not_win_repair(mocked_is_win, mock_settings):
"""Test that the Windows repair stylesheet is not returned when not in Windows"""
def settings_values(key):
if key == 'advanced/ui_theme_name':
return UiThemes.DefaultLight
else:
return True
# GIVEN: We're on Windows and Theme is not QDarkStyle
mocked_is_win.return_value = False
mock_settings.value = MagicMock(side_effect=settings_values)
# WHEN: get_application_stylesheet() is called
result = get_application_stylesheet()
# THEN: result should not return Windows repair stylesheet
mock_settings.value.assert_has_calls([call('advanced/ui_theme_name'), call('advanced/alternate rows')])
assert result != WIN_REPAIR_STYLESHEET
@patch('openlp.core.ui.style.HAS_DARK_THEME', False)
def test_get_library_stylesheet_automatic_ui_theme(mock_settings):
"""Test that the media manager stylesheet is returned for Automatic UI theme"""
# GIVEN: UI theme is Automatic
mock_settings.value.return_value = UiThemes.Automatic
# WHEN: get_library_stylesheet() is called
result = get_library_stylesheet()
# THEN: The correct stylesheet should be returned
# THEN: the correct stylesheet should be returned
assert result == MEDIA_MANAGER_STYLE
@patch('openlp.core.ui.style.HAS_DARK_STYLE', True)
def test_get_library_stylesheet_dark_style(mock_settings):
"""Test that no stylesheet is returned when the dark theme is enabled"""
# GIVEN: No dark style
mock_settings.value.return_value = True
@patch('openlp.core.ui.style.HAS_DARK_THEME', False)
def test_get_library_stylesheet_defaultlight_ui_theme(mock_settings):
"""Test that the media manager stylesheet is returned for Default Light UI theme"""
# GIVEN: UI theme is DefaultLight
mock_settings.value.return_value = UiThemes.DefaultLight
# WHEN: get_library_stylesheet() is called
result = get_library_stylesheet()
# THEN: the correct stylesheet should be returned
assert result == MEDIA_MANAGER_STYLE
@patch('openlp.core.ui.style.HAS_DARK_THEME', False)
def test_get_library_stylesheet_defaultdark_ui_theme(mock_settings):
"""Test that the media manager stylesheet is returned for Default Dark UI theme"""
# GIVEN: UI theme is DefaultDark
mock_settings.value.return_value = UiThemes.DefaultDark
# WHEN: get_library_stylesheet() is called
result = get_library_stylesheet()
# THEN: the correct stylesheet should be returned
assert result == MEDIA_MANAGER_STYLE
@skipIf(not hasattr(openlp.core.ui.style, 'qdarkstyle'), 'qdarkstyle is not installed')
@patch('openlp.core.ui.style.HAS_DARK_THEME', True)
def test_get_library_stylesheet_qdarktheme_ui_theme(mock_settings):
"""Test that the media manager stylesheet is not returned for QDarkStyle UI theme"""
# GIVEN: UI theme is QDarkStyle
mock_settings.value.return_value = UiThemes.QDarkStyle
# WHEN: get_library_stylesheet() is called
result = get_library_stylesheet()
# THEN: The correct stylesheet should be returned
assert result == ''
@patch('openlp.core.ui.style.HAS_DARK_THEME', False)
@patch('openlp.core.ui.style.is_system_darkmode')
def test_is_ui_theme_automatic_dark_when_system_dark(mocked_is_system_darkmode, mock_settings):
"""Test that the Automatic UI Theme is Dark on System Dark Theme"""
# GIVEN: UI theme is Automatic and System Theme is Dark
mock_settings.value.return_value = UiThemes.Automatic
mocked_is_system_darkmode.return_value = True
# WHEN: is_ui_theme_dark() is called
result = is_ui_theme_dark()
# THEN: the result should be true
assert result is True
@patch('openlp.core.ui.style.HAS_DARK_THEME', False)
@patch('openlp.core.ui.style.is_system_darkmode')
def test_is_ui_theme_dark_automatic_light_when_system_light(mocked_is_system_darkmode, mock_settings):
"""Test that the Automatic UI Theme is not Dark on System Light Theme"""
# GIVEN: UI theme is Automatic and System Theme is Light
mocked_is_system_darkmode.return_value = False
mock_settings.value.return_value = UiThemes.Automatic
# WHEN: is_ui_theme_dark() is called
result = is_ui_theme_dark()
# THEN: the result should be false
assert result is False
@patch('openlp.core.ui.style.HAS_DARK_THEME', False)
def test_is_ui_theme_dark_defaultlight_not_dark(mock_settings):
"""Test that the DefaultLight UI Theme is not Dark"""
# GIVEN: UI theme is DefaultLight
mock_settings.value.return_value = UiThemes.DefaultLight
# WHEN: is_ui_theme_dark() is called
result = is_ui_theme_dark()
# THEN: the result should be false
assert result is False
@patch('openlp.core.ui.style.HAS_DARK_THEME', False)
def test_is_ui_theme_dark_defaultdark_dark(mock_settings):
"""Test that the DefaultDark UI Theme is Dark"""
# GIVEN: UI theme is DefaultDark
mock_settings.value.return_value = UiThemes.DefaultDark
# WHEN: is_ui_theme_dark() is called
result = is_ui_theme_dark()
# THEN: the result should be true
assert result is True
@skipIf(not hasattr(openlp.core.ui.style, 'qdarkstyle'), 'qdarkstyle is not installed')
@patch('openlp.core.ui.style.HAS_DARK_THEME', True)
def test_is_ui_theme_dark_qdarkstyle_dark(mock_settings):
"""Test that the QDarkStyle UI Theme is Dark"""
# GIVEN: UI theme is DefaultDark
mock_settings.value.return_value = UiThemes.QDarkStyle
# WHEN: is_ui_theme_dark() is called
result = is_ui_theme_dark()
# THEN: the result should be true
assert result is True
@patch('openlp.core.ui.style.HAS_DARK_THEME', False)
def test_set_default_theme_defaultdark_theme_sets_palette(mock_settings):
"""Test that the set_default_theme sets App Palette for DefaultDark UI theme"""
# GIVEN: UI theme is DefaultDark
mock_settings.value.return_value = UiThemes.DefaultDark
mock_app = MagicMock()
# WHEN: set_default_theme() is called
set_default_theme(mock_app)
# THEN: app palette should be changed
mock_app.setPalette.assert_called_once()
@patch('openlp.core.ui.style.HAS_DARK_THEME', False)
@patch('openlp.core.ui.style.is_system_darkmode')
def test_set_default_theme_automatic_theme_system_dark_sets_palette(mocked_is_system_darkmode, mock_settings):
"""Test that the set_default_theme sets App Palette for Automatic UI theme on System with Dark Theme"""
# GIVEN: UI theme is Automatic on System with Dark Theme
mock_settings.value.return_value = UiThemes.Automatic
mocked_is_system_darkmode.return_value = True
mock_app = MagicMock()
# WHEN: set_default_theme() is called
set_default_theme(mock_app)
# THEN: app palette should be changed
mock_app.setPalette.assert_called_once()
@patch('openlp.core.ui.style.HAS_DARK_THEME', False)
@patch('openlp.core.ui.style.set_default_darkmode')
def test_set_default_theme_defaultdark_theme_calls_set_default_darkmode(mock_set_default_darkmode, mock_settings):
"""Test that the set_default_theme calls set_default_darkmode for DefaultDark UI theme"""
# GIVEN: UI theme is DefaultDark
mock_settings.value.return_value = UiThemes.DefaultDark
mock_app = MagicMock()
# WHEN: set_default_theme() is called
set_default_theme(mock_app)
# THEN: set_default_darkmode should be changed
mock_set_default_darkmode.assert_called_once()
@patch('openlp.core.ui.style.HAS_DARK_THEME', False)
@patch('openlp.core.ui.style.set_default_darkmode')
@patch('openlp.core.ui.style.is_system_darkmode')
def test_set_default_theme_automatic_theme_calls_set_default_darkmode(mock_is_system_darkmode,
mock_set_default_darkmode, mock_settings):
"""Test that the set_default_theme calls set_default_darkmode for Automatic UI theme on system dark theme"""
# GIVEN: UI theme is Automatic and System is using Dark Theme
mock_settings.value.return_value = UiThemes.Automatic
mock_app = MagicMock()
mock_is_system_darkmode.return_value = True
# WHEN: set_default_theme() is called
set_default_theme(mock_app)
# THEN: set_default_darkmode should be changed
mock_set_default_darkmode.assert_called_once()
@patch('openlp.core.ui.style.HAS_DARK_THEME', False)
@patch('openlp.core.ui.style.is_system_darkmode')
def test_set_default_theme_automatic_theme_system_light_not_sets_palette(mocked_is_system_darkmode, mock_settings):
"""Test that the set_default_theme doesnt't set App Palette for Automatic UI theme on System Light Theme"""
# GIVEN: UI theme is Automatic with System Light Theme
mock_settings.value.return_value = UiThemes.Automatic
mocked_is_system_darkmode.return_value = False
mock_app = MagicMock()
# WHEN: set_default_theme() is called
set_default_theme(mock_app)
# THEN: app palette should not be changed
mock_app.setPalette.assert_not_called()
@patch('openlp.core.ui.style.HAS_DARK_THEME', False)
@patch('openlp.core.ui.style.set_default_lightmode')
def test_set_default_theme_defaultlight_theme_calls_set_default_lightmode(mock_set_default_lightmode, mock_settings):
"""Test that the set_default_theme calls set_default_darkmode for DefaultLight UI theme"""
# GIVEN: UI theme is DefaultLight
mock_settings.value.return_value = UiThemes.DefaultLight
mock_app = MagicMock()
# WHEN: set_default_theme() is called
set_default_theme(mock_app)
# THEN: set_default_darkmode should be changed
mock_set_default_lightmode.assert_called_once()