High DPI Fixes

This commit is contained in:
Raoul Snyman 2023-11-26 04:17:58 +00:00
parent 8ddab1dc16
commit cf558fbe9b
8 changed files with 123 additions and 10 deletions

View File

@ -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.api.deploy import check_for_remote_update
from openlp.core.common.applocation import AppLocation 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.i18n import LanguageManager, UiStrings, translate
from openlp.core.common.mixins import LogMixin from openlp.core.common.mixins import LogMixin
from openlp.core.common.path import create_paths, resolve from openlp.core.common.path import create_paths, resolve
@ -395,6 +396,51 @@ def backup_if_version_changed(settings):
return True 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(): def main():
""" """
The main function which parses command line options and then runs The main function which parses command line options and then runs
@ -423,6 +469,11 @@ def main():
'lib' / 'QtWebEngineCore.framework' / 'Versions' / '5' / 'lib' / 'QtWebEngineCore.framework' / 'Versions' / '5' /
'Helpers' / 'QtWebEngineProcess.app' / 'Contents' / 'MacOS' / 'Helpers' / 'QtWebEngineProcess.app' / 'Contents' / 'MacOS' /
'QtWebEngineProcess') '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 # Initialise the resources
qInitResources() qInitResources()
# Now create and actually run the application. # Now create and actually run the application.
@ -433,6 +484,15 @@ def main():
application.setOrganizationDomain('openlp.org') application.setOrganizationDomain('openlp.org')
application.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) application.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
application.setAttribute(QtCore.Qt.AA_DontCreateNativeWidgetSiblings, 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: if args.portable:
application.setApplicationName('OpenLPPortable') application.setApplicationName('OpenLPPortable')
Settings.setDefaultFormat(Settings.IniFormat) Settings.setDefaultFormat(Settings.IniFormat)
@ -447,7 +507,6 @@ def main():
portable_path = resolve(portable_path) portable_path = resolve(portable_path)
data_path = portable_path / 'Data' data_path = portable_path / 'Data'
set_up_logging(portable_path / 'Other') set_up_logging(portable_path / 'Other')
set_up_web_engine_cache(portable_path / 'Other' / 'web_cache')
log.info('Running portable') log.info('Running portable')
portable_settings_path = data_path / 'OpenLP.ini' portable_settings_path = data_path / 'OpenLP.ini'
# Make this our settings file # Make this our settings file
@ -463,7 +522,6 @@ def main():
else: else:
application.setApplicationName('OpenLP') application.setApplicationName('OpenLP')
set_up_logging(AppLocation.get_directory(AppLocation.CacheDir)) 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 # Set the libvlc environment variable if we're frozen
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
# Path to libvlc and the plugins # Path to libvlc and the plugins
@ -482,6 +540,23 @@ def main():
# Initialise the Registry # Initialise the Registry
Registry.create() Registry.create()
settings = Settings() 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) Registry().register('settings', settings)
if settings.value('advanced/protect data directory'): if settings.value('advanced/protect data directory'):
# attempt to create a file lock # attempt to create a file lock

View File

@ -156,3 +156,12 @@ class SongFirstSlideMode(IntEnum):
Default = 0 # No cover Default = 0 # No cover
Songbook = 1 Songbook = 1
Footer = 2 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

View File

