diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index ec8fe6358f..5b7ea82709 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -28,6 +28,7 @@ from calibre.constants import ( islinux, ismacos, iswindows, isxp, numeric_version, plugins_loc ) from calibre.ebooks.metadata import MetaInformation +from calibre.gui2.geometry import geometry_for_restore_as_dict from calibre.gui2.linux_file_dialogs import ( check_for_linux_native_dialogs, linux_native_dialog ) @@ -44,7 +45,7 @@ from calibre.utils.resources import user_dir from polyglot import queue from polyglot.builtins import iteritems, string_or_bytes -del pqc +del pqc, geometry_for_restore_as_dict NO_URL_FORMATTING = QUrl.UrlFormattingOption.None_ diff --git a/src/calibre/gui2/geometry.py b/src/calibre/gui2/geometry.py new file mode 100644 index 0000000000..ac1d0652a5 --- /dev/null +++ b/src/calibre/gui2/geometry.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2022, Kovid Goyal + + +# See https://bugreports.qt.io/browse/QTBUG-69104 for why we need to implement +# our own restore geometry + + +from qt.core import QRect, QScreen, QSize, QWidget, QApplication, Qt + + +def size_as_dict(self: QSize): + return {'width': self.width(), 'height': self.height()} + + +def rect_as_dict(self: QRect): + return {'x': self.left(), 'y': self.top(), 'width': self.width(), 'height': self.height()} + + +def screen_as_dict(self: QScreen): + try: + num = QApplication.instance().screens().index(self) + except Exception: + num = -1 + return { + 'name': self.name(), + 'serial': self.serialNumber(), + 'manufacturer': self.manufacturer(), + 'model': self.model(), + 'depth': self.depth(), + 'device_pixel_ratio': self.devicePixelRatio(), + 'size_in_logical_pixels': size_as_dict(self.size()), + 'geometry_in_logical_pixels': rect_as_dict(self.geometry()), + 'index_in_screens_list': num, + } + + +def geometry_for_restore_as_dict(self: QWidget): + s = self.screen() + if s is None: + return {} + return { + 'screen': screen_as_dict(s), + 'geometry': rect_as_dict(self.geometry()), + 'frame_geometry': rect_as_dict(self.frameGeometry()), + 'normal_geometry': rect_as_dict(self.normalGeometry()), + 'maximized': self.isMaximized(), + 'full_screened': self.isFullScreen(), + } + + +def save_geometry(self: QWidget, prefs: dict, name: str): + x = geometry_for_restore_as_dict(self) + if x: + prefs.set(f'geometry-of-{name}', x) + + +def find_matching_screen(screen_as_dict): + screens = QApplication.instance().screens() + size = QSize(**screen_as_dict['size']) + screens_of_matching_size = tuple( + s for s in screens if + s.size() == size and s.devicePixelRatio() == screen_as_dict['device_pixel_ratio']) + if screen_as_dict['serial']: + for q in screens_of_matching_size: + if q.serialNumber() == screen_as_dict['serial']: + return q + + def match(key, val): + v = screen_as_dict[key] + return bool(not v or v == val) + + for q in screens_of_matching_size: + if match('name', q.name()) and match('manufacturer', q.manufacturer()) and match('model', q.model()): + return q + + +def _restore_geometry(self: QWidget, prefs: dict, name: str) -> bool: + x = prefs.get(f'geometry-of-{name}') + if not x: + return False + s = find_matching_screen(x['screen']) + if s is None: + p = self.nativeParentWidget() + if p is not None: + s = p.screen() + if s is None: + s = self.screen() + if s is None: + s = QApplication.instance().primaryScreen() + if s is None: + return False + saved_geometry = QRect(**x['geometry']) + saved_frame_geometry = QRect(**x['frame_geometry']) + if not saved_geometry.isValid() or not saved_frame_geometry.isValid(): + return False + frame_height = max(0, saved_frame_geometry.height() - saved_geometry.height()) + frame_width = max(0, saved_frame_geometry.width() - saved_geometry.width()) + available_geometry = s.availableGeometry() + available_size = QSize(available_geometry.width() - frame_width, available_geometry.height() - frame_height) + sz = QSize(min(saved_geometry.width(), available_size.width()), min(saved_geometry.height(), available_size.height())) + if not sz.isValid(): + return False + max_left = available_geometry.left() + (available_size.width() - sz.width()) + max_top = available_geometry.top() + (available_size.height() - sz.height()) + geometry = QRect(min(saved_geometry.left(), max_left), min(saved_geometry.top(), max_top), sz.width(), sz.height()) + ws = self.windowState() + if ws & (Qt.WindowState.WindowFullScreen | Qt.WindowState.WindowMaximized): + return True + self.setGeometry(geometry) + if x['full_screened']: + self.showFullScreen() + elif x['maximized']: + self.showMaximized() + + +def restore_geometry(self: QWidget, prefs: dict, name: str) -> bool: + if restore_geometry(self, prefs, name): + return True + sz = self.sizeHint() + if sz.isValid(): + self.resize(self.sizeHint()) + return False + + +QWidget.save_geometry = save_geometry +QWidget.restore_geometry = restore_geometry