summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Bruhin <me@the-compiler.org>2022-04-24 18:06:07 +0200
committerFlorian Bruhin <me@the-compiler.org>2022-04-25 17:23:16 +0200
commita20bb67a878b2e68abf8268c1b0a27f018d01352 (patch)
tree123c876f78a2e00ae8f0c7d3e9f28a1a80f6d8c5
parentd1c7f9482662d1aacd252a7876ff501f3c7b18af (diff)
downloadqutebrowser-a20bb67a878b2e68abf8268c1b0a27f018d01352.tar.gz
qutebrowser-a20bb67a878b2e68abf8268c1b0a27f018d01352.zip
mypy: Upgrade to PyQt5-stubs 5.15.6.0
For some unknown reason, those new stubs cause a *lot* of things now to be checked by mypy which formerly probably got skipped due to Any being implied somewhere. The stubs themselves mainly improved, with a couple of regressions too. In total, there were some 337 (!) new mypy errors. This commit fixes almost all of them, and the next commit improves a fix to get things down to 0 errors again. Overview of the changes: ==== qutebrowser/app.py - Drop type ignore due to improved stubs. ==== qutebrowser/browser/browsertab.py - Specify the type of _widget members more closely than just QWidget. This is debatable: I suppose the abstract stuff shouldn't need to know anything about the concrete backends at all. But it seems like we cut some corners when initially implementing things, and put some code in browsertab.py just because the APIs of both backends happened to be compatible. Perhaps something to reconsider once we drop QtWebKit and hopefully implement a dummy backend. - Add an additional assertion in AbstractAction.run_string. This is already covered by the isinstance(member, self.action_base) above it, but that's too dynamic for mypy to understand. - Fix the return type of AbstractScroller.pos_px, which is a QPoint (with x and y components), not a single int. - Fix the return type of AbstractScroller.pos_perc, which is a Tuple (with x and y components), not a single int. - Fix the argument types of AbstractScroller.to_perc, as it's possible to pass fractional percentages too. - Specify the type for AbstractHistoryPrivate._history. See above (_widget) re this being debatable. - Fix the return type of AbstractTabPrivate.event_target(), which can be None (see #3888). - Fix the return type of AbstractTabPrivate.run_js_sync, which is Any (the JS return value), not None. - Fix the argument type for AbstractTabPrivate.toggle_inspector: position can be None to use the last used position. - Declare the type of sub-objects of AbstractTab. - Fix the return value of AbstractTab.icon(), which is the QIcon, not None. ==== qutebrowser/browser/commands.py - Make sure the active window is a MainWindow (with a .win_id attribute). ==== qutebrowser/browser/downloadview.py - Add _model() which makes sure that self.model() is a DownloadModel, not None or any other model. This is needed because other methods access a variety of custom attributes on it, e.g. last_index(). ==== qutebrowser/browser/greasemonkey.py - Add an ignore for AbstractDownload.requested_url which we patch onto the downloads. Probably would be nicer to add it as a proper attribute which always gets set by the DownloadManager. ==== qutebrowser/browser/hints.py - Remove type ignores for QUrl.toString(). - Add a new type ignore for combining different URL flags (which works, but is not exactly type safe... still probably a regression in the stubs). - Make sure the things we get back from self._get_keyparser are what we actually expect. Probably should introduce a TypedDict (and/or overloads for _get_keyparser with typing.Literal) to teach mypy about the exact return value. See #7098. This is needed because we access Hint/NormalKeyParser-specific attributes such as .set_inhibited_timout() or .update_bindings(). ==== qutebrowser/browser/inspector.py - Similar changes than in browsertab.py to make some types where we share API (e.g. .setPage()) more concrete. Didn't work out unfortunately, see next commit. ==== qutebrowser/browser/network/pac.py - Remove now unneeded type ignore for signal. ==== qutebrowser/browser/qtnetworkdownloads.py - Make sure that downloads is a qtnetworkdownloads.DownloadItem (rather than an AbstractDownload), so that we can call ._uses_nam() on it. ==== qutebrowser/browser/qutescheme.py - Remove now unneeded type ignore for QUrl flags. ==== qutebrowser/browser/urlmarks.py - Specify the type of UrlMarkManager._lineparser, as those only get initialized in _init_lineparser of subclasses, so mypy doesn't know it's supposed to exist. ==== qutebrowser/browser/webelem.py - New casts to turn single KeyboardModifier (enum) entries into KeyboardModifiers (flags). Might not be needed anymore with Qt 6. - With that, casting the final value is now unneeded. ==== qutebrowser/browser/webengine/notification.py - Remove now unneeded type ignore for signal. - Make sure the self.sender() we get in HerbeNotificationAdapter._on_finished() is a QProcess, not just any QObject. ==== qutebrowser/browser/webengine/webenginedownloads.py - Remove now unneeded type ignores for signals. ==== qutebrowser/browser/webengine/webengineelem.py - Specify the type of WebEngineElement._tab. - Remove now unneeded type ignore for mixed flags. ==== qutebrowser/browser/webengine/webengineinspector.py - See changes to inspector.py and next commit. - Remove now unneeded type ignore for signal. ==== qutebrowser/browser/webengine/webenginequtescheme.py - Remove now unneeded type ignore for mixed flags. ==== qutebrowser/browser/webengine/webenginesettings.py - Ignore access of .setter attribute which we patch onto QWebEngineProfile. Would be nice to have a subclass or wrapper-class instead. ==== qutebrowser/browser/webengine/webenginetab.py - Specified the type of _widget members more closely than just QWidget. See browsertab.py changes for details. - Remove some now-unneeded type ignores for creating FindFlags. - Specify more concrete types for WebEngineTab members where we actually need to access WebEngine-specific attributes. - Make sure the page we get is our custom WebEnginePage subclass, not just any QWebEnginePage. This is needed because we access custom attributes on it. ==== qutebrowser/browser/webengine/webview.py - Make sure the page we get is our custom WebEnginePage subclass, not just any QWebEnginePage. This is needed because we access custom attributes on it. ==== qutebrowser/browser/webkit/network/networkreply.py - Remove now unneeded type ignores for signals. ==== qutebrowser/browser/webkit/webkitinspector.py - See changes to inspector.py and next commit. ==== qutebrowser/browser/webkit/webkittab.py - Specify the type of _widget members more closely than just QWidget. See browsertab.py changes for details. - Add a type ignore for WebKitAction because our workaround needs to treat them as ints (which is allowed by PyQt, even if not type-safe). - Add new ignores for findText calls: The text is a QString and can be None; the flags are valid despite mypy thinking they aren't (stubs regression?). - Specify the type for WebKitHistoryPrivate._history, because we access WebKit-specific attributes. See above (_widget) re this being debatable. - Make mypy aware that .currentFrame() and .frameAt() can return None (stubs regression?). - Make sure the .page() and .page().networkAccessManager() are our subclasses rather than the more generic QtWebKit objects, as we use custom attributes. - Add new type ignores for signals (stubs regression!) ==== qutebrowser/browser/webkit/webpage.py - Make sure the .networkAccessManager() is our subclass rather than the more generic QtWebKit object, as we use custom attributes. - Replace a cast by a type ignore. The cast didn't work anymore. ==== qutebrowser/browser/webkit/webview.py - Make sure the .page() is our subclass rather than the more generic QtWebKit object, as we use custom attributes. ==== qutebrowser/commands/userscripts.py - Remove now unneeded type ignore for signal. ==== qutebrowser/completion/completer.py - Add a new _completion() getter (which ensures it actually gets the completion view) rather than accessing the .parent() directly (which could be any QObject). ==== qutebrowser/completion/completiondelegate.py - Make sure self.parent() is a CompletionView (no helper method as there is only one instance). - Remove a now-unneeded type ignore for adding QSizes. ==== qutebrowser/completion/completionwidget.py - Add a ._model() getter which ensures that we get a CompletionModel (with custom attributes) rather than Qt's .model() which can be any QAbstractItemModel (or None). - Removed a now-unneeded type ignore for OR-ing flags. ==== qutebrowser/completion/models/completionmodel.py - Remove now unneeded type ignores for signals. - Ignore a complaint about .set_pattern() not being defined. Completion categories don't share any common parent class, so it would be good to introduce a typing.Protocol for this. See #7098. ==== qutebrowser/components/misccommands.py - Removed a now-unneeded type ignore for OR-ing flags. ==== qutebrowser/components/readlinecommands.py - Make sure QApplication.instance() is a QApplication (and not just a QCoreApplication). This includes the former "not None" check. ==== qutebrowser/components/scrollcommands.py - Add basic annotation for "funcs" dict. Could have a callable protocol to specify it needs a count kwarg, see #7098. ==== qutebrowser/config/stylesheet.py - Correctly specify that stylesheet apply to QWidgets, not any QObject. - Ignore an attr-defined for obj.STYLESHEET. Perhaps could somehow teach mypy about this with overloads and protocols (stylesheet for set_register being None => STYLESHEET needs to be defined, otherwise anything goes), but perhaps not worth the troble. See #7098. ==== qutebrowser/keyinput/keyutils.py - Remove some now-unneeded type ignores and add a cast for using a single enum value as flags. Might need to look at this again with Qt 6 support. ==== qutebrowser/keyinput/modeman.py - Add a FIXME for using a TypedDict, see comments for hints.py above. ==== qutebrowser/mainwindow/mainwindow.py - Remove now-unneeded type ignores for calling with OR-ed flags. - Improve where we cast from WindowType to WindowFlags, no int needed - Use new .tab_bar() getter, see below. ==== qutebrowser/mainwindow/prompt.py - Remove now-unneeded type ignores for calling with OR-ed flags. ==== qutebrowser/mainwindow/statusbar/bar.py - Adjust type ignores around @pyqtProperty. The fact one is still needed seems like a stub regression. ==== qutebrowser/mainwindow/statusbar/command.py - Fix type for setText() override (from QLineEdit): text can be None (QString in C++). ==== qutebrowser/mainwindow/statusbar/url.py - Adjust type ignores around @pyqtProperty. The fact one is still needed seems like a stub regression. ==== qutebrowser/mainwindow/tabbedbrowser.py - Specify that TabDeque manages browser tabs, not any QWidgets. It accesses AbstractTab-specific attributes. - Make sure that the .tabBar() we get is a tabwidget.TabBar, as we access .maybe_hide. - Fix the annotations for stored marks: Scroll positions are a QPoint, not int. - Add _current_tab() and _tab_by_idx() wrappers for .currentWidget() and .widget(), which ensures that the return values are valid AbstractTabs (or None for _tab_by_idx). This is needed because we access AbstractTab-specific attributes. - For some places, where the tab can be None, continue using .currentTab() but add asserts. - Remove some now-unneeded [unreachable] ignores, as mypy knows about the None possibility now. ==== qutebrowser/mainwindow/tabwidget.py - Add new tab_bar() and _tab_by_idx() helpers which check that the .tabBar() and .widget() are of type TabBar and AbstractTab, respectively. - Add additional assertions where we expect ._tab_by_idx() to never be None. - Remove dead code in get_tab_fields for handling a None y scroll position. I was unable to find any place in the code where this could be set to None. - Remove some now-unneeded type ignores and casts, as mypy now knows that _type_by_idx() could be None. - Work around a strange instance where mypy complains about not being able to find the type of TabBar.drag_in_progress from TabWidget._toggle_visibility, despite it clearly being shown as a bool *inside* that class without any annotation. - Add a ._tab_widget() getter in TabBar which ensures that the .parent() is in fact a TabWidget. ==== qutebrowser/misc/crashsignal.py - Remove now unneeded type ignores for signals. ==== qutebrowser/misc/editor.py - Remove now unneeded type ignores for signals. ==== qutebrowser/misc/ipc.py - Remove now unneeded type ignores for signals. - Add new type ignores for .error() which is both a signal and a getter (stub regression?). Won't be relevant for Qt 6 anymore, as the signal was renamed to errorOccurred in 5.15. ==== qutebrowser/misc/objects.py - Make sure mypy knows that objects.app is our custom Application (with custom attributes) rather than any QApplication. ==== qutebrowser/utils/objreg.py - Ignore attr-defined for .win_id attributes. Maybe could add a typing.Protocol, but ideally, the whole objreg stuff should die one day anyways. ==== tests/unit/completion/test_completer.py - Make CompletionWidgetStub inherit from CompletionView so that it passes the new isinstance() asserts in completer.py (see above).
-rw-r--r--misc/requirements/requirements-mypy.txt2
-rw-r--r--qutebrowser/app.py3
-rw-r--r--qutebrowser/browser/browsertab.py63
-rw-r--r--qutebrowser/browser/commands.py1
-rw-r--r--qutebrowser/browser/downloadview.py17
-rw-r--r--qutebrowser/browser/greasemonkey.py3
-rw-r--r--qutebrowser/browser/hints.py10
-rw-r--r--qutebrowser/browser/inspector.py16
-rw-r--r--qutebrowser/browser/network/pac.py3
-rw-r--r--qutebrowser/browser/qtnetworkdownloads.py1
-rw-r--r--qutebrowser/browser/qutescheme.py4
-rw-r--r--qutebrowser/browser/urlmarks.py2
-rw-r--r--qutebrowser/browser/webelem.py13
-rw-r--r--qutebrowser/browser/webengine/notification.py6
-rw-r--r--qutebrowser/browser/webengine/webenginedownloads.py8
-rw-r--r--qutebrowser/browser/webengine/webengineelem.py4
-rw-r--r--qutebrowser/browser/webengine/webengineinspector.py11
-rw-r--r--qutebrowser/browser/webengine/webenginequtescheme.py2
-rw-r--r--qutebrowser/browser/webengine/webenginesettings.py7
-rw-r--r--qutebrowser/browser/webengine/webenginetab.py36
-rw-r--r--qutebrowser/browser/webengine/webview.py5
-rw-r--r--qutebrowser/browser/webkit/network/networkreply.py21
-rw-r--r--qutebrowser/browser/webkit/webkitinspector.py4
-rw-r--r--qutebrowser/browser/webkit/webkittab.py76
-rw-r--r--qutebrowser/browser/webkit/webpage.py11
-rw-r--r--qutebrowser/browser/webkit/webview.py4
-rw-r--r--qutebrowser/commands/userscripts.py3
-rw-r--r--qutebrowser/completion/completer.py12
-rw-r--r--qutebrowser/completion/completiondelegate.py4
-rw-r--r--qutebrowser/completion/completionwidget.py42
-rw-r--r--qutebrowser/completion/models/completionmodel.py7
-rw-r--r--qutebrowser/components/misccommands.py2
-rw-r--r--qutebrowser/components/readlinecommands.py2
-rw-r--r--qutebrowser/components/scrollcommands.py4
-rw-r--r--qutebrowser/config/stylesheet.py7
-rw-r--r--qutebrowser/keyinput/keyutils.py17
-rw-r--r--qutebrowser/keyinput/modeman.py1
-rw-r--r--qutebrowser/mainwindow/mainwindow.py12
-rw-r--r--qutebrowser/mainwindow/prompt.py4
-rw-r--r--qutebrowser/mainwindow/statusbar/bar.py5
-rw-r--r--qutebrowser/mainwindow/statusbar/command.py3
-rw-r--r--qutebrowser/mainwindow/statusbar/url.py5
-rw-r--r--qutebrowser/mainwindow/tabbedbrowser.py76
-rw-r--r--qutebrowser/mainwindow/tabwidget.py80
-rw-r--r--qutebrowser/misc/crashsignal.py3
-rw-r--r--qutebrowser/misc/editor.py3
-rw-r--r--qutebrowser/misc/ipc.py17
-rw-r--r--qutebrowser/misc/objects.py4
-rw-r--r--qutebrowser/utils/objreg.py4
-rw-r--r--tests/unit/completion/test_completer.py6
50 files changed, 408 insertions, 248 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..4f782c3ee 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), active_win
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/greasemonkey.py b/qutebrowser/browser/greasemonkey.py
index 5abb9a137..3b9c321e3 100644
--- a/qutebrowser/browser/greasemonkey.py
+++ b/qutebrowser/browser/greasemonkey.py
@@ -380,7 +380,7 @@ class GreasemonkeyManager(QObject):
"""
# See if we are still waiting on any required scripts for this one
for dl in self._in_progress_dls:
- if dl.requested_url in script.requires:
+ if dl.requested_url in script.requires: # type: ignore[attr-defined]
return False
# Need to add the required scripts to the IIFE now
@@ -414,6 +414,7 @@ class GreasemonkeyManager(QObject):
force_overwrite=True)
download = download_manager.get(QUrl(url), target=target,
auto_remove=True)
+ # FIXME:mypy Build this into downloads instead of patching here?
download.requested_url = url
self._in_progress_dls.append(download)
if download.successful:
diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py
index 2e4e8e4b4..ed933e10c 100644
--- a/qutebrowser/browser/hints.py
+++ b/qutebrowser/browser/hints.py
@@ -254,8 +254,8 @@ class HintActions:
flags = QUrl.FullyEncoded | QUrl.RemovePassword
if url.scheme() == 'mailto':
- flags |= QUrl.RemoveScheme
- urlstr = url.toString(flags) # type: ignore[arg-type]
+ flags |= QUrl.RemoveScheme # type: ignore[operator]
+ 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
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
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
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..a720402f5 100644
--- a/qutebrowser/browser/qtnetworkdownloads.py
+++ b/qutebrowser/browser/qtnetworkdownloads.py
@@ -601,6 +601,7 @@ class DownloadManager(downloads.AbstractDownloadManager):
"""
assert nam.adopted_downloads == 0
for download in self.downloads:
+ assert isinstance(download, DownloadItem), download
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/urlmarks.py b/qutebrowser/browser/urlmarks.py
index 944ec23d4..4e173f293 100644
--- a/qutebrowser/browser/urlmarks.py
+++ b/qutebrowser/browser/urlmarks.py
@@ -74,6 +74,8 @@ class UrlMarkManager(QObject):
changed = pyqtSignal()
+ _lineparser: lineparser.LineParser
+
def __init__(self, parent=None):
"""Initialize and read quickmarks."""
super().__init__(parent)
diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py
index 05b0eadb3..344292166 100644
--- a/qutebrowser/browser/webelem.py
+++ b/qutebrowser/browser/webelem.py
@@ -19,7 +19,7 @@
"""Generic web element related code."""
-from typing import cast, TYPE_CHECKING, Iterator, Optional, Set, Union
+from typing import cast, TYPE_CHECKING, Iterator, Optional, Set, Union, Dict
import collections.abc
from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer, QRect, QPoint
@@ -345,18 +345,19 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a
log.webelem.debug("Sending fake click to {!r} at position {} with "
"target {}".format(self, pos, click_target))
- target_modifiers = {
- usertypes.ClickTarget.normal: Qt.NoModifier,
+ target_modifiers: Dict[usertypes.ClickTarget, Qt.KeyboardModifiers] = {
+ usertypes.ClickTarget.normal: cast(Qt.KeyboardModifiers, Qt.NoModifier),
usertypes.ClickTarget.window: Qt.AltModifier | Qt.ShiftModifier,
- usertypes.ClickTarget.tab: Qt.ControlModifier,
- usertypes.ClickTarget.tab_bg: Qt.ControlModifier,
+ usertypes.ClickTarget.tab: cast(Qt.KeyboardModifiers, Qt.ControlModifier),
+ usertypes.ClickTarget.tab_bg:
+ cast(Qt.KeyboardModifiers, Qt.ControlModifier),
}
if config.val.tabs.background:
target_modifiers[usertypes.ClickTarget.tab] |= Qt.ShiftModifier
else:
target_modifiers[usertypes.ClickTarget.tab_bg] |= Qt.ShiftModifier
- modifiers = cast(Qt.KeyboardModifiers, target_modifiers[click_target])
+ modifiers = target_modifiers[click_target]
events = [
QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton, Qt.NoModifier),
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..58babc70c 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), view
+ 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..15729ccdc 100644
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -29,8 +29,8 @@ from typing import cast, Union, Optional
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QTimer, QUrl,
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
@@ -191,6 +195,8 @@ class WebEngineSearch(browsertab.AbstractSearch):
back yet.
"""
+ _widget: webview.WebEngineView
+
def __init__(self, tab, parent=None):
super().__init__(tab, parent)
self._flags = self._empty_flags()
@@ -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
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..4dd1b2a71 100644
--- a/qutebrowser/browser/webengine/webview.py
+++ b/qutebrowser/browser/webengine/webview.py
@@ -70,7 +70,10 @@ class WebEngineView(QWebEngineView):
return self.focusProxy()
def shutdown(self):
- self.page().shutdown()
+ """Shut down the underlying page."""
+ page = self.page()
+ assert isinstance(page, WebEnginePage), page
+ 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..24d232c9c 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
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), page
+ 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..9c7495478 100644
--- a/qutebrowser/browser/webkit/webpage.py
+++ b/qutebrowser/browser/webkit/webpage.py
@@ -21,7 +21,6 @@
import html
import functools
-from typing import cast
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint
from PyQt5.QtGui import QDesktopServices
@@ -241,6 +240,7 @@ class BrowserPage(QWebPage):
if download_manager.has_downloads_with_nam(nam):
nam.setParent(download_manager)
else:
+ assert isinstance(nam, networkmanager.NetworkManager), nam
nam.shutdown()
def display_content(self, reply, mimetype):
@@ -371,11 +371,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..be087c855 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
+ 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..b94077c6d 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), completion
+ 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..ae02426a5 100644
--- a/qutebrowser/completion/completiondelegate.py
+++ b/qutebrowser/completion/completiondelegate.py
@@ -33,6 +33,7 @@ from PyQt5.QtGui import (QIcon, QPalette, QTextDocument, QTextOption,
from qutebrowser.config import config
from qutebrowser.utils import qtutils
+from qutebrowser.completion import completionwidget
class _Highlighter(QSyntaxHighlighter):
@@ -233,6 +234,7 @@ class CompletionItemDelegate(QStyledItemDelegate):
if index.parent().isValid():
view = self.parent()
+ assert isinstance(view, completionwidget.CompletionView), view
pattern = view.pattern
columns_to_filter = index.model().columns_to_filter(index)
if index.column() in columns_to_filter and pattern:
@@ -299,7 +301,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..4b8fee299 100644
--- a/qutebrowser/completion/models/completionmodel.py
+++ b/qutebrowser/completion/models/completionmodel.py
@@ -180,10 +180,11 @@ 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]
+ # FIXME:mypy define a Protocol for set_pattern?
+ cat.set_pattern(pattern) # 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..37fa92127 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), qapp
w = qapp.focusWidget()
if isinstance(w, QLineEdit):
diff --git a/qutebrowser/components/scrollcommands.py b/qutebrowser/components/scrollcommands.py
index 757c23b2b..2287041d7 100644
--- a/qutebrowser/components/scrollcommands.py
+++ b/qutebrowser/components/scrollcommands.py
@@ -19,6 +19,7 @@
"""Scrolling-related commands."""
+from typing import Dict, Callable
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..b08d67a3b 100644
--- a/qutebrowser/config/stylesheet.py
+++ b/qutebrowser/config/stylesheet.py
@@ -23,13 +23,14 @@ 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
from qutebrowser.utils import jinja, log
-def set_register(obj: QObject,
+def set_register(obj: QWidget,
stylesheet: str = None, *,
update: bool = True) -> None:
"""Set the stylesheet for an object.
@@ -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
@@ -81,7 +82,7 @@ class _StyleSheetObserver(QObject):
if update:
self.setParent(self._obj)
if stylesheet is None:
- self._stylesheet: str = obj.STYLESHEET
+ self._stylesheet: str = obj.STYLESHEET # type: ignore[attr-defined]
else:
self._stylesheet = stylesheet
diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py
index f74c59aa7..0362e09f3 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:
@@ -254,9 +254,9 @@ def _modifiers_to_string(modifiers: _ModifierType) -> str:
modifier.
"""
_assert_plain_modifier(modifiers)
- altgr = Qt.GroupSwitchModifier
- if modifiers & altgr: # type: ignore[operator]
- modifiers &= ~altgr # type: ignore[operator, assignment]
+ altgr = cast(Qt.KeyboardModifiers, Qt.GroupSwitchModifier)
+ 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)
@@ -619,14 +619,15 @@ class KeySequence:
# (or "Cmd") in a key binding name to actually represent what's on the
# keyboard.
if utils.is_mac:
+ # FIXME:qt6 Reevaluate the type ignores below
if modifiers & Qt.ControlModifier and modifiers & Qt.MetaModifier:
pass
elif modifiers & Qt.ControlModifier:
modifiers &= ~Qt.ControlModifier
- modifiers |= Qt.MetaModifier
+ modifiers |= Qt.MetaModifier # type: ignore[assignment]
elif modifiers & Qt.MetaModifier:
modifiers &= ~Qt.MetaModifier
- modifiers |= Qt.ControlModifier
+ modifiers |= Qt.ControlModifier # type: ignore[assignment]
keys = list(self._iter_keys())
keys.append(key | int(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..d7229bf31 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(
@@ -602,7 +600,7 @@ class MainWindow(QWidget):
super().resizeEvent(e)
self._update_overlay_geometries()
self._downloadview.updateGeometry()
- self.tabbed_browser.widget.tabBar().refresh()
+ self.tabbed_browser.widget.tab_bar().refresh()
def showEvent(self, e):
"""Extend showEvent to register us as the last-visible-main-window.
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..a971d76aa 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
@@ -92,7 +91,7 @@ class UrlText(textbase.TextBase):
self._normal_url = None
self._normal_url_type = UrlType.normal
- @pyqtProperty(str)
+ @pyqtProperty(str) # type: ignore[type-var]
def urltype(self):
"""Getter for self.urltype, so it can be used as Qt property.
diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py
index a96f6d583..68b4adfdb 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,8 @@ 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)
+ self.cur_fullscreen_requested.connect(self.widget.tab_bar().maybe_hide)
+
self.widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
# load_finished instead of load_started as WORKAROUND for
@@ -235,8 +241,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 +379,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 +528,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 +547,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 +568,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 +848,7 @@ class TabbedBrowser(QWidget):
mode in modeman.INPUT_MODES):
tab = self.widget.currentWidget()
if tab is not None:
+ assert isinstance(tab, browsertab.AbstractTab), tab
tab.data.input_mode = mode
@pyqtSlot(usertypes.KeyMode)
@@ -833,6 +862,7 @@ class TabbedBrowser(QWidget):
widget))
widget.setFocus()
if config.val.tabs.mode_on_change == 'restore':
+ assert isinstance(widget, browsertab.AbstractTab), widget
widget.data.input_mode = usertypes.KeyMode.normal
@pyqtSlot(int)
@@ -842,9 +872,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 +1052,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 +1073,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..bbd5af8a0 100644
--- a/qutebrowser/mainwindow/tabwidget.py
+++ b/qutebrowser/mainwindow/tabwidget.py
@@ -22,13 +22,13 @@
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)
from PyQt5.QtWidgets import (QTabWidget, QTabBar, QSizePolicy, QCommonStyle,
QStyle, QStylePainter, QStyleOptionTab,
- QStyleFactory, QWidget)
+ QStyleFactory)
from PyQt5.QtGui import QIcon, QPalette, QColor
from qutebrowser.utils import qtutils, objreg, utils, usertypes, log
@@ -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), bar
+ 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), tab
+ 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)
@@ -404,7 +411,10 @@ class TabBar(QTabBar):
self._auto_hide_timer.timeout.connect(self.maybe_hide)
self._on_show_switching_delay_changed()
self.setAutoFillBackground(True)
- self.drag_in_progress = False
+ # FIXME:mypy Is it a mypy bug that we need to specify bool here?
+ # Otherwise, we get "Cannot determine type of "drag_in_progress" in
+ # TabWidget._toggle_visibility below.
+ self.drag_in_progress: bool = False
stylesheet.set_register(self)
self.ensurePolished()
config.instance.changed.connect(self._on_config_changed)
@@ -423,9 +433,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), parent
+ 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 +643,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..8c5c05046 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]
- if socket.error() not in [QLocalSocket.UnknownSocketError,
- QLocalSocket.PeerClosedError]:
+ socket.error.connect(self.on_error)
+ if socket.error() not in [ # type: ignore[operator]
+ 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)
+ self.on_error(socket.error()) # type: ignore[operator]
+ 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)
diff --git a/qutebrowser/utils/objreg.py b/qutebrowser/utils/objreg.py
index 0819a5d0a..c3cdb1071 100644
--- a/qutebrowser/utils/objreg.py
+++ b/qutebrowser/utils/objreg.py
@@ -170,7 +170,7 @@ def _get_tab_registry(win_id: _WindowTab,
window: Optional[QWidget] = QApplication.activeWindow()
if window is None or not hasattr(window, 'win_id'):
raise RegistryUnavailableError('tab')
- win_id = window.win_id
+ win_id = window.win_id # type: ignore[attr-defined]
elif win_id is None:
raise TypeError("window is None with scope tab!")
@@ -205,7 +205,7 @@ def _get_window_registry(window: _WindowTab) -> ObjectRegistry:
raise RegistryUnavailableError('window')
try:
- return win.registry
+ return win.registry # type: ignore[attr-defined]
except AttributeError:
raise RegistryUnavailableError('window')
diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py
index 467357d32..3c0a0bc07 100644
--- a/tests/unit/completion/test_completer.py
+++ b/tests/unit/completion/test_completer.py
@@ -25,7 +25,7 @@ import pytest
from PyQt5.QtCore import QObject
from PyQt5.QtGui import QStandardItemModel
-from qutebrowser.completion import completer
+from qutebrowser.completion import completer, completionwidget
from qutebrowser.commands import command
from qutebrowser.api import cmdutils
@@ -48,12 +48,12 @@ class FakeCompletionModel(QStandardItemModel):
self.info = info
-class CompletionWidgetStub(QObject):
+class CompletionWidgetStub(completionwidget.CompletionView):
"""Stub for the CompletionView."""
def __init__(self, parent=None):
- super().__init__(parent)
+ super().__init__(cmd=None, win_id=0, parent=parent)
self.hide = unittest.mock.Mock()
self.show = unittest.mock.Mock()
self.set_pattern = unittest.mock.Mock()