From e2db790593eee8329fe8b168e82043bcbb19cbae Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 24 Apr 2022 18:06:07 +0200 Subject: wip --- misc/requirements/requirements-mypy.txt | 2 +- qutebrowser/app.py | 3 +- qutebrowser/browser/browsertab.py | 63 ++++++++++------- qutebrowser/browser/commands.py | 1 + qutebrowser/browser/downloadview.py | 17 +++-- qutebrowser/browser/hints.py | 8 ++- qutebrowser/browser/inspector.py | 16 +++-- qutebrowser/browser/network/pac.py | 3 +- qutebrowser/browser/qtnetworkdownloads.py | 3 +- qutebrowser/browser/qutescheme.py | 4 +- qutebrowser/browser/webengine/notification.py | 6 +- .../browser/webengine/webenginedownloads.py | 8 +-- qutebrowser/browser/webengine/webengineelem.py | 4 +- .../browser/webengine/webengineinspector.py | 11 +-- .../browser/webengine/webenginequtescheme.py | 2 +- qutebrowser/browser/webengine/webenginesettings.py | 7 +- qutebrowser/browser/webengine/webenginetab.py | 34 ++++++++-- qutebrowser/browser/webengine/webview.py | 4 +- qutebrowser/browser/webkit/network/networkreply.py | 21 ++---- qutebrowser/browser/webkit/webkitinspector.py | 4 +- qutebrowser/browser/webkit/webkittab.py | 76 +++++++++++++++------ qutebrowser/browser/webkit/webpage.py | 10 +-- qutebrowser/browser/webkit/webview.py | 4 +- qutebrowser/commands/userscripts.py | 3 +- qutebrowser/completion/completer.py | 12 +++- qutebrowser/completion/completiondelegate.py | 2 +- qutebrowser/completion/completionwidget.py | 42 ++++++++---- qutebrowser/completion/models/completionmodel.py | 4 +- qutebrowser/components/misccommands.py | 2 +- qutebrowser/components/readlinecommands.py | 2 +- qutebrowser/components/scrollcommands.py | 4 +- qutebrowser/config/stylesheet.py | 3 +- qutebrowser/keyinput/keyutils.py | 10 +-- qutebrowser/keyinput/modeman.py | 1 + qutebrowser/mainwindow/mainwindow.py | 10 ++- qutebrowser/mainwindow/prompt.py | 4 +- qutebrowser/mainwindow/statusbar/bar.py | 5 +- qutebrowser/mainwindow/statusbar/command.py | 3 +- qutebrowser/mainwindow/statusbar/url.py | 3 +- qutebrowser/mainwindow/tabbedbrowser.py | 79 +++++++++++++++------- qutebrowser/mainwindow/tabwidget.py | 73 ++++++++++++-------- qutebrowser/misc/crashsignal.py | 3 +- qutebrowser/misc/editor.py | 3 +- qutebrowser/misc/ipc.py | 11 ++- qutebrowser/misc/objects.py | 4 +- 45 files changed, 373 insertions(+), 221 deletions(-) diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index d5a266759..28ea9f06a 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -11,7 +11,7 @@ mypy==0.942 mypy-extensions==0.4.3 pluggy==1.0.0 Pygments==2.11.2 -PyQt5-stubs==5.15.2.0 +PyQt5-stubs==5.15.6.0 tomli==2.0.1 types-PyYAML==6.0.6 typing_extensions==4.2.0 diff --git a/qutebrowser/app.py b/qutebrowser/app.py index c046475b5..f74617133 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -562,8 +562,7 @@ class Application(QApplication): log.init.debug("Initializing application...") self.launch_time = datetime.datetime.now() - self.focusObjectChanged.connect( # type: ignore[attr-defined] - self.on_focus_object_changed) + self.focusObjectChanged.connect(self.on_focus_object_changed) self.setAttribute(Qt.AA_UseHighDpiPixmaps, True) self.new_window.connect(self._on_new_window) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 661c5f68b..699fe1b0b 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -24,7 +24,7 @@ import itertools import functools import dataclasses from typing import (cast, TYPE_CHECKING, Any, Callable, Iterable, List, Optional, - Sequence, Set, Type, Union) + Sequence, Set, Type, Union, Tuple) from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt, QEvent, QPoint, QRect) @@ -35,12 +35,12 @@ from PyQt5.QtNetwork import QNetworkAccessManager if TYPE_CHECKING: from PyQt5.QtWebKit import QWebHistory, QWebHistoryItem - from PyQt5.QtWebKitWidgets import QWebPage + from PyQt5.QtWebKitWidgets import QWebPage, QWebView from PyQt5.QtWebEngineWidgets import ( - QWebEngineHistory, QWebEngineHistoryItem, QWebEnginePage) + QWebEngineHistory, QWebEngineHistoryItem, QWebEnginePage, QWebEngineView) from qutebrowser.keyinput import modeman -from qutebrowser.config import config +from qutebrowser.config import config, websettings from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils, urlutils, message, jinja) from qutebrowser.misc import miscwidgets, objects, sessions @@ -53,6 +53,7 @@ if TYPE_CHECKING: tab_id_gen = itertools.count(0) +_WidgetType = Union["QWebView", "QWebEngineView"] def create(win_id: int, @@ -156,7 +157,7 @@ class AbstractAction: action_base: Type[Union['QWebPage.WebAction', 'QWebEnginePage.WebAction']] def __init__(self, tab: 'AbstractTab') -> None: - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._tab = tab def exit_fullscreen(self) -> None: @@ -172,6 +173,7 @@ class AbstractAction: member = getattr(self.action_class, name, None) if not isinstance(member, self.action_base): raise WebTabError("{} is not a valid web action!".format(name)) + assert member is not None # for mypy self._widget.triggerPageAction(member) def show_source(self, pygments: bool = False) -> None: @@ -229,7 +231,7 @@ class AbstractPrinting: """Attribute ``printing`` of AbstractTab for printing the page.""" def __init__(self, tab: 'AbstractTab') -> None: - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._tab = tab def check_pdf_support(self) -> None: @@ -308,7 +310,7 @@ class AbstractSearch(QObject): def __init__(self, tab: 'AbstractTab', parent: QWidget = None): super().__init__(parent) self._tab = tab - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self.text: Optional[str] = None self.search_displayed = False @@ -372,7 +374,7 @@ class AbstractZoom(QObject): def __init__(self, tab: 'AbstractTab', parent: QWidget = None) -> None: super().__init__(parent) self._tab = tab - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) # Whether zoom was changed from the default. self._default_zoom_changed = False self._init_neighborlist() @@ -466,7 +468,7 @@ class AbstractCaret(QObject): mode_manager: modeman.ModeManager, parent: QWidget = None) -> None: super().__init__(parent) - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._mode_manager = mode_manager mode_manager.entered.connect(self._on_mode_entered) mode_manager.left.connect(self._on_mode_left) @@ -559,7 +561,7 @@ class AbstractScroller(QObject): def __init__(self, tab: 'AbstractTab', parent: QWidget = None): super().__init__(parent) self._tab = tab - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) if 'log-scroll-pos' in objects.debug_flags: self.perc_changed.connect(self._log_scroll_pos_change) @@ -568,16 +570,16 @@ class AbstractScroller(QObject): log.webview.vdebug( # type: ignore[attr-defined] "Scroll position changed to {}".format(self.pos_px())) - def _init_widget(self, widget: QWidget) -> None: + def _init_widget(self, widget: _WidgetType) -> None: self._widget = widget - def pos_px(self) -> int: + def pos_px(self) -> QPoint: raise NotImplementedError - def pos_perc(self) -> int: + def pos_perc(self) -> Tuple[int, int]: raise NotImplementedError - def to_perc(self, x: int = None, y: int = None) -> None: + def to_perc(self, x: float = None, y: float = None) -> None: raise NotImplementedError def to_point(self, point: QPoint) -> None: @@ -627,6 +629,8 @@ class AbstractHistoryPrivate: """Private API related to the history.""" + _history: Union["QWebHistory", "QWebEngineHistory"] + def serialize(self) -> bytes: """Serialize into an opaque format understood by self.deserialize.""" raise NotImplementedError @@ -711,7 +715,7 @@ class AbstractElements: _ErrorCallback = Callable[[Exception], None] def __init__(self, tab: 'AbstractTab') -> None: - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._tab = tab def find_css(self, selector: str, @@ -772,7 +776,7 @@ class AbstractAudio(QObject): def __init__(self, tab: 'AbstractTab', parent: QWidget = None) -> None: super().__init__(parent) - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._tab = tab def set_muted(self, muted: bool, override: bool = False) -> None: @@ -804,11 +808,11 @@ class AbstractTabPrivate: def __init__(self, mode_manager: modeman.ModeManager, tab: 'AbstractTab') -> None: - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._tab = tab self._mode_manager = mode_manager - def event_target(self) -> QWidget: + def event_target(self) -> Optional[QWidget]: """Return the widget events should be sent to.""" raise NotImplementedError @@ -848,7 +852,7 @@ class AbstractTabPrivate: def shutdown(self) -> None: raise NotImplementedError - def run_js_sync(self, code: str) -> None: + def run_js_sync(self, code: str) -> Any: """Run javascript sync. Result will be returned when running JS is complete. @@ -867,7 +871,7 @@ class AbstractTabPrivate: self._tab.data.inspector = None self.toggle_inspector(inspector.Position.window) - def toggle_inspector(self, position: inspector.Position) -> None: + def toggle_inspector(self, position: Optional[inspector.Position]) -> None: """Show/hide (and if needed, create) the web inspector for this tab.""" tabdata = self._tab.data if tabdata.inspector is None: @@ -944,6 +948,19 @@ class AbstractTab(QWidget): # for a given hostname anyways. _insecure_hosts: Set[str] = set() + # Sub-APIs initialized by subclasses + history: AbstractHistory + scroller: AbstractScroller + caret: AbstractCaret + zoom: AbstractZoom + search: AbstractSearch + printing: AbstractPrinting + action: AbstractAction + elements: AbstractElements + audio: AbstractAudio + private_api: AbstractTabPrivate + settings: websettings.AbstractSettings + def __init__(self, *, win_id: int, mode_manager: 'modeman.ModeManager', private: bool, @@ -962,7 +979,7 @@ class AbstractTab(QWidget): self.data = TabData() self._layout = miscwidgets.WrapperLayout(self) - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._progress = 0 self._load_status = usertypes.LoadStatus.none self._tab_event_filter = eventfilter.TabEventFilter( @@ -976,7 +993,7 @@ class AbstractTab(QWidget): self.before_load_started.connect(self._on_before_load_started) - def _set_widget(self, widget: QWidget) -> None: + def _set_widget(self, widget: Union["QWebView", "QWebEngineView"]) -> None: # pylint: disable=protected-access self._widget = widget self.data.splitter = miscwidgets.InspectorSplitter( @@ -1195,7 +1212,7 @@ class AbstractTab(QWidget): def title(self) -> str: raise NotImplementedError - def icon(self) -> None: + def icon(self) -> QIcon: raise NotImplementedError def set_html(self, html: str, base_url: QUrl = QUrl()) -> None: diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 00d5e521f..7f9c4810d 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -913,6 +913,7 @@ class CommandDispatcher: # Not sure how you enter a command without an active window... raise cmdutils.CommandError( "No window specified and couldn't find active window!") + assert isinstance(active_win, mainwindow.MainWindow) win_id = active_win.win_id if win_id not in objreg.window_registry: diff --git a/qutebrowser/browser/downloadview.py b/qutebrowser/browser/downloadview.py index 69c58741a..8187a1002 100644 --- a/qutebrowser/browser/downloadview.py +++ b/qutebrowser/browser/downloadview.py @@ -86,6 +86,15 @@ class DownloadView(QListView): count = model.rowCount() return utils.get_repr(self, count=count) + def _model(self) -> downloads.DownloadModel: + """Get the current download model. + + Ensures the model is not None. + """ + model = self.model() + assert isinstance(model, downloads.DownloadModel), model + return model + @pyqtSlot() def _update_geometry(self): """Wrapper to call updateGeometry. @@ -112,7 +121,7 @@ class DownloadView(QListView): """ if not index.isValid(): return - item = self.model().data(index, downloads.ModelRole.item) + item = self._model().data(index, downloads.ModelRole.item) if item.done and item.successful: item.open_file() item.remove() @@ -126,7 +135,7 @@ class DownloadView(QListView): Args: item: The DownloadItem to get the actions for, or None. """ - model = self.model() + model = self._model() actions: _ActionListType = [] if item is None: pass @@ -154,7 +163,7 @@ class DownloadView(QListView): """Show the context menu.""" index = self.indexAt(point) if index.isValid(): - item = self.model().data(index, downloads.ModelRole.item) + item = self._model().data(index, downloads.ModelRole.item) else: item = None self._menu = QMenu(self) @@ -176,7 +185,7 @@ class DownloadView(QListView): def sizeHint(self): """Return sizeHint based on the view contents.""" - idx = self.model().last_index() + idx = self._model().last_index() bottom = self.visualRect(idx).bottom() if bottom != -1: margins = self.contentsMargins() diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 2e4e8e4b4..335508b3d 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -255,7 +255,7 @@ class HintActions: flags = QUrl.FullyEncoded | QUrl.RemovePassword if url.scheme() == 'mailto': flags |= QUrl.RemoveScheme - urlstr = url.toString(flags) # type: ignore[arg-type] + urlstr = url.toString(flags) new_content = urlstr @@ -356,8 +356,7 @@ class HintActions: url: The URL to open as a QUrl. context: The HintContext to use. """ - urlstr = url.toString( - QUrl.FullyEncoded | QUrl.RemovePassword) # type: ignore[arg-type] + urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword) args = context.get_args(urlstr) commandrunner = runners.CommandRunner(self._win_id) commandrunner.run_safely('spawn ' + ' '.join(args)) @@ -657,6 +656,7 @@ class HintManager(QObject): self._context.labels[string] = label keyparser = self._get_keyparser(usertypes.KeyMode.hint) + assert isinstance(keyparser, modeparsers.HintKeyParser) keyparser.update_bindings(strings) modeman.enter(self._win_id, usertypes.KeyMode.hint, @@ -852,6 +852,7 @@ class HintManager(QObject): # apply auto_follow_timeout timeout = config.val.hints.auto_follow_timeout normal_parser = self._get_keyparser(usertypes.KeyMode.normal) + assert isinstance(normal_parser, modeparsers.NormalKeyParser) normal_parser.set_inhibited_timeout(timeout) # unpacking gets us the first (and only) key in the dict. self._fire(*visible) @@ -927,6 +928,7 @@ class HintManager(QObject): self._context.labels[string] = label keyparser = self._get_keyparser(usertypes.KeyMode.hint) + assert isinstance(keyparser, modeparsers.HintKeyParser) keyparser.update_bindings(strings, preserve_filter=True) # Note: filter_hints can be called with non-None filterstr only diff --git a/qutebrowser/browser/inspector.py b/qutebrowser/browser/inspector.py index 2b40e97e4..51cad3709 100644 --- a/qutebrowser/browser/inspector.py +++ b/qutebrowser/browser/inspector.py @@ -22,7 +22,7 @@ import base64 import binascii import enum -from typing import cast, Optional +from typing import cast, Optional, Union, TYPE_CHECKING from PyQt5.QtWidgets import QWidget from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QEvent @@ -34,6 +34,14 @@ from qutebrowser.utils import log, usertypes from qutebrowser.keyinput import modeman from qutebrowser.misc import miscwidgets +if TYPE_CHECKING: + from PyQt5.QtWebKitWidgets import QWebInspector, QWebPage + from PyQt5.QtWebEngineWidgets import QWebEnginePage + from qutebrowser.browser.webengine import webengineinspector + + +_WidgetType = Union["QWebInspector", "webengineinspector.WebEngineInspectorView"] + class Position(enum.Enum): @@ -93,7 +101,7 @@ class AbstractWebInspector(QWidget): win_id: int, parent: QWidget = None) -> None: super().__init__(parent) - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._layout = miscwidgets.WrapperLayout(self) self._splitter = splitter self._position: Optional[Position] = None @@ -105,7 +113,7 @@ class AbstractWebInspector(QWidget): eventfilter=self._event_filter, parent=self) - def _set_widget(self, widget: QWidget) -> None: + def _set_widget(self, widget: _WidgetType) -> None: self._widget = widget self._widget.setWindowTitle("Web Inspector") self._widget.installEventFilter(self._child_event_filter) @@ -198,7 +206,7 @@ class AbstractWebInspector(QWidget): geom = base64.b64encode(data).decode('ASCII') configfiles.state['inspector']['window'] = geom - def inspect(self, page: QWidget) -> None: + def inspect(self, page: Union["QWebPage", "QWebEnginePage"]) -> None: """Inspect the given QWeb(Engine)Page.""" raise NotImplementedError diff --git a/qutebrowser/browser/network/pac.py b/qutebrowser/browser/network/pac.py index 3a544c78f..99078ab20 100644 --- a/qutebrowser/browser/network/pac.py +++ b/qutebrowser/browser/network/pac.py @@ -270,8 +270,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)) - self._reply.finished.connect( # type: ignore[attr-defined] - self._finish) + self._reply.finished.connect(self._finish) @pyqtSlot() def _finish(self): diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py index 8adb7ea20..a77289efb 100644 --- a/qutebrowser/browser/qtnetworkdownloads.py +++ b/qutebrowser/browser/qtnetworkdownloads.py @@ -24,7 +24,7 @@ import os.path import shutil import functools import dataclasses -from typing import Dict, IO, Optional +from typing import Dict, IO, Optional, List from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer, QUrl from PyQt5.QtWidgets import QApplication @@ -601,6 +601,7 @@ class DownloadManager(downloads.AbstractDownloadManager): """ assert nam.adopted_downloads == 0 for download in self.downloads: + assert isinstance(download, DownloadItem) if download._uses_nam(nam): # pylint: disable=protected-access nam.adopt_download(download) return nam.adopted_downloads diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 68e36d249..c0da8ac94 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -128,9 +128,7 @@ def data_for_url(url: QUrl) -> Tuple[str, bytes]: Return: A (mimetype, data) tuple. """ - norm_url = url.adjusted( - QUrl.NormalizePathSegments | # type: ignore[arg-type] - QUrl.StripTrailingSlash) + norm_url = url.adjusted(QUrl.NormalizePathSegments | QUrl.StripTrailingSlash) if norm_url != url: raise Redirect(norm_url) diff --git a/qutebrowser/browser/webengine/notification.py b/qutebrowser/browser/webengine/notification.py index 2b77a5ac4..57b593ce1 100644 --- a/qutebrowser/browser/webengine/notification.py +++ b/qutebrowser/browser/webengine/notification.py @@ -288,7 +288,7 @@ class NotificationBridgePresenter(QObject): qt_notification.show() self._active_notifications[notification_id] = qt_notification - qt_notification.closed.connect( # type: ignore[attr-defined] + qt_notification.closed.connect( functools.partial(self._adapter.on_web_closed, notification_id)) def _find_replaces_id( @@ -632,6 +632,7 @@ class HerbeNotificationAdapter(AbstractNotificationAdapter): self.close_id.emit(pid) else: proc = self.sender() + assert isinstance(proc, QProcess), proc stderr = proc.readAllStandardError() raise Error(f'herbe exited with status {code}: {stderr}') @@ -757,8 +758,7 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): QDBusServiceWatcher.WatchForUnregistration, self, ) - self._watcher.serviceUnregistered.connect( # type: ignore[attr-defined] - self._on_service_unregistered) + self._watcher.serviceUnregistered.connect(self._on_service_unregistered) test_service = 'test-notification-service' in objects.debug_flags service = self.TEST_SERVICE if test_service else self.SERVICE diff --git a/qutebrowser/browser/webengine/webenginedownloads.py b/qutebrowser/browser/webengine/webenginedownloads.py index a6a2a1b93..a96f49d6b 100644 --- a/qutebrowser/browser/webengine/webenginedownloads.py +++ b/qutebrowser/browser/webengine/webenginedownloads.py @@ -44,10 +44,8 @@ class DownloadItem(downloads.AbstractDownloadItem): parent: QObject = None) -> None: super().__init__(manager=manager, parent=manager) self._qt_item = qt_item - qt_item.downloadProgress.connect( # type: ignore[attr-defined] - self.stats.on_download_progress) - qt_item.stateChanged.connect( # type: ignore[attr-defined] - self._on_state_changed) + qt_item.downloadProgress.connect(self.stats.on_download_progress) + qt_item.stateChanged.connect(self._on_state_changed) # Ensure wrapped qt_item is deleted manually when the wrapper object # is deleted. See https://github.com/qutebrowser/qutebrowser/issues/3373 @@ -92,7 +90,7 @@ class DownloadItem(downloads.AbstractDownloadItem): def _do_die(self): progress_signal = self._qt_item.downloadProgress - progress_signal.disconnect() # type: ignore[attr-defined] + progress_signal.disconnect() if self._qt_item.state() != QWebEngineDownloadItem.DownloadInterrupted: self._qt_item.cancel() diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 5d4c6ad9a..75b7a51ba 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -37,6 +37,8 @@ class WebEngineElement(webelem.AbstractWebElement): """A web element for QtWebEngine, using JS under the hood.""" + _tab: "webenginetab.WebEngineTab" + def __init__(self, js_dict: Dict[str, Any], tab: 'webenginetab.WebEngineTab') -> None: super().__init__(tab) @@ -248,7 +250,7 @@ class WebEngineElement(webelem.AbstractWebElement): # (it does so with a 0ms QTimer...) # This is also used in Qt's tests: # https://github.com/qt/qtwebengine/commit/5e572e88efa7ba7c2b9138ec19e606d3e345ac90 - QApplication.processEvents( # type: ignore[call-overload] + QApplication.processEvents( QEventLoop.ExcludeSocketNotifiers | QEventLoop.ExcludeUserInputEvents) diff --git a/qutebrowser/browser/webengine/webengineinspector.py b/qutebrowser/browser/webengine/webengineinspector.py index ae31c0bee..d30a716de 100644 --- a/qutebrowser/browser/webengine/webengineinspector.py +++ b/qutebrowser/browser/webengine/webengineinspector.py @@ -49,13 +49,17 @@ class WebEngineInspectorView(QWebEngineView): See WebEngineView.createWindow for details. """ - return self.page().inspectedPage().view().createWindow(wintype) + view = self.page().inspectedPage().view() + assert isinstance(view, QWebEngineView) + return view.createWindow(wintype) class WebEngineInspector(inspector.AbstractWebInspector): """A web inspector for QtWebEngine with Qt API support.""" + _widget: WebEngineInspectorView + def __init__(self, splitter: miscwidgets.InspectorSplitter, win_id: int, parent: QWidget = None) -> None: @@ -66,8 +70,7 @@ class WebEngineInspector(inspector.AbstractWebInspector): self._settings = webenginesettings.WebEngineSettings(view.settings()) self._set_widget(view) page = view.page() - page.windowCloseRequested.connect( # type: ignore[attr-defined] - self._on_window_close_requested) + page.windowCloseRequested.connect(self._on_window_close_requested) def _on_window_close_requested(self) -> None: """Called when the 'x' was clicked in the devtools.""" @@ -96,7 +99,7 @@ class WebEngineInspector(inspector.AbstractWebInspector): "please install the qt5-qtwebengine-devtools " "Fedora package.") - def inspect(self, page: QWebEnginePage) -> None: # type: ignore[override] + def inspect(self, page: QWebEnginePage) -> None: inspector_page = self._widget.page() inspector_page.setInspectedPage(page) self._settings.update_for_url(inspector_page.requestedUrl()) diff --git a/qutebrowser/browser/webengine/webenginequtescheme.py b/qutebrowser/browser/webengine/webenginequtescheme.py index 64361f7c4..9e073951a 100644 --- a/qutebrowser/browser/webengine/webenginequtescheme.py +++ b/qutebrowser/browser/webengine/webenginequtescheme.py @@ -138,6 +138,6 @@ def init(): assert not QWebEngineUrlScheme.schemeByName(b'qute').name() scheme = QWebEngineUrlScheme(b'qute') scheme.setFlags( - QWebEngineUrlScheme.LocalScheme | # type: ignore[arg-type] + QWebEngineUrlScheme.LocalScheme | QWebEngineUrlScheme.LocalAccessAllowed) QWebEngineUrlScheme.registerScheme(scheme) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 5430cec77..0b25726c0 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -332,9 +332,9 @@ class ProfileSetter: def _update_settings(option): """Update global settings when qwebsettings changed.""" _global_settings.update_setting(option) - default_profile.setter.update_setting(option) + default_profile.setter.update_setting(option) # type: ignore[attr-defined] if private_profile: - private_profile.setter.update_setting(option) + private_profile.setter.update_setting(option) # type: ignore[attr-defined] def _init_user_agent_str(ua): @@ -352,8 +352,9 @@ def _init_profile(profile: QWebEngineProfile) -> None: This currently only contains the steps which are shared between a private and a non-private profile (at the moment, only the default profile). """ + # FIXME:mypy subclass QWebEngineProfile instead? profile.setter = ProfileSetter(profile) # type: ignore[attr-defined] - profile.setter.init_profile() + profile.setter.init_profile() # type: ignore[attr-defined] _qute_scheme_handler.install(profile) _req_interceptor.install(profile) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 7d355d10e..28182d1b6 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -30,7 +30,7 @@ from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QTimer, QUr QObject) from PyQt5.QtNetwork import QAuthenticator from PyQt5.QtWidgets import QWidget -from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript, QWebEngineHistory +from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineView, QWebEngineScript, QWebEngineHistory from qutebrowser.config import config from qutebrowser.browser import browsertab, eventfilter, shared, webelem, greasemonkey @@ -57,6 +57,8 @@ class WebEngineAction(browsertab.AbstractAction): """QtWebEngine implementations related to web actions.""" + _widget: webview.WebEngineView + action_class = QWebEnginePage action_base = QWebEnginePage.WebAction @@ -79,6 +81,8 @@ class WebEnginePrinting(browsertab.AbstractPrinting): """QtWebEngine implementations related to printing.""" + _widget: webview.WebEngineView + def check_pdf_support(self): pass @@ -190,6 +194,8 @@ class WebEngineSearch(browsertab.AbstractSearch): _pending_searches: How many searches have been started but not called back yet. """ + + _widget: webview.WebEngineView def __init__(self, tab, parent=None): super().__init__(tab, parent) @@ -199,7 +205,7 @@ class WebEngineSearch(browsertab.AbstractSearch): self._wrap_handler = _WebEngineSearchWrapHandler() def _empty_flags(self): - return QWebEnginePage.FindFlags(0) # type: ignore[call-overload] + return QWebEnginePage.FindFlags(0) def _args_to_flags(self, reverse, ignore_case): flags = self._empty_flags() @@ -275,8 +281,7 @@ class WebEngineSearch(browsertab.AbstractSearch): def prev_result(self, *, result_cb=None): # The int() here makes sure we get a copy of the flags. - flags = QWebEnginePage.FindFlags( - int(self._flags)) # type: ignore[call-overload] + flags = QWebEnginePage.FindFlags(int(self._flags)) if flags & QWebEnginePage.FindBackward: if self._wrap_handler.prevent_wrapping(going_up=False): return @@ -493,6 +498,8 @@ class WebEngineScroller(browsertab.AbstractScroller): """QtWebEngine implementations related to scrolling.""" + _widget: webview.WebEngineView + def __init__(self, tab, parent=None): super().__init__(tab, parent) self._pos_perc = (0, 0) @@ -713,6 +720,8 @@ class WebEngineZoom(browsertab.AbstractZoom): """QtWebEngine implementations related to zooming.""" + _widget: webview.WebEngineView + def _set_factor_internal(self, factor): self._widget.setZoomFactor(factor) @@ -799,6 +808,8 @@ class WebEngineAudio(browsertab.AbstractAudio): If that's the case, we leave it alone. """ + _widget: webview.WebEngineView + def __init__(self, tab, parent=None): super().__init__(tab, parent) self._overridden = False @@ -870,6 +881,8 @@ class _WebEnginePermissions(QObject): """Handling of various permission-related signals.""" + _widget: webview.WebEngineView + # Using 0 as WORKAROUND for: # https://www.riverbankcomputing.com/pipermail/pyqt/2019-July/041903.html @@ -898,7 +911,7 @@ class _WebEnginePermissions(QObject): def __init__(self, tab, parent=None): super().__init__(parent) self._tab = tab - self._widget = cast(QWidget, None) + self._widget = cast(webview.WebEngineView, None) assert self._options.keys() == self._messages.keys() def connect_signals(self): @@ -1033,10 +1046,12 @@ class _Quirk: class _WebEngineScripts(QObject): + _widget: webview.WebEngineView + def __init__(self, tab, parent=None): super().__init__(parent) self._tab = tab - self._widget = cast(QWidget, None) + self._widget = cast(webview.WebEngineView, None) self._greasemonkey = greasemonkey.gm_manager def connect_signals(self): @@ -1240,6 +1255,8 @@ class WebEngineTabPrivate(browsertab.AbstractTabPrivate): """QtWebEngine-related methods which aren't part of the public API.""" + _widget: webview.WebEngineView + def networkaccessmanager(self): return None @@ -1275,6 +1292,10 @@ class WebEngineTab(browsertab.AbstractTab): abort_questions = pyqtSignal() + _widget: QWebEngineView + search: WebEngineSearch + audio: WebEngineAudio + def __init__(self, *, win_id, mode_manager, private, parent=None): super().__init__(win_id=win_id, mode_manager=mode_manager, @@ -1685,6 +1706,7 @@ class WebEngineTab(browsertab.AbstractTab): def _connect_signals(self): view = self._widget page = view.page() + assert isinstance(page, webview.WebEnginePage) page.windowCloseRequested.connect(self.window_close_requested) page.linkHovered.connect(self.link_hovered) diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 76ce1a42e..6e72df014 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -70,7 +70,9 @@ class WebEngineView(QWebEngineView): return self.focusProxy() def shutdown(self): - self.page().shutdown() + page = self.page() + assert isinstance(page, WebEnginePage) + page.shutdown() def createWindow(self, wintype): """Called by Qt when a page wants to create a new window. diff --git a/qutebrowser/browser/webkit/network/networkreply.py b/qutebrowser/browser/webkit/network/networkreply.py index c1ead3209..d5c724d63 100644 --- a/qutebrowser/browser/webkit/network/networkreply.py +++ b/qutebrowser/browser/webkit/network/networkreply.py @@ -59,15 +59,9 @@ class FixedDataNetworkReply(QNetworkReply): # For some reason, a segfault will be triggered if these lambdas aren't # there. # pylint: disable=unnecessary-lambda - QTimer.singleShot( - 0, - lambda: self.metaDataChanged.emit()) # type: ignore[attr-defined] - QTimer.singleShot( - 0, - lambda: self.readyRead.emit()) # type: ignore[attr-defined] - QTimer.singleShot( - 0, - lambda: self.finished.emit()) # type: ignore[attr-defined] + QTimer.singleShot(0, lambda: self.metaDataChanged.emit()) + QTimer.singleShot(0, lambda: self.readyRead.emit()) + QTimer.singleShot(0, lambda: self.finished.emit()) @pyqtSlot() def abort(self): @@ -122,10 +116,8 @@ class ErrorNetworkReply(QNetworkReply): # the device to avoid getting a warning. self.setOpenMode(QIODevice.ReadOnly) self.setError(error, errorstring) - QTimer.singleShot(0, lambda: - self.error.emit(error)) # type: ignore[attr-defined] - QTimer.singleShot(0, lambda: - self.finished.emit()) # type: ignore[attr-defined] + QTimer.singleShot(0, lambda: self.error.emit(error)) + QTimer.singleShot(0, lambda: self.finished.emit()) def abort(self): """Do nothing since it's a fake reply.""" @@ -152,8 +144,7 @@ class RedirectNetworkReply(QNetworkReply): def __init__(self, new_url, parent=None): super().__init__(parent) self.setAttribute(QNetworkRequest.RedirectionTargetAttribute, new_url) - QTimer.singleShot(0, lambda: - self.finished.emit()) # type: ignore[attr-defined] + QTimer.singleShot(0, lambda: self.finished.emit()) def abort(self): """Called when there's e.g. a redirection limit.""" diff --git a/qutebrowser/browser/webkit/webkitinspector.py b/qutebrowser/browser/webkit/webkitinspector.py index 57bcd40ea..2d4b95caf 100644 --- a/qutebrowser/browser/webkit/webkitinspector.py +++ b/qutebrowser/browser/webkit/webkitinspector.py @@ -31,6 +31,8 @@ class WebKitInspector(inspector.AbstractWebInspector): """A web inspector for QtWebKit.""" + _widget = QWebInspector + def __init__(self, splitter: miscwidgets.InspectorSplitter, win_id: int, parent: QWidget = None) -> None: @@ -38,7 +40,7 @@ class WebKitInspector(inspector.AbstractWebInspector): qwebinspector = QWebInspector() self._set_widget(qwebinspector) - def inspect(self, page: QWebPage) -> None: # type: ignore[override] + def inspect(self, page: QWebPage) -> None: settings = QWebSettings.globalSettings() settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True) self._widget.setPage(page) diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 7a41b995c..6be096acf 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -32,8 +32,9 @@ from PyQt5.QtWebKit import QWebSettings, QWebHistory, QWebElement from PyQt5.QtPrintSupport import QPrinter from qutebrowser.browser import browsertab, shared -from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem, +from qutebrowser.browser.webkit import (webview, webpage, tabhistory, webkitelem, webkitsettings, webkitinspector) +from qutebrowser.browser.webkit.network import networkmanager from qutebrowser.utils import qtutils, usertypes, utils, log, debug, resources from qutebrowser.keyinput import modeman from qutebrowser.qt import sip @@ -46,6 +47,8 @@ class WebKitAction(browsertab.AbstractAction): action_class = QWebPage action_base = QWebPage.WebAction + _widget: webview.WebView + def exit_fullscreen(self): raise browsertab.UnsupportedOperationError @@ -69,7 +72,7 @@ class WebKitAction(browsertab.AbstractAction): 'Unselect': QWebPage.ToggleVideoFullscreen + 2, } if name in new_actions: - self._widget.triggerPageAction(new_actions[name]) + self._widget.triggerPageAction(new_actions[name]) # type: ignore[arg-type] return super().run_string(name) @@ -79,6 +82,8 @@ class WebKitPrinting(browsertab.AbstractPrinting): """QtWebKit implementations related to printing.""" + _widget: webview.WebView + def check_pdf_support(self): pass @@ -101,6 +106,8 @@ class WebKitSearch(browsertab.AbstractSearch): """QtWebKit implementations related to searching on the page.""" + _widget: webview.WebView + def __init__(self, tab, parent=None): super().__init__(tab, parent) self._flags = self._empty_flags() @@ -153,7 +160,8 @@ class WebKitSearch(browsertab.AbstractSearch): self.search_displayed = False # We first clear the marked text, then the highlights self._widget.findText('') - self._widget.findText('', QWebPage.HighlightAllOccurrences) + self._widget.findText( + '', QWebPage.HighlightAllOccurrences) # type: ignore[arg-type] def search(self, text, *, ignore_case=usertypes.IgnoreCase.never, reverse=False, wrap=True, result_cb=None): @@ -179,7 +187,7 @@ class WebKitSearch(browsertab.AbstractSearch): def next_result(self, *, result_cb=None): self.search_displayed = True - found = self._widget.findText(self.text, self._flags) + found = self._widget.findText(self.text, self._flags) # type: ignore[arg-type] self._call_cb(result_cb, found, self.text, self._flags, 'next_result') def prev_result(self, *, result_cb=None): @@ -191,7 +199,7 @@ class WebKitSearch(browsertab.AbstractSearch): flags &= ~QWebPage.FindBackward else: flags |= QWebPage.FindBackward - found = self._widget.findText(self.text, flags) + found = self._widget.findText(self.text, flags) # type: ignore[arg-type] self._call_cb(result_cb, found, self.text, flags, 'prev_result') @@ -199,6 +207,8 @@ class WebKitCaret(browsertab.AbstractCaret): """QtWebKit implementations related to moving the cursor/selection.""" + _widget: webview.WebView + def __init__(self, tab: 'WebKitTab', mode_manager: modeman.ModeManager, @@ -515,6 +525,8 @@ class WebKitZoom(browsertab.AbstractZoom): """QtWebKit implementations related to zooming.""" + _widget: webview.WebView + def _set_factor_internal(self, factor): self._widget.setZoomFactor(factor) @@ -525,6 +537,8 @@ class WebKitScroller(browsertab.AbstractScroller): # FIXME:qtwebengine When to use the main frame, when the current one? + _widget: webview.WebView + def pos_px(self): return self._widget.page().mainFrame().scrollPosition() @@ -624,6 +638,8 @@ class WebKitHistoryPrivate(browsertab.AbstractHistoryPrivate): """History-related methods which are not part of the extension API.""" + _history: QWebHistory + def __init__(self, tab: 'WebKitTab') -> None: self._tab = tab self._history = cast(QWebHistory, None) @@ -695,6 +711,7 @@ class WebKitElements(browsertab.AbstractElements): """QtWebKit implementations related to elements on the page.""" _tab: 'WebKitTab' + _widget: webview.WebView def find_css(self, selector, callback, error_cb, *, only_visible=False): utils.unused(error_cb) @@ -730,7 +747,7 @@ class WebKitElements(browsertab.AbstractElements): self.find_css('#' + elem_id, find_id_cb, error_cb=lambda exc: None) def find_focused(self, callback): - frame = self._widget.page().currentFrame() + frame = cast(Optional[QWebFrame], self._widget.page().currentFrame()) if frame is None: callback(None) return @@ -744,7 +761,7 @@ class WebKitElements(browsertab.AbstractElements): def find_at_pos(self, pos, callback): assert pos.x() >= 0 assert pos.y() >= 0 - frame = self._widget.page().frameAt(pos) + frame = cast(Optional[QWebFrame], self._widget.page().frameAt(pos)) if frame is None: # This happens when we click inside the webview, but not actually # on the QWebPage - for example when clicking the scrollbar @@ -796,6 +813,8 @@ class WebKitTabPrivate(browsertab.AbstractTabPrivate): """QtWebKit-related methods which aren't part of the public API.""" + _widget: webview.WebView + def networkaccessmanager(self): return self._widget.page().networkAccessManager() @@ -821,6 +840,8 @@ class WebKitTab(browsertab.AbstractTab): """A QtWebKit tab in the browser.""" + _widget: webview.WebView + def __init__(self, *, win_id, mode_manager, private, parent=None): super().__init__(win_id=win_id, mode_manager=mode_manager, @@ -912,6 +933,7 @@ class WebKitTab(browsertab.AbstractTab): def _on_load_started(self): super()._on_load_started() nam = self._widget.page().networkAccessManager() + assert isinstance(nam, networkmanager.NetworkManager) nam.netrc_used = False # Make sure the icon is cleared when navigating to a page without one. self.icon_changed.emit(QIcon()) @@ -929,7 +951,9 @@ class WebKitTab(browsertab.AbstractTab): when using error pages... See https://github.com/qutebrowser/qutebrowser/issues/84 """ - self._on_load_finished(not self._widget.page().error_occurred) + page = self._widget.page() + assert isinstance(page, webpage.BrowserPage) + self._on_load_finished(not page.error_occurred) @pyqtSlot() def _on_webkit_icon_changed(self): @@ -984,18 +1008,30 @@ class WebKitTab(browsertab.AbstractTab): view = self._widget page = view.page() frame = page.mainFrame() - page.windowCloseRequested.connect(self.window_close_requested) - page.linkHovered.connect(self.link_hovered) - page.loadProgress.connect(self._on_load_progress) - frame.loadStarted.connect(self._on_load_started) + page.windowCloseRequested.connect( # type: ignore[attr-defined] + self.window_close_requested) + page.linkHovered.connect( # type: ignore[attr-defined] + self.link_hovered) + page.loadProgress.connect( # type: ignore[attr-defined] + self._on_load_progress) + frame.loadStarted.connect( # type: ignore[attr-defined] + self._on_load_started) view.scroll_pos_changed.connect(self.scroller.perc_changed) - view.titleChanged.connect(self.title_changed) - view.urlChanged.connect(self._on_url_changed) + view.titleChanged.connect( # type: ignore[attr-defined] + self.title_changed) + view.urlChanged.connect( # type: ignore[attr-defined] + self._on_url_changed) view.shutting_down.connect(self.shutting_down) page.networkAccessManager().sslErrors.connect(self._on_ssl_errors) - frame.loadFinished.connect(self._on_frame_load_finished) - view.iconChanged.connect(self._on_webkit_icon_changed) - page.frameCreated.connect(self._on_frame_created) - frame.contentsSizeChanged.connect(self._on_contents_size_changed) - frame.initialLayoutCompleted.connect(self._on_history_trigger) - page.navigation_request.connect(self._on_navigation_request) + frame.loadFinished.connect( # type: ignore[attr-defined] + self._on_frame_load_finished) + view.iconChanged.connect( # type: ignore[attr-defined] + self._on_webkit_icon_changed) + page.frameCreated.connect( # type: ignore[attr-defined] + self._on_frame_created) + frame.contentsSizeChanged.connect( # type: ignore[attr-defined] + self._on_contents_size_changed) + frame.initialLayoutCompleted.connect( # type: ignore[attr-defined] + self._on_history_trigger) + page.navigation_request.connect( # type: ignore[attr-defined] + self._on_navigation_request) diff --git a/qutebrowser/browser/webkit/webpage.py b/qutebrowser/browser/webkit/webpage.py index ddbd78de2..0adc003d1 100644 --- a/qutebrowser/browser/webkit/webpage.py +++ b/qutebrowser/browser/webkit/webpage.py @@ -241,6 +241,7 @@ class BrowserPage(QWebPage): if download_manager.has_downloads_with_nam(nam): nam.setParent(download_manager) else: + assert isinstance(nam, networkmanager.NetworkManager) nam.shutdown() def display_content(self, reply, mimetype): @@ -371,11 +372,10 @@ class BrowserPage(QWebPage): self.setFeaturePermission, frame, feature, QWebPage.PermissionDeniedByUser) - url = frame.url().adjusted(cast(QUrl.FormattingOptions, - QUrl.RemoveUserInfo | - QUrl.RemovePath | - QUrl.RemoveQuery | - QUrl.RemoveFragment)) + url = frame.url().adjusted(QUrl.RemoveUserInfo | # type: ignore[operator] + QUrl.RemovePath | + QUrl.RemoveQuery | + QUrl.RemoveFragment) question = shared.feature_permission( url=url, option=options[feature], msg=messages[feature], diff --git a/qutebrowser/browser/webkit/webview.py b/qutebrowser/browser/webkit/webview.py index 289e29920..e54c4af50 100644 --- a/qutebrowser/browser/webkit/webview.py +++ b/qutebrowser/browser/webkit/webview.py @@ -109,7 +109,9 @@ class WebView(QWebView): settings = self.settings() settings.setAttribute(QWebSettings.JavascriptEnabled, False) self.stop() - self.page().shutdown() + page = self.page() + assert isinstance(page, webpage.BrowserPage) + page.shutdown() def createWindow(self, wintype): """Called by Qt when a page wants to create a new window. diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py index 8282aa7c7..c776324ac 100644 --- a/qutebrowser/commands/userscripts.py +++ b/qutebrowser/commands/userscripts.py @@ -63,8 +63,7 @@ class _QtFIFOReader(QObject): self._fifo = os.fdopen(fd, 'r') self._notifier = QSocketNotifier(cast(sip.voidptr, fd), QSocketNotifier.Read, self) - self._notifier.activated.connect( # type: ignore[attr-defined] - self.read_line) + self._notifier.activated.connect(self.read_line) @pyqtSlot() def read_line(self): diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index 8b0de9d8a..b7d49a274 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -29,6 +29,7 @@ from qutebrowser.commands import parser, cmdexc from qutebrowser.misc import objects, split from qutebrowser.utils import log, utils, debug, objreg from qutebrowser.completion.models import miscmodels +from qutebrowser.completion import completionwidget if TYPE_CHECKING: from qutebrowser.browser import browsertab @@ -74,10 +75,15 @@ class Completer(QObject): def __repr__(self): return utils.get_repr(self) + def _completion(self) -> completionwidget.CompletionView: + """Convenience method to get the current completion.""" + completion = self.parent() + assert isinstance(completion, completionwidget.CompletionView) + return completion + def _model(self): """Convenience method to get the current completion model.""" - completion = self.parent() - return completion.model() + return self._completion().model() def _get_new_completion(self, before_cursor, under_cursor): """Get the completion function based on the current command text. @@ -230,7 +236,7 @@ class Completer(QObject): @pyqtSlot() def _update_completion(self): """Check if completions are available and activate them.""" - completion = self.parent() + completion = self._completion() if self._cmd.prefix() != ':': # This is a search or gibberish, so we don't need to complete diff --git a/qutebrowser/completion/completiondelegate.py b/qutebrowser/completion/completiondelegate.py index 9ea82e876..e233a31bc 100644 --- a/qutebrowser/completion/completiondelegate.py +++ b/qutebrowser/completion/completiondelegate.py @@ -299,7 +299,7 @@ class CompletionItemDelegate(QStyledItemDelegate): size = self._style.sizeFromContents(QStyle.CT_ItemViewItem, self._opt, docsize, self._opt.widget) qtutils.ensure_valid(size) - return size + QSize(10, 3) # type: ignore[operator] + return size + QSize(10, 3) def paint(self, painter, option, index): """Override the QStyledItemDelegate paint function. diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index dd8e267fe..de9100a2e 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -30,6 +30,7 @@ from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QItemSelectionModel, QSize from qutebrowser.config import config, stylesheet from qutebrowser.completion import completiondelegate +from qutebrowser.completion.models import completionmodel from qutebrowser.utils import utils, usertypes, debug, log, qtutils from qutebrowser.api import cmdutils if TYPE_CHECKING: @@ -148,6 +149,15 @@ class CompletionView(QTreeView): def __repr__(self): return utils.get_repr(self) + def _model(self) -> completionmodel.CompletionModel: + """Get the current completion model. + + Ensures the model is not None. + """ + model = self.model() + assert isinstance(model, completionmodel.CompletionModel), model + return model + @pyqtSlot(str) def _on_config_changed(self, option): if option in ['completion.height', 'completion.shrink']: @@ -158,7 +168,7 @@ class CompletionView(QTreeView): if self.model() is None: return width = self.size().width() - column_widths = self.model().column_widths + column_widths = self._model().column_widths pixel_widths = [(width * perc // 100) for perc in column_widths] delta = self.verticalScrollBar().sizeHint().width() @@ -182,21 +192,22 @@ class CompletionView(QTreeView): Return: A QModelIndex. """ + model = self._model() idx = self.selectionModel().currentIndex() if not idx.isValid(): # No item selected yet if upwards: - return self.model().last_item() + return model.last_item() else: - return self.model().first_item() + return model.first_item() while True: idx = self.indexAbove(idx) if upwards else self.indexBelow(idx) # wrap around if we arrived at beginning/end if not idx.isValid() and upwards: - return self.model().last_item() + return model.last_item() elif not idx.isValid() and not upwards: - idx = self.model().first_item() + idx = model.first_item() self.scrollTo(idx.parent()) return idx elif idx.parent().isValid(): @@ -216,7 +227,7 @@ class CompletionView(QTreeView): """ old_idx = self.selectionModel().currentIndex() idx = old_idx - model = self.model() + model = self._model() if not idx.isValid(): # No item selected yet @@ -259,6 +270,7 @@ class CompletionView(QTreeView): A QModelIndex. """ idx = self.selectionModel().currentIndex() + model = self._model() if not idx.isValid(): return self._next_idx(upwards).sibling(0, 0) idx = idx.parent() @@ -267,10 +279,10 @@ class CompletionView(QTreeView): idx = idx.sibling(idx.row() + direction, 0) if not idx.isValid() and upwards: # wrap around to the first item of the last category - return self.model().last_item().sibling(0, 0) + return model.last_item().sibling(0, 0) elif not idx.isValid() and not upwards: # wrap around to the first item of the first category - idx = self.model().first_item() + idx = model.first_item() self.scrollTo(idx.parent()) return idx elif idx.isValid() and idx.child(0, 0).isValid(): @@ -327,7 +339,7 @@ class CompletionView(QTreeView): selmodel.setCurrentIndex( idx, - QItemSelectionModel.ClearAndSelect | # type: ignore[arg-type] + QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) # if the last item is focused, try to fetch more @@ -335,7 +347,7 @@ class CompletionView(QTreeView): if not self.visualRect(next_idx).isValid(): self.expandAll() - count = self.model().count() + count = self._model().count() if count == 0: self.hide() elif count == 1 and config.val.completion.quick: @@ -382,14 +394,14 @@ class CompletionView(QTreeView): return self.pattern = pattern with debug.log_time(log.completion, 'Set pattern {}'.format(pattern)): - self.model().set_pattern(pattern) + self._model().set_pattern(pattern) self.selectionModel().clear() self._maybe_update_geometry() self._maybe_show() def _maybe_show(self): if (config.val.completion.show == 'always' and - self.model().count() > 0): + self._model().count() > 0): self.show() else: self.hide() @@ -435,7 +447,7 @@ class CompletionView(QTreeView): indexes = selected.indexes() if not indexes: return - data = str(self.model().data(indexes[0])) + data = str(self._model().data(indexes[0])) self.selection_changed.emit(data) def resizeEvent(self, e): @@ -458,7 +470,7 @@ class CompletionView(QTreeView): index = self.currentIndex() if not index.isValid(): raise cmdutils.CommandError("No item selected!") - self.model().delete_cur_item(index) + self._model().delete_cur_item(index) @cmdutils.register(instance='completion', modes=[usertypes.KeyMode.command], scope='window') @@ -473,7 +485,7 @@ class CompletionView(QTreeView): index = self.currentIndex() if not index.isValid(): raise cmdutils.CommandError("No item selected!") - text = self.model().data(index) + text = self._model().data(index) if not utils.supports_selection(): sel = False diff --git a/qutebrowser/completion/models/completionmodel.py b/qutebrowser/completion/models/completionmodel.py index 236b25533..4236c7777 100644 --- a/qutebrowser/completion/models/completionmodel.py +++ b/qutebrowser/completion/models/completionmodel.py @@ -180,10 +180,10 @@ class CompletionModel(QAbstractItemModel): pattern: The filter pattern to set. """ log.completion.debug("Setting completion pattern '{}'".format(pattern)) - self.layoutAboutToBeChanged.emit() # type: ignore[attr-defined] + self.layoutAboutToBeChanged.emit() for cat in self._categories: cat.set_pattern(pattern) - self.layoutChanged.emit() # type: ignore[attr-defined] + self.layoutChanged.emit() def first_item(self): """Return the index of the first child (non-category) in the model.""" diff --git a/qutebrowser/components/misccommands.py b/qutebrowser/components/misccommands.py index fe908b7d2..c20e8e290 100644 --- a/qutebrowser/components/misccommands.py +++ b/qutebrowser/components/misccommands.py @@ -83,7 +83,7 @@ def _print_preview(tab: apitypes.Tab) -> None: diag = QPrintPreviewDialog(tab) diag.setAttribute(Qt.WA_DeleteOnClose) diag.setWindowFlags( - diag.windowFlags() | # type: ignore[operator, arg-type] + diag.windowFlags() | Qt.WindowMaximizeButtonHint | Qt.WindowMinimizeButtonHint) diag.paintRequested.connect(functools.partial( diff --git a/qutebrowser/components/readlinecommands.py b/qutebrowser/components/readlinecommands.py index 4ac03041c..a0631f3d1 100644 --- a/qutebrowser/components/readlinecommands.py +++ b/qutebrowser/components/readlinecommands.py @@ -42,7 +42,7 @@ class _ReadlineBridge: """Get the currently active QLineEdit.""" # FIXME add this to api.utils or so qapp = QApplication.instance() - assert qapp is not None + assert isinstance(qapp, QApplication) w = qapp.focusWidget() if isinstance(w, QLineEdit): diff --git a/qutebrowser/components/scrollcommands.py b/qutebrowser/components/scrollcommands.py index 757c23b2b..faa31a931 100644 --- a/qutebrowser/components/scrollcommands.py +++ b/qutebrowser/components/scrollcommands.py @@ -19,6 +19,7 @@ """Scrolling-related commands.""" +from typing import Dict, Callable, Any from qutebrowser.api import cmdutils, apitypes @@ -54,7 +55,8 @@ def scroll(tab: apitypes.Tab, direction: str, count: int = 1) -> None: (up/down/left/right/top/bottom). count: multiplier """ - funcs = { + # FIXME:mypy Use a callback protocol to enforce having 'count'? + funcs: Dict[str, Callable[..., None]] = { 'up': tab.scroller.up, 'down': tab.scroller.down, 'left': tab.scroller.left, diff --git a/qutebrowser/config/stylesheet.py b/qutebrowser/config/stylesheet.py index 2927aec08..2c0c5fb07 100644 --- a/qutebrowser/config/stylesheet.py +++ b/qutebrowser/config/stylesheet.py @@ -23,6 +23,7 @@ import functools from typing import Optional, FrozenSet from PyQt5.QtCore import pyqtSlot, QObject +from PyQt5.QtWidgets import QWidget from qutebrowser.config import config from qutebrowser.misc import debugcachestats @@ -71,7 +72,7 @@ class _StyleSheetObserver(QObject): None. """ - def __init__(self, obj: QObject, + def __init__(self, obj: QWidget, stylesheet: Optional[str], update: bool) -> None: super().__init__() self._obj = obj diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index f74c59aa7..465145386 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -156,7 +156,7 @@ def _assert_plain_key(key: Qt.Key) -> None: def _assert_plain_modifier(key: _ModifierType) -> None: """Make sure this is a modifier without a key mixed in.""" mask = Qt.KeyboardModifierMask - assert not key & ~mask, hex(key) # type: ignore[operator] + assert not key & ~mask, hex(key) def _is_printable(key: Qt.Key) -> bool: @@ -255,8 +255,8 @@ def _modifiers_to_string(modifiers: _ModifierType) -> str: """ _assert_plain_modifier(modifiers) altgr = Qt.GroupSwitchModifier - if modifiers & altgr: # type: ignore[operator] - modifiers &= ~altgr # type: ignore[operator, assignment] + if modifiers & altgr: + modifiers &= ~altgr result = 'AltGr+' else: result = '' @@ -416,7 +416,7 @@ class KeyInfo: return '' text = QKeySequence(self.key).toString() - if not self.modifiers & Qt.ShiftModifier: # type: ignore[operator] + if not self.modifiers & Qt.ShiftModifier: text = text.lower() return text @@ -473,7 +473,7 @@ class KeySequence: """Iterate over KeyInfo objects.""" for key_and_modifiers in self._iter_keys(): key = Qt.Key(int(key_and_modifiers) & ~Qt.KeyboardModifierMask) - modifiers = Qt.KeyboardModifiers( # type: ignore[call-overload] + modifiers = Qt.KeyboardModifiers( int(key_and_modifiers) & Qt.KeyboardModifierMask) yield KeyInfo(key=key, modifiers=modifiers) diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 3c47fafe3..24c701401 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -37,6 +37,7 @@ from qutebrowser.misc import objects INPUT_MODES = [usertypes.KeyMode.insert, usertypes.KeyMode.passthrough] PROMPT_MODES = [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno] +# FIXME:mypy TypedDict? ParserDictType = MutableMapping[usertypes.KeyMode, basekeyparser.BaseKeyParser] diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index 7f62c2dc4..74522a091 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -92,7 +92,7 @@ def raise_window(window, alert=True): window.setWindowState(window.windowState() | Qt.WindowActive) window.raise_() # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-69568 - QCoreApplication.processEvents( # type: ignore[call-overload] + QCoreApplication.processEvents( QEventLoop.ExcludeUserInputEvents | QEventLoop.ExcludeSocketNotifiers) if not sip.isdeleted(window): @@ -561,11 +561,11 @@ class MainWindow(QWidget): def _set_decoration(self, hidden): """Set the visibility of the window decoration via Qt.""" - window_flags: int = Qt.Window + window_flags = cast(Qt.WindowFlags, Qt.Window) refresh_window = self.isVisible() if hidden: window_flags |= Qt.CustomizeWindowHint | Qt.NoDropShadowWindowHint - self.setWindowFlags(cast(Qt.WindowFlags, window_flags)) + self.setWindowFlags(window_flags) if refresh_window: self.show() @@ -574,9 +574,7 @@ class MainWindow(QWidget): if not config.val.content.fullscreen.window: if on: self.state_before_fullscreen = self.windowState() - self.setWindowState( - Qt.WindowFullScreen | # type: ignore[arg-type] - self.state_before_fullscreen) # type: ignore[operator] + self.setWindowState(Qt.WindowFullScreen | self.state_before_fullscreen) elif self.isFullScreen(): self.setWindowState(self.state_before_fullscreen) log.misc.debug('on: {}, state before fullscreen: {}'.format( diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 6fea16093..f7a04bee0 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -784,7 +784,7 @@ class FilenamePrompt(_BasePrompt): selmodel.setCurrentIndex( idx, - QItemSelectionModel.ClearAndSelect | # type: ignore[arg-type] + QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) self._insert_path(idx, clicked=False) @@ -808,7 +808,7 @@ class DownloadFilenamePrompt(FilenamePrompt): def __init__(self, question, parent=None): super().__init__(question, parent) self._file_model.setFilter( - QDir.AllDirs | QDir.Drives | QDir.NoDotAndDotDot) # type: ignore[arg-type] + QDir.AllDirs | QDir.Drives | QDir.NoDotAndDotDot) def accept(self, value=None, save=False): done = super().accept(value, save) diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index 8bad290be..46cf083bd 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -22,8 +22,7 @@ import enum import dataclasses -from PyQt5.QtCore import (pyqtSignal, pyqtSlot, # type: ignore[attr-defined] - pyqtProperty, Qt, QSize, QTimer) +from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt, QSize, QTimer from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy from qutebrowser.browser import browsertab @@ -300,7 +299,7 @@ class StatusBar(QWidget): padding = config.val.statusbar.padding self._hbox.setContentsMargins(padding.left, 0, padding.right, 0) - @pyqtProperty('QStringList') + @pyqtProperty('QStringList') # type: ignore[type-var] def color_flags(self): """Getter for self.color_flags, so it can be used as Qt property.""" return self._color_flags.to_stringlist() diff --git a/qutebrowser/mainwindow/statusbar/command.py b/qutebrowser/mainwindow/statusbar/command.py index 199f0a103..95076380a 100644 --- a/qutebrowser/mainwindow/statusbar/command.py +++ b/qutebrowser/mainwindow/statusbar/command.py @@ -19,6 +19,7 @@ """The commandline in the statusbar.""" +from typing import Optional from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QSize from PyQt5.QtGui import QKeyEvent @@ -240,7 +241,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit): self.clear_completion_selection.emit() self.hide_completion.emit() - def setText(self, text: str) -> None: + def setText(self, text: Optional[str]) -> None: """Extend setText to set prefix and make sure the prompt is ok.""" if not text: pass diff --git a/qutebrowser/mainwindow/statusbar/url.py b/qutebrowser/mainwindow/statusbar/url.py index 99818e284..ca9b15779 100644 --- a/qutebrowser/mainwindow/statusbar/url.py +++ b/qutebrowser/mainwindow/statusbar/url.py @@ -21,8 +21,7 @@ import enum -from PyQt5.QtCore import (pyqtSlot, pyqtProperty, # type: ignore[attr-defined] - QUrl) +from PyQt5.QtCore import pyqtSlot, pyqtProperty, QUrl from qutebrowser.mainwindow.statusbar import textbase from qutebrowser.config import stylesheet diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index a96f6d583..840e69cd0 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -28,7 +28,7 @@ from typing import ( Any, Deque, List, Mapping, MutableMapping, MutableSequence, Optional, Tuple) from PyQt5.QtWidgets import QSizePolicy, QWidget, QApplication -from PyQt5.QtCore import pyqtSignal, pyqtSlot, QTimer, QUrl +from PyQt5.QtCore import pyqtSignal, pyqtSlot, QTimer, QUrl, QPoint from qutebrowser.config import config from qutebrowser.keyinput import modeman @@ -73,14 +73,14 @@ class TabDeque: size = config.val.tabs.focus_stack_size if size < 0: size = None - self._stack: Deque[weakref.ReferenceType[QWidget]] = collections.deque( - maxlen=size) + self._stack: Deque[weakref.ReferenceType[browsertab.AbstractTab]] = ( + collections.deque(maxlen=size)) # Items that have been removed from the primary stack. - self._stack_deleted: List[weakref.ReferenceType[QWidget]] = [] + self._stack_deleted: List[weakref.ReferenceType[browsertab.AbstractTab]] = [] self._ignore_next = False self._keep_deleted_next = False - def on_switch(self, old_tab: QWidget) -> None: + def on_switch(self, old_tab: browsertab.AbstractTab) -> None: """Record tab switch events.""" if self._ignore_next: self._ignore_next = False @@ -92,24 +92,29 @@ class TabDeque: self._keep_deleted_next = False self._stack.append(tab) - def prev(self, cur_tab: QWidget) -> QWidget: + def prev(self, cur_tab: browsertab.AbstractTab) -> browsertab.AbstractTab: """Get the 'previous' tab in the stack. Throws IndexError on failure. """ - tab: Optional[QWidget] = None + tab: Optional[browsertab.AbstractTab] = None while tab is None or tab.pending_removal or tab is cur_tab: tab = self._stack.pop()() self._stack_deleted.append(weakref.ref(cur_tab)) self._ignore_next = True return tab - def next(self, cur_tab: QWidget, *, keep_overflow: bool = True) -> QWidget: + def next( + self, + cur_tab: browsertab.AbstractTab, + *, + keep_overflow: bool = True, + ) -> browsertab.AbstractTab: """Get the 'next' tab in the stack. Throws IndexError on failure. """ - tab: Optional[QWidget] = None + tab: Optional[browsertab.AbstractTab] = None while tab is None or tab.pending_removal or tab is cur_tab: tab = self._stack_deleted.pop()() # On next tab-switch, current tab will be added to stack as normal. @@ -118,7 +123,7 @@ class TabDeque: self._keep_deleted_next = True return tab - def last(self, cur_tab: QWidget) -> QWidget: + def last(self, cur_tab: browsertab.AbstractTab) -> browsertab.AbstractTab: """Get the last tab. Throws IndexError on failure. @@ -216,7 +221,11 @@ class TabbedBrowser(QWidget): self.widget.tabCloseRequested.connect(self.on_tab_close_requested) self.widget.new_tab_requested.connect(self.tabopen) self.widget.currentChanged.connect(self._on_current_changed) - self.cur_fullscreen_requested.connect(self.widget.tabBar().maybe_hide) + + tabbar = self.widget.tabBar() + assert isinstance(tabbar, tabwidget.TabBar) + self.cur_fullscreen_requested.connect(tabbar.maybe_hide) + self.widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # load_finished instead of load_started as WORKAROUND for @@ -235,8 +244,8 @@ class TabbedBrowser(QWidget): self._now_focused = None self.search_text = None self.search_options: Mapping[str, Any] = {} - self._local_marks: MutableMapping[QUrl, MutableMapping[str, int]] = {} - self._global_marks: MutableMapping[str, Tuple[int, QUrl]] = {} + 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.is_private = private self.tab_deque = TabDeque() @@ -373,6 +382,25 @@ class TabbedBrowser(QWidget): tab.history_item_triggered.connect( history.web_history.add_from_tab) + def _current_tab(self) -> browsertab.AbstractTab: + """Get the current browser tab. + + Note: The assert ensures the current tab is never None. + """ + tab = self.widget.currentWidget() + assert isinstance(tab, browsertab.AbstractTab), tab + return tab + + def _tab_by_idx(self, idx: int) -> Optional[browsertab.AbstractTab]: + """Get a browser tab by index. + + If no tab was found at the given index, None is returned. + """ + tab = self.widget.widget(idx) + if tab is not None: + assert isinstance(tab, browsertab.AbstractTab), tab + return tab + def current_url(self): """Get the URL of the current tab. @@ -503,13 +531,15 @@ class TabbedBrowser(QWidget): ] only_one_tab_open = self.widget.count() == 1 if only_one_tab_open and last_close_replaces: - no_history = len(self.widget.widget(0).history) == 1 + tab = self._tab_by_idx(0) + assert tab is not None + no_history = len(tab.history) == 1 urls = { 'blank': QUrl('about:blank'), 'startpage': config.val.url.start_pages[0], 'default-page': config.val.url.default_page, } - first_tab_url = self.widget.widget(0).url() + first_tab_url = tab.url() last_close_urlstr = urls[last_close].toString().rstrip('/') first_tab_urlstr = first_tab_url.toString().rstrip('/') last_close_url_used = first_tab_urlstr == last_close_urlstr @@ -520,7 +550,8 @@ class TabbedBrowser(QWidget): for entry in reversed(entries): if use_current_tab: - newtab = self.widget.widget(0) + newtab = self._tab_by_idx(0) + assert newtab is not None use_current_tab = False else: newtab = self.tabopen(background=False, idx=entry.index) @@ -540,14 +571,14 @@ class TabbedBrowser(QWidget): if newtab or self.widget.currentWidget() is None: self.tabopen(url, background=False) else: - self.widget.currentWidget().load_url(url) + self._current_tab().load_url(url) @pyqtSlot(int) def on_tab_close_requested(self, idx): """Close a tab via an index.""" - tab = self.widget.widget(idx) + tab = self._tab_by_idx(idx) if tab is None: - log.webview.debug( # type: ignore[unreachable] + log.webview.debug( "Got invalid tab {} for index {}!".format(tab, idx)) return self.tab_close_prompt_if_pinned( @@ -820,6 +851,7 @@ class TabbedBrowser(QWidget): mode in modeman.INPUT_MODES): tab = self.widget.currentWidget() if tab is not None: + assert isinstance(tab, browsertab.AbstractTab) tab.data.input_mode = mode @pyqtSlot(usertypes.KeyMode) @@ -833,6 +865,7 @@ class TabbedBrowser(QWidget): widget)) widget.setFocus() if config.val.tabs.mode_on_change == 'restore': + assert isinstance(widget, browsertab.AbstractTab) widget.data.input_mode = usertypes.KeyMode.normal @pyqtSlot(int) @@ -842,9 +875,9 @@ class TabbedBrowser(QWidget): if idx == -1 or self.is_shutting_down: # closing the last tab (before quitting) or shutting down return - tab = self.widget.widget(idx) + tab = self._tab_by_idx(idx) if tab is None: - log.webview.debug( # type: ignore[unreachable] + log.webview.debug( "on_current_changed got called with invalid index {}" .format(idx)) return @@ -1022,7 +1055,7 @@ class TabbedBrowser(QWidget): if key != "'": message.error("Failed to set mark: url invalid") return - point = self.widget.currentWidget().scroller.pos_px() + point = self._current_tab().scroller.pos_px() if key.isupper(): self._global_marks[key] = point, url @@ -1043,7 +1076,7 @@ class TabbedBrowser(QWidget): except qtutils.QtValueError: urlkey = None - tab = self.widget.currentWidget() + tab = self._current_tab() if key.isupper(): if key in self._global_marks: diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 511c2c309..2239b5ad3 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -22,7 +22,7 @@ import functools import contextlib import dataclasses -from typing import Optional, cast +from typing import Optional, Dict, Any from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QSize, QRect, QPoint, QTimer, QUrl) @@ -76,17 +76,30 @@ class TabWidget(QTabWidget): @config.change_filter('tabs') def _init_config(self): """Initialize attributes based on the config.""" - tabbar = self.tabBar() self.setMovable(True) self.setTabsClosable(False) position = config.val.tabs.position selection_behavior = config.val.tabs.select_on_remove self.setTabPosition(position) - tabbar.vertical = position in [ # type: ignore[attr-defined] - QTabWidget.West, QTabWidget.East] + + tabbar = self._tab_bar() + tabbar.vertical = position in [QTabWidget.West, QTabWidget.East] tabbar.setSelectionBehaviorOnRemove(selection_behavior) tabbar.refresh() + def _tab_bar(self) -> "TabBar": + """Get the TabBar for this TabWidget.""" + bar = self.tabBar() + assert isinstance(bar, TabBar) + return bar + + def _tab_by_idx(self, idx: int) -> Optional[browsertab.AbstractTab]: + """Get the tab at the given index.""" + tab = self.widget(idx) + if tab is not None: + assert isinstance(tab, browsertab.AbstractTab) + return tab + def set_tab_indicator_color(self, idx, color): """Set the tab indicator color. @@ -94,17 +107,17 @@ class TabWidget(QTabWidget): idx: The tab index. color: A QColor. """ - bar = self.tabBar() + bar = self._tab_bar() bar.set_tab_data(idx, 'indicator-color', color) bar.update(bar.tabRect(idx)) def tab_indicator_color(self, idx): """Get the tab indicator color for the given index.""" - return self.tabBar().tab_indicator_color(idx) + return self._tab_bar().tab_indicator_color(idx) def set_page_title(self, idx, title): """Set the tab title user data.""" - tabbar = self.tabBar() + tabbar = self._tab_bar() if config.cache['tabs.tooltips']: # always show only plain title in tooltips @@ -115,7 +128,7 @@ class TabWidget(QTabWidget): def page_title(self, idx): """Get the tab title user data.""" - return self.tabBar().page_title(idx) + return self._tab_bar().page_title(idx) def update_tab_title(self, idx, field=None): """Update the tab text for the given tab. @@ -126,7 +139,8 @@ class TabWidget(QTabWidget): is only set if the given field is in the template. """ assert idx != -1 - tab = self.widget(idx) + tab = self._tab_by_idx(idx) + assert tab is not None if tab.data.pinned: fmt = config.cache['tabs.title.format_pinned'] else: @@ -142,7 +156,7 @@ class TabWidget(QTabWidget): def left_align(num): return str(num).ljust(len(str(self.count()))) - bar = self.tabBar() + bar = self._tab_bar() cur_idx = bar.currentIndex() if idx == cur_idx: rel_idx = left_align(idx + 1) + " " @@ -164,14 +178,12 @@ class TabWidget(QTabWidget): def get_tab_fields(self, idx): """Get the tab field data.""" - tab = self.widget(idx) - if tab is None: - log.misc.debug( # type: ignore[unreachable] - "Got None-tab in get_tab_fields!") + tab = self._tab_by_idx(idx) + assert tab is not None page_title = self.page_title(idx) - fields = {} + fields: Dict[str, Any] = {} fields['id'] = tab.tab_id fields['current_title'] = page_title fields['title_sep'] = ' - ' if page_title else '' @@ -206,9 +218,7 @@ class TabWidget(QTabWidget): fields['protocol'] = url.scheme() y = tab.scroller.pos_perc()[1] - if y is None: - scroll_pos = '???' - elif y <= 0: + if y <= 0: scroll_pos = 'top' elif y >= 100: scroll_pos = 'bot' @@ -228,7 +238,7 @@ class TabWidget(QTabWidget): non-visible. To avoid flickering, disable repaint updates while we work. """ - bar = self.tabBar() + bar = self._tab_bar() toggle = (self.count() > 10 and not bar.drag_in_progress and bar.isVisible()) @@ -317,7 +327,7 @@ class TabWidget(QTabWidget): @pyqtSlot(int) def _on_current_changed(self, index): """Emit the tab_index_changed signal if the current tab changed.""" - self.tabBar().on_current_changed() + self._tab_bar().on_current_changed() self.update_tab_titles() self.tab_index_changed.emit(index, self.count()) @@ -332,16 +342,13 @@ class TabWidget(QTabWidget): Return: The tab URL as QUrl. """ - tab = self.widget(idx) - if tab is None: - url = QUrl() # type: ignore[unreachable] - else: - url = tab.url() + tab = self._tab_by_idx(idx) + url = QUrl() if tab is None else tab.url() # It's possible for url to be invalid, but the caller will handle that. qtutils.ensure_valid(url) return url - def update_tab_favicon(self, tab: QWidget) -> None: + def update_tab_favicon(self, tab: browsertab.AbstractTab) -> None: """Update favicon of the given tab.""" idx = self.indexOf(tab) @@ -353,11 +360,11 @@ class TabWidget(QTabWidget): def setTabIcon(self, idx: int, icon: QIcon) -> None: """Always show tab icons for pinned tabs in some circumstances.""" - tab = cast(Optional[browsertab.AbstractTab], self.widget(idx)) + tab = self._tab_by_idx(idx) if (icon.isNull() and config.cache['tabs.favicons.show'] != 'never' and config.cache['tabs.pinned.shrink'] and - not self.tabBar().vertical and + not self._tab_bar().vertical and tab is not None and tab.data.pinned): icon = self.style().standardIcon(QStyle.SP_FileIcon) super().setTabIcon(idx, icon) @@ -423,9 +430,15 @@ class TabBar(QTabBar): def __repr__(self): return utils.get_repr(self, count=self.count()) + def _tab_widget(self): + """Get the TabWidget we're in.""" + parent = self.parent() + assert isinstance(parent, TabWidget) + return parent + def _current_tab(self): """Get the current tab object.""" - return self.parent().currentWidget() + return self._tab_widget().currentWidget() @pyqtSlot(str) def _on_config_changed(self, option: str) -> None: @@ -627,7 +640,7 @@ class TabBar(QTabBar): raise IndexError("Tab index ({}) out of range ({})!".format( index, self.count())) - widget = self.parent().widget(index) + widget = self._tab_widget().widget(index) if widget is None: # This could happen when Qt calls tabSizeHint while initializing # tabs. diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py index d94d3ec54..a79118044 100644 --- a/qutebrowser/misc/crashsignal.py +++ b/qutebrowser/misc/crashsignal.py @@ -361,8 +361,7 @@ class SignalHandler(QObject): self._notifier = QSocketNotifier(cast(sip.voidptr, read_fd), QSocketNotifier.Read, self) - self._notifier.activated.connect( # type: ignore[attr-defined] - self.handle_signal_wakeup) + self._notifier.activated.connect(self.handle_signal_wakeup) self._orig_wakeup_fd = signal.set_wakeup_fd(write_fd) # pylint: enable=import-error,no-member,useless-suppression else: diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index 3ef84284d..a4bc1d8ab 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -207,8 +207,7 @@ class ExternalEditor(QObject): if not ok: log.procs.error("Failed to watch path: {}" .format(self._filename)) - self._watcher.fileChanged.connect( # type: ignore[attr-defined] - self._on_file_changed) + self._watcher.fileChanged.connect(self._on_file_changed) args = [self._sub_placeholder(arg, line, column) for arg in editor[1:]] log.procs.debug("Calling \"{}\" with args {}".format(executable, args)) diff --git a/qutebrowser/misc/ipc.py b/qutebrowser/misc/ipc.py index 77b9e8f6c..f5955b0e3 100644 --- a/qutebrowser/misc/ipc.py +++ b/qutebrowser/misc/ipc.py @@ -191,8 +191,7 @@ class IPCServer(QObject): self._atime_timer.setTimerType(Qt.VeryCoarseTimer) self._server = QLocalServer(self) - self._server.newConnection.connect( # type: ignore[attr-defined] - self.handle_connection) + self._server.newConnection.connect(self.handle_connection) self._socket = None self._old_socket = None @@ -270,18 +269,16 @@ class IPCServer(QObject): log.ipc.debug("Client connected (socket 0x{:x}).".format(id(socket))) self._socket = socket self._timer.start() - socket.readyRead.connect( # type: ignore[attr-defined] - self.on_ready_read) + socket.readyRead.connect(self.on_ready_read) if socket.canReadLine(): log.ipc.debug("We can read a line immediately.") self.on_ready_read() - socket.error.connect(self.on_error) # type: ignore[attr-defined] + socket.error.connect(self.on_error) if socket.error() not in [QLocalSocket.UnknownSocketError, QLocalSocket.PeerClosedError]: log.ipc.debug("We got an error immediately.") self.on_error(socket.error()) - socket.disconnected.connect( # type: ignore[attr-defined] - self.on_disconnected) + socket.disconnected.connect(self.on_disconnected) if socket.state() == QLocalSocket.UnconnectedState: log.ipc.debug("Socket was disconnected immediately.") self.on_disconnected() diff --git a/qutebrowser/misc/objects.py b/qutebrowser/misc/objects.py index 00a1ef35d..63a9cb2dd 100644 --- a/qutebrowser/misc/objects.py +++ b/qutebrowser/misc/objects.py @@ -26,7 +26,7 @@ import argparse from typing import TYPE_CHECKING, Any, Dict, Set, Union, cast if TYPE_CHECKING: - from PyQt5.QtWidgets import QApplication + from qutebrowser import app from qutebrowser.utils import usertypes from qutebrowser.commands import command @@ -47,4 +47,4 @@ backend: Union['usertypes.Backend', NoBackend] = NoBackend() commands: Dict[str, 'command.Command'] = {} debug_flags: Set[str] = set() args = cast(argparse.Namespace, None) -qapp = cast('QApplication', None) +qapp = cast('app.Application', None) -- cgit v1.2.3-54-g00ecf