First attempt at multi-window support.

This commit is contained in:
Florian Bruhin 2014-09-28 22:13:14 +02:00
parent 64a119afb2
commit fb6cb62f93
32 changed files with 724 additions and 485 deletions

View File

@ -36,10 +36,10 @@ from PyQt5.QtCore import (pyqtSlot, QTimer, QEventLoop, Qt, QStandardPaths,
qInstallMessageHandler, QObject, QUrl)
import qutebrowser
from qutebrowser.commands import userscripts, runners, cmdutils
from qutebrowser.commands import runners, cmdutils
from qutebrowser.config import style, config, websettings
from qutebrowser.network import qutescheme, proxy
from qutebrowser.browser import quickmarks, cookies, downloads, cache, hints
from qutebrowser.browser import quickmarks, cookies, downloads, cache
from qutebrowser.widgets import mainwindow, console, crash
from qutebrowser.keyinput import modeman
from qutebrowser.utils import (log, version, message, utilcmds, readline,
@ -53,11 +53,11 @@ class Application(QApplication):
Attributes:
_args: ArgumentParser instance.
_commandrunner: The main CommandRunner instance.
_shutting_down: True if we're currently shutting down.
_quit_status: The current quitting status.
_crashdlg: The crash dialog currently open.
_crashlogfile: A file handler to the fatal crash logfile.
_event_filter: The EventFilter for the application.
"""
def __init__(self, args):
@ -74,7 +74,6 @@ class Application(QApplication):
self._shutting_down = False
self._crashdlg = None
self._crashlogfile = None
self._commandrunner = None
if args.debug:
# We don't enable this earlier because some imports trigger
@ -110,16 +109,15 @@ class Application(QApplication):
self._init_modules()
log.init.debug("Initializing eventfilter...")
mode_manager = objreg.get('mode-manager')
self.installEventFilter(mode_manager)
self._event_filter = modeman.EventFilter(self)
self.installEventFilter(self._event_filter)
log.init.debug("Connecting signals...")
self._connect_signals()
modeman.enter(usertypes.KeyMode.normal, 'init')
log.init.debug("Showing mainwindow...")
if not args.nowindow:
objreg.get('main-window').show()
objreg.get('main-window', scope='window', window=0).show()
log.init.debug("Applying python hacks...")
self._python_hacks()
@ -134,10 +132,6 @@ class Application(QApplication):
def _init_modules(self):
"""Initialize all 'modules' which need to be initialized."""
log.init.debug("Initializing message-bridge...")
message_bridge = message.MessageBridge(self)
objreg.register('message-bridge', message_bridge)
log.init.debug("Initializing readline-bridge...")
readline_bridge = readline.ReadlineBridge()
objreg.register('readline-bridge', readline_bridge)
@ -146,35 +140,23 @@ class Application(QApplication):
config.init(self._args)
log.init.debug("Initializing crashlog...")
self._handle_segfault()
log.init.debug("Initializing modes...")
modeman.init()
log.init.debug("Initializing websettings...")
websettings.init()
log.init.debug("Initializing quickmarks...")
quickmarks.init()
log.init.debug("Initializing proxy...")
proxy.init()
log.init.debug("Initializing userscripts...")
userscripts.init()
log.init.debug("Initializing utility commands...")
utilcmds.init()
log.init.debug("Initializing cookies...")
cookie_jar = cookies.CookieJar(self)
objreg.register('cookie-jar', cookie_jar)
log.init.debug("Initializing cache...")
diskcache = cache.DiskCache(self)
objreg.register('cache', diskcache)
log.init.debug("Initializing commands...")
self._commandrunner = runners.CommandRunner()
log.init.debug("Initializing search...")
search_runner = runners.SearchRunner(self)
objreg.register('search-runner', search_runner)
log.init.debug("Initializing downloads...")
download_manager = downloads.DownloadManager(self)
objreg.register('download-manager', download_manager)
log.init.debug("Initializing main window...")
main_window = mainwindow.MainWindow()
objreg.register('main-window', main_window)
mainwindow.create_window()
log.init.debug("Initializing debug console...")
debug_console = console.ConsoleWidget()
objreg.register('debug-console', debug_console)
@ -240,18 +222,18 @@ class Application(QApplication):
# we make sure the GUI is refreshed here, so the start seems faster.
self.processEvents(QEventLoop.ExcludeUserInputEvents |
QEventLoop.ExcludeSocketNotifiers)
tabbed_browser = objreg.get('tabbed-browser')
tabbed_browser = objreg.get('tabbed-browser', scope='window', window=0)
for cmd in self._args.command:
if cmd.startswith(':'):
log.init.debug("Startup cmd {}".format(cmd))
self._commandrunner.run_safely_init(cmd.lstrip(':'))
self._commandrunner.run_safely_init(0, cmd.lstrip(':'))
else:
log.init.debug("Startup URL {}".format(cmd))
try:
url = urlutils.fuzzy_url(cmd)
except urlutils.FuzzyUrlError as e:
message.error("Error in startup argument '{}': {}".format(
cmd, e))
message.error(0, "Error in startup argument '{}': "
"{}".format(cmd, e))
else:
tabbed_browser.tabopen(url)
@ -261,7 +243,8 @@ class Application(QApplication):
try:
url = urlutils.fuzzy_url(urlstr)
except urlutils.FuzzyUrlError as e:
message.error("Error when opening startpage: {}".format(e))
message.error(0, "Error when opening startpage: "
"{}".format(e))
else:
tabbed_browser.tabopen(url)
@ -281,88 +264,9 @@ class Application(QApplication):
def _connect_signals(self):
"""Connect all signals to their slots."""
# pylint: disable=too-many-statements, too-many-locals
# syntactic sugar
kp = objreg.get('keyparsers')
main_window = objreg.get('main-window')
status = main_window.status
completion = objreg.get('completion')
tabs = objreg.get('tabbed-browser')
cmd = objreg.get('status-command')
completer = objreg.get('completer')
search_runner = objreg.get('search-runner')
message_bridge = objreg.get('message-bridge')
mode_manager = objreg.get('mode-manager')
prompter = objreg.get('prompter')
download_manager = objreg.get('download-manager')
config_obj = objreg.get('config')
key_config = objreg.get('key-config')
# misc
self.lastWindowClosed.connect(self.shutdown)
tabs.quit.connect(self.shutdown)
mode_manager.entered.connect(hints.on_mode_entered)
# status bar
mode_manager.entered.connect(status.on_mode_entered)
mode_manager.left.connect(status.on_mode_left)
mode_manager.left.connect(cmd.on_mode_left)
mode_manager.left.connect(prompter.on_mode_left)
# commands
cmd.got_cmd.connect(self._commandrunner.run_safely)
cmd.got_search.connect(search_runner.search)
cmd.got_search_rev.connect(search_runner.search_rev)
cmd.returnPressed.connect(tabs.setFocus)
search_runner.do_search.connect(tabs.search)
kp[usertypes.KeyMode.normal].keystring_updated.connect(
status.keystring.setText)
tabs.got_cmd.connect(self._commandrunner.run_safely)
# messages
message_bridge.s_error.connect(status.disp_error)
message_bridge.s_info.connect(status.disp_temp_text)
message_bridge.s_set_text.connect(status.set_text)
message_bridge.s_maybe_reset_text.connect(status.txt.maybe_reset_text)
message_bridge.s_set_cmd_text.connect(cmd.set_cmd_text)
message_bridge.s_question.connect(prompter.ask_question,
Qt.DirectConnection)
# config
config_obj.style_changed.connect(style.get_stylesheet.cache_clear)
for obj in kp.values():
key_config.changed.connect(obj.on_keyconfig_changed)
# statusbar
# FIXME some of these probably only should be triggered on mainframe
# loadStarted.
tabs.current_tab_changed.connect(status.prog.on_tab_changed)
tabs.cur_progress.connect(status.prog.setValue)
tabs.cur_load_finished.connect(status.prog.hide)
tabs.cur_load_started.connect(status.prog.on_load_started)
tabs.current_tab_changed.connect(status.percentage.on_tab_changed)
tabs.cur_scroll_perc_changed.connect(status.percentage.set_perc)
tabs.current_tab_changed.connect(status.txt.on_tab_changed)
tabs.cur_statusbar_message.connect(status.txt.on_statusbar_message)
tabs.cur_load_started.connect(status.txt.on_load_started)
tabs.current_tab_changed.connect(status.url.on_tab_changed)
tabs.cur_url_text_changed.connect(status.url.set_url)
tabs.cur_link_hovered.connect(status.url.set_hover_url)
tabs.cur_load_status_changed.connect(status.url.on_load_status_changed)
# command input / completion
mode_manager.left.connect(tabs.on_mode_left)
cmd.clear_completion_selection.connect(
completion.on_clear_completion_selection)
cmd.hide_completion.connect(completion.hide)
cmd.update_completion.connect(completer.on_update_completion)
completer.change_completed_part.connect(cmd.on_change_completed_part)
# downloads
tabs.start_download.connect(download_manager.fetch)
def _get_widgets(self):
"""Get a string list of all widgets."""
@ -433,8 +337,11 @@ class Application(QApplication):
def _save_geometry(self):
"""Save the window geometry to the state config."""
last_win_id = sorted(objreg.window_registry)[-1]
main_window = objreg.get('main-window', scope='window',
window=last_win_id)
state_config = objreg.get('state-config')
data = bytes(objreg.get('main-window').saveGeometry())
data = bytes(main_window.saveGeometry())
geom = base64.b64encode(data).decode('ASCII')
try:
state_config.add_section('geometry')
@ -517,6 +424,11 @@ class Application(QApplication):
self._destroy_crashlogfile()
sys.exit(1)
@cmdutils.register(instance='app', name=['quit', 'q'])
def quit(self):
"""Quit qutebrowser."""
QApplication.closeAllWindows()
@cmdutils.register(instance='app', ignore_args=True)
def restart(self, shutdown=True, pages=None):
"""Restart qutebrowser while keeping existing tabs open."""
@ -640,8 +552,13 @@ class Application(QApplication):
return
self._shutting_down = True
log.destroy.debug("Shutting down with status {}...".format(status))
prompter = objreg.get('prompter', None)
if prompter is not None and prompter.shutdown():
deferrer = False
for win_id in objreg.window_registry:
prompter = objreg.get('prompter', None, scope='window',
window=win_id)
if prompter is not None and prompter.shutdown():
deferrer = True
if deferrer:
# If shutdown was called while we were asking a question, we're in
# a still sub-eventloop (which gets quitted now) and not in the
# main one.
@ -663,7 +580,7 @@ class Application(QApplication):
# Remove eventfilter
try:
log.destroy.debug("Removing eventfilter...")
self.removeEventFilter(objreg.get('mode-manager'))
self.removeEventFilter(self._event_filter)
except KeyError:
pass
# Close all tabs

View File

@ -23,7 +23,7 @@ import re
import os
import subprocess
import posixpath
from functools import partial
import functools
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt, QUrl
@ -53,33 +53,42 @@ class CommandDispatcher:
Attributes:
_editor: The ExternalEditor object.
_win_id: The window ID the CommandDispatcher is associated with.
_timers: A list of timers for :later
"""
def __init__(self):
def __init__(self, win_id):
self._editor = None
self._win_id = win_id
self._timers = []
def __repr__(self):
return utils.get_repr(self)
def _tabbed_browser(self):
"""Convienence method to get the right tabbed-browser."""
return objreg.get('tabbed-browser', scope='window',
window=self._win_id)
def _count(self):
"""Convenience method to get the widget count."""
return objreg.get('tabbed-browser').count()
return self._tabbed_browser().count()
def _set_current_index(self, idx):
"""Convenience method to set the current widget index."""
return objreg.get('tabbed-browser').setCurrentIndex(idx)
return self._tabbed_browser().setCurrentIndex(idx)
def _current_index(self):
"""Convenience method to get the current widget index."""
return objreg.get('tabbed-browser').currentIndex()
return self._tabbed_browser().currentIndex()
def _current_url(self):
"""Convenience method to get the current url."""
return objreg.get('tabbed-browser').current_url()
return self._tabbed_browser().current_url()
def _current_widget(self):
"""Get the currently active widget from a command."""
widget = objreg.get('tabbed-browser').currentWidget()
widget = self._tabbed_browser().currentWidget()
if widget is None:
raise cmdexc.CommandError("No WebView available yet!")
return widget
@ -92,7 +101,7 @@ class CommandDispatcher:
tab: Whether to open in a new tab.
background: Whether to open in the background.
"""
tabbed_browser = objreg.get('tabbed-browser')
tabbed_browser = self._tabbed_browser()
if tab and background:
raise cmdexc.CommandError("Only one of -t/-b can be given!")
elif tab:
@ -114,7 +123,7 @@ class CommandDispatcher:
The widget with the given tab ID if count is given.
None if no widget was found.
"""
tabbed_browser = objreg.get('tabbed-browser')
tabbed_browser = self._tabbed_browser()
if count is None:
return tabbed_browser.currentWidget()
elif 1 <= count <= self._count():
@ -177,7 +186,7 @@ class CommandDispatcher:
tab = objreg.get('last-focused-tab')
except KeyError:
raise cmdexc.CommandError("No last focused tab!")
idx = objreg.get('tabbed-browser').indexOf(tab)
idx = self._tabbed_browser().indexOf(tab)
if idx == -1:
raise cmdexc.CommandError("Last focused tab vanished!")
self._set_current_index(idx)
@ -190,7 +199,7 @@ class CommandDispatcher:
except PermissionError:
raise cmdexc.CommandError("Failed to delete tempfile...")
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def tab_close(self, count=None):
"""Close the current/[count]th tab.
@ -204,10 +213,10 @@ class CommandDispatcher:
tab = self._cntwidget(count)
if tab is None:
return
objreg.get('tabbed-browser').close_tab(tab)
self._tabbed_browser().close_tab(tab)
@cmdutils.register(instance='command-dispatcher', name='open',
split=False)
split=False, scope='window')
def openurl(self, url, bg=False, tab=False, count=None):
"""Open a URL in the current/[count]th tab.
@ -229,14 +238,15 @@ class CommandDispatcher:
if count is None:
# We want to open a URL in the current tab, but none exists
# yet.
objreg.get('tabbed-browser').tabopen(url)
self._tabbed_browser().tabopen(url)
else:
# Explicit count with a tab that doesn't exist.
return
else:
curtab.openurl(url)
@cmdutils.register(instance='command-dispatcher', name='reload')
@cmdutils.register(instance='command-dispatcher', name='reload',
scope='window')
def reloadpage(self, count=None):
"""Reload the current/[count]th tab.
@ -247,7 +257,7 @@ class CommandDispatcher:
if tab is not None:
tab.reload()
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def stop(self, count=None):
"""Stop loading in the current/[count]th tab.
@ -258,7 +268,8 @@ class CommandDispatcher:
if tab is not None:
tab.stop()
@cmdutils.register(instance='command-dispatcher', name='print')
@cmdutils.register(instance='command-dispatcher', name='print',
scope='window')
def printpage(self, preview=False, count=None):
"""Print the current/[count]th tab.
@ -282,7 +293,7 @@ class CommandDispatcher:
diag.setAttribute(Qt.WA_DeleteOnClose)
diag.open(lambda: tab.print(diag.printer()))
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def tab_clone(self, bg=False):
"""Duplicate the current tab.
@ -293,7 +304,7 @@ class CommandDispatcher:
The new QWebView.
"""
curtab = self._current_widget()
tabbed_browser = objreg.get('tabbed-browser')
tabbed_browser = self._tabbed_browser()
newtab = tabbed_browser.tabopen(background=bg, explicit=True)
history = qtutils.serialize(curtab.history())
qtutils.deserialize(history, newtab.history())
@ -311,7 +322,7 @@ class CommandDispatcher:
else:
widget.go_back()
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def back(self, tab=False, bg=False, count=1):
"""Go back in the history of the current tab.
@ -322,7 +333,7 @@ class CommandDispatcher:
"""
self._back_forward(tab, bg, count, forward=False)
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def forward(self, tab=False, bg=False, count=1):
"""Go forward in the history of the current tab.
@ -380,7 +391,7 @@ class CommandDispatcher:
url.setPath(new_path)
self._open(url, tab, background=False)
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def navigate(self, where: ('prev', 'next', 'up', 'increment', 'decrement'),
tab=False):
"""Open typical prev/next links or navigate using the URL path.
@ -419,7 +430,8 @@ class CommandDispatcher:
raise ValueError("Got called with invalid value {} for "
"`where'.".format(where))
@cmdutils.register(instance='command-dispatcher', hide=True)
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window')
def scroll(self, dx: float, dy: float, count=1):
"""Scroll the current tab by 'count * dx/dy'.
@ -434,7 +446,8 @@ class CommandDispatcher:
cmdutils.check_overflow(dy, 'int')
self._current_widget().page().currentFrame().scroll(dx, dy)
@cmdutils.register(instance='command-dispatcher', hide=True)
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window')
def scroll_perc(self, perc: float=None,
horizontal: {'flag': 'x'}=False, count=None):
"""Scroll to a specific percentage of the page.
@ -450,7 +463,8 @@ class CommandDispatcher:
self._scroll_percent(perc, count,
Qt.Horizontal if horizontal else Qt.Vertical)
@cmdutils.register(instance='command-dispatcher', hide=True)
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window')
def scroll_page(self, x: float, y: float, count=1):
"""Scroll the frame page-wise.
@ -467,7 +481,7 @@ class CommandDispatcher:
cmdutils.check_overflow(dy, 'int')
frame.scroll(dx, dy)
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def yank(self, title=False, sel=False):
"""Yank the current URL/title to the clipboard or primary selection.
@ -477,7 +491,7 @@ class CommandDispatcher:
"""
clipboard = QApplication.clipboard()
if title:
s = objreg.get('tabbed-browser').tabText(self._current_index())
s = self._tabbed_browser().tabText(self._current_index())
else:
s = self._current_url().toString(
QUrl.FullyEncoded | QUrl.RemovePassword)
@ -490,9 +504,9 @@ class CommandDispatcher:
log.misc.debug("Yanking to {}: '{}'".format(target, s))
clipboard.setText(s, mode)
what = 'Title' if title else 'URL'
message.info("{} yanked to {}".format(what, target))
message.info(self._win_id, "{} yanked to {}".format(what, target))
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def zoom_in(self, count=1):
"""Increase the zoom level for the current tab.
@ -502,7 +516,7 @@ class CommandDispatcher:
tab = self._current_widget()
tab.zoom(count)
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def zoom_out(self, count=1):
"""Decrease the zoom level for the current tab.
@ -512,7 +526,7 @@ class CommandDispatcher:
tab = self._current_widget()
tab.zoom(-count)
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def zoom(self, zoom=None, count=None):
"""Set the zoom level for the current tab.
@ -530,24 +544,24 @@ class CommandDispatcher:
tab = self._current_widget()
tab.zoom_perc(level)
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def tab_only(self):
"""Close all tabs except for the current one."""
tabbed_browser = objreg.get('tabbed-browser')
tabbed_browser = self._tabbed_browser()
for tab in tabbed_browser.widgets():
if tab is self._current_widget():
continue
tabbed_browser.close_tab(tab)
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def undo(self):
"""Re-open a closed tab (optionally skipping [count] closed tabs)."""
try:
objreg.get('tabbed-browser').undo()
self._tabbed_browser().undo()
except IndexError:
raise cmdexc.CommandError("Nothing to undo!")
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def tab_prev(self, count=1):
"""Switch to the previous tab, or switch [count] tabs back.
@ -562,7 +576,7 @@ class CommandDispatcher:
else:
raise cmdexc.CommandError("First tab")
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def tab_next(self, count=1):
"""Switch to the next tab, or switch [count] tabs forward.
@ -577,7 +591,7 @@ class CommandDispatcher:
else:
raise cmdexc.CommandError("Last tab")
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def paste(self, sel=False, tab=False, bg=False):
"""Open a page from the clipboard.
@ -603,7 +617,7 @@ class CommandDispatcher:
raise cmdexc.CommandError(e)
self._open(url, tab, bg)
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def tab_focus(self, index: (int, 'last')=None, count=None):
"""Select the tab given as argument/[count].
@ -627,7 +641,7 @@ class CommandDispatcher:
raise cmdexc.CommandError("There's no tab with index {}!".format(
idx))
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def tab_move(self, direction: ('+', '-')=None, count=None):
"""Move the current tab.
@ -651,7 +665,7 @@ class CommandDispatcher:
if not 0 <= new_idx < self._count():
raise cmdexc.CommandError("Can't move tab to position {}!".format(
new_idx))
tabbed_browser = objreg.get('tabbed-browser')
tabbed_browser = self._tabbed_browser()
tab = self._current_widget()
cur_idx = self._current_index()
icon = tabbed_browser.tabIcon(cur_idx)
@ -666,7 +680,8 @@ class CommandDispatcher:
finally:
tabbed_browser.setUpdatesEnabled(True)
@cmdutils.register(instance='command-dispatcher', split=False)
@cmdutils.register(instance='command-dispatcher', split=False,
scope='window')
def spawn(self, *args):
"""Spawn a command in a shell.
@ -684,12 +699,12 @@ class CommandDispatcher:
log.procs.debug("Executing: {}".format(args))
subprocess.Popen(args)
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def home(self):
"""Open main startpage in current tab."""
self.openurl(config.get('general', 'startpage')[0])
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def run_userscript(self, cmd, *args: {'nargs': '*'}):
"""Run an userscript given as argument.
@ -697,14 +712,15 @@ class CommandDispatcher:
cmd: The userscript to run.
args: Arguments to pass to the userscript.
"""
userscripts.run(cmd, *args, url=self._current_url())
userscripts.run(cmd, *args, url=self._current_url(),
win_id=self._win_id)
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def quickmark_save(self):
"""Save the current page as a quickmark."""
quickmarks.prompt_save(self._current_url())
quickmarks.prompt_save(self._win_id, self._current_url())
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def quickmark_load(self, name, tab=False, bg=False):
"""Load a quickmark.
@ -720,7 +736,8 @@ class CommandDispatcher:
urlstr, url.errorString()))
self._open(url, tab, bg)
@cmdutils.register(instance='command-dispatcher', name='inspector')
@cmdutils.register(instance='command-dispatcher', name='inspector',
scope='window')
def toggle_inspector(self):
"""Toggle the web inspector."""
cur = self._current_widget()
@ -742,13 +759,13 @@ class CommandDispatcher:
else:
cur.inspector.show()
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def download_page(self):
"""Download the current page."""
page = self._current_widget().page()
objreg.get('download-manager').get(self._current_url(), page)
@cmdutils.register(instance='command-dispatcher')
@cmdutils.register(instance='command-dispatcher', scope='window')
def view_source(self):
"""Show the source of the current page."""
# pylint doesn't seem to like pygments...
@ -762,12 +779,13 @@ class CommandDispatcher:
formatter = pygments.formatters.HtmlFormatter(
full=True, linenos='table')
highlighted = pygments.highlight(html, lexer, formatter)
tab = objreg.get('tabbed-browser').tabopen(explicit=True)
tab = self._tabbed_browser().tabopen(explicit=True)
tab.setHtml(highlighted, self._current_url())
tab.viewing_source = True
@cmdutils.register(instance='command-dispatcher', name='help',
completion=[usertypes.Completion.helptopic])
completion=[usertypes.Completion.helptopic],
scope='window')
def show_help(self, topic=None):
r"""Show help about a command or setting.
@ -803,9 +821,35 @@ class CommandDispatcher:
raise cmdexc.CommandError("Invalid help topic {}!".format(topic))
self.openurl('qute://help/{}'.format(path))
@cmdutils.register(instance='command-dispatcher', scope='window')
def later(self, ms: int, *command):
"""Execute a command after some time.
Args:
ms: How many milliseconds to wait.
*command: The command to run, with optional args.
"""
timer = usertypes.Timer(name='later')
timer.setSingleShot(True)
if ms < 0:
raise cmdexc.CommandError("I can't run something in the past!")
try:
timer.setInterval(ms)
except OverflowError:
raise cmdexc.CommandError("Numeric argument is too large for internal "
"int representation.")
self._timers.append(timer)
cmdline = ' '.join(command)
commandrunner = objreg.get('command-runner', scope='window',
window=self._win_id)
timer.timeout.connect(functools.partial(commandrunner.run_safely, cmdline))
timer.timeout.connect(lambda: self._timers.remove(timer))
timer.start()
@cmdutils.register(instance='command-dispatcher',
modes=[usertypes.KeyMode.insert],
hide=True)
hide=True, scope='window')
def open_editor(self):
"""Open an external editor with the currently selected form field.
@ -829,9 +873,10 @@ class CommandDispatcher:
text = str(elem)
else:
text = elem.evaluateJavaScript('this.value')
self._editor = editor.ExternalEditor(objreg.get('tabbed-browser'))
self._editor = editor.ExternalEditor(
win_id, self._tabbed_browser())
self._editor.editing_finished.connect(
partial(self.on_editing_finished, elem))
functools.partial(self.on_editing_finished, elem))
self._editor.edit(text)
def on_editing_finished(self, elem, text):

