First attempt at multi-window support.
This commit is contained in:
parent
64a119afb2
commit
fb6cb62f93
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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))
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -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 "
|
||||
|
@ -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 = ''
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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'}})
|
||||
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user