From 92dea988c01e74596cc6ed698e88ac56df392c14 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 19 Jul 2023 17:07:04 +0200 Subject: Initial upgrade to new PyQt6 stubs --- qutebrowser/browser/browsertab.py | 8 +++- qutebrowser/browser/downloadview.py | 12 +++-- qutebrowser/browser/inspector.py | 9 ++-- qutebrowser/browser/network/pac.py | 1 + qutebrowser/browser/network/proxy.py | 4 +- qutebrowser/browser/webengine/notification.py | 4 +- qutebrowser/browser/webengine/tabhistory.py | 4 +- qutebrowser/browser/webengine/webview.py | 4 +- qutebrowser/completion/completiondelegate.py | 1 + qutebrowser/completion/completionwidget.py | 40 ++++++++++++----- qutebrowser/config/configfiles.py | 4 +- qutebrowser/keyinput/eventfilter.py | 5 ++- qutebrowser/mainwindow/prompt.py | 3 ++ qutebrowser/mainwindow/statusbar/command.py | 3 +- qutebrowser/mainwindow/statusbar/url.py | 4 +- qutebrowser/mainwindow/tabbedbrowser.py | 33 +++++++++----- qutebrowser/mainwindow/tabwidget.py | 28 ++++++++---- qutebrowser/misc/consolewidget.py | 1 + qutebrowser/misc/guiprocess.py | 4 +- qutebrowser/misc/ipc.py | 6 +-- qutebrowser/misc/miscwidgets.py | 28 +++++++++--- qutebrowser/misc/nativeeventfilter.py | 5 ++- qutebrowser/utils/debug.py | 1 + qutebrowser/utils/log.py | 18 +++++++- qutebrowser/utils/qtutils.py | 65 +++++++++++++++++++++++++-- qutebrowser/utils/standarddir.py | 4 +- qutebrowser/utils/urlutils.py | 6 +-- qutebrowser/utils/utils.py | 13 ++++-- qutebrowser/utils/version.py | 7 ++- 29 files changed, 244 insertions(+), 81 deletions(-) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 47cba5922..eacd61dbb 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -286,10 +286,16 @@ class AbstractPrinting(QObject): """ raise NotImplementedError + def _do_print(self) -> None: + assert self._dialog is not None + printer = self._dialog.printer() + assert printer is not None + self.to_printer(printer) + def show_dialog(self) -> None: """Print with a QPrintDialog.""" self._dialog = dialog = QPrintDialog(self._tab) - self._dialog.open(lambda: self.to_printer(dialog.printer())) + self._dialog.open(self._do_print) # Gets cleaned up in on_printing_finished diff --git a/qutebrowser/browser/downloadview.py b/qutebrowser/browser/downloadview.py index f4790bc9f..8f04f8d9d 100644 --- a/qutebrowser/browser/downloadview.py +++ b/qutebrowser/browser/downloadview.py @@ -78,10 +78,11 @@ class DownloadView(QListView): def __repr__(self): model = self.model() - if model is None: - count = 'None' # type: ignore[unreachable] - else: + count: Union[int, str] + if qtutils.is_not_none(model): count = model.rowCount() + else: + count = 'None' return utils.get_repr(self, count=count) def _model(self) -> downloads.DownloadModel: @@ -173,9 +174,12 @@ class DownloadView(QListView): assert name is not None assert handler is not None action = self._menu.addAction(name) + assert action is not None action.triggered.connect(handler) if actions: - self._menu.popup(self.viewport().mapToGlobal(point)) + viewport = self.viewport() + assert viewport is not None + self._menu.popup(viewport.mapToGlobal(point)) def minimumSizeHint(self): """Override minimumSizeHint so the size is correct in a layout.""" diff --git a/qutebrowser/browser/inspector.py b/qutebrowser/browser/inspector.py index a2ce67750..15ff6c48a 100644 --- a/qutebrowser/browser/inspector.py +++ b/qutebrowser/browser/inspector.py @@ -28,7 +28,7 @@ from qutebrowser.qt.gui import QCloseEvent from qutebrowser.browser import eventfilter from qutebrowser.config import configfiles, config -from qutebrowser.utils import log, usertypes +from qutebrowser.utils import log, usertypes, qtutils from qutebrowser.keyinput import modeman from qutebrowser.misc import miscwidgets @@ -70,8 +70,9 @@ class _EventFilter(QObject): clicked = pyqtSignal() - def eventFilter(self, _obj: QObject, event: QEvent) -> bool: + def eventFilter(self, _obj: Optional[QObject], event: Optional[QEvent]) -> bool: """Translate mouse presses to a clicked signal.""" + assert event is not None if event.type() == QEvent.Type.MouseButtonPress: self.clicked.emit() return False @@ -162,7 +163,7 @@ class AbstractWebInspector(QWidget): self.shutdown() return elif position == Position.window: - self.setParent(None) # type: ignore[call-overload] + self.setParent(qtutils.allow_none(None)) self._load_state_geometry() else: self._splitter.set_inspector(self, position) @@ -195,7 +196,7 @@ class AbstractWebInspector(QWidget): if not ok: log.init.warning("Error while loading geometry.") - def closeEvent(self, _e: QCloseEvent) -> None: + def closeEvent(self, _e: Optional[QCloseEvent]) -> None: """Save the geometry when closed.""" data = self._widget.saveGeometry().data() geom = base64.b64encode(data).decode('ASCII') diff --git a/qutebrowser/browser/network/pac.py b/qutebrowser/browser/network/pac.py index fcca5d45d..54c1195af 100644 --- a/qutebrowser/browser/network/pac.py +++ b/qutebrowser/browser/network/pac.py @@ -277,6 +277,7 @@ class PACFetcher(QObject): """Fetch the proxy from the remote URL.""" assert self._manager is not None self._reply = self._manager.get(QNetworkRequest(self._pac_url)) + assert self._reply is not None self._reply.finished.connect(self._finish) @pyqtSlot() diff --git a/qutebrowser/browser/network/proxy.py b/qutebrowser/browser/network/proxy.py index 714823d2c..0351adf0e 100644 --- a/qutebrowser/browser/network/proxy.py +++ b/qutebrowser/browser/network/proxy.py @@ -21,7 +21,7 @@ from qutebrowser.qt.core import QUrl, pyqtSlot from qutebrowser.qt.network import QNetworkProxy, QNetworkProxyFactory from qutebrowser.config import config, configtypes -from qutebrowser.utils import message, usertypes, urlutils, utils +from qutebrowser.utils import message, usertypes, urlutils, utils, qtutils from qutebrowser.misc import objects from qutebrowser.browser.network import pac @@ -51,7 +51,7 @@ def _warn_for_pac(): @pyqtSlot() def shutdown(): QNetworkProxyFactory.setApplicationProxyFactory( - None) # type: ignore[arg-type] + qtutils.allow_none(None)) class ProxyFactory(QNetworkProxyFactory): diff --git a/qutebrowser/browser/webengine/notification.py b/qutebrowser/browser/webengine/notification.py index 4d08e914e..d101c616c 100644 --- a/qutebrowser/browser/webengine/notification.py +++ b/qutebrowser/browser/webengine/notification.py @@ -1108,7 +1108,9 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): if padding and self._quirks.no_padded_images: return None - bits = qimage.constBits().asstring(size) + bits_ptr = qimage.constBits() + assert bits_ptr is not None + bits = bits_ptr.asstring(size) image_data.add(QByteArray(bits)) image_data.endStructure() diff --git a/qutebrowser/browser/webengine/tabhistory.py b/qutebrowser/browser/webengine/tabhistory.py index 30f3facdb..2848142ef 100644 --- a/qutebrowser/browser/webengine/tabhistory.py +++ b/qutebrowser/browser/webengine/tabhistory.py @@ -153,6 +153,8 @@ def serialize(items): for item in items: _serialize_item(item, stream) - stream.device().reset() + dev = stream.device() + assert dev is not None + dev.reset() qtutils.check_qdatastream(stream) return stream, data, cur_user_data diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 9d58c0379..ade008143 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -54,7 +54,9 @@ class WebEngineView(QWebEngineView): self._win_id = win_id self._tabdata = tabdata - theme_color = self.style().standardPalette().color(QPalette.ColorRole.Base) + style = self.style() + assert style is not None + theme_color = style.standardPalette().color(QPalette.ColorRole.Base) if private: assert webenginesettings.private_profile is not None profile = webenginesettings.private_profile diff --git a/qutebrowser/completion/completiondelegate.py b/qutebrowser/completion/completiondelegate.py index e497e1204..cc5859ca6 100644 --- a/qutebrowser/completion/completiondelegate.py +++ b/qutebrowser/completion/completiondelegate.py @@ -291,6 +291,7 @@ class CompletionItemDelegate(QStyledItemDelegate): self._opt = QStyleOptionViewItem(option) self.initStyleOption(self._opt, index) self._style = self._opt.widget.style() + assert self._style is not None self._get_textdoc(index) assert self._doc is not None diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index 01527e763..665757e89 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -156,6 +156,15 @@ class CompletionView(QTreeView): assert isinstance(model, completionmodel.CompletionModel), model return model + def _selection_model(self) -> QItemSelectionModel: + """Get the current selection model. + + Ensures the model is not None. + """ + model = self.selectionModel() + assert model is not None + return model + @pyqtSlot(str) def _on_config_changed(self, option): if option in ['completion.height', 'completion.shrink']: @@ -169,7 +178,9 @@ class CompletionView(QTreeView): column_widths = self._model().column_widths pixel_widths = [(width * perc // 100) for perc in column_widths] - delta = self.verticalScrollBar().sizeHint().width() + bar = self.verticalScrollBar() + assert bar is not None + delta = bar.sizeHint().width() for i, width in reversed(list(enumerate(pixel_widths))): if width > delta: pixel_widths[i] -= delta @@ -191,7 +202,7 @@ class CompletionView(QTreeView): A QModelIndex. """ model = self._model() - idx = self.selectionModel().currentIndex() + idx = self._selection_model().currentIndex() if not idx.isValid(): # No item selected yet if upwards: @@ -223,7 +234,7 @@ class CompletionView(QTreeView): Return: A QModelIndex. """ - old_idx = self.selectionModel().currentIndex() + old_idx = self._selection_model().currentIndex() idx = old_idx model = self._model() @@ -267,7 +278,7 @@ class CompletionView(QTreeView): Return: A QModelIndex. """ - idx = self.selectionModel().currentIndex() + idx = self._selection_model().currentIndex() model = self._model() if not idx.isValid(): return self._next_idx(upwards).sibling(0, 0) @@ -323,7 +334,7 @@ class CompletionView(QTreeView): if not self._active: return - selmodel = self.selectionModel() + selmodel = self._selection_model() indices = { 'next': lambda: self._next_idx(upwards=False), 'prev': lambda: self._next_idx(upwards=True), @@ -363,9 +374,10 @@ class CompletionView(QTreeView): Args: model: The model to use. """ - if self.model() is not None and model is not self.model(): - self.model().deleteLater() - self.selectionModel().deleteLater() + old_model = self.model() + if old_model is not None and model is not old_model: + old_model.deleteLater() + self._selection_model().deleteLater() self.setModel(model) @@ -395,7 +407,7 @@ class CompletionView(QTreeView): self.pattern = pattern with debug.log_time(log.completion, 'Set pattern {}'.format(pattern)): self._model().set_pattern(pattern) - self.selectionModel().clear() + self._selection_model().clear() self._maybe_update_geometry() self._maybe_show() @@ -415,7 +427,7 @@ class CompletionView(QTreeView): def on_clear_completion_selection(self): """Clear the selection model when an item is activated.""" self.hide() - selmod = self.selectionModel() + selmod = self._selection_model() if selmod is not None: selmod.clearSelection() selmod.clearCurrentIndex() @@ -426,14 +438,18 @@ class CompletionView(QTreeView): confheight = str(config.val.completion.height) if confheight.endswith('%'): perc = int(confheight.rstrip('%')) - height = self.window().height() * perc // 100 + window = self.window() + assert window is not None + height = window.height() * perc // 100 else: height = int(confheight) # Shrink to content size if needed and shrinking is enabled if config.val.completion.shrink: + bar = self.horizontalScrollBar() + assert bar is not None contents_height = ( self.viewportSizeHint().height() + - self.horizontalScrollBar().sizeHint().height()) + bar.sizeHint().height()) if contents_height <= height: height = contents_height # The width isn't really relevant as we're expanding anyways. diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py index 2a13133ae..ac593cfae 100644 --- a/qutebrowser/config/configfiles.py +++ b/qutebrowser/config/configfiles.py @@ -108,7 +108,9 @@ class StateConfig(configparser.ConfigParser): for sect, key in deleted_keys: self[sect].pop(key, None) - self['general']['qt_version'] = qVersion() + qt_version = qVersion() + assert qt_version is not None + self['general']['qt_version'] = qt_version self['general']['qtwe_version'] = self._qtwe_version_str() self['general']['chromium_version'] = self._chromium_version_str() self['general']['version'] = qutebrowser.__version__ diff --git a/qutebrowser/keyinput/eventfilter.py b/qutebrowser/keyinput/eventfilter.py index 3f8d2779f..40581b3c1 100644 --- a/qutebrowser/keyinput/eventfilter.py +++ b/qutebrowser/keyinput/eventfilter.py @@ -17,7 +17,7 @@ """Global Qt event filter which dispatches key events.""" -from typing import cast +from typing import cast, Optional from qutebrowser.qt.core import pyqtSlot, QObject, QEvent from qutebrowser.qt.gui import QKeyEvent, QWindow @@ -75,7 +75,7 @@ class EventFilter(QObject): # No window available yet, or not a MainWindow return False - def eventFilter(self, obj: QObject, event: QEvent) -> bool: + def eventFilter(self, obj: Optional[QObject], event: Optional[QEvent]) -> bool: """Handle an event. Args: @@ -85,6 +85,7 @@ class EventFilter(QObject): Return: True if the event should be filtered, False if it's passed through. """ + assert event is not None ev_type = event.type() if self._log_qt_events: diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 26a2ae886..17772b2ea 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -303,6 +303,7 @@ class PromptContainer(QWidget): item = self._layout.takeAt(0) if item is not None: widget = item.widget() + assert widget is not None log.prompt.debug("Deleting old prompt {}".format(widget)) widget.hide() widget.deleteLater() @@ -366,6 +367,7 @@ class PromptContainer(QWidget): item = self._layout.takeAt(0) if item is not None: widget = item.widget() + assert widget is not None log.prompt.debug("Deleting prompt {}".format(widget)) widget.hide() widget.deleteLater() @@ -780,6 +782,7 @@ class FilenamePrompt(_BasePrompt): # This duplicates some completion code, but I don't see a nicer way... assert which in ['prev', 'next'], which selmodel = self._file_view.selectionModel() + assert selmodel is not None parent = self._file_view.rootIndex() first_index = self._file_model.index(0, 0, parent) diff --git a/qutebrowser/mainwindow/statusbar/command.py b/qutebrowser/mainwindow/statusbar/command.py index b9c78d623..68bacd0b0 100644 --- a/qutebrowser/mainwindow/statusbar/command.py +++ b/qutebrowser/mainwindow/statusbar/command.py @@ -264,13 +264,14 @@ class Command(misc.CommandLineEdit): text = cast(str, text) super().setText(text) - def keyPressEvent(self, e: QKeyEvent) -> None: + def keyPressEvent(self, e: Optional[QKeyEvent]) -> None: """Override keyPressEvent to ignore Return key presses, and add Shift-Ins. If this widget is focused, we are in passthrough key mode, and Enter/Shift+Enter/etc. will cause QLineEdit to think it's finished without command_accept to be called. """ + assert e is not None if machinery.IS_QT5: # FIXME:v4 needed for Qt 5 typing shift = cast(Qt.KeyboardModifiers, Qt.KeyboardModifier.ShiftModifier) else: diff --git a/qutebrowser/mainwindow/statusbar/url.py b/qutebrowser/mainwindow/statusbar/url.py index 54faf232d..7892b3e83 100644 --- a/qutebrowser/mainwindow/statusbar/url.py +++ b/qutebrowser/mainwindow/statusbar/url.py @@ -116,7 +116,9 @@ class UrlText(textbase.TextBase): if old_urltype != self._urltype: # We can avoid doing an unpolish here because the new style will # always override the old one. - self.style().polish(self) + style = self.style() + assert style is not None + style.polish(self) @pyqtSlot(usertypes.LoadStatus) def on_load_status_changed(self, status): diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index da3392a7e..918434eae 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -249,7 +249,7 @@ class TabbedBrowser(QWidget): self.search_options: Mapping[str, Any] = {} self._local_marks: MutableMapping[QUrl, MutableMapping[str, QPoint]] = {} self._global_marks: MutableMapping[str, Tuple[QPoint, QUrl]] = {} - self.default_window_icon = self.widget.window().windowIcon() + self.default_window_icon = self._window().windowIcon() self.is_private = private self.tab_deque = TabDeque() config.instance.changed.connect(self._on_config_changed) @@ -302,11 +302,10 @@ class TabbedBrowser(QWidget): widgets = [] for i in range(self.widget.count()): widget = self.widget.widget(i) - if widget is None: - log.webview.debug( # type: ignore[unreachable] - "Got None-widget in tabbedbrowser!") - else: + if qtutils.is_not_none(widget): widgets.append(widget) + else: + log.webview.debug("Got None-widget in tabbedbrowser!") return widgets def _update_window_title(self, field=None): @@ -330,7 +329,8 @@ class TabbedBrowser(QWidget): fields['id'] = self._win_id title = title_format.format(**fields) - self.widget.window().setWindowTitle(title) + + self._window().setWindowTitle(title) def _connect_tab_signals(self, tab): """Set up the needed signals for tab.""" @@ -396,6 +396,15 @@ class TabbedBrowser(QWidget): assert isinstance(tab, browsertab.AbstractTab), tab return tab + def _window(self) -> QWidget: + """Get the current window widget. + + Note: This asserts if there is no window. + """ + window = self.widget.window() + assert window is not None + return window + def _tab_by_idx(self, idx: int) -> Optional[browsertab.AbstractTab]: """Get a browser tab by index. @@ -662,11 +671,12 @@ class TabbedBrowser(QWidget): # Make sure the background tab has the correct initial size. # With a foreground tab, it's going to be resized correctly by the # layout anyways. - tab.resize(self.widget.currentWidget().size()) + current_widget = self._current_tab() + tab.resize(current_widget.size()) self.widget.tab_index_changed.emit(self.widget.currentIndex(), self.widget.count()) # Refocus webview in case we lost it by spawning a bg tab - self.widget.currentWidget().setFocus() + current_widget.setFocus() else: self.widget.setCurrentWidget(tab) @@ -739,7 +749,7 @@ class TabbedBrowser(QWidget): tab.data.keep_icon = False elif (config.cache['tabs.tabs_are_windows'] and tab.data.should_show_icon()): - self.widget.window().setWindowIcon(self.default_window_icon) + self._window().setWindowIcon(self.default_window_icon) @pyqtSlot() def _on_load_status_changed(self, tab): @@ -863,8 +873,9 @@ class TabbedBrowser(QWidget): def on_mode_left(self, mode): """Give focus to current tab if command mode was left.""" widget = self.widget.currentWidget() - if widget is None: - return # type: ignore[unreachable] + if not qtutils.is_not_none(widget): + return + if mode in [usertypes.KeyMode.command] + modeman.PROMPT_MODES: log.modes.debug("Left status-input mode, focusing {!r}".format( widget)) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index fe9ce1e06..150c820a8 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -355,7 +355,9 @@ class TabWidget(QTabWidget): self.setTabIcon(idx, icon) if config.val.tabs.tabs_are_windows: - self.window().setWindowIcon(tab.icon()) + window = self.window() + assert window is not None + window.setWindowIcon(tab.icon()) def setTabIcon(self, idx: int, icon: QIcon) -> None: """Always show tab icons for pinned tabs in some circumstances.""" @@ -365,7 +367,9 @@ class TabWidget(QTabWidget): config.cache['tabs.pinned.shrink'] and not self.tab_bar().vertical and tab is not None and tab.data.pinned): - icon = self.style().standardIcon(QStyle.StandardPixmap.SP_FileIcon) + style = self.style() + assert style is not None + icon = style.standardIcon(QStyle.StandardPixmap.SP_FileIcon) super().setTabIcon(idx, icon) @@ -809,6 +813,12 @@ class TabBarStyle(QProxyStyle): ICON_PADDING = 4 + def _base_style(self) -> QStyle: + """Get the base style.""" + style = self.baseStyle() + assert style is not None + return style + def _draw_indicator(self, layouts, opt, p): """Draw the tab indicator. @@ -836,7 +846,7 @@ class TabBarStyle(QProxyStyle): icon_state = (QIcon.State.On if opt.state & QStyle.StateFlag.State_Selected else QIcon.State.Off) icon = opt.icon.pixmap(opt.iconSize, icon_mode, icon_state) - self.baseStyle().drawItemPixmap(p, layouts.icon, Qt.AlignmentFlag.AlignCenter, icon) + self._base_style().drawItemPixmap(p, layouts.icon, Qt.AlignmentFlag.AlignCenter, icon) def drawControl(self, element, opt, p, widget=None): """Override drawControl to draw odd tabs in a different color. @@ -853,7 +863,7 @@ class TabBarStyle(QProxyStyle): if element not in [QStyle.ControlElement.CE_TabBarTab, QStyle.ControlElement.CE_TabBarTabShape, QStyle.ControlElement.CE_TabBarTabLabel]: # Let the real style draw it. - self.baseStyle().drawControl(element, opt, p, widget) + self._base_style().drawControl(element, opt, p, widget) return layouts = self._tab_layout(opt) @@ -876,7 +886,7 @@ class TabBarStyle(QProxyStyle): self._draw_icon(layouts, opt, p) alignment = (config.cache['tabs.title.alignment'] | Qt.AlignmentFlag.AlignVCenter | Qt.TextFlag.TextHideMnemonic) - self.baseStyle().drawItemText( + self._base_style().drawItemText( p, layouts.text, int(alignment), @@ -906,7 +916,7 @@ class TabBarStyle(QProxyStyle): QStyle.PixelMetric.PM_TabBarScrollButtonWidth]: return 0 else: - return self.baseStyle().pixelMetric(metric, option, widget) + return self._base_style().pixelMetric(metric, option, widget) def subElementRect(self, sr, opt, widget=None): """Override subElementRect to use our own _tab_layout implementation. @@ -936,7 +946,7 @@ class TabBarStyle(QProxyStyle): # style differences... return QCommonStyle.subElementRect(self, sr, opt, widget) else: - return self.baseStyle().subElementRect(sr, opt, widget) + return self._base_style().subElementRect(sr, opt, widget) def _tab_layout(self, opt): """Compute the text/icon rect from the opt rect. @@ -983,7 +993,7 @@ class TabBarStyle(QProxyStyle): text_rect.adjust( icon_rect.width() + TabBarStyle.ICON_PADDING, 0, 0, 0) - text_rect = self.baseStyle().visualRect(opt.direction, opt.rect, text_rect) + text_rect = self._base_style().visualRect(opt.direction, opt.rect, text_rect) return Layouts(text=text_rect, icon=icon_rect, indicator=indicator_rect) @@ -1018,5 +1028,5 @@ class TabBarStyle(QProxyStyle): icon_top = text_rect.center().y() + 1 - tab_icon_size.height() // 2 icon_rect = QRect(QPoint(text_rect.left(), icon_top), tab_icon_size) - icon_rect = self.baseStyle().visualRect(opt.direction, opt.rect, icon_rect) + icon_rect = self._base_style().visualRect(opt.direction, opt.rect, icon_rect) return icon_rect diff --git a/qutebrowser/misc/consolewidget.py b/qutebrowser/misc/consolewidget.py index 6d8d9916f..641798190 100644 --- a/qutebrowser/misc/consolewidget.py +++ b/qutebrowser/misc/consolewidget.py @@ -125,6 +125,7 @@ class ConsoleTextEdit(QTextEdit): self.moveCursor(QTextCursor.MoveOperation.End) self.insertPlainText(text) scrollbar = self.verticalScrollBar() + assert scrollbar is not None scrollbar.setValue(scrollbar.maximum()) diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index a50849d29..9f3b272f6 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -27,7 +27,7 @@ from typing import Mapping, Sequence, Dict, Optional from qutebrowser.qt.core import (pyqtSlot, pyqtSignal, QObject, QProcess, QProcessEnvironment, QByteArray, QUrl, Qt) -from qutebrowser.utils import message, log, utils, usertypes, version +from qutebrowser.utils import message, log, utils, usertypes, version, qtutils from qutebrowser.api import cmdutils, apitypes from qutebrowser.completion.models import miscmodels @@ -394,7 +394,7 @@ class GUIProcess(QObject): log.procs.debug("Starting process.") self._pre_start(cmd, args) self._proc.start( - self.resolved_cmd, # type: ignore[arg-type] + qtutils.allow_none(self.resolved_cmd), args, ) self._post_start() diff --git a/qutebrowser/misc/ipc.py b/qutebrowser/misc/ipc.py index fb1b1ac22..05d9a6a33 100644 --- a/qutebrowser/misc/ipc.py +++ b/qutebrowser/misc/ipc.py @@ -28,7 +28,7 @@ from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, Qt from qutebrowser.qt.network import QLocalSocket, QLocalServer, QAbstractSocket import qutebrowser -from qutebrowser.utils import log, usertypes, error, standarddir, utils, debug +from qutebrowser.utils import log, usertypes, error, standarddir, utils, debug, qtutils from qutebrowser.qt import sip @@ -260,8 +260,8 @@ class IPCServer(QObject): id(self._socket))) return socket = self._server.nextPendingConnection() - if socket is None: - log.ipc.debug( # type: ignore[unreachable] + if not qtutils.is_not_none(socket): + log.ipc.debug( "No new connection to handle.") return log.ipc.debug("Client connected (socket 0x{:x}).".format(id(socket))) diff --git a/qutebrowser/misc/miscwidgets.py b/qutebrowser/misc/miscwidgets.py index ba33da775..f41cb9df4 100644 --- a/qutebrowser/misc/miscwidgets.py +++ b/qutebrowser/misc/miscwidgets.py @@ -25,7 +25,7 @@ from qutebrowser.qt.widgets import (QLineEdit, QWidget, QHBoxLayout, QLabel, from qutebrowser.qt.gui import QValidator, QPainter, QResizeEvent from qutebrowser.config import config, configfiles -from qutebrowser.utils import utils, log, usertypes, debug +from qutebrowser.utils import utils, log, usertypes, debug, qtutils from qutebrowser.misc import cmdhistory from qutebrowser.browser import inspector from qutebrowser.keyinput import keyutils, modeman @@ -185,7 +185,10 @@ class _FoldArrow(QWidget): elem = QStyle.PrimitiveElement.PE_IndicatorArrowRight else: elem = QStyle.PrimitiveElement.PE_IndicatorArrowDown - self.style().drawPrimitive(elem, opt, painter, self) + + style = self.style() + assert style is not None + style.drawPrimitive(elem, opt, painter, self) def minimumSizeHint(self): """Return a sensible size.""" @@ -241,10 +244,10 @@ class WrapperLayout(QLayout): if self._widget is None: return assert self._container is not None - self._widget.setParent(None) # type: ignore[call-overload] + self._widget.setParent(qtutils.allow_none(None)) self._widget.deleteLater() self._widget = None - self._container.setFocusProxy(None) # type: ignore[arg-type] + self._container.setFocusProxy(qtutils.allow_none(None)) class FullscreenNotification(QLabel): @@ -270,9 +273,17 @@ class FullscreenNotification(QLabel): self.resize(self.sizeHint()) if config.val.content.fullscreen.window: - geom = self.parentWidget().geometry() + parent = self.parentWidget() + assert parent is not None + geom = parent.geometry() else: - geom = self.window().windowHandle().screen().geometry() + window = self.window() + assert window is not None + handle = window.windowHandle() + assert handle is not None + screen = handle.screen() + assert screen is not None + geom = screen.geometry() self.move((geom.width() - self.sizeHint().width()) // 2, 30) def set_timeout(self, timeout): @@ -327,6 +338,8 @@ class InspectorSplitter(QSplitter): main_widget = self.widget(self._main_idx) inspector_widget = self.widget(self._inspector_idx) + assert main_widget is not None + assert inspector_widget is not None if not inspector_widget.isVisible(): raise inspector.Error("No inspector inside main window") @@ -439,8 +452,9 @@ class InspectorSplitter(QSplitter): self._preferred_size = sizes[self._inspector_idx] self._save_preferred_size() - def resizeEvent(self, e: QResizeEvent) -> None: + def resizeEvent(self, e: Optional[QResizeEvent]) -> None: """Window resize event.""" + assert e is not None super().resizeEvent(e) if self.count() == 2: self._adjust_size() diff --git a/qutebrowser/misc/nativeeventfilter.py b/qutebrowser/misc/nativeeventfilter.py index 4562ea82d..5fad3359c 100644 --- a/qutebrowser/misc/nativeeventfilter.py +++ b/qutebrowser/misc/nativeeventfilter.py @@ -20,7 +20,7 @@ This entire file is a giant WORKAROUND for https://bugreports.qt.io/browse/QTBUG-114334. """ -from typing import Tuple, Union, cast +from typing import Tuple, Union, cast, Optional import enum import ctypes import ctypes.util @@ -150,11 +150,12 @@ class NativeEventFilter(QAbstractNativeEventFilter): xcb.xcb_disconnect(conn) def nativeEventFilter( - self, evtype: Union[bytes, QByteArray], message: sip.voidptr + self, evtype: Union[bytes, QByteArray], message: Optional[sip.voidptr] ) -> Tuple[bool, _PointerRetType]: """Handle XCB events.""" # We're only installed when the platform plugin is xcb assert evtype == b"xcb_generic_event_t", evtype + assert message is not None # We cast to xcb_ge_generic_event_t, which overlaps with xcb_generic_event_t. # .extension and .event_type will only make sense if this is an diff --git a/qutebrowser/utils/debug.py b/qutebrowser/utils/debug.py index a8b436d79..82de30702 100644 --- a/qutebrowser/utils/debug.py +++ b/qutebrowser/utils/debug.py @@ -68,6 +68,7 @@ def log_signals(obj: QObject) -> QObject: def connect_log_slot(obj: QObject) -> None: """Helper function to connect all signals to a logging slot.""" metaobj = obj.metaObject() + assert metaobj is not None for i in range(metaobj.methodCount()): meta_method = metaobj.method(i) qtutils.ensure_valid(meta_method) diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 521f52b5b..5ac150702 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -31,9 +31,10 @@ import json import inspect import argparse from typing import (TYPE_CHECKING, Any, Iterator, Mapping, MutableSequence, - Optional, Set, Tuple, Union, TextIO, Literal, cast) + Optional, Set, Tuple, Union, TextIO, Literal, cast, Callable) from qutebrowser.qt import core as qtcore +from qutebrowser.qt import machinery # Optional imports try: import colorama @@ -234,6 +235,19 @@ def _init_py_warnings() -> None: def disable_qt_msghandler() -> Iterator[None]: """Contextmanager which temporarily disables the Qt message handler.""" old_handler = qtcore.qInstallMessageHandler(None) + if machinery.IS_QT6: + # cast str to Optional[str] to be compatible with PyQt6 type hints for + # qInstallMessageHandler + old_handler = cast( + Optional[ + Callable[ + [qtcore.QtMsgType, qtcore.QMessageLogContext, Optional[str]], + None + ] + ], + old_handler, + ) + try: yield finally: @@ -379,7 +393,7 @@ def change_console_formatter(level: int) -> None: def qt_message_handler(msg_type: qtcore.QtMsgType, context: qtcore.QMessageLogContext, - msg: str) -> None: + msg: Optional[str]) -> None: """Qt message handler to redirect qWarning etc. to the logging system. Args: diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py index cc34057ef..781e43f08 100644 --- a/qutebrowser/utils/qtutils.py +++ b/qutebrowser/utils/qtutils.py @@ -32,7 +32,7 @@ import pathlib import operator import contextlib from typing import (Any, AnyStr, TYPE_CHECKING, BinaryIO, IO, Iterator, - Optional, Union, Tuple, Protocol, cast) + Optional, Union, Tuple, Protocol, cast, TypeVar) from qutebrowser.qt import machinery, sip from qutebrowser.qt.core import (qVersion, QEventLoop, QDataStream, QByteArray, @@ -46,6 +46,7 @@ except ImportError: # pragma: no cover if TYPE_CHECKING: from qutebrowser.qt.webkit import QWebHistory from qutebrowser.qt.webenginecore import QWebEngineHistory + from typing_extensions import TypeGuard # added in Python 3.10 from qutebrowser.misc import objects from qutebrowser.utils import usertypes, utils @@ -102,7 +103,11 @@ def version_check(version: str, parsed = utils.VersionNumber.parse(version) op = operator.eq if exact else operator.ge - result = op(utils.VersionNumber.parse(qVersion()), parsed) + + qversion = qVersion() + assert qversion is not None + result = op(utils.VersionNumber.parse(qversion), parsed) + if compiled and result: # qVersion() ==/>= parsed, now check if QT_VERSION_STR ==/>= parsed. result = op(utils.VersionNumber.parse(QT_VERSION_STR), parsed) @@ -535,24 +540,58 @@ def interpolate_color( if colorspace is None: if percent == 100: - return QColor(*end.getRgb()) + r, g, b, a = end.getRgb() + assert r is not None + assert g is not None + assert b is not None + assert a is not None + return QColor(r, g, b, a) else: - return QColor(*start.getRgb()) + r, g, b, a = start.getRgb() + assert r is not None + assert g is not None + assert b is not None + assert a is not None + return QColor(r, g, b, a) out = QColor() if colorspace == QColor.Spec.Rgb: r1, g1, b1, a1 = start.getRgb() r2, g2, b2, a2 = end.getRgb() + assert r1 is not None + assert g1 is not None + assert b1 is not None + assert a1 is not None + assert r2 is not None + assert g2 is not None + assert b2 is not None + assert a2 is not None components = _get_color_percentage(r1, g1, b1, a1, r2, g2, b2, a2, percent) out.setRgb(*components) elif colorspace == QColor.Spec.Hsv: h1, s1, v1, a1 = start.getHsv() h2, s2, v2, a2 = end.getHsv() + assert h1 is not None + assert s1 is not None + assert v1 is not None + assert a1 is not None + assert h2 is not None + assert s2 is not None + assert v2 is not None + assert a2 is not None components = _get_color_percentage(h1, s1, v1, a1, h2, s2, v2, a2, percent) out.setHsv(*components) elif colorspace == QColor.Spec.Hsl: h1, s1, l1, a1 = start.getHsl() h2, s2, l2, a2 = end.getHsl() + assert h1 is not None + assert s1 is not None + assert l1 is not None + assert a1 is not None + assert h2 is not None + assert s2 is not None + assert l2 is not None + assert a2 is not None components = _get_color_percentage(h1, s1, l1, a1, h2, s2, l2, a2, percent) out.setHsl(*components) else: @@ -611,3 +650,21 @@ def extract_enum_val(val: Union[sip.simplewrapper, int, enum.Enum]) -> int: elif isinstance(val, sip.simplewrapper): return int(val) # type: ignore[call-overload] return val + +T = TypeVar("T") + +def is_not_none(obj: Optional[T]) -> "TypeGuard[T]": + """Check if a Qt object is None. + + PyQt6 marks things as Optional[...], but PyQt5 doesn't. + By using this function, we can use the same type hints for both. + """ + return obj is not None + + +if machinery.IS_QT5: + def allow_none(obj: Optional[T]) -> T: + return cast(T, obj) +else: + def allow_none(obj: Optional[T]) -> Optional[T]: + return obj diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py index 884a26376..762125c11 100644 --- a/qutebrowser/utils/standarddir.py +++ b/qutebrowser/utils/standarddir.py @@ -28,7 +28,7 @@ from typing import Iterator, Optional from qutebrowser.qt.core import QStandardPaths from qutebrowser.qt.widgets import QApplication -from qutebrowser.utils import log, debug, utils, version +from qutebrowser.utils import log, debug, utils, version, qtutils # The cached locations _locations = {} @@ -65,7 +65,7 @@ def _unset_organization() -> Iterator[None]: qapp = QApplication.instance() if qapp is not None: orgname = qapp.organizationName() - qapp.setOrganizationName(None) # type: ignore[arg-type] + qapp.setOrganizationName(qtutils.allow_none(None)) try: yield finally: diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index e00c9dab2..b3f03b256 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -179,9 +179,9 @@ def _get_search_url(txt: str) -> QUrl: url = QUrl.fromUserInput(evaluated) else: url = QUrl.fromUserInput(config.val.url.searchengines[engine]) - url.setPath(None) # type: ignore[arg-type] - url.setFragment(None) # type: ignore[arg-type] - url.setQuery(None) # type: ignore[call-overload] + url.setPath(qtutils.allow_none(None)) + url.setFragment(qtutils.allow_none(None)) + url.setQuery(qtutils.allow_none(None)) qtutils.ensure_valid(url) return url diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index 6b450aa29..dd3cf6ac3 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -519,6 +519,13 @@ def sanitize_filename(name: str, return name +def _clipboard() -> QClipboard: + """Get the QClipboard and make sure it's not None.""" + clipboard = QApplication.clipboard() + assert clipboard is not None + return clipboard + + def set_clipboard(data: str, selection: bool = False) -> None: """Set the clipboard to some given data.""" global fake_clipboard @@ -530,7 +537,7 @@ def set_clipboard(data: str, selection: bool = False) -> None: fake_clipboard = data else: mode = QClipboard.Mode.Selection if selection else QClipboard.Mode.Clipboard - QApplication.clipboard().setText(data, mode=mode) + _clipboard().setText(data, mode=mode) def get_clipboard(selection: bool = False, fallback: bool = False) -> str: @@ -556,7 +563,7 @@ def get_clipboard(selection: bool = False, fallback: bool = False) -> str: fake_clipboard = None else: mode = QClipboard.Mode.Selection if selection else QClipboard.Mode.Clipboard - data = QApplication.clipboard().text(mode=mode) + data = _clipboard().text(mode=mode) target = "Primary selection" if selection else "Clipboard" if not data.strip(): @@ -568,7 +575,7 @@ def get_clipboard(selection: bool = False, fallback: bool = False) -> str: def supports_selection() -> bool: """Check if the OS supports primary selection.""" - return QApplication.clipboard().supportsSelection() + return _clipboard().supportsSelection() def open_file(filename: str, cmdline: str = None) -> None: diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 782261745..358e8e19b 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -886,7 +886,10 @@ def version_info() -> str: if objects.qapp: style = objects.qapp.style() - lines.append('Style: {}'.format(style.metaObject().className())) + assert style is not None + metaobj = style.metaObject() + assert metaobj is not None + lines.append('Style: {}'.format(metaobj.className())) lines.append('Platform plugin: {}'.format(objects.qapp.platformName())) lines.append('OpenGL: {}'.format(opengl_info())) @@ -1005,7 +1008,7 @@ def opengl_info() -> Optional[OpenGLInfo]: # pragma: no cover vendor, version = override.split(', ', maxsplit=1) return OpenGLInfo.parse(vendor=vendor, version=version) - old_context = cast(Optional[QOpenGLContext], QOpenGLContext.currentContext()) + old_context: Optional[QOpenGLContext] = QOpenGLContext.currentContext() old_surface = None if old_context is None else old_context.surface() surface = QOffscreenSurface() -- cgit v1.2.3-54-g00ecf