View File

@ -31,6 +31,7 @@ from qutebrowser.config import config
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.utils import (message, http, usertypes, log, utils, qtutils,
objreg)
from qutebrowser.widgets import mainwindow
class DownloadItem(QObject):
@ -431,4 +432,4 @@ class DownloadManager(QObject):
@pyqtSlot(str)
def on_error(self, msg):
"""Display error message on download errors."""
message.error("Download error: {}".format(msg))
message.error('current', "Download error: {}".format(msg))

View File

@ -43,10 +43,10 @@ Target = usertypes.enum('Target', ['normal', 'tab', 'tab_bg', 'yank',
@pyqtSlot(usertypes.KeyMode)
def on_mode_entered(mode):
def on_mode_entered(mode, win_id):
"""Stop hinting when insert mode was entered."""
if mode == usertypes.KeyMode.insert:
modeman.maybe_leave(usertypes.KeyMode.hint, 'insert mode')
modeman.maybe_leave(win_id, usertypes.KeyMode.hint, 'insert mode')
class HintContext:
@ -100,6 +100,7 @@ class HintManager(QObject):
Attributes:
_context: The HintContext for the current invocation.
_win_id: The window ID this HintManager is associated with.
Signals:
mouse_event: Mouse event to be posted in the web view.
@ -137,15 +138,14 @@ class HintManager(QObject):
mouse_event = pyqtSignal('QMouseEvent')
set_open_target = pyqtSignal(str)
def __init__(self, parent=None):
"""Constructor.
Args:
frame: The QWebFrame to use for finding elements and drawing.
"""
def __init__(self, win_id, parent=None):
"""Constructor."""
super().__init__(parent)
self._win_id = win_id
self._context = None
objreg.get('mode-manager').left.connect(self.on_mode_left)
mode_manager = objreg.get('mode-manager', scope='window',
window=win_id)
mode_manager.left.connect(self.on_mode_left)
def _cleanup(self):
"""Clean up after hinting."""
@ -155,7 +155,9 @@ class HintManager(QObject):
except webelem.IsNullError:
pass
text = self.HINT_TEXTS[self._context.target]
objreg.get('message-bridge').maybe_reset_text(text)
message_bridge = objreg.get('message-bridge', scope='window',
window=self._win_id)
message_bridge.maybe_reset_text(text)
self._context = None
def _hint_strings(self, elems):
@ -331,8 +333,8 @@ class HintManager(QObject):
mode = QClipboard.Selection if sel else QClipboard.Clipboard
urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
QApplication.clipboard().setText(urlstr, mode)
message.info("URL yanked to {}".format("primary selection" if sel
else "clipboard"))
message.info(self._win_id, "URL yanked to {}".format(
"primary selection" if sel else "clipboard"))
def _preset_cmd_text(self, url):
"""Preset a commandline text based on a hint URL.
@ -343,7 +345,7 @@ class HintManager(QObject):
qtutils.ensure_valid(url)
urlstr = url.toDisplayString(QUrl.FullyEncoded)
args = self._context.get_args(urlstr)
message.set_cmd_text(' '.join(args))
message.set_cmd_text(self._win_id, ' '.join(args))
def _download(self, elem):
"""Download a hint URL.
@ -353,7 +355,8 @@ class HintManager(QObject):
"""
url = self._resolve_url(elem)
if url is None:
message.error("No suitable link found for this element.",
message.error(self._win_id,
"No suitable link found for this element.",
immediately=True)
return
qtutils.ensure_valid(url)
@ -570,7 +573,8 @@ class HintManager(QObject):
objreg.get('message-bridge').set_text(self.HINT_TEXTS[target])
self._connect_frame_signals()
try:
modeman.enter(usertypes.KeyMode.hint, 'HintManager.start')
modeman.enter(self._win_id, usertypes.KeyMode.hint,
'HintManager.start')
except modeman.ModeLockedError:
self._cleanup()
@ -614,7 +618,7 @@ class HintManager(QObject):
visible[k] = e
if not visible:
# Whoops, filtered all hints
modeman.leave(usertypes.KeyMode.hint, 'all filtered')
modeman.leave(self._win_id, usertypes.KeyMode.hint, 'all filtered')
elif len(visible) == 1 and config.get('hints', 'auto-follow'):
# unpacking gets us the first (and only) key in the dict.
self.fire(*visible)
@ -653,14 +657,16 @@ class HintManager(QObject):
elif self._context.target in url_handlers:
url = self._resolve_url(elem)
if url is None:
message.error("No suitable link found for this element.",
message.error(self._win_id,
"No suitable link found for this element.",
immediately=True)
return
url_handlers[self._context.target](url)
else:
raise ValueError("No suitable handler found!")
if self._context.target != Target.rapid:
modeman.maybe_leave(usertypes.KeyMode.hint, 'followed')
modeman.maybe_leave(self._win_id, usertypes.KeyMode.hint,
'followed')
@cmdutils.register(instance='hintmanager', scope='tab', hide=True)
def follow_hint(self):

View File

@ -47,7 +47,7 @@ def init():
try:
key, url = line.split(maxsplit=1)
except ValueError:
message.error("Invalid quickmark '{}'".format(line))
message.error(0, "Invalid quickmark '{}'".format(line))
else:
marks[key] = url
@ -58,20 +58,21 @@ def save():
linecp.save()
def prompt_save(url):
def prompt_save(win_id, url):
"""Prompt for a new quickmark name to be added and add it.
Args:
win_id: The current window ID.
url: The quickmark url as a QUrl.
"""
qtutils.ensure_valid(url)
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
message.ask_async("Add quickmark:", usertypes.PromptMode.text,
message.ask_async(win_id, "Add quickmark:", usertypes.PromptMode.text,
functools.partial(quickmark_add, urlstr))
@cmdutils.register()
def quickmark_add(url, name):
def quickmark_add(url, name, win_id):
"""Add a new quickmark.
Args:
@ -81,10 +82,10 @@ def quickmark_add(url, name):
# We don't raise cmdexc.CommandError here as this can be called async via
# prompt_save.
if not name:
message.error("Can't set mark with empty name!")
message.error(win_id, "Can't set mark with empty name!")
return
if not url:
message.error("Can't set mark with empty URL!")
message.error(win_id, "Can't set mark with empty URL!")
return
def set_mark():
@ -92,7 +93,7 @@ def quickmark_add(url, name):
marks[name] = url
if name in marks:
message.confirm_async("Override existing quickmark?", set_mark,
message.confirm_async(win_id, "Override existing quickmark?", set_mark,
default=True)
else:
set_mark()

View File

@ -34,12 +34,19 @@ class SignalFilter(QObject):
Signals are only passed to the parent TabbedBrowser if they originated in
the currently shown widget.
Attributes:
_win_id: The window ID this SignalFilter is associated with.
Class attributes:
BLACKLIST: List of signal names which should not be logged.
"""
BLACKLIST = ['cur_scroll_perc_changed', 'cur_progress']
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._win_id = win_id
def create(self, signal, tab):
"""Factory for partial _filter_signals functions.
@ -73,7 +80,8 @@ class SignalFilter(QObject):
The target signal if the sender was the current widget.
"""
log_signal = debug.signal_name(signal) not in self.BLACKLIST
tabbed_browser = objreg.get('tabbed-browser')
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=self._win_id)
try:
tabidx = tabbed_browser.indexOf(tab)
except RuntimeError:

View File

@ -41,6 +41,7 @@ class BrowserPage(QWebPage):
Attributes:
_extension_handlers: Mapping of QWebPage extensions to their handlers.
_networkmnager: The NetworkManager used.
_win_id: The window ID this BrowserPage is associated with.
Signals:
start_download: Emitted when a file should be downloaded.
@ -48,13 +49,14 @@ class BrowserPage(QWebPage):
start_download = pyqtSignal('QNetworkReply*')
def __init__(self, parent=None):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._win_id = win_id
self._extension_handlers = {
QWebPage.ErrorPageExtension: self._handle_errorpage,
QWebPage.ChooseMultipleFilesExtension: self._handle_multiple_files,
}
self._networkmanager = networkmanager.NetworkManager(self)
self._networkmanager = networkmanager.NetworkManager(win_id, self)
self.setNetworkAccessManager(self._networkmanager)
self.setForwardUnsupportedContent(True)
self.printRequested.connect(self.on_print_requested)
@ -75,8 +77,8 @@ class BrowserPage(QWebPage):
http://www.riverbankcomputing.com/pipermail/pyqt/2014-June/034385.html
"""
answer = message.ask("js: {}".format(msg), usertypes.PromptMode.text,
default)
answer = message.ask(self._win_id, "js: {}".format(msg),
usertypes.PromptMode.text, default)
if answer is None:
return (False, "")
else:
@ -153,8 +155,8 @@ class BrowserPage(QWebPage):
def on_print_requested(self, frame):
"""Handle printing when requested via javascript."""
if not qtutils.check_print_compat():
message.error("Printing on Qt < 5.3.0 on Windows is broken, "
"please upgrade!", immediately=True)
message.error(self._win_id, "Printing on Qt < 5.3.0 on Windows is "
"broken, please upgrade!", immediately=True)
return
printdiag = QPrintDialog()
printdiag.setAttribute(Qt.WA_DeleteOnClose)
@ -243,11 +245,12 @@ class BrowserPage(QWebPage):
def javaScriptAlert(self, _frame, msg):
"""Override javaScriptAlert to use the statusbar."""
message.ask("[js alert] {}".format(msg), usertypes.PromptMode.alert)
message.ask(self._win_id, "[js alert] {}".format(msg),
usertypes.PromptMode.alert)
def javaScriptConfirm(self, _frame, msg):
"""Override javaScriptConfirm to use the statusbar."""
ans = message.ask("[js confirm] {}".format(msg),
ans = message.ask(self._win_id, "[js confirm] {}".format(msg),
usertypes.PromptMode.yesno)
return bool(ans)
@ -267,8 +270,8 @@ class BrowserPage(QWebPage):
def shouldInterruptJavaScript(self):
"""Override shouldInterruptJavaScript to use the statusbar."""
answer = message.ask("Interrupt long-running javascript?",
usertypes.PromptMode.yesno)
answer = message.ask(self._win_id, "Interrupt long-running "
"javascript?", usertypes.PromptMode.yesno)
if answer is None:
answer = True
return answer
@ -293,7 +296,8 @@ class BrowserPage(QWebPage):
url = request.url()
urlstr = url.toDisplayString()
if not url.isValid():
message.error("Invalid link {} clicked!".format(urlstr))
message.error(self._win_id, "Invalid link {} clicked!".format(
urlstr))
log.webview.debug(url.errorString())
return False
if self.view().open_target == usertypes.ClickTarget.tab:

View File

@ -95,13 +95,18 @@ class Command:
self._type_conv = type_conv
self._name_conv = name_conv
def _check_prerequisites(self):
def _check_prerequisites(self, win_id):
"""Check if the command is permitted to run currently.
Args:
win_id: The window ID the command is run in.
Raise:
PrerequisitesError if the command can't be called currently.
"""
curmode = objreg.get('mode-manager').mode()
mode_manager = objreg.get('mode-manager', scope='window',
window=win_id)
curmode = mode_manager.mode()
if self._modes is not None and curmode not in self._modes:
mode_names = '/'.join(mode.name for mode in self._modes)
raise cmdexc.PrerequisitesError(
@ -294,15 +299,19 @@ class Command:
else:
return type(param.default)
def _get_self_arg(self, param, args):
def _get_self_arg(self, win_id, param, args):
"""Get the self argument for a function call.
Arguments:
win_id: The window id this command should be executed in.
param: The count parameter.
args: The positional argument list. Gets modified directly.
"""
assert param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
obj = objreg.get(self._instance, scope=self._scope)
if self._scope is not 'window':
win_id = None
obj = objreg.get(self._instance, scope=self._scope,
window=win_id)
args.append(obj)
def _get_count_arg(self, param, args, kwargs):
@ -340,9 +349,12 @@ class Command:
value = self._type_conv[param.name](value)
return name, value
def _get_call_args(self):
def _get_call_args(self, win_id):
"""Get arguments for a function call.
Args:
win_id: The window id this command should be executed in.
Return:
An (args, kwargs) tuple.
"""
@ -354,13 +366,13 @@ class Command:
if self.ignore_args:
if self._instance is not None:
param = list(signature.parameters.values())[0]
self._get_self_arg(param, args)
self._get_self_arg(win_id, param, args)
return args, kwargs
for i, param in enumerate(signature.parameters.values()):
if i == 0 and self._instance is not None:
# Special case for 'self'.
self._get_self_arg(param, args)
self._get_self_arg(win_id, param, args)
continue
elif param.name == 'count':
# Special case for 'count'.
@ -380,12 +392,13 @@ class Command:
self.name, param.kind, param.name))
return args, kwargs
def run(self, args=None, count=None):
def run(self, win_id, args=None, count=None):
"""Run the command.
Note we don't catch CommandError here as it might happen async.
Args:
win_id: The window ID the command is run in.
args: Arguments to the command.
count: Command repetition count.
"""
@ -398,15 +411,15 @@ class Command:
try:
self.namespace = self.parser.parse_args(args)
except argparser.ArgumentParserError as e:
message.error('{}: {}'.format(self.name, e))
message.error(win_id, '{}: {}'.format(self.name, e))
return
except argparser.ArgumentParserExit as e:
log.commands.debug("argparser exited with status {}: {}".format(
e.status, e))
return
self._count = count
posargs, kwargs = self._get_call_args()
self._check_prerequisites()
posargs, kwargs = self._get_call_args(win_id)
self._check_prerequisites(win_id)
log.commands.debug('Calling {}'.format(
debug.format_call(self.handler, posargs, kwargs)))
self.handler(*posargs, **kwargs)

View File

@ -114,7 +114,7 @@ class SearchRunner(QObject):
"""
self._search(text, rev=True)
@cmdutils.register(instance='search-runner', hide=True)
@cmdutils.register(instance='search-runner', hide=True, scope='window')
def search_next(self, count=1):
"""Continue the search to the ([count]th) next term.
@ -128,7 +128,7 @@ class SearchRunner(QObject):
for _ in range(count):
self.do_search.emit(self._text, self._flags)
@cmdutils.register(instance='search-runner', hide=True)
@cmdutils.register(instance='search-runner', hide=True, scope='window')
def search_prev(self, count=1):
"""Continue the search to the ([count]th) previous term.
@ -159,11 +159,13 @@ class CommandRunner:
Attributes:
_cmd: The command which was parsed.
_args: The arguments which were parsed.
_win_id: The window this CommandRunner is associated with.
"""
def __init__(self):
def __init__(self, win_id):
self._cmd = None
self._args = []
self._win_id = win_id
def _get_alias(self, text, alias_no_args):
"""Get an alias from the config.
@ -280,9 +282,9 @@ class CommandRunner:
self.parse(text)
args = replace_variables(self._args)
if count is not None:
self._cmd.run(args, count=count)
self._cmd.run(self._win_id, args, count=count)
else:
self._cmd.run(args)
self._cmd.run(self._win_id, args)
@pyqtSlot(str, int)
def run_safely(self, text, count=None):
@ -290,7 +292,7 @@ class CommandRunner:
try:
self.run(text, count)
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
message.error(e, immediately=True)
message.error(self._win_id, e, immediately=True)
@pyqtSlot(str, int)
def run_safely_init(self, text, count=None):
@ -301,4 +303,4 @@ class CommandRunner:
try:
self.run(text, count)
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
message.error(e)
message.error(self._win_id, e)

View File

@ -37,7 +37,7 @@ from qutebrowser.commands import runners, cmdexc
_runners = []
_commandrunner = None
_commandrunners = []
class _BlockingFIFOReader(QObject):
@ -98,6 +98,7 @@ class _BaseUserscriptRunner(QObject):
Attributes:
_filepath: The path of the file/FIFO which is being read.
_proc: The QProcess which is being executed.
_win_id: The window ID this runner is associated with.
Class attributes:
PROCESS_MESSAGES: A mapping of QProcess::ProcessError members to
@ -122,8 +123,9 @@ class _BaseUserscriptRunner(QObject):
QProcess.UnknownError: "An unknown error occurred.",
}
def __init__(self, parent=None):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._win_id = win_id
self._filepath = None
self._proc = None
@ -153,7 +155,8 @@ class _BaseUserscriptRunner(QObject):
except PermissionError as e:
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.
message.error("Failed to delete tempfile... ({})".format(e))
message.error(self._win_id,
"Failed to delete tempfile... ({})".format(e))
self._filepath = None
self._proc = None
@ -181,7 +184,8 @@ class _BaseUserscriptRunner(QObject):
msg = self.PROCESS_MESSAGES[error]
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.
message.error("Error while calling userscript: {}".format(msg))
message.error(self._win_id,
"Error while calling userscript: {}".format(msg))
class _POSIXUserscriptRunner(_BaseUserscriptRunner):
@ -196,8 +200,8 @@ class _POSIXUserscriptRunner(_BaseUserscriptRunner):
_thread: The QThread where reader runs.
"""
def __init__(self, parent=None):
super().__init__(parent)
def __init__(self, win_id, parent=None):
super().__init__(win_id, parent)
self._reader = None
self._thread = None
@ -263,8 +267,8 @@ class _WindowsUserscriptRunner(_BaseUserscriptRunner):
_oshandle: The oshandle of the temp file.
"""
def __init__(self, parent=None):
super().__init__(parent)
def __init__(self, win_id, parent=None):
super().__init__(win_id, parent)
self._oshandle = None
def _cleanup(self):
@ -327,19 +331,17 @@ else:
UserscriptRunner = _DummyUserscriptRunner
def init():
"""Initialize the global _commandrunner."""
global _commandrunner
_commandrunner = runners.CommandRunner()
def run(cmd, *args, url):
def run(cmd, *args, url, win_id):
"""Convenience method to run an userscript."""
# We don't remove the password in the URL here, as it's probably safe to
# pass via env variable..
urlstr = url.toString(QUrl.FullyEncoded)
runner = UserscriptRunner()
runner.got_cmd.connect(_commandrunner.run_safely)
commandrunner = runners.CommandRunner(win_id)
runner = UserscriptRunner(win_id)
runner.got_cmd.connect(commandrunner.run_safely)
runner.run(cmd, *args, env={'QUTE_URL': urlstr})
_runners.append(runner)
_commandrunners.append(commandrunner)
runner.finished.connect(functools.partial(_runners.remove, runner))
commandrunner.finished.connect(
functools.partial(_commandrunners.remove, commandrunner))

View File

@ -438,7 +438,7 @@ class ConfigManager(QObject):
@cmdutils.register(name='set', instance='config',
completion=[Completion.section, Completion.option,
Completion.value])
def set_command(self, sectname: {'name': 'section'},
def set_command(self, win_id, sectname: {'name': 'section'},
optname: {'name': 'option'}, value=None, temp=False):
"""Set an option.
@ -458,8 +458,8 @@ class ConfigManager(QObject):
try:
if optname.endswith('?'):
val = self.get(sectname, optname[:-1], transformed=False)
message.info("{} {} = {}".format(sectname, optname[:-1], val),
immediately=True)
message.info(win_id, "{} {} = {}".format(
sectname, optname[:-1], val), immediately=True)
else:
if value is None:
raise cmdexc.CommandError("set: The following arguments "

View File

@ -53,6 +53,7 @@ class BaseKeyParser(QObject):
Attributes:
bindings: Bound keybindings
special_bindings: Bound special bindings (<Foo>).
_win_id: The window ID this keyparser is associated with.
_warn_on_keychains: Whether a warning should be logged when binding
keychains in a section which does not support them.
_keystring: The currently entered key sequence
@ -73,9 +74,10 @@ class BaseKeyParser(QObject):
'none'])
Type = usertypes.enum('Type', ['chain', 'special'])
def __init__(self, parent=None, supports_count=None,
def __init__(self, win_id, parent=None, supports_count=None,
supports_chains=False):
super().__init__(parent)
self._win_id = win_id
self._timer = None
self._modename = None
self._keystring = ''

View File

@ -32,16 +32,16 @@ class CommandKeyParser(BaseKeyParser):
_commandrunner: CommandRunner instance.
"""
def __init__(self, parent=None, supports_count=None,
def __init__(self, win_id, parent=None, supports_count=None,
supports_chains=False):
super().__init__(parent, supports_count, supports_chains)
self._commandrunner = runners.CommandRunner()
super().__init__(win_id, parent, supports_count, supports_chains)
self._commandrunner = runners.CommandRunner(win_id)
def execute(self, cmdstr, _keytype, count=None):
try:
self._commandrunner.run(cmdstr, count)
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
message.error(e, immediately=True)
message.error(self._win_id, e, immediately=True)
class PassthroughKeyParser(CommandKeyParser):
@ -56,7 +56,7 @@ class PassthroughKeyParser(CommandKeyParser):
do_log = False
def __init__(self, mode, parent=None, warn=True):
def __init__(self, win_id, mode, parent=None, warn=True):
"""Constructor.
Args:
@ -64,7 +64,7 @@ class PassthroughKeyParser(CommandKeyParser):
parent: Qt parent.
warn: Whether to warn if an ignored key was bound.
"""
super().__init__(parent, supports_chains=False)
super().__init__(win_id, parent, supports_chains=False)
self._warn_on_keychains = warn
self.read_config(mode)
self._mode = mode

View File

@ -43,22 +43,23 @@ class NotInModeError(Exception):
"""Exception raised when we want to leave a mode we're not in."""
def init():
"""Inizialize the mode manager and the keyparsers."""
def init(win_id, parent):
"""Inizialize the mode manager and the keyparsers for the given win_id."""
KM = usertypes.KeyMode # pylint: disable=invalid-name
modeman = ModeManager(objreg.get('app'))
objreg.register('mode-manager', modeman)
modeman = ModeManager(win_id, parent)
objreg.register('mode-manager', modeman, scope='window', window=win_id)
keyparsers = {
KM.normal: modeparsers.NormalKeyParser(modeman),
KM.hint: modeparsers.HintKeyParser(modeman),
KM.insert: keyparser.PassthroughKeyParser('insert', modeman),
KM.passthrough: keyparser.PassthroughKeyParser('passthrough', modeman),
KM.command: keyparser.PassthroughKeyParser('command', modeman),
KM.prompt: keyparser.PassthroughKeyParser('prompt', modeman,
KM.normal: modeparsers.NormalKeyParser(win_id, modeman),
KM.hint: modeparsers.HintKeyParser(win_id, modeman),
KM.insert: keyparser.PassthroughKeyParser(win_id, 'insert', modeman),
KM.passthrough: keyparser.PassthroughKeyParser(win_id, 'passthrough',
modeman),
KM.command: keyparser.PassthroughKeyParser(win_id, 'command', modeman),
KM.prompt: keyparser.PassthroughKeyParser(win_id, 'prompt', modeman,
warn=False),
KM.yesno: modeparsers.PromptKeyParser(modeman),
KM.yesno: modeparsers.PromptKeyParser(win_id, modeman),
}
objreg.register('keyparsers', keyparsers)
objreg.register('keyparsers', keyparsers, scope='window', window=win_id)
modeman.register(KM.normal, keyparsers[KM.normal].handle)
modeman.register(KM.hint, keyparsers[KM.hint].handle)
modeman.register(KM.insert, keyparsers[KM.insert].handle, passthrough=True)
@ -68,35 +69,66 @@ def init():
passthrough=True)
modeman.register(KM.prompt, keyparsers[KM.prompt].handle, passthrough=True)
modeman.register(KM.yesno, keyparsers[KM.yesno].handle)
return modeman
def enter(mode, reason=None):
def _get_modeman(win_id):
"""Get a modemanager object."""
return objreg.get('mode-manager', scope='window', window=win_id)
def enter(win_id, mode, reason=None):
"""Enter the mode 'mode'."""
objreg.get('mode-manager').enter(mode, reason)
_get_modeman(win_id).enter(mode, reason)
def leave(mode, reason=None):
def leave(win_id, mode, reason=None):
"""Leave the mode 'mode'."""
objreg.get('mode-manager').leave(mode, reason)
_get_modeman(win_id).leave(mode, reason)
def maybe_enter(mode, reason=None):
def maybe_enter(win_id, mode, reason=None):
"""Convenience method to enter 'mode' without exceptions."""
try:
objreg.get('mode-manager').enter(mode, reason)
_get_modeman(win_id).enter(mode, reason)
except ModeLockedError:
pass
def maybe_leave(mode, reason=None):
def maybe_leave(win_id, mode, reason=None):
"""Convenience method to leave 'mode' without exceptions."""
try:
objreg.get('mode-manager').leave(mode, reason)
_get_modeman(win_id).leave(mode, reason)
except NotInModeError as e:
# This is rather likely to happen, so we only log to debug log.
log.modes.debug("{} (leave reason: {})".format(e, reason))
class EventFilter(QObject):
"""Event filter which passes the event to the corrent ModeManager."""
def __init__(self, parent=None):
super().__init__(parent)
self._activated = True
def eventFilter(self, obj, event):
if not self._activated:
return False
try:
modeman = objreg.get('mode-manager', scope='window',
window='current')
return modeman.eventFilter(obj, event)
except objreg.RegistryUnavailableError:
# No window available yet
return False
except:
# If there is an exception in here and we leave the eventfilter
# activated, we'll get an infinite loop and a stack overflow.
self._activated = False
raise
class ModeManager(QObject):
"""Manager for keyboard modes.
@ -106,6 +138,7 @@ class ModeManager(QObject):
locked: Whether current mode is locked. This means the current mode can
still be left (then locked will be reset), but no new mode can
be entered while the current mode is active.
_win_id: The window ID of this ModeManager
_handlers: A dictionary of modes and their handlers.
_mode_stack: A list of the modes we're currently in, with the active
one on the right.
@ -116,16 +149,19 @@ class ModeManager(QObject):
Signals:
entered: Emitted when a mode is entered.
arg: The mode which has been entered.
arg1: The mode which has been entered.
arg2: The window ID of this mode manager.
left: Emitted when a mode is left.
arg: The mode which has been left.
arg1: The mode which has been left.
arg2: The window ID of this mode manager.
"""
entered = pyqtSignal(usertypes.KeyMode)
left = pyqtSignal(usertypes.KeyMode)
entered = pyqtSignal(usertypes.KeyMode, int)
left = pyqtSignal(usertypes.KeyMode, int)
def __init__(self, parent=None):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._win_id = win_id
self.locked = False
self._handlers = {}
self.passthrough = []
@ -246,9 +282,9 @@ class ModeManager(QObject):
return
self._mode_stack.append(mode)
log.modes.debug("New mode stack: {}".format(self._mode_stack))
self.entered.emit(mode)
self.entered.emit(mode, self._win_id)
@cmdutils.register(instance='mode-manager', hide=True)
@cmdutils.register(instance='mode-manager', hide=True, scope='window')
def enter_mode(self, mode):
"""Enter a key mode.
@ -279,10 +315,11 @@ class ModeManager(QObject):
log.modes.debug("Leaving mode {}{}, new mode stack {}".format(
mode, '' if reason is None else ' (reason: {})'.format(reason),
self._mode_stack))
self.left.emit(mode)
self.left.emit(mode, self._win_id)
@cmdutils.register(instance='mode-manager', name='leave-mode',
not_modes=[usertypes.KeyMode.normal], hide=True)
not_modes=[usertypes.KeyMode.normal], hide=True,
scope='window')
def leave_current_mode(self):
"""Leave the mode we're currently in."""
if self.mode() == usertypes.KeyMode.normal:
@ -316,8 +353,8 @@ class ModeManager(QObject):
# We already handled this same event at some point earlier, so
# we're not interested in it anymore.
return False
if (QApplication.instance().activeWindow() is not
objreg.get('main-window')):
if (QApplication.instance().activeWindow() not in
objreg.window_registry.values()):
# Some other window (print dialog, etc.) is focused so we pass
# the event through.
return False

View File

@ -39,8 +39,9 @@ class NormalKeyParser(keyparser.CommandKeyParser):
"""KeyParser for normalmode with added STARTCHARS detection."""
def __init__(self, parent=None):
super().__init__(parent, supports_count=True, supports_chains=True)
def __init__(self, win_id, parent=None):
super().__init__(win_id, parent, supports_count=True,
supports_chains=True)
self.read_config('normal')
def __repr__(self):
@ -57,7 +58,7 @@ class NormalKeyParser(keyparser.CommandKeyParser):
"""
txt = e.text().strip()
if not self._keystring and any(txt == c for c in STARTCHARS):
message.set_cmd_text(txt)
message.set_cmd_text(self._win_id, txt)
return True
return super()._handle_single_key(e)
@ -66,8 +67,9 @@ class PromptKeyParser(keyparser.CommandKeyParser):
"""KeyParser for yes/no prompts."""
def __init__(self, parent=None):
super().__init__(parent, supports_count=False, supports_chains=True)
def __init__(self, win_id, parent=None):
super().__init__(win_id, parent, supports_count=False,
supports_chains=True)
# We don't want an extra section for this in the config, so we just
# abuse the prompt section.
self.read_config('prompt')
@ -85,8 +87,9 @@ class HintKeyParser(keyparser.CommandKeyParser):
_last_press: The nature of the last keypress, a LastPress member.
"""
def __init__(self, parent=None):
super().__init__(parent, supports_count=False, supports_chains=True)
def __init__(self, win_id, parent=None):
super().__init__(win_id, parent, supports_count=False,
supports_chains=True)
self._filtertext = ''
self._last_press = LastPress.none
self.read_config('hint')

View File

@ -42,14 +42,16 @@ class NetworkManager(QNetworkAccessManager):
_requests: Pending requests.
_scheme_handlers: A dictionary (scheme -> handler) of supported custom
schemes.
_win_id: The window ID this NetworkManager is associated with.
"""
def __init__(self, parent=None):
def __init__(self, win_id, parent=None):
log.init.debug("Initializing NetworkManager")
super().__init__(parent)
self._win_id = win_id
self._requests = []
self._scheme_handlers = {
'qute': qutescheme.QuteSchemeHandler(),
'qute': qutescheme.QuteSchemeHandler(win_id),
}
# We have a shared cookie jar and cache - we restore their parents so
@ -100,20 +102,22 @@ class NetworkManager(QNetworkAccessManager):
return
for err in errors:
# FIXME we might want to use warn here (non-fatal error)
message.error('SSL error: {}'.format(err.errorString()))
message.error(self._win_id,
'SSL error: {}'.format(err.errorString()))
reply.ignoreSslErrors()
@pyqtSlot('QNetworkReply', 'QAuthenticator')
def on_authentication_required(self, _reply, authenticator):
"""Called when a website needs authentication."""
answer = message.ask("Username ({}):".format(authenticator.realm()),
answer = message.ask(self._win_id,
"Username ({}):".format(authenticator.realm()),
mode=usertypes.PromptMode.user_pwd)
self._fill_authenticator(authenticator, answer)
@pyqtSlot('QNetworkProxy', 'QAuthenticator')
def on_proxy_authentication_required(self, _proxy, authenticator):
"""Called when a proxy needs authentication."""
answer = message.ask("Proxy username ({}):".format(
answer = message.ask(self._win_id, "Proxy username ({}):".format(
authenticator.realm()), mode=usertypes.PromptMode.user_pwd)
self._fill_authenticator(authenticator, answer)

View File

@ -78,14 +78,14 @@ class QuteSchemeHandler(schemehandler.SchemeHandler):
request, data, 'text/html', self.parent())
def qute_pyeval(_request):
def qute_pyeval(_win_id, _request):
"""Handler for qute:pyeval. Return HTML content as bytes."""
html = jinja.env.get_template('pre.html').render(
title='pyeval', content=pyeval_output)
return html.encode('UTF-8', errors='xmlcharrefreplace')
def qute_version(_request):
def qute_version(_win_id, _request):
"""Handler for qute:version. Return HTML content as bytes."""
html = jinja.env.get_template('version.html').render(
title='Version info', version=version.version(),
@ -93,7 +93,7 @@ def qute_version(_request):
return html.encode('UTF-8', errors='xmlcharrefreplace')
def qute_plainlog(_request):
def qute_plainlog(_win_id, _request):
"""Handler for qute:plainlog. Return HTML content as bytes."""
if log.ram_handler is None:
text = "Log output was disabled."
@ -103,7 +103,7 @@ def qute_plainlog(_request):
return html.encode('UTF-8', errors='xmlcharrefreplace')
def qute_log(_request):
def qute_log(_win_id, _request):
"""Handler for qute:log. Return HTML content as bytes."""
if log.ram_handler is None:
html_log = None
@ -114,12 +114,12 @@ def qute_log(_request):
return html.encode('UTF-8', errors='xmlcharrefreplace')
def qute_gpl(_request):
def qute_gpl(_win_id, _request):
"""Handler for qute:gpl. Return HTML content as bytes."""
return utils.read_file('html/COPYING.html').encode('ASCII')
def qute_help(request):
def qute_help(win_id, request):
"""Handler for qute:help. Return HTML content as bytes."""
try:
utils.read_file('html/doc/index.html')
@ -140,8 +140,8 @@ def qute_help(request):
else:
urlpath = urlpath.lstrip('/')
if not docutils.docs_up_to_date(urlpath):
message.error("Your documentation is outdated! Please re-run scripts/"
"asciidoc2html.py.")
message.error(win_id, "Your documentation is outdated! Please re-run "
"scripts/asciidoc2html.py.")
path = 'html/doc/{}'.format(urlpath)
return utils.read_file(path).encode('UTF-8', errors='xmlcharrefreplace')

View File

@ -28,7 +28,14 @@ from PyQt5.QtCore import pyqtSlot, QObject, QIODevice, QByteArray, QTimer
class SchemeHandler(QObject):
"""Abstract base class for custom scheme handlers."""
"""Abstract base class for custom scheme handlers.
Attributes:
_win_id: The window ID this scheme handler is associated with.
"""
def __init__(self, win_id):
self._win_id = win_id
def createRequest(self, op, request, outgoing_data):
"""Create a new request.

View File

@ -54,7 +54,7 @@ class ArgTests(unittest.TestCase):
"""
def setUp(self):
self.editor = editor.ExternalEditor()
self.editor = editor.ExternalEditor(0)
def test_simple_start_args(self):
"""Test starting editor without arguments."""
@ -102,7 +102,7 @@ class FileHandlingTests(unittest.TestCase):
"""
def setUp(self):
self.editor = editor.ExternalEditor()
self.editor = editor.ExternalEditor(0)
editor.config = stubs.ConfigStub(
{'general': {'editor': [''], 'editor-encoding': 'utf-8'}})
@ -141,7 +141,7 @@ class TextModifyTests(unittest.TestCase):
"""
def setUp(self):
self.editor = editor.ExternalEditor()
self.editor = editor.ExternalEditor(0)
self.editor.editing_finished = mock.Mock()
editor.config = stubs.ConfigStub(
{'general': {'editor': [''], 'editor-encoding': 'utf-8'}})
@ -211,7 +211,7 @@ class ErrorMessageTests(unittest.TestCase):
# pylint: disable=maybe-no-member
def setUp(self):
self.editor = editor.ExternalEditor()
self.editor = editor.ExternalEditor(0)
editor.config = stubs.ConfigStub(
{'general': {'editor': [''], 'editor-encoding': 'utf-8'}})

View File

@ -35,6 +35,7 @@ class Completer(QObject):
Attributes:
_ignore_change: Whether to ignore the next completion update.
_models: dict of available completion models.
_win_id: The window ID this completer is in.
Signals:
change_completed_part: Text which should be substituted for the word
@ -47,8 +48,9 @@ class Completer(QObject):
change_completed_part = pyqtSignal(str, bool)
def __init__(self, parent=None):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._win_id = win_id
self._ignore_change = False
self._models = {
@ -63,7 +65,9 @@ class Completer(QObject):
def _model(self):
"""Convienience method to get the current completion model."""
return objreg.get('completion').model()
completion = objreg.get('completion', scope='window',
window=self._win_id)
return completion.model()
def _init_static_completions(self):
"""Initialize the static completion models."""
@ -192,7 +196,8 @@ class Completer(QObject):
log.completion.debug("Ignoring completion update")
return
completion = objreg.get('completion')
completion = objreg.get('completion', scope='window',
window=self._win_id)
if prefix != ':':
# This is a search or gibberish, so we don't need to complete

View File

@ -37,16 +37,18 @@ class ExternalEditor(QObject):
_oshandle: The OS level handle to the tmpfile.
_filehandle: The file handle to the tmpfile.
_proc: The QProcess of the editor.
_win_id: The window ID the ExternalEditor is associated with.
"""
editing_finished = pyqtSignal(str)
def __init__(self, parent=None):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._text = None
self._oshandle = None
self._filename = None
self._proc = None
self._win_id = win_id
def _cleanup(self):
"""Clean up temporary files after the editor closed."""
@ -56,7 +58,8 @@ class ExternalEditor(QObject):
except PermissionError as e:
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.
message.error("Failed to delete tempfile... ({})".format(e))
message.error(self._win_id,
"Failed to delete tempfile... ({})".format(e))
def on_proc_closed(self, exitcode, exitstatus):
"""Write the editor text into the form field and clean up tempfile.
@ -75,8 +78,9 @@ class ExternalEditor(QObject):
if exitcode != 0:
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.
message.error("Editor did quit abnormally (status {})!".format(
exitcode))
message.error(
self._win_id, "Editor did quit abnormally (status "
"{})!".format(exitcode))
return
encoding = config.get('general', 'editor-encoding')
with open(self._filename, 'r', encoding=encoding) as f:
@ -100,7 +104,8 @@ class ExternalEditor(QObject):
}
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.
message.error("Error while calling editor: {}".format(messages[error]))
message.error(self._win_id,
"Error while calling editor: {}".format(messages[error]))
self._cleanup()
def edit(self, text):

View File

@ -24,33 +24,41 @@ from PyQt5.QtCore import pyqtSignal, QObject, QTimer
from qutebrowser.utils import usertypes, log, objreg, utils
def error(message, immediately=False):
def _get_bridge(win_id):
"""Get the correct MessageBridge instance for a window."""
return objreg.get('message-bridge', scope='window', window=win_id)
def error(win_id, message, immediately=False):
"""Convienience function to display an error message in the statusbar.
Args:
See MessageBridge.error.
win_id: The ID of the window which is calling this function.
others: See MessageBridge.error.
"""
objreg.get('message-bridge').error(message, immediately)
_get_bridge(win_id).error(message, immediately)
def info(message, immediately=True):
def info(win_id, message, immediately=True):
"""Convienience function to display an info message in the statusbar.
Args:
See MessageBridge.info.
win_id: The ID of the window which is calling this function.
others: See MessageBridge.info.
"""
objreg.get('message-bridge').info(message, immediately)
_get_bridge(win_id).info(message, immediately)
def set_cmd_text(txt):
def set_cmd_text(win_id, txt):
"""Convienience function to Set the statusbar command line to a text."""
objreg.get('message-bridge').set_cmd_text(txt)
_get_bridge(win_id).set_cmd_text(txt)
def ask(message, mode, default=None):
def ask(win_id, message, mode, default=None):
"""Ask a modular question in the statusbar (blocking).
Args:
win_id: The ID of the window which is calling this function.
message: The message to display to the user.
mode: A PromptMode.
default: The default value to display.
@ -62,24 +70,30 @@ def ask(message, mode, default=None):
q.text = message
q.mode = mode
q.default = default
objreg.get('message-bridge').ask(q, blocking=True)
_get_bridge(win_id).ask(q, blocking=True)
q.deleteLater()
return q.answer
def alert(message):
"""Display an alert which needs to be confirmed."""
def alert(win_id, message):
"""Display an alert which needs to be confirmed.
Args:
win_id: The ID of the window which is calling this function.
message: The message to show.
"""
q = usertypes.Question()
q.text = message
q.mode = usertypes.PromptMode.alert
objreg.get('message-bridge').ask(q, blocking=True)
_get_bridge(win_id).ask(q, blocking=True)
q.deleteLater()
def ask_async(message, mode, handler, default=None):
def ask_async(win_id, message, mode, handler, default=None):
"""Ask an async question in the statusbar.
Args:
win_id: The ID of the window which is calling this function.
message: The message to display to the user.
mode: A PromptMode.
handler: The function to get called with the answer as argument.
@ -87,7 +101,7 @@ def ask_async(message, mode, handler, default=None):
"""
if not isinstance(mode, usertypes.PromptMode):
raise TypeError("Mode {} is no PromptMode member!".format(mode))
bridge = objreg.get('message-bridge')
bridge = _get_bridge(win_id)
q = usertypes.Question(bridge)
q.text = message
q.mode = mode
@ -97,16 +111,17 @@ def ask_async(message, mode, handler, default=None):
bridge.ask(q, blocking=False)
def confirm_async(message, yes_action, no_action=None, default=None):
def confirm_async(win_id, message, yes_action, no_action=None, default=None):
"""Ask a yes/no question to the user and execute the given actions.
Args:
win_id: The ID of the window which is calling this function.
message: The message to display to the user.
yes_action: Callable to be called when the user answered yes.
no_action: Callable to be called when the user answered no.
default: True/False to set a default value, or None.
"""
bridge = objreg.get('message-bridge')
bridge = _get_bridge(win_id)
q = usertypes.Question(bridge)
q.text = message
q.mode = usertypes.PromptMode.yesno

View File

@ -82,30 +82,51 @@ global_registry = ObjectRegistry()
# The object registry of object registries.
meta_registry = ObjectRegistry()
meta_registry['global'] = global_registry
# The window registry.
window_registry = ObjectRegistry()
def _get_registry(scope):
def _get_registry(scope, window):
"""Get the correct registry for a given scope."""
if window is not None and scope is not 'window':
raise TypeError("window is set with scope {}".format(scope))
if scope == 'global':
return global_registry
elif scope == 'tab':
widget = get('tabbed-browser').currentWidget()
app = get('app')
win = app.activeWindow()
tabbed_browser = get('tabbed-browser', scope='window', window=win)
widget = tabbed_browser.currentWidget()
if widget is None:
raise RegistryUnavailableError(scope)
return widget.registry
elif scope == 'window':
if window is None:
raise TypeError("window is None with scope window!")
if window is 'current':
app = get('app')
win = app.activeWindow()
if win is None:
raise RegistryUnavailableError(scope)
else:
try:
win = window_registry[window]
except KeyError:
raise RegistryUnavailableError(scope)
return win.registry
elif scope == 'meta':
return meta_registry
else:
raise ValueError("Invalid scope '{}'!".format(scope))
def get(name, default=_UNSET, scope='global'):
def get(name, default=_UNSET, scope='global', window=None):
"""Helper function to get an object.
Args:
default: A default to return if the object does not exist.
"""
reg = _get_registry(scope)
reg = _get_registry(scope, window)
try:
return reg[name]
except KeyError:
@ -115,7 +136,7 @@ def get(name, default=_UNSET, scope='global'):
raise
def register(name, obj, update=False, scope=None, registry=None):
def register(name, obj, update=False, scope=None, registry=None, window=None):
"""Helper function to register an object.
Args:
@ -131,14 +152,14 @@ def register(name, obj, update=False, scope=None, registry=None):
else:
if scope is None:
scope = 'global'
reg = _get_registry(scope)
reg = _get_registry(scope, window)
if not update and name in reg:
raise KeyError("Object '{}' is already registered ({})!".format(
name, repr(reg[name])))
reg[name] = obj
def delete(name, scope='global'):
def delete(name, scope='global', window=None):
"""Helper function to unregister an object."""
reg = _get_registry(scope)
reg = _get_registry(scope, window)
del reg[name]

View File

@ -25,46 +25,11 @@ import functools
from PyQt5.QtCore import QCoreApplication
from qutebrowser.utils import usertypes, log, objreg
from qutebrowser.commands import runners, cmdexc, cmdutils
from qutebrowser.utils import log, objreg
from qutebrowser.commands import cmdutils
from qutebrowser.config import style
_timers = []
_commandrunner = None
def init():
"""Initialize the global _commandrunner."""
global _commandrunner
_commandrunner = runners.CommandRunner()
@cmdutils.register()
def later(ms: int, *command):
"""Execute a command after some time.
Args:
ms: How many milliseconds to wait.
*command: The command to run, with optional args.
"""
timer = usertypes.Timer(name='later')
timer.setSingleShot(True)
if ms < 0:
raise cmdexc.CommandError("I can't run something in the past!")
try:
timer.setInterval(ms)
except OverflowError:
raise cmdexc.CommandError("Numeric argument is too large for internal "
"int representation.")
_timers.append(timer)
cmdline = ' '.join(command)
timer.timeout.connect(functools.partial(
_commandrunner.run_safely, cmdline))
timer.timeout.connect(lambda: _timers.remove(timer))
timer.start()
@cmdutils.register(debug=True)
def debug_crash(typ: ('exception', 'segfault')='exception'):
"""Crash for debugging purposes.

View File

@ -89,11 +89,12 @@ class CompletionView(QTreeView):
resize_completion = pyqtSignal()
def __init__(self, parent=None):
def __init__(self, win_id, parent=None):
super().__init__(parent)
objreg.register('completion', self)
completer_obj = completer.Completer()
objreg.register('completer', completer_obj)
objreg.register('completion', self, scope='window', window=win_id)
completer_obj = completer.Completer(win_id)
objreg.register('completer', completer_obj, scope='window',
window=win_id)
self.enabled = config.get('completion', 'show')
config.on_change(self.set_enabled, 'completion', 'show')
# FIXME
@ -211,13 +212,13 @@ class CompletionView(QTreeView):
selmod.clearCurrentIndex()
@cmdutils.register(instance='completion', hide=True,
modes=[usertypes.KeyMode.command])
modes=[usertypes.KeyMode.command], scope='window')
def completion_item_prev(self):
"""Select the previous completion item."""
self._next_prev_item(prev=True)
@cmdutils.register(instance='completion', hide=True,
modes=[usertypes.KeyMode.command])
modes=[usertypes.KeyMode.command], scope='window')
def completion_item_next(self):
"""Select the next completion item."""
self._next_prev_item(prev=False)

View File

@ -21,15 +21,27 @@
import binascii
import base64
import itertools
import functools
from PyQt5.QtCore import pyqtSlot, QRect, QPoint, QTimer
from PyQt5.QtCore import pyqtSlot, QRect, QPoint, QTimer, Qt
from PyQt5.QtWidgets import QWidget, QVBoxLayout
from qutebrowser.commands import cmdutils
from qutebrowser.commands import runners, cmdutils
from qutebrowser.config import config
from qutebrowser.utils import message, log, usertypes, qtutils, objreg, utils
from qutebrowser.widgets import tabbedbrowser, completion, downloads
from qutebrowser.widgets.statusbar import bar
from qutebrowser.keyinput import modeman
from qutebrowser.browser import hints
win_id_gen = itertools.count(0)
def create_window():
win_id = next(win_id_gen)
win = MainWindow(win_id)
class MainWindow(QWidget):
@ -44,12 +56,84 @@ class MainWindow(QWidget):
_downloadview: The DownloadView widget.
_tabbed_browser: The TabbedBrowser widget.
_vbox: The main QVBoxLayout.
_commandrunner: The main CommandRunner instance.
"""
def __init__(self, parent=None):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._commandrunner = None
self.win_id = win_id
self.registry = objreg.ObjectRegistry()
objreg.register('window-{}'.format(win_id), self.registry,
scope='meta')
objreg.window_registry[win_id] = self
objreg.register('main-window', self, scope='window', window=win_id)
message_bridge = message.MessageBridge(self)
objreg.register('message-bridge', message_bridge, scope='window',
window=win_id)
self.setWindowTitle('qutebrowser')
if win_id == 0:
self._load_geometry()
else:
self._set_default_geometry()
log.init.debug("Initial mainwindow geometry: {}".format(
self.geometry()))
self._vbox = QVBoxLayout(self)
self._vbox.setContentsMargins(0, 0, 0, 0)
self._vbox.setSpacing(0)
self._downloadview = downloads.DownloadView()
self._vbox.addWidget(self._downloadview)
self._downloadview.show()
self._tabbed_browser = tabbedbrowser.TabbedBrowser(win_id)
self._tabbed_browser.title_changed.connect(self.setWindowTitle)
objreg.register('tabbed-browser', self._tabbed_browser, scope='window',
window=win_id)
self._vbox.addWidget(self._tabbed_browser)
self._completion = completion.CompletionView(win_id, self)
self.status = bar.StatusBar(win_id)
self._vbox.addWidget(self.status)
objreg.register('status-command', self.status.cmd, scope='window',
window=win_id)
self._commandrunner = runners.CommandRunner(win_id)
log.init.debug("Initializing search...")
search_runner = runners.SearchRunner(self)
objreg.register('search-runner', search_runner, scope='window',
window=win_id)
log.init.debug("Initializing modes...")
mode_manager = modeman.init(self.win_id, self)
log.init.debug("Initializing eventfilter...")
self.installEventFilter(mode_manager)
self._connect_signals()
QTimer.singleShot(0, functools.partial(
modeman.enter, win_id, usertypes.KeyMode.normal, 'init'))
# When we're here the statusbar might not even really exist yet, so
# resizing will fail. Therefore, we use singleShot QTimers to make sure
# we defer this until everything else is initialized.
QTimer.singleShot(0, self._connect_resize_completion)
config.on_change(self.resize_completion, 'completion', 'height')
config.on_change(self.resize_completion, 'completion', 'shrink')
#self.retranslateUi(MainWindow)
#self.tabWidget.setCurrentIndex(0)
#QtCore.QMetaObject.connectSlotsByName(MainWindow)
def __repr__(self):
return utils.get_repr(self)
def _load_geometry(self):
"""Load the geometry from the state file."""
state_config = objreg.get('state-config')
try:
data = state_config['geometry']['mainwindow']
@ -71,40 +155,6 @@ class MainWindow(QWidget):
log.init.warning("Error while restoring geometry.")
self._set_default_geometry()
log.init.debug("Initial mainwindow geometry: {}".format(
self.geometry()))
self._vbox = QVBoxLayout(self)
self._vbox.setContentsMargins(0, 0, 0, 0)
self._vbox.setSpacing(0)
self._downloadview = downloads.DownloadView()
self._vbox.addWidget(self._downloadview)
self._downloadview.show()
self._tabbed_browser = tabbedbrowser.TabbedBrowser()
self._tabbed_browser.title_changed.connect(self.setWindowTitle)
objreg.register('tabbed-browser', self._tabbed_browser)
self._vbox.addWidget(self._tabbed_browser)
self._completion = completion.CompletionView(self)
self.status = bar.StatusBar()
self._vbox.addWidget(self.status)
# When we're here the statusbar might not even really exist yet, so
# resizing will fail. Therefore, we use singleShot QTimers to make sure
# we defer this until everything else is initialized.
QTimer.singleShot(0, self._connect_resize_completion)
config.on_change(self.resize_completion, 'completion', 'height')
config.on_change(self.resize_completion, 'completion', 'shrink')
#self.retranslateUi(MainWindow)
#self.tabWidget.setCurrentIndex(0)
#QtCore.QMetaObject.connectSlotsByName(MainWindow)
def __repr__(self):
return utils.get_repr(self)
def _connect_resize_completion(self):
"""Connect the resize_completion signal and resize it once."""
self._completion.resize_completion.connect(self.resize_completion)
@ -114,6 +164,94 @@ class MainWindow(QWidget):
"""Set some sensible default geometry."""
self.setGeometry(QRect(50, 50, 800, 600))
def _get_object(self, name):
"""Get an object for this window in the object registry."""
return objreg.get(name, scope='window', window=self.win_id)
def _connect_signals(self):
"""Connect all mainwindow signals."""
app = objreg.get('app')
download_manager = objreg.get('download-manager')
key_config = objreg.get('key-config')
config_obj = objreg.get('config')
status = self._get_object('statusbar')
keyparsers = self._get_object('keyparsers')
completion = self._get_object('completion')
tabs = self._get_object('tabbed-browser')
cmd = self._get_object('status-command')
completer = self._get_object('completer')
search_runner = self._get_object('search-runner')
message_bridge = self._get_object('message-bridge')
mode_manager = self._get_object('mode-manager')
prompter = self._get_object('prompter')
# misc
self._tabbed_browser.quit.connect(app.shutdown)
mode_manager.entered.connect(hints.on_mode_entered)
# status bar
mode_manager.entered.connect(status.on_mode_entered)
mode_manager.left.connect(status.on_mode_left)
mode_manager.left.connect(cmd.on_mode_left)
mode_manager.left.connect(prompter.on_mode_left)
# commands
keyparsers[usertypes.KeyMode.normal].keystring_updated.connect(
status.keystring.setText)
cmd.got_cmd.connect(self._commandrunner.run_safely)
cmd.got_search.connect(search_runner.search)
cmd.got_search_rev.connect(search_runner.search_rev)
cmd.returnPressed.connect(tabs.setFocus)
search_runner.do_search.connect(tabs.search)
tabs.got_cmd.connect(self._commandrunner.run_safely)
# config
for obj in keyparsers.values():
key_config.changed.connect(obj.on_keyconfig_changed)
# messages
message_bridge.s_error.connect(status.disp_error)
message_bridge.s_info.connect(status.disp_temp_text)
message_bridge.s_set_text.connect(status.set_text)
message_bridge.s_maybe_reset_text.connect(status.txt.maybe_reset_text)
message_bridge.s_set_cmd_text.connect(cmd.set_cmd_text)
message_bridge.s_question.connect(prompter.ask_question,
Qt.DirectConnection)
# statusbar
# FIXME some of these probably only should be triggered on mainframe
# loadStarted.
tabs.current_tab_changed.connect(status.prog.on_tab_changed)
tabs.cur_progress.connect(status.prog.setValue)
tabs.cur_load_finished.connect(status.prog.hide)
tabs.cur_load_started.connect(status.prog.on_load_started)
tabs.current_tab_changed.connect(status.percentage.on_tab_changed)
tabs.cur_scroll_perc_changed.connect(status.percentage.set_perc)
tabs.current_tab_changed.connect(status.txt.on_tab_changed)
tabs.cur_statusbar_message.connect(status.txt.on_statusbar_message)
tabs.cur_load_started.connect(status.txt.on_load_started)
tabs.current_tab_changed.connect(status.url.on_tab_changed)
tabs.cur_url_text_changed.connect(status.url.set_url)
tabs.cur_link_hovered.connect(status.url.set_hover_url)
tabs.cur_load_status_changed.connect(status.url.on_load_status_changed)
# command input / completion
mode_manager.left.connect(tabs.on_mode_left)
cmd.clear_completion_selection.connect(
completion.on_clear_completion_selection)
cmd.hide_completion.connect(completion.hide)
cmd.update_completion.connect(completer.on_update_completion)
completer.change_completed_part.connect(cmd.on_change_completed_part)
# downloads
tabs.start_download.connect(download_manager.fetch)
@pyqtSlot()
def resize_completion(self):
"""Adjust completion according to config."""
@ -143,9 +281,9 @@ class MainWindow(QWidget):
if rect.isValid():
self._completion.setGeometry(rect)
@cmdutils.register(instance='main-window', name=['quit', 'q'])
@cmdutils.register(instance='main-window', scope='window')
def close(self):
"""Quit qutebrowser.
"""Close the current window.
//
@ -175,9 +313,13 @@ class MainWindow(QWidget):
else:
text = "Close {} {}?".format(
count, "tab" if count == 1 else "tabs")
confirmed = message.ask(text, usertypes.PromptMode.yesno,
default=True)
confirmed = message.ask(self.win_id, text,
usertypes.PromptMode.yesno, default=True)
if confirmed:
e.accept()
else:
e.ignore()
mode_manager = objreg.get('mode-manager', scope='window',
window=self.win_id)
log.destroy.debug("Removing eventfilter...")
self.removeEventFilter(mode_manager)

View File

@ -46,7 +46,7 @@ class StatusBar(QWidget):
percentage: The Percentage widget in the statusbar.
url: The UrlText widget in the statusbar.
prog: The Progress widget in the statusbar.
_cmd: The Command widget in the statusbar.
cmd: The Command widget in the statusbar.
_hbox: The main QHBoxLayout.
_stack: The QStackedLayout with cmd/txt widgets.
_text_queue: A deque of (error, text) tuples to be displayed.
@ -57,6 +57,7 @@ class StatusBar(QWidget):
the command widget.
_previous_widget: A PreviousWidget member - the widget which was
displayed when an error interrupted it.
_win_id: The window ID the statusbar is associated with.
Class attributes:
_error: If there currently is an error, accessed through the error
@ -113,14 +114,16 @@ class StatusBar(QWidget):
}
"""
def __init__(self, parent=None):
def __init__(self, win_id, parent=None):
super().__init__(parent)
objreg.register('statusbar', self, scope='window', window=win_id)
self.setObjectName(self.__class__.__name__)
self.setAttribute(Qt.WA_StyledBackground)
style.set_register_stylesheet(self)
self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
self._win_id = win_id
self._option = None
self._last_text_time = None
@ -132,9 +135,8 @@ class StatusBar(QWidget):
self._hbox.addLayout(self._stack)
self._stack.setContentsMargins(0, 0, 0, 0)
self._cmd = command.Command()
objreg.register('status-command', self._cmd)
self._stack.addWidget(self._cmd)
self.cmd = command.Command(win_id)
self._stack.addWidget(self.cmd)
self.txt = textwidget.Text()
self._stack.addWidget(self.txt)
@ -145,12 +147,12 @@ class StatusBar(QWidget):
self.set_pop_timer_interval()
config.on_change(self.set_pop_timer_interval, 'ui', 'message-timeout')
self.prompt = prompt.Prompt()
self.prompt = prompt.Prompt(win_id)
self._stack.addWidget(self.prompt)
self._previous_widget = PreviousWidget.none
self._cmd.show_cmd.connect(self._show_cmd_widget)
self._cmd.hide_cmd.connect(self._hide_cmd_widget)
self.cmd.show_cmd.connect(self._show_cmd_widget)
self.cmd.hide_cmd.connect(self._hide_cmd_widget)
self._hide_cmd_widget()
self.prompt.show_prompt.connect(self._show_prompt_widget)
self.prompt.hide_prompt.connect(self._hide_prompt_widget)
@ -262,7 +264,7 @@ class StatusBar(QWidget):
if self._text_pop_timer.isActive():
self._timer_was_active = True
self._text_pop_timer.stop()
self._stack.setCurrentWidget(self._cmd)
self._stack.setCurrentWidget(self.cmd)
def _hide_cmd_widget(self):
"""Show temporary text instead of command widget."""
@ -378,7 +380,9 @@ class StatusBar(QWidget):
@pyqtSlot(usertypes.KeyMode)
def on_mode_entered(self, mode):
"""Mark certain modes in the commandline."""
if mode in objreg.get('mode-manager').passthrough:
mode_manager = objreg.get('mode-manager', scope='window',
window=self._win_id)
if mode in mode_manager.passthrough:
text = "-- {} MODE --".format(mode.name.upper())
self.txt.set_text(self.txt.Text.normal, text)
if mode == usertypes.KeyMode.insert:
@ -387,7 +391,9 @@ class StatusBar(QWidget):
@pyqtSlot(usertypes.KeyMode)
def on_mode_left(self, mode):
"""Clear marked mode."""
if mode in objreg.get('mode-manager').passthrough:
mode_manager = objreg.get('mode-manager', scope='window',
window=self._win_id)
if mode in mode_manager.passthrough:
self.txt.set_text(self.txt.Text.normal, '')
if mode == usertypes.KeyMode.insert:
self._set_insert_active(False)

View File

@ -35,6 +35,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
Attributes:
_cursor_part: The part the cursor is currently over.
_win_id: The window ID this widget is associated with.
Signals:
got_cmd: Emitted when a command is triggered by the user.
@ -68,9 +69,10 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
# See http://www.saltycrane.com/blog/2008/01/how-to-capture-tab-key-press-event-with/
# for a possible fix.
def __init__(self, parent=None):
def __init__(self, win_id, parent=None):
misc.CommandLineEdit.__init__(self, parent)
misc.MinimalLineEditMixin.__init__(self)
self._win_id = win_id
self._cursor_part = 0
self.history.history = objreg.get('command-history').data
self._empty_item_idx = None
@ -100,7 +102,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
# Text is only whitespace so we treat this as a single element with
# the whitespace.
return [text]
runner = runners.CommandRunner()
runner = runners.CommandRunner(self._win_id)
parts = runner.parse(text, fallback=True, alias_no_args=False)
if self._empty_item_idx is not None:
log.completion.debug("Empty element queued at {}, "
@ -158,7 +160,8 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
self.setFocus()
self.show_cmd.emit()
@cmdutils.register(instance='status-command', name='set-cmd-text')
@cmdutils.register(instance='status-command', name='set-cmd-text',
scope='window')
def set_cmd_text_command(self, text):
"""Preset the statusbar to some text.
@ -170,7 +173,9 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
Args:
text: The commandline to set.
"""
url = objreg.get('tabbed-browser').current_url().toString(
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=self._win_id)
url = tabbed_browser.current_url().toString(
QUrl.FullyEncoded | QUrl.RemovePassword)
# FIXME we currently replace the URL in any place in the arguments,
# rather than just replacing it if it is a dedicated argument. We could
@ -214,7 +219,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
self.show_cmd.emit()
@cmdutils.register(instance='status-command', hide=True,
modes=[usertypes.KeyMode.command])
modes=[usertypes.KeyMode.command], scope='window')
def command_history_prev(self):
"""Go back in the commandline history."""
try:
@ -229,7 +234,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
self.set_cmd_text(item)
@cmdutils.register(instance='status-command', hide=True,
modes=[usertypes.KeyMode.command])
modes=[usertypes.KeyMode.command], scope='window')
def command_history_next(self):
"""Go forward in the commandline history."""
if not self.history.is_browsing():
@ -242,7 +247,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
self.set_cmd_text(item)
@cmdutils.register(instance='status-command', hide=True,
modes=[usertypes.KeyMode.command])
modes=[usertypes.KeyMode.command], scope='window')
def command_accept(self):
"""Execute the command currently in the commandline.
@ -258,7 +263,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
}
text = self.text()
self.history.append(text)
modeman.leave(usertypes.KeyMode.command, 'cmd accept')
modeman.leave(self._win_id, usertypes.KeyMode.command, 'cmd accept')
if text[0] in signals:
signals[text[0]].emit(text.lstrip(text[0]))
@ -295,7 +300,8 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
def focusInEvent(self, e):
"""Extend focusInEvent to enter command mode."""
modeman.maybe_enter(usertypes.KeyMode.command, 'cmd focus')
modeman.maybe_enter(self._win_id, usertypes.KeyMode.command,
'cmd focus')
super().focusInEvent(e)
def setText(self, text):

View File

@ -53,7 +53,7 @@ class Prompt(QWidget):
show_prompt = pyqtSignal()
hide_prompt = pyqtSignal()
def __init__(self, parent=None):
def __init__(self, win_id, parent=None):
super().__init__(parent)
objreg.register('prompt', self)
self._hbox = QHBoxLayout(self)
@ -66,8 +66,9 @@ class Prompt(QWidget):
self.lineedit = PromptLineEdit()
self._hbox.addWidget(self.lineedit)
prompter_obj = prompter.Prompter()
objreg.register('prompter', prompter_obj)
prompter_obj = prompter.Prompter(win_id)
objreg.register('prompter', prompter_obj, scope='window',
window=win_id)
def __repr__(self):
return utils.get_repr(self)

View File

@ -60,13 +60,15 @@ class Prompter:
_question: A Question object with the question to be asked to the user.
_loops: A list of local EventLoops to spin in when blocking.
_queue: A deque of waiting questions.
_win_id: The window ID this object is associated with.
"""
def __init__(self):
def __init__(self, win_id):
self._question = None
self._loops = []
self._queue = collections.deque()
self._busy = False
self._win_id = win_id
def __repr__(self):
return utils.get_repr(self, loops=len(self._loops),
@ -189,7 +191,7 @@ class Prompter:
if self._question.answer is None and not self._question.is_aborted:
self._question.cancel()
@cmdutils.register(instance='prompter', hide=True,
@cmdutils.register(instance='prompter', hide=True, scope='window',
modes=[usertypes.KeyMode.prompt,
usertypes.KeyMode.yesno])
def prompt_accept(self):
@ -212,27 +214,31 @@ class Prompter:
# User just entered a password
password = prompt.lineedit.text()
self._question.answer = (self._question.user, password)
modeman.leave(usertypes.KeyMode.prompt, 'prompt accept')
modeman.leave(self._win_id, usertypes.KeyMode.prompt,
'prompt accept')
self._question.done()
elif self._question.mode == usertypes.PromptMode.text:
# User just entered text.
self._question.answer = prompt.lineedit.text()
modeman.leave(usertypes.KeyMode.prompt, 'prompt accept')
modeman.leave(self._win_id, usertypes.KeyMode.prompt,
'prompt accept')
self._question.done()
elif self._question.mode == usertypes.PromptMode.yesno:
# User wants to accept the default of a yes/no question.
self._question.answer = self._question.default
modeman.leave(usertypes.KeyMode.yesno, 'yesno accept')
modeman.leave(self._win_id, usertypes.KeyMode.yesno,
'yesno accept')
self._question.done()
elif self._question.mode == usertypes.PromptMode.alert:
# User acknowledged an alert
self._question.answer = None
modeman.leave(usertypes.KeyMode.prompt, 'alert accept')
modeman.leave(self._win_id, usertypes.KeyMode.prompt,
'alert accept')
self._question.done()
else:
raise ValueError("Invalid question mode!")
@cmdutils.register(instance='prompter', hide=True,
@cmdutils.register(instance='prompter', hide=True, scope='window',
modes=[usertypes.KeyMode.yesno])
def prompt_yes(self):
"""Answer yes to a yes/no prompt."""
@ -240,10 +246,10 @@ class Prompter:
# We just ignore this if we don't have a yes/no question.
return
self._question.answer = True
modeman.leave(usertypes.KeyMode.yesno, 'yesno accept')
modeman.leave(self._win_id, usertypes.KeyMode.yesno, 'yesno accept')
self._question.done()
@cmdutils.register(instance='prompter', hide=True,
@cmdutils.register(instance='prompter', hide=True, scope='window',
modes=[usertypes.KeyMode.yesno])
def prompt_no(self):
"""Answer no to a yes/no prompt."""
@ -251,7 +257,7 @@ class Prompter:
# We just ignore this if we don't have a yes/no question.
return
self._question.answer = False
modeman.leave(usertypes.KeyMode.yesno, 'prompt accept')
modeman.leave(self._win_id, usertypes.KeyMode.yesno, 'prompt accept')
self._question.done()
@pyqtSlot(usertypes.Question, bool)
@ -284,10 +290,11 @@ class Prompter:
self._question = question
mode = self._display_question()
question.aborted.connect(lambda: modeman.maybe_leave(mode, 'aborted'))
question.aborted.connect(
lambda: modeman.maybe_leave(self._win_id, mode, 'aborted'))
mode_manager = objreg.get('mode-manager')
try:
modeman.enter(mode, 'question asked')
modeman.enter(self._win_id, mode, 'question asked')
except modeman.ModeLockedError:
if mode_manager.mode() != usertypes.KeyMode.prompt:
question.abort()

View File

@ -51,6 +51,7 @@ class TabbedBrowser(tabwidget.TabWidget):
emitted if the signal occured in the current tab.
Attributes:
_win_id: The window ID this tabbedbrowser is associated with.
_tabs: A list of open tabs.
_filter: A SignalFilter instance.
_now_focused: The tab which is focused now.
@ -97,8 +98,9 @@ class TabbedBrowser(tabwidget.TabWidget):
current_tab_changed = pyqtSignal(webview.WebView)
title_changed = pyqtSignal(str)
def __init__(self, parent=None):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._win_id = win_id
self._tab_insert_idx_left = 0
self._tab_insert_idx_right = -1
self.tabCloseRequested.connect(self.on_tab_close_requested)
@ -107,9 +109,10 @@ class TabbedBrowser(tabwidget.TabWidget):
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self._tabs = []
self._undo_stack = []
self._filter = signalfilter.SignalFilter(self)
dispatcher = commands.CommandDispatcher()
objreg.register('command-dispatcher', dispatcher)
self._filter = signalfilter.SignalFilter(win_id, self)
dispatcher = commands.CommandDispatcher(win_id)
objreg.register('command-dispatcher', dispatcher, scope='window',
window=win_id)
self._now_focused = None
# FIXME adjust this to font size
self.setIconSize(QSize(12, 12))
@ -315,7 +318,7 @@ class TabbedBrowser(tabwidget.TabWidget):
if url is not None:
qtutils.ensure_valid(url)
log.webview.debug("Creating new tab with URL {}".format(url))
tab = webview.WebView(self)
tab = webview.WebView(self._win_id, self)
self._connect_tab_signals(tab)
self._tabs.append(tab)
if explicit:
@ -366,19 +369,19 @@ class TabbedBrowser(tabwidget.TabWidget):
old_scroll_pos = widget.scroll_pos
found = widget.findText(text, flags)
if not found and not flags & QWebPage.HighlightAllOccurrences and text:
message.error("Text '{}' not found on page!".format(text),
immediately=True)
message.error(self._win_id, "Text '{}' not found on "
"page!".format(text), immediately=True)
else:
backward = int(flags) & QWebPage.FindBackward
def check_scroll_pos():
"""Check if the scroll position got smaller and show info."""
if not backward and widget.scroll_pos < old_scroll_pos:
message.info("Search hit BOTTOM, continuing at TOP",
immediately=True)
message.info(self._win_id, "Search hit BOTTOM, continuing "
"at TOP", immediately=True)
elif backward and widget.scroll_pos > old_scroll_pos:
message.info("Search hit TOP, continuing at BOTTOM",
immediately=True)
message.info(self._win_id, "Search hit TOP, continuing at "
"BOTTOM", immediately=True)
# We first want QWebPage to refresh.
QTimer.singleShot(0, check_scroll_pos)
@ -411,8 +414,10 @@ class TabbedBrowser(tabwidget.TabWidget):
@pyqtSlot()
def on_cur_load_started(self):
"""Leave insert/hint mode when loading started."""
modeman.maybe_leave(usertypes.KeyMode.insert, 'load started')
modeman.maybe_leave(usertypes.KeyMode.hint, 'load started')
modeman.maybe_leave(self._win_id, usertypes.KeyMode.insert,
'load started')
modeman.maybe_leave(self._win_id, usertypes.KeyMode.hint,
'load started')
@pyqtSlot(webview.WebView, str)
def on_title_changed(self, tab, text):
@ -495,7 +500,8 @@ class TabbedBrowser(tabwidget.TabWidget):
"""Set last-focused-tab and leave hinting mode when focus changed."""
tab = self.widget(idx)
tab.setFocus()
modeman.maybe_leave(usertypes.KeyMode.hint, 'tab changed')
modeman.maybe_leave(self._win_id, usertypes.KeyMode.hint,
'tab changed')
if self._now_focused is not None:
objreg.register('last-focused-tab', self._now_focused, update=True)
self._now_focused = tab

View File

@ -64,6 +64,7 @@ class WebView(QWebView):
_force_open_target: Override for open_target.
_check_insertmode: If True, in mouseReleaseEvent we should check if we
need to enter/leave insert mode.
_win_id: The window ID of the view.
Signals:
scroll_pos_changed: Scroll percentage of current tab changed.
@ -79,8 +80,9 @@ class WebView(QWebView):
load_status_changed = pyqtSignal(str)
url_text_changed = pyqtSignal(str)
def __init__(self, parent=None):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._win_id = win_id
self.load_status = LoadStatus.none
self._check_insertmode = False
self.inspector = None
@ -100,9 +102,9 @@ class WebView(QWebView):
self.registry = objreg.ObjectRegistry()
self.tab_id = next(tab_id_gen)
objreg.register('webview', self, registry=self.registry)
page = webpage.BrowserPage(self)
page = webpage.BrowserPage(win_id, self)
self.setPage(page)
hintmanager = hints.HintManager(self)
hintmanager = hints.HintManager(win_id, self)
hintmanager.mouse_event.connect(self.on_mouse_event)
hintmanager.set_open_target.connect(self.set_force_open_target)
objreg.register('hintmanager', hintmanager, registry=self.registry)
@ -153,13 +155,13 @@ class WebView(QWebView):
try:
self.go_back()
except cmdexc.CommandError as ex:
message.error(ex, immediately=True)
message.error(self._win_id, ex, immediately=True)
elif e.button() == Qt.XButton2:
# Forward button on mice which have it.
try:
self.go_forward()
except cmdexc.CommandError as ex:
message.error(ex, immediately=True)
message.error(self._win_id, ex, immediately=True)
def _mousepress_insertmode(self, e):
"""Switch to insert mode when an editable element was clicked.
@ -200,11 +202,13 @@ class WebView(QWebView):
if ((hitresult.isContentEditable() and elem.is_writable()) or
elem.is_editable()):
log.mouse.debug("Clicked editable element!")
modeman.maybe_enter(usertypes.KeyMode.insert, 'click')
modeman.maybe_enter(self._win_id, usertypes.KeyMode.insert,
'click')
else:
log.mouse.debug("Clicked non-editable element!")
if config.get('input', 'auto-leave-insert-mode'):
modeman.maybe_leave(usertypes.KeyMode.insert, 'click')
modeman.maybe_leave(self._win_id, usertypes.KeyMode.insert,
'click')
def mouserelease_insertmode(self):
"""If we have an insertmode check scheduled, handle it."""
@ -218,11 +222,13 @@ class WebView(QWebView):
return
if elem.is_editable():
log.mouse.debug("Clicked editable element (delayed)!")
modeman.maybe_enter(usertypes.KeyMode.insert, 'click-delayed')
modeman.maybe_enter(self._win_id, usertypes.KeyMode.insert,
'click-delayed')
else:
log.mouse.debug("Clicked non-editable element (delayed)!")
if config.get('input', 'auto-leave-insert-mode'):
modeman.maybe_leave(usertypes.KeyMode.insert, 'click-delayed')
modeman.maybe_leave(self._win_id, usertypes.KeyMode.insert,
'click-delayed')
def _mousepress_opentarget(self, e):
"""Set the open target when something was clicked.
@ -293,7 +299,7 @@ class WebView(QWebView):
if perc < 0:
raise cmdexc.CommandError("Can't zoom {}%!".format(perc))
self.setZoomFactor(float(perc) / 100)
message.info("Zoom level: {}%".format(perc))
message.info(self._win_id, "Zoom level: {}%".format(perc))
def zoom(self, offset):
"""Increase/Decrease the zoom level.
@ -371,7 +377,8 @@ class WebView(QWebView):
return
log.modes.debug("focus element: {}".format(repr(elem)))
if elem.is_editable():
modeman.maybe_enter(usertypes.KeyMode.insert, 'load finished')
modeman.maybe_enter(self._win_id, usertypes.KeyMode.insert,
'load finished')
@pyqtSlot(str)
def set_force_open_target(self, target):