@ -32,7 +32,7 @@ from tempfile import gettempdir
from PyQt5 import QtCore, QtGui from PyQt5 import QtCore, QtGui
from openlp.core.common import SlideLimits, ThemeLevel 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 DisplayStyle, LanguageSelection, SongFirstSlideMode, SongSearch, PluginStatus
from openlp.core.common.json import OpenLPJSONDecoder, OpenLPJSONEncoder, is_serializable from openlp.core.common.json import OpenLPJSONDecoder, OpenLPJSONEncoder, is_serializable
from openlp.core.common.path import files_to_paths, str_to_path 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/search as type': True,
'advanced/ui_theme_name': UiThemes.Automatic, 'advanced/ui_theme_name': UiThemes.Automatic,
'advanced/delete service item confirmation': False, 'advanced/delete service item confirmation': False,
'advanced/hidpi mode': HiDPIMode.Default,
'alerts/font face': QtGui.QFont().family(), 'alerts/font face': QtGui.QFont().family(),
'alerts/font size': 40, 'alerts/font size': 40,
'alerts/db type': 'sqlite', 'alerts/db type': 'sqlite',
@ -528,6 +529,8 @@ class Settings(QtCore.QSettings):
QtCore.QSettings.__init__(self, Settings.__file_path__, Settings.IniFormat) QtCore.QSettings.__init__(self, Settings.__file_path__, Settings.IniFormat)
else: else:
QtCore.QSettings.__init__(self, *args) QtCore.QSettings.__init__(self, *args)
def init_default_shortcuts(self):
# Add shortcuts here so QKeySequence has a QApplication instance to use. # Add shortcuts here so QKeySequence has a QApplication instance to use.
Settings.__default_settings__.update({ Settings.__default_settings__.update({
'shortcuts/aboutItem': [QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_F1)], 'shortcuts/aboutItem': [QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_F1)],

View File

@ -110,6 +110,22 @@ class AdvancedTab(SettingsTab):
self.prefer_windowed_capture_check_box.setObjectName('prefer_windowed_capture_check_box') self.prefer_windowed_capture_check_box.setObjectName('prefer_windowed_capture_check_box')
self.display_workaround_layout.addWidget(self.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) 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 # Proxies
self.proxy_widget = ProxyWidget(self.right_column) self.proxy_widget = ProxyWidget(self.right_column)
self.right_layout.addWidget(self.proxy_widget) self.right_layout.addWidget(self.proxy_widget)
@ -117,10 +133,11 @@ class AdvancedTab(SettingsTab):
self.left_layout.addStretch() self.left_layout.addStretch()
self.right_layout.addStretch() self.right_layout.addStretch()
# Set up all the connections and things # 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_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_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.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): def retranslate_ui(self):
""" """
@ -153,6 +170,11 @@ class AdvancedTab(SettingsTab):
translate('OpenLP.AdvancedTab', 'Disable display transparency')) translate('OpenLP.AdvancedTab', 'Disable display transparency'))
self.prefer_windowed_capture_check_box.setText( self.prefer_windowed_capture_check_box.setText(
translate('OpenLP.AdvancedTab', 'Prefer window capture instead of screen capture')) 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() self.proxy_widget.retranslate_ui()
def load(self): def load(self):
@ -178,6 +200,7 @@ class AdvancedTab(SettingsTab):
self.data_directory_new_label.hide() self.data_directory_new_label.hide()
self.data_directory_path_edit.hide() self.data_directory_path_edit.hide()
self.data_directory_protect_check_box.setChecked(self.settings.value('advanced/protect data directory')) 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): def save(self):
""" """
@ -193,6 +216,7 @@ class AdvancedTab(SettingsTab):
self.settings_form.register_post_process('config_screen_changed') 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/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/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() self.proxy_widget.save()
def cancel(self): def cancel(self):
@ -287,13 +311,14 @@ class AdvancedTab(SettingsTab):
self.data_directory_cancel_button.hide() self.data_directory_cancel_button.hide()
self.new_data_directory_has_files_label.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. Notify user about required restart.
:param checked: The state of the check box (boolean). :param checked: The state of the check box (boolean).
""" """
QtWidgets.QMessageBox.information(self, translate('OpenLP.AdvancedTab', 'Restart Required'), if self.isVisible():
translate('OpenLP.AdvancedTab', QtWidgets.QMessageBox.information(self, translate('OpenLP.AdvancedTab', 'Restart Required'),
'This change will only take effect once OpenLP ' translate('OpenLP.AdvancedTab',
'has been restarted.')) 'This change will only take effect once OpenLP '
'has been restarted.'))

View File

@ -43,6 +43,6 @@ class SplashScreen(QtWidgets.QSplashScreen):
self.setObjectName('splashScreen') self.setObjectName('splashScreen')
self.setContextMenuPolicy(QtCore.Qt.PreventContextMenu) self.setContextMenuPolicy(QtCore.Qt.PreventContextMenu)
splash_image = QtGui.QPixmap(':/graphics/openlp-splash-screen.png') splash_image = QtGui.QPixmap(':/graphics/openlp-splash-screen.png')
splash_image.setDevicePixelRatio(self.devicePixelRatioF())
self.setPixmap(splash_image) self.setPixmap(splash_image)
self.setMask(splash_image.mask())
self.resize(370, 370) self.resize(370, 370)

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 166 KiB

View File

@ -75,6 +75,7 @@ def settings(qapp, registry):
Settings().setDefaultFormat(QtCore.QSettings.Format.IniFormat) Settings().setDefaultFormat(QtCore.QSettings.Format.IniFormat)
# Needed on windows to make sure a Settings object is available during the tests # Needed on windows to make sure a Settings object is available during the tests
sets = Settings() sets = Settings()
sets.init_default_shortcuts()
sets.setValue('themes/global theme', 'my_theme') sets.setValue('themes/global theme', 'my_theme')
registry.register('settings', sets) registry.register('settings', sets)
registry.register('settings_thread', sets) registry.register('settings_thread', sets)