diff --git a/openlp/core/app.py b/openlp/core/app.py index 35ab3de86..61ea82a2f 100644 --- a/openlp/core/app.py +++ b/openlp/core/app.py @@ -39,6 +39,7 @@ from PyQt5 import QtCore, QtGui, QtWebEngineWidgets, QtWidgets # noqa from openlp.core.api.deploy import check_for_remote_update from openlp.core.common.applocation import AppLocation +from openlp.core.common.enum import HiDPIMode from openlp.core.common.i18n import LanguageManager, UiStrings, translate from openlp.core.common.mixins import LogMixin from openlp.core.common.path import create_paths, resolve @@ -395,6 +396,51 @@ def backup_if_version_changed(settings): return True +def apply_dpi_adjustments_stage_qt(hidpi_mode, qt_args): + if hidpi_mode == HiDPIMode.Windows_Unaware: + os.environ['QT_SCALE_FACTOR'] = '1' + os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '0' + os.environ['QT_ENABLE_HIGHDPI_SCALING'] = '0' + if is_win(): + try: + platform_index = qt_args.index('-platform') + qt_args[platform_index + 1] += ' windows:dpiawareness=0' + except ValueError: + qt_args.extend(['-platform', 'windows:dpiawareness=0']) + else: + QtWidgets.QApplication.setAttribute(QtCore.Qt.ApplicationAttribute.AA_EnableHighDpiScaling) + if hidpi_mode == HiDPIMode.Default: + no_custom_factor_rounding = not ('QT_SCALE_FACTOR_ROUNDING_POLICY' in os.environ + and bool(os.environ['QT_SCALE_FACTOR_ROUNDING_POLICY'].strip())) + if no_custom_factor_rounding: + # TODO Won't be needed on PyQt6, PassThrough is the default + os.environ['QT_SCALE_FACTOR_ROUNDING_POLICY'] = 'PassThrough' + + +def apply_dpi_adjustments_stage_application(hidpi_mode, application): + """ + Apply OpenLP DPI adjustments to bypass Windows and QT bugs (unless disabled on settings) + + :param args: OpenLP startup arguments + :param settings: The settings object + :param stage: The stage of app + """ + if hidpi_mode == HiDPIMode.Default: + no_custom_factor_rounding = not ('QT_SCALE_FACTOR_ROUNDING_POLICY' in os.environ + and bool(os.environ['QT_SCALE_FACTOR_ROUNDING_POLICY'].strip())) + if no_custom_factor_rounding and hasattr(QtWidgets.QApplication, 'setHighDpiScaleFactorRoundingPolicy'): + # TODO Won't be needed on PyQt6, PassThrough is the default + application.setHighDpiScaleFactorRoundingPolicy(QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough) + if is_win() and application.devicePixelRatio() > 1.0: + # Increasing font size to match pixel ratio (Windows only) + # TODO: Review on PyQt6 migration + font = application.font() + font.setPointSizeF(font.pointSizeF() * application.devicePixelRatio()) + application.setFont(font) + if hidpi_mode != HiDPIMode.Windows_Unaware: + application.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) + + def main(): """ The main function which parses command line options and then runs @@ -423,6 +469,11 @@ def main(): 'lib' / 'QtWebEngineCore.framework' / 'Versions' / '5' / 'Helpers' / 'QtWebEngineProcess.app' / 'Contents' / 'MacOS' / 'QtWebEngineProcess') + no_custom_factor_rounding = not ('QT_SCALE_FACTOR_ROUNDING_POLICY' in os.environ + and bool(os.environ['QT_SCALE_FACTOR_ROUNDING_POLICY'].strip())) + if no_custom_factor_rounding: + # TODO Won't be needed on PyQt6 + os.environ['QT_SCALE_FACTOR_ROUNDING_POLICY'] = 'PassThrough' # Initialise the resources qInitResources() # Now create and actually run the application. @@ -433,6 +484,15 @@ def main(): application.setOrganizationDomain('openlp.org') application.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) application.setAttribute(QtCore.Qt.AA_DontCreateNativeWidgetSiblings, True) + if no_custom_factor_rounding and hasattr(QtWidgets.QApplication, 'setHighDpiScaleFactorRoundingPolicy'): + # TODO: Check won't be needed on PyQt6 + application.setHighDpiScaleFactorRoundingPolicy(QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough) + if is_win() and application.devicePixelRatio() > 1.0: + # Increasing font size to match pixel ratio (Windows only) + font = application.font() + # font.setPointSizeF(font.pointSizeF() * application.devicePixelRatio()) + font.setPointSizeF(font.pointSizeF() * application.devicePixelRatio()) + application.setFont(font) if args.portable: application.setApplicationName('OpenLPPortable') Settings.setDefaultFormat(Settings.IniFormat) @@ -447,7 +507,6 @@ def main(): portable_path = resolve(portable_path) data_path = portable_path / 'Data' set_up_logging(portable_path / 'Other') - set_up_web_engine_cache(portable_path / 'Other' / 'web_cache') log.info('Running portable') portable_settings_path = data_path / 'OpenLP.ini' # Make this our settings file @@ -463,7 +522,6 @@ def main(): else: application.setApplicationName('OpenLP') set_up_logging(AppLocation.get_directory(AppLocation.CacheDir)) - set_up_web_engine_cache(AppLocation.get_directory(AppLocation.CacheDir) / 'web_cache') # Set the libvlc environment variable if we're frozen if getattr(sys, 'frozen', False): # Path to libvlc and the plugins @@ -482,6 +540,23 @@ def main(): # Initialise the Registry Registry.create() settings = Settings() + # Doing HiDPI adjustments that need to be done before QCoreApplication instantiation. + hidpi_mode = settings.value('advanced/hidpi mode') + apply_dpi_adjustments_stage_qt(hidpi_mode, qt_args) + # Instantiating QCoreApplication + application = QtWidgets.QApplication(qt_args) + application.setOrganizationName('OpenLP') + application.setOrganizationDomain('openlp.org') + application.setAttribute(QtCore.Qt.AA_DontCreateNativeWidgetSiblings, True) + # Doing HiDPI adjustments that need to be done after QCoreApplication instantiation. + apply_dpi_adjustments_stage_application(hidpi_mode, application) + settings.init_default_shortcuts() + if args.portable: + application.setApplicationName('OpenLPPortable') + set_up_web_engine_cache(portable_path / 'Other' / 'web_cache') + else: + application.setApplicationName('OpenLP') + set_up_web_engine_cache(AppLocation.get_directory(AppLocation.CacheDir) / 'web_cache') Registry().register('settings', settings) if settings.value('advanced/protect data directory'): # attempt to create a file lock diff --git a/openlp/core/common/enum.py b/openlp/core/common/enum.py index bc6c6a723..7ccb8a1bb 100644 --- a/openlp/core/common/enum.py +++ b/openlp/core/common/enum.py @@ -156,3 +156,12 @@ class SongFirstSlideMode(IntEnum): Default = 0 # No cover Songbook = 1 Footer = 2 + + +@unique +class HiDPIMode(IntEnum): + Default = 0, + # Legacy HiDPI mode is the default Qt behavior, without any OpenLP-specific HiDPI modifications + Legacy = 1, + # (Windows only) Make the OpenLP run unaware of any screen scaling. + Windows_Unaware = 2 diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 23f617789..00ea8f519 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -32,7 +32,7 @@ from tempfile import gettempdir from PyQt5 import QtCore, QtGui from openlp.core.common import SlideLimits, ThemeLevel -from openlp.core.common.enum import AlertLocation, BibleSearch, CustomSearch, ImageThemeMode, LayoutStyle, \ +from openlp.core.common.enum import AlertLocation, BibleSearch, CustomSearch, HiDPIMode, ImageThemeMode, LayoutStyle, \ DisplayStyle, LanguageSelection, SongFirstSlideMode, SongSearch, PluginStatus from openlp.core.common.json import OpenLPJSONDecoder, OpenLPJSONEncoder, is_serializable from openlp.core.common.path import files_to_paths, str_to_path @@ -200,6 +200,7 @@ class Settings(QtCore.QSettings): 'advanced/search as type': True, 'advanced/ui_theme_name': UiThemes.Automatic, 'advanced/delete service item confirmation': False, + 'advanced/hidpi mode': HiDPIMode.Default, 'alerts/font face': QtGui.QFont().family(), 'alerts/font size': 40, 'alerts/db type': 'sqlite', @@ -528,6 +529,8 @@ class Settings(QtCore.QSettings): QtCore.QSettings.__init__(self, Settings.__file_path__, Settings.IniFormat) else: QtCore.QSettings.__init__(self, *args) + + def init_default_shortcuts(self): # Add shortcuts here so QKeySequence has a QApplication instance to use. Settings.__default_settings__.update({ 'shortcuts/aboutItem': [QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_F1)], diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index 5e5bdba46..61cff515b 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -110,6 +110,22 @@ class AdvancedTab(SettingsTab): self.prefer_windowed_capture_check_box.setObjectName('prefer_windowed_capture_check_box') self.display_workaround_layout.addWidget(self.prefer_windowed_capture_check_box) self.left_layout.addWidget(self.display_workaround_group_box) + # Misc Workarounds + self.misc_workaround_group_box = QtWidgets.QGroupBox(self.left_column) + self.misc_workaround_group_box.setObjectName('misc_workaround_group_box') + self.misc_workaround_layout = QtWidgets.QVBoxLayout(self.misc_workaround_group_box) + self.misc_workaround_layout.setObjectName('misc_workaround_layout') + self.hidpi_mode_widget = QtWidgets.QWidget(self.misc_workaround_group_box) + self.hidpi_mode_layout = QtWidgets.QHBoxLayout(self.hidpi_mode_widget) + self.hidpi_mode_layout.setContentsMargins(0, 0, 0, 0) + self.hidpi_mode_label = QtWidgets.QLabel(self.hidpi_mode_widget) + self.hidpi_mode_combobox = QtWidgets.QComboBox(self.hidpi_mode_widget) + self.hidpi_mode_combobox.addItems(['', '', '']) + self.hidpi_mode_layout.addWidget(self.hidpi_mode_label) + self.hidpi_mode_layout.addWidget(self.hidpi_mode_combobox) + self.hidpi_mode_widget.setLayout(self.hidpi_mode_layout) + self.misc_workaround_layout.addWidget(self.hidpi_mode_widget) + self.left_layout.addWidget(self.misc_workaround_group_box) # Proxies self.proxy_widget = ProxyWidget(self.right_column) self.right_layout.addWidget(self.proxy_widget) @@ -117,10 +133,11 @@ class AdvancedTab(SettingsTab): self.left_layout.addStretch() self.right_layout.addStretch() # Set up all the connections and things - self.alternate_rows_check_box.toggled.connect(self.on_alternate_rows_check_box_toggled) + self.alternate_rows_check_box.toggled.connect(self.on_restart_needed) self.data_directory_path_edit.pathChanged.connect(self.on_data_directory_path_edit_path_changed) self.data_directory_cancel_button.clicked.connect(self.on_data_directory_cancel_button_clicked) self.data_directory_copy_check_box.toggled.connect(self.on_data_directory_copy_check_box_toggled) + self.hidpi_mode_combobox.currentIndexChanged.connect(self.on_restart_needed) def retranslate_ui(self): """ @@ -153,6 +170,11 @@ class AdvancedTab(SettingsTab): translate('OpenLP.AdvancedTab', 'Disable display transparency')) self.prefer_windowed_capture_check_box.setText( translate('OpenLP.AdvancedTab', 'Prefer window capture instead of screen capture')) + self.misc_workaround_group_box.setTitle(translate('OpenLP.AdvancedTab', 'Miscellaneous Workarounds')) + self.hidpi_mode_label.setText(translate('OpenLP.AdvancedTab', 'HiDPI Mode:')) + self.hidpi_mode_combobox.setItemText(0, translate('OpenLP.AdvancedTab', 'Default')) + self.hidpi_mode_combobox.setItemText(1, translate('OpenLP.AdvancedTab', 'Legacy')) + self.hidpi_mode_combobox.setItemText(2, translate('OpenLP.AdvancedTab', 'DPI Unaware (Windows only)')) self.proxy_widget.retranslate_ui() def load(self): @@ -178,6 +200,7 @@ class AdvancedTab(SettingsTab): self.data_directory_new_label.hide() self.data_directory_path_edit.hide() self.data_directory_protect_check_box.setChecked(self.settings.value('advanced/protect data directory')) + self.hidpi_mode_combobox.setCurrentIndex(self.settings.value('advanced/hidpi mode')) def save(self): """ @@ -193,6 +216,7 @@ class AdvancedTab(SettingsTab): self.settings_form.register_post_process('config_screen_changed') self.settings.setValue('advanced/alternate rows', self.alternate_rows_check_box.isChecked()) self.settings.setValue('advanced/protect data directory', self.data_directory_protect_check_box.isChecked()) + self.settings.setValue('advanced/hidpi mode', self.hidpi_mode_combobox.currentIndex()) self.proxy_widget.save() def cancel(self): @@ -287,13 +311,14 @@ class AdvancedTab(SettingsTab): self.data_directory_cancel_button.hide() self.new_data_directory_has_files_label.hide() - def on_alternate_rows_check_box_toggled(self, checked): + def on_restart_needed(self, _): """ Notify user about required restart. :param checked: The state of the check box (boolean). """ - QtWidgets.QMessageBox.information(self, translate('OpenLP.AdvancedTab', 'Restart Required'), - translate('OpenLP.AdvancedTab', - 'This change will only take effect once OpenLP ' - 'has been restarted.')) + if self.isVisible(): + QtWidgets.QMessageBox.information(self, translate('OpenLP.AdvancedTab', 'Restart Required'), + translate('OpenLP.AdvancedTab', + 'This change will only take effect once OpenLP ' + 'has been restarted.')) diff --git a/openlp/core/ui/splashscreen.py b/openlp/core/ui/splashscreen.py index cee099912..d87d1a9f1 100644 --- a/openlp/core/ui/splashscreen.py +++ b/openlp/core/ui/splashscreen.py @@ -43,6 +43,6 @@ class SplashScreen(QtWidgets.QSplashScreen): self.setObjectName('splashScreen') self.setContextMenuPolicy(QtCore.Qt.PreventContextMenu) splash_image = QtGui.QPixmap(':/graphics/openlp-splash-screen.png') + splash_image.setDevicePixelRatio(self.devicePixelRatioF()) self.setPixmap(splash_image) - self.setMask(splash_image.mask()) self.resize(370, 370) diff --git a/resources/images/openlp-splash-screen-old.png b/resources/images/openlp-splash-screen-old.png new file mode 100644 index 000000000..09785488a Binary files /dev/null and b/resources/images/openlp-splash-screen-old.png differ diff --git a/resources/images/openlp-splash-screen.png b/resources/images/openlp-splash-screen.png index 09785488a..c267c8fb4 100644 Binary files a/resources/images/openlp-splash-screen.png and b/resources/images/openlp-splash-screen.png differ diff --git a/tests/conftest.py b/tests/conftest.py index e091a8d55..4875942bf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -75,6 +75,7 @@ def settings(qapp, registry): Settings().setDefaultFormat(QtCore.QSettings.Format.IniFormat) # Needed on windows to make sure a Settings object is available during the tests sets = Settings() + sets.init_default_shortcuts() sets.setValue('themes/global theme', 'my_theme') registry.register('settings', sets) registry.register('settings_thread', sets)