From 73221cef9bef3d29afea07304cb7f72c4ffc2ff9 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 21 Jul 2020 20:05:59 +0000 Subject: [PATCH] Remove and add screens without reloading screens. Do not show screen-change-popup when the settings form is visible. --- openlp/core/app.py | 6 +- openlp/core/display/screens.py | 128 ++++++++++++------ openlp/core/ui/mainwindow.py | 13 ++ openlp/core/ui/slidecontroller.py | 9 +- openlp/core/widgets/layouts.py | 4 +- .../openlp_core/display/test_screens.py | 16 +-- .../openlp_core/ui/test_mainwindow.py | 14 +- .../presentations/test_pdfcontroller.py | 13 +- 8 files changed, 138 insertions(+), 65 deletions(-) diff --git a/openlp/core/app.py b/openlp/core/app.py index cddc888da..addfb74b2 100644 --- a/openlp/core/app.py +++ b/openlp/core/app.py @@ -83,7 +83,7 @@ class OpenLP(QtCore.QObject, LogMixin): self.server.close_server() return result - def run(self, args): + def run(self, args, app): """ Run the OpenLP application. @@ -97,7 +97,7 @@ class OpenLP(QtCore.QObject, LogMixin): args.remove('OpenLP') self.args.extend(args) # Decide how many screens we have and their size - screens = ScreenList.create(QtWidgets.QApplication.desktop()) + screens = ScreenList.create(app) # First time checks in settings has_run_wizard = self.settings.value('core/has run wizard') if not has_run_wizard: @@ -435,4 +435,4 @@ def main(): log.debug('Could not find translators.') if args and not args.no_error_form: sys.excepthook = app.hook_exception - sys.exit(app.run(qt_args)) + sys.exit(app.run(qt_args, application)) diff --git a/openlp/core/display/screens.py b/openlp/core/display/screens.py index c73e2ae26..34b915e31 100644 --- a/openlp/core/display/screens.py +++ b/openlp/core/display/screens.py @@ -23,6 +23,7 @@ The :mod:`screen` module provides management functionality for a machines' displays. """ import logging +import copy from functools import cmp_to_key from PyQt5 import QtCore, QtWidgets @@ -77,7 +78,14 @@ class Screen(object): """ Returns the geometry to use when displaying. This property decides between the native and custom geometries """ - return self.custom_geometry or self.geometry + # If custom geometry is used, convert to absolute position + if self.custom_geometry: + adjusted_custom_geometry = copy.deepcopy(self.custom_geometry) + adjusted_custom_geometry.moveTo(self.geometry.x() + adjusted_custom_geometry.x(), + self.geometry.y() + adjusted_custom_geometry.y()) + return adjusted_custom_geometry + else: + return self.geometry @classmethod def from_dict(cls, screen_dict): @@ -147,6 +155,13 @@ class Screen(object): screen_dict['custom_geometry']['width'], screen_dict['custom_geometry']['height']) + def on_geometry_changed(self, geometry): + """ + Callback function for when the screens geometry changes + """ + self.geometry = geometry + Registry().execute('config_screen_changed') + class ScreenList(metaclass=Singleton): """ @@ -202,37 +217,66 @@ class ScreenList(metaclass=Singleton): return None @classmethod - def create(cls, desktop): + def create(cls, application): """ Initialise the screen list. - :param desktop: A QDesktopWidget object. + :param desktop: A QApplication object. """ screen_list = cls() - screen_list.desktop = desktop - screen_list.desktop.resized.connect(screen_list.on_screen_resolution_changed) - screen_list.desktop.screenCountChanged.connect(screen_list.on_screen_count_changed) - screen_list.desktop.primaryScreenChanged.connect(screen_list.on_primary_screen_changed) + screen_list.application = application + screen_list.application.primaryScreenChanged.connect(screen_list.on_primary_screen_changed) + screen_list.application.screenAdded.connect(screen_list.on_screen_added) + screen_list.application.screenRemoved.connect(screen_list.on_screen_removed) screen_list.update_screens() cls.settings = Registry().get('settings') screen_list.load_screen_settings() return screen_list + def find_new_display_screen(self): + """ + If more than 1 screen, set first non-primary screen to display, otherwise just set the available screen as + display. + """ + if len(self) > 1: + for screen in self: + if not screen.is_primary: + screen.is_display = True + break + else: + self[0].is_display = True + def load_screen_settings(self): """ Loads the screen size and the screen number from the settings. """ screen_settings = self.settings.value('core/screens') if screen_settings: + need_new_display_screen = False for number, screen_dict in screen_settings.items(): # Sometimes this loads as a string instead of an int number = int(number) - if self.has_screen(number): + # Compare geometry, primarity of screen from settings with avilable screens + if self.has_screen(screen_dict): + # If match was found, we're all happy, update with custom geometry, display info, if available self[number].update(screen_dict) else: - self.screens.append(Screen.from_dict(screen_dict)) + # If no match, ignore this screen, also need to find new display screen if the discarded screen was + # marked as such. + if screen_dict['is_display']: + need_new_display_screen = True + if need_new_display_screen: + QtWidgets.QMessageBox.warning(None, translate('OpenLP.Screen', + 'Screen settings and screen setup is not the same'), + translate('OpenLP.Screen', + 'There is a mismatch between screens and screen settings. ' + 'OpenLP will try to automatically select a display screen, but ' + 'you should consider updating the screen settings.'), + QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok)) + self.find_new_display_screen() else: - self[len(self) - 1].is_display = True + # if no settings we need to set a display + self.find_new_display_screen() def save_screen_settings(self): """ @@ -285,14 +329,15 @@ class ScreenList(metaclass=Singleton): if can_save: self.save_screen_settings() - def has_screen(self, number): + def has_screen(self, screen_dict): """ Confirms a screen is known. - :param number: The screen number (int). + :param screen_dict: The dict descrebing the screen. """ for screen in self.screens: - if screen.number == number: + if screen.to_dict()['geometry'] == screen_dict['geometry'] \ + and screen.is_primary == screen_dict['is_primary']: return True return False @@ -316,39 +361,44 @@ class ScreenList(metaclass=Singleton): else: return 0 self.screens = [] - os_screens = QtWidgets.QApplication.screens() + os_screens = self.application.screens() os_screens.sort(key=cmp_to_key(_screen_compare)) for number, screen in enumerate(os_screens): self.screens.append( - Screen(number, screen.geometry(), is_primary=self.desktop.primaryScreen() == number)) + Screen(number, screen.geometry(), is_primary=self.application.primaryScreen() == screen)) + screen.geometryChanged.connect(self.screens[-1].on_geometry_changed) - def on_screen_resolution_changed(self, number): + def on_screen_added(self, changed_screen): """ - Called when the resolution of a screen has changed. + Called when a screen has been added - ``number`` - The number of the screen, which size has changed. + :param changed_screen: The screen which has been plugged. """ - log.info('screen_resolution_changed {number:d}'.format(number=number)) + number = len(self.screens) + self.screens.append(Screen(number, changed_screen.geometry(), + is_primary=self.application.primaryScreen() == changed_screen)) + changed_screen.geometryChanged.connect(self.screens[-1].on_geometry_changed) + Registry().execute('config_screen_changed') + + def on_screen_removed(self, removed_screen): + """ + Called when a screen has been removed. + + :param changed_screen: The screen which has been unplugged. + """ + # Remove screens + removed_screen_number = -1 for screen in self.screens: - if number == screen.number: - screen.geometry = self.desktop.screenGeometry(number) - screen.is_primary = self.desktop.primaryScreen() == number - Registry().execute('config_screen_changed') - break - - def on_screen_count_changed(self, changed_screen=None): - """ - Called when a screen has been added or removed. - - ``changed_screen`` - The screen's number which has been (un)plugged. - """ - screen_count = self.desktop.screenCount() - log.info('screen_count_changed {count:d}'.format(count=screen_count)) - # Update the list of screens - self.update_screens() - # Reload setting tabs to apply possible changes. + # once the screen that must be removed has been found, update numbering + if removed_screen_number >= 0: + screen.number -= 1 + # find the screen that is removed + if removed_screen.geometry() == screen.geometry: + removed_screen_number = screen.number + removed_screen_is_display = self.screens[removed_screen_number].is_display + self.screens.pop(removed_screen_number) + if removed_screen_is_display: + self.find_new_display_screen() Registry().execute('config_screen_changed') def on_primary_screen_changed(self): @@ -356,5 +406,5 @@ class ScreenList(metaclass=Singleton): The primary screen has changed, let's sort it out and then notify everyone """ for screen in self.screens: - screen.is_primary = self.desktop.primaryScreen() == screen.number + screen.is_primary = self.desktop.primaryScreen().geometry() == screen.geometry Registry().execute('config_screen_changed') diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index baa9a5eaa..b4ba0b74a 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -521,6 +521,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert # Media Manager self.media_tool_box.currentChanged.connect(self.on_media_tool_box_changed) self.application.set_busy_cursor() + # Timestamp for latest screen-change-popup. Used to prevent spamming the user with popups + self.screen_change_timestamp = None # Simple message boxes Registry().register_function('theme_update_global', self.default_theme_changed) Registry().register_function('config_screen_changed', self.screen_changed) @@ -999,6 +1001,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert self.setFocus() self.activateWindow() self.application.set_normal_cursor() + # if a warning has been shown within the last 5 seconds, skip showing again to avoid spamming user, + # also do not show if the settings window is visible + if not self.settings_form.isVisible() and \ + not self.screen_change_timestamp or (datetime.now() - self.screen_change_timestamp).seconds > 5: + QtWidgets.QMessageBox.warning(self, translate('OpenLP.MainWindow', 'Screen setup has changed'), + translate('OpenLP.MainWindow', + 'The screen setup has changed. ' + 'OpenLP will try to automatically select a display screen, but ' + 'you should consider updating the screen settings.'), + QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok)) + self.screen_change_timestamp = datetime.now() def closeEvent(self, event): """ diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 61f04eb10..7dd56441e 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -661,9 +661,16 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties): """ Settings dialog has changed the screen size of adjust output and screen previews. """ + size = self.screens.current.display_geometry.size() if self.is_live and self.displays: for display in self.displays: - display.resize(self.screens.current.display_geometry.size()) + display.resize(size) + old_preview_width = self.preview_display.size().width() + scale = old_preview_width / size.width() + new_preview_size = size * scale + self.ratio = self.screens.current.display_geometry.width() / self.screens.current.display_geometry.height() + self.preview_display.resize(new_preview_size) + self.slide_layout.set_aspect_ratio(self.ratio) def __add_actions_to_widget(self, widget): """ diff --git a/openlp/core/widgets/layouts.py b/openlp/core/widgets/layouts.py index a36286e92..313c78f7e 100644 --- a/openlp/core/widgets/layouts.py +++ b/openlp/core/widgets/layouts.py @@ -60,8 +60,10 @@ class AspectRatioLayout(QtWidgets.QLayout): :param float aspect_ratio: The aspect ratio to set """ - # TODO: Update the layout/widget if this changes self._aspect_ratio = aspect_ratio + # Update the layout/widget + geo = self.geometry() + self.setGeometry(geo) aspect_ratio = property(get_aspect_ratio, set_aspect_ratio) diff --git a/tests/functional/openlp_core/display/test_screens.py b/tests/functional/openlp_core/display/test_screens.py index cae3bc010..0ee07909f 100644 --- a/tests/functional/openlp_core/display/test_screens.py +++ b/tests/functional/openlp_core/display/test_screens.py @@ -74,17 +74,15 @@ def test_create_screen_list(mocked_screens, settings): """ Create the screen list """ - # GIVEN: Mocked desktop - mocked_desktop = MagicMock() - mocked_desktop.screenCount.return_value = 2 - mocked_desktop.primaryScreen.return_value = 0 - mocked_screens.return_value = [ - MagicMock(**{'geometry.return_value': QtCore.QRect(0, 0, 1024, 768)}), - MagicMock(**{'geometry.return_value': QtCore.QRect(1024, 0, 1024, 768)}) - ] + # GIVEN: Mocked application + mocked_application = MagicMock() + mocked_screen1 = MagicMock(**{'geometry.return_value': QtCore.QRect(0, 0, 1024, 768)}) + mocked_screen2 = MagicMock(**{'geometry.return_value': QtCore.QRect(1024, 0, 1024, 768)}) + mocked_application.screens.return_value = [mocked_screen1, mocked_screen2] + mocked_application.primaryScreen.return_value = mocked_screen1 # WHEN: create() is called - screen_list = ScreenList.create(mocked_desktop) + screen_list = ScreenList.create(mocked_application) # THEN: The correct screens have been set up assert screen_list.screens[0].number == 0 diff --git a/tests/functional/openlp_core/ui/test_mainwindow.py b/tests/functional/openlp_core/ui/test_mainwindow.py index bcf4e2d3a..4376ae6f9 100644 --- a/tests/functional/openlp_core/ui/test_mainwindow.py +++ b/tests/functional/openlp_core/ui/test_mainwindow.py @@ -47,7 +47,7 @@ def _create_mock_action(parent, name, **kwargs): @pytest.yield_fixture() -def main_window(state, settings): +def main_window(state, settings, mocked_qapp): app = Registry().get('application') app.set_busy_cursor = MagicMock() app.set_normal_cursor = MagicMock() @@ -59,11 +59,13 @@ def main_window(state, settings): mocked_add_toolbar_action.side_effect = _create_mock_action renderer_patcher = patch('openlp.core.display.render.Renderer') renderer_patcher.start() - mocked_desktop = MagicMock() - mocked_desktop.screenCount.return_value = 1 - mocked_desktop.screenGeometry.return_value = QtCore.QRect(0, 0, 1024, 768) - mocked_desktop.primaryScreen.return_value = 1 - ScreenList.create(mocked_desktop) + mocked_screen = MagicMock() + mocked_screen.geometry.return_value = QtCore.QRect(0, 0, 1024, 768) + mocked_qapp.screens = MagicMock() + mocked_qapp.screens.return_value = [mocked_screen] + mocked_qapp.primaryScreen = MagicMock() + mocked_qapp.primaryScreen.return_value = mocked_screen + ScreenList.create(mocked_qapp) mainwindow = MainWindow() yield mainwindow del mainwindow diff --git a/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py index 7ef4b0088..cba26003b 100644 --- a/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py +++ b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py @@ -38,14 +38,15 @@ from tests.utils.constants import RESOURCE_PATH @pytest.yield_fixture() -def pdf_env(settings, mock_plugin): +def pdf_env(settings, mock_plugin, mocked_qapp): temp_folder_path = Path(mkdtemp()) thumbnail_folder_path = Path(mkdtemp()) - desktop = MagicMock() - desktop.primaryScreen.return_value = SCREEN['primary'] - desktop.screenCount.return_value = SCREEN['number'] - desktop.screenGeometry.return_value = SCREEN['size'] - ScreenList.create(desktop) + mocked_screen = MagicMock() + mocked_screen.geometry.return_value = QtCore.QRect(0, 0, 1024, 768) + mocked_qapp.screens.return_value = [mocked_screen] + mocked_qapp.primaryScreen = MagicMock() + mocked_qapp.primaryScreen.return_value = mocked_screen + ScreenList.create(mocked_qapp) yield settings, mock_plugin, temp_folder_path, thumbnail_folder_path rmtree(thumbnail_folder_path) rmtree(temp_folder_path)