2014-06-19 09:04:37 +02:00
|
|
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et
|
|
|
|
|
2014-04-17 09:44:26 +02:00
|
|
|
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
|
|
|
#
|
|
|
|
# This file is part of qutebrowser.
|
|
|
|
#
|
|
|
|
# qutebrowser is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# qutebrowser is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2014-05-27 15:56:44 +02:00
|
|
|
"""Command dispatcher for TabbedBrowser."""
|
2014-04-17 09:44:26 +02:00
|
|
|
|
2014-04-29 18:00:22 +02:00
|
|
|
import os
|
2014-05-03 14:25:22 +02:00
|
|
|
import subprocess
|
2014-04-29 18:00:22 +02:00
|
|
|
from functools import partial
|
|
|
|
|
2014-04-22 12:10:27 +02:00
|
|
|
from PyQt5.QtWidgets import QApplication
|
2014-06-20 16:33:01 +02:00
|
|
|
from PyQt5.QtCore import Qt, QUrl
|
2014-04-17 09:44:26 +02:00
|
|
|
from PyQt5.QtGui import QClipboard
|
2014-05-06 18:31:08 +02:00
|
|
|
from PyQt5.QtPrintSupport import QPrintDialog, QPrintPreviewDialog
|
2014-05-26 15:35:05 +02:00
|
|
|
from PyQt5.QtWebKitWidgets import QWebInspector
|
2014-04-17 09:44:26 +02:00
|
|
|
|
|
|
|
import qutebrowser.commands.utils as cmdutils
|
2014-04-29 18:00:22 +02:00
|
|
|
import qutebrowser.config.config as config
|
2014-05-05 07:45:36 +02:00
|
|
|
import qutebrowser.browser.hints as hints
|
2014-05-14 23:29:18 +02:00
|
|
|
import qutebrowser.utils.message as message
|
|
|
|
import qutebrowser.utils.webelem as webelem
|
2014-05-22 16:44:47 +02:00
|
|
|
import qutebrowser.browser.quickmarks as quickmarks
|
2014-05-23 16:11:55 +02:00
|
|
|
import qutebrowser.utils.log as log
|
2014-06-20 16:33:01 +02:00
|
|
|
import qutebrowser.utils.url as urlutils
|
2014-06-16 10:21:03 +02:00
|
|
|
from qutebrowser.utils.misc import (check_overflow, shell_escape,
|
|
|
|
check_print_compat)
|
2014-05-21 15:47:21 +02:00
|
|
|
from qutebrowser.utils.editor import ExternalEditor
|
2014-05-14 18:00:40 +02:00
|
|
|
from qutebrowser.commands.exceptions import CommandError
|
2014-05-21 19:53:58 +02:00
|
|
|
from qutebrowser.commands.userscripts import UserscriptRunner
|
2014-04-17 09:44:26 +02:00
|
|
|
|
|
|
|
|
2014-05-27 15:12:43 +02:00
|
|
|
class CommandDispatcher:
|
2014-04-17 09:44:26 +02:00
|
|
|
|
|
|
|
"""Command dispatcher for TabbedBrowser.
|
|
|
|
|
|
|
|
Contains all commands which are related to the current tab.
|
|
|
|
|
|
|
|
We can't simply add these commands to BrowserTab directly and use
|
2014-05-17 22:38:07 +02:00
|
|
|
currentWidget() for TabbedBrowser.cmd because at the time
|
2014-04-17 09:44:26 +02:00
|
|
|
cmdutils.register() decorators are run, currentWidget() will return None.
|
|
|
|
|
|
|
|
Attributes:
|
2014-04-17 17:44:27 +02:00
|
|
|
_tabs: The TabbedBrowser object.
|
2014-05-21 15:37:18 +02:00
|
|
|
_editor: The ExternalEditor object.
|
2014-05-21 19:53:58 +02:00
|
|
|
_userscript_runners: A list of userscript runners.
|
2014-04-17 09:44:26 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, parent):
|
|
|
|
"""Constructor.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
parent: The TabbedBrowser for this dispatcher.
|
|
|
|
"""
|
2014-05-21 19:53:58 +02:00
|
|
|
self._userscript_runners = []
|
2014-04-17 17:44:27 +02:00
|
|
|
self._tabs = parent
|
2014-05-21 15:37:18 +02:00
|
|
|
self._editor = None
|
2014-04-17 09:44:26 +02:00
|
|
|
|
|
|
|
def _scroll_percent(self, perc=None, count=None, orientation=None):
|
|
|
|
"""Inner logic for scroll_percent_(x|y).
|
|
|
|
|
|
|
|
Args:
|
|
|
|
perc: How many percent to scroll, or None
|
|
|
|
count: How many percent to scroll, or None
|
|
|
|
orientation: Qt.Horizontal or Qt.Vertical
|
|
|
|
"""
|
|
|
|
if perc is None and count is None:
|
|
|
|
perc = 100
|
|
|
|
elif perc is None:
|
|
|
|
perc = int(count)
|
|
|
|
else:
|
|
|
|
perc = float(perc)
|
2014-05-17 22:38:07 +02:00
|
|
|
perc = check_overflow(perc, 'int', fatal=False)
|
2014-05-27 16:04:45 +02:00
|
|
|
frame = self._tabs.currentWidget().page().currentFrame()
|
2014-05-19 09:50:56 +02:00
|
|
|
m = frame.scrollBarMaximum(orientation)
|
|
|
|
if m == 0:
|
|
|
|
return
|
|
|
|
frame.setScrollBarValue(orientation, int(m * perc / 100))
|
2014-04-17 09:44:26 +02:00
|
|
|
|
2014-05-01 16:35:26 +02:00
|
|
|
def _prevnext(self, prev, newtab):
|
|
|
|
"""Inner logic for {tab,}{prev,next}page."""
|
|
|
|
widget = self._tabs.currentWidget()
|
2014-05-27 16:04:45 +02:00
|
|
|
frame = widget.page().currentFrame()
|
2014-05-01 16:35:26 +02:00
|
|
|
if frame is None:
|
2014-05-14 18:00:40 +02:00
|
|
|
raise CommandError("No frame focused!")
|
2014-05-01 16:40:14 +02:00
|
|
|
widget.hintmanager.follow_prevnext(frame, widget.url(), prev, newtab)
|
2014-05-01 16:35:26 +02:00
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
def _tab_move_absolute(self, idx):
|
|
|
|
"""Get an index for moving a tab absolutely.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
idx: The index to get, as passed as count.
|
|
|
|
"""
|
|
|
|
if idx is None:
|
|
|
|
return 0
|
|
|
|
elif idx == 0:
|
|
|
|
return self._tabs.count() - 1
|
|
|
|
else:
|
|
|
|
return idx - 1
|
|
|
|
|
|
|
|
def _tab_move_relative(self, direction, delta):
|
|
|
|
"""Get an index for moving a tab relatively.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
direction: + or - for relative moving, None for absolute.
|
|
|
|
delta: Delta to the current tab.
|
|
|
|
"""
|
|
|
|
if delta is None:
|
|
|
|
raise ValueError
|
|
|
|
if direction == '-':
|
|
|
|
return self._tabs.currentIndex() - delta
|
|
|
|
elif direction == '+':
|
|
|
|
return self._tabs.currentIndex() + delta
|
|
|
|
|
|
|
|
def _editor_cleanup(self, oshandle, filename):
|
|
|
|
"""Clean up temporary file when the editor was closed."""
|
|
|
|
os.close(oshandle)
|
|
|
|
try:
|
|
|
|
os.remove(filename)
|
|
|
|
except PermissionError:
|
|
|
|
raise CommandError("Failed to delete tempfile...")
|
|
|
|
|
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
|
|
def tab_close(self, count=None):
|
|
|
|
"""Close the current/[count]th tab.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
count: The tab index to close, or None
|
|
|
|
|
|
|
|
Emit:
|
|
|
|
quit: If last tab was closed and last-close in config is set to
|
|
|
|
quit.
|
|
|
|
"""
|
|
|
|
tab = self._tabs.cntwidget(count)
|
|
|
|
if tab is None:
|
|
|
|
return
|
2014-05-18 08:18:20 +02:00
|
|
|
self._tabs.close_tab(tab)
|
2014-05-17 22:38:07 +02:00
|
|
|
|
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd', name='open',
|
2014-05-02 10:20:08 +02:00
|
|
|
split=False)
|
2014-06-20 16:33:01 +02:00
|
|
|
def openurl(self, urlstr, count=None):
|
2014-05-15 16:27:34 +02:00
|
|
|
"""Open a URL in the current/[count]th tab.
|
2014-04-17 09:44:26 +02:00
|
|
|
|
|
|
|
Args:
|
2014-06-20 16:33:01 +02:00
|
|
|
urlstr: The URL to open, as string.
|
2014-04-17 09:44:26 +02:00
|
|
|
count: The tab index to open the URL in, or None.
|
|
|
|
"""
|
2014-04-17 17:44:27 +02:00
|
|
|
tab = self._tabs.cntwidget(count)
|
2014-06-20 16:33:01 +02:00
|
|
|
try:
|
|
|
|
url = urlutils.fuzzy_url(urlstr)
|
2014-06-20 23:57:52 +02:00
|
|
|
except urlutils.FuzzyUrlError as e:
|
2014-06-20 16:33:01 +02:00
|
|
|
raise CommandError(e)
|
2014-04-17 09:44:26 +02:00
|
|
|
if tab is None:
|
|
|
|
if count is None:
|
2014-05-15 16:27:34 +02:00
|
|
|
# We want to open a URL in the current tab, but none exists
|
2014-04-17 09:44:26 +02:00
|
|
|
# yet.
|
2014-04-17 17:44:27 +02:00
|
|
|
self._tabs.tabopen(url)
|
2014-04-17 09:44:26 +02:00
|
|
|
else:
|
|
|
|
# Explicit count with a tab that doesn't exist.
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
tab.openurl(url)
|
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd', name='reload')
|
2014-04-17 09:44:26 +02:00
|
|
|
def reloadpage(self, count=None):
|
|
|
|
"""Reload the current/[count]th tab.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
count: The tab index to reload, or None.
|
|
|
|
"""
|
2014-04-17 17:44:27 +02:00
|
|
|
tab = self._tabs.cntwidget(count)
|
2014-04-17 09:44:26 +02:00
|
|
|
if tab is not None:
|
|
|
|
tab.reload()
|
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
2014-04-17 09:44:26 +02:00
|
|
|
def stop(self, count=None):
|
|
|
|
"""Stop loading in the current/[count]th tab.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
count: The tab index to stop, or None.
|
|
|
|
"""
|
2014-04-17 17:44:27 +02:00
|
|
|
tab = self._tabs.cntwidget(count)
|
2014-04-17 09:44:26 +02:00
|
|
|
if tab is not None:
|
|
|
|
tab.stop()
|
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
2014-05-16 23:01:40 +02:00
|
|
|
def print_preview(self, count=None):
|
2014-04-22 11:16:45 +02:00
|
|
|
"""Preview printing of the current/[count]th tab.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
count: The tab index to print, or None.
|
|
|
|
"""
|
2014-06-16 10:21:03 +02:00
|
|
|
if not check_print_compat():
|
|
|
|
raise CommandError("Printing on Qt < 5.3.0 on Windows is broken, "
|
|
|
|
"please upgrade!")
|
2014-04-22 11:16:45 +02:00
|
|
|
tab = self._tabs.cntwidget(count)
|
|
|
|
if tab is not None:
|
2014-05-09 17:40:19 +02:00
|
|
|
preview = QPrintPreviewDialog()
|
2014-06-17 14:33:15 +02:00
|
|
|
preview.setAttribute(Qt.WA_DeleteOnClose)
|
2014-04-22 11:16:45 +02:00
|
|
|
preview.paintRequested.connect(tab.print)
|
|
|
|
preview.exec_()
|
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd', name='print')
|
2014-04-17 09:44:26 +02:00
|
|
|
def printpage(self, count=None):
|
|
|
|
"""Print the current/[count]th tab.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
count: The tab index to print, or None.
|
|
|
|
"""
|
2014-06-16 10:21:03 +02:00
|
|
|
if not check_print_compat():
|
|
|
|
raise CommandError("Printing on Qt < 5.3.0 on Windows is broken, "
|
|
|
|
"please upgrade!")
|
2014-04-17 17:44:27 +02:00
|
|
|
tab = self._tabs.cntwidget(count)
|
2014-04-17 09:44:26 +02:00
|
|
|
if tab is not None:
|
2014-05-09 17:40:19 +02:00
|
|
|
printdiag = QPrintDialog()
|
2014-06-17 14:33:15 +02:00
|
|
|
printdiag.setAttribute(Qt.WA_DeleteOnClose)
|
2014-04-22 11:16:45 +02:00
|
|
|
printdiag.open(lambda: tab.print(printdiag.printer()))
|
2014-04-17 09:44:26 +02:00
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
2014-04-17 09:44:26 +02:00
|
|
|
def back(self, count=1):
|
|
|
|
"""Go back in the history of the current tab.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
count: How many pages to go back.
|
|
|
|
"""
|
|
|
|
for _ in range(count):
|
2014-05-14 18:00:40 +02:00
|
|
|
self._tabs.currentWidget().go_back()
|
2014-04-17 09:44:26 +02:00
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
2014-04-17 09:44:26 +02:00
|
|
|
def forward(self, count=1):
|
|
|
|
"""Go forward in the history of the current tab.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
count: How many pages to go forward.
|
|
|
|
"""
|
|
|
|
for _ in range(count):
|
2014-05-14 18:00:40 +02:00
|
|
|
self._tabs.currentWidget().go_forward()
|
2014-04-17 09:44:26 +02:00
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
2014-05-05 07:45:36 +02:00
|
|
|
def hint(self, groupstr='all', targetstr='normal'):
|
2014-04-19 17:50:11 +02:00
|
|
|
"""Start hinting.
|
|
|
|
|
|
|
|
Args:
|
2014-05-05 07:45:36 +02:00
|
|
|
groupstr: The hinting mode to use.
|
|
|
|
targetstr: Where to open the links.
|
2014-04-19 17:50:11 +02:00
|
|
|
"""
|
2014-04-21 23:44:45 +02:00
|
|
|
widget = self._tabs.currentWidget()
|
2014-05-27 16:04:45 +02:00
|
|
|
frame = widget.page().mainFrame()
|
2014-04-25 13:21:14 +02:00
|
|
|
if frame is None:
|
2014-05-14 18:00:40 +02:00
|
|
|
raise CommandError("No frame focused!")
|
2014-05-05 07:45:36 +02:00
|
|
|
try:
|
2014-05-16 23:01:40 +02:00
|
|
|
group = getattr(webelem.Group, groupstr.replace('-', '_'))
|
2014-05-05 07:45:36 +02:00
|
|
|
except AttributeError:
|
2014-05-14 18:00:40 +02:00
|
|
|
raise CommandError("Unknown hinting group {}!".format(groupstr))
|
2014-05-05 07:45:36 +02:00
|
|
|
try:
|
2014-05-16 23:01:40 +02:00
|
|
|
target = getattr(hints.Target, targetstr.replace('-', '_'))
|
2014-05-05 07:45:36 +02:00
|
|
|
except AttributeError:
|
2014-05-14 18:00:40 +02:00
|
|
|
raise CommandError("Unknown hinting target {}!".format(targetstr))
|
2014-05-06 07:11:20 +02:00
|
|
|
widget.hintmanager.start(frame, widget.url(), group, target)
|
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
|
2014-05-06 07:11:20 +02:00
|
|
|
def follow_hint(self):
|
|
|
|
"""Follow the currently selected hint."""
|
|
|
|
self._tabs.currentWidget().hintmanager.follow_hint()
|
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
2014-05-16 23:01:40 +02:00
|
|
|
def prev_page(self):
|
2014-05-01 16:35:26 +02:00
|
|
|
"""Open a "previous" link."""
|
|
|
|
self._prevnext(prev=True, newtab=False)
|
2014-05-01 15:27:32 +02:00
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
2014-05-16 23:01:40 +02:00
|
|
|
def next_page(self):
|
2014-05-01 16:35:26 +02:00
|
|
|
"""Open a "next" link."""
|
|
|
|
self._prevnext(prev=False, newtab=False)
|
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
2014-05-16 23:01:40 +02:00
|
|
|
def prev_page_tab(self):
|
2014-05-01 16:35:26 +02:00
|
|
|
"""Open a "previous" link in a new tab."""
|
|
|
|
self._prevnext(prev=True, newtab=True)
|
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
2014-05-16 23:01:40 +02:00
|
|
|
def next_page_tab(self):
|
2014-05-01 16:35:26 +02:00
|
|
|
"""Open a "next" link in a new tab."""
|
|
|
|
self._prevnext(prev=False, newtab=True)
|
2014-05-01 15:27:32 +02:00
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
|
2014-04-17 09:44:26 +02:00
|
|
|
def scroll(self, dx, dy, count=1):
|
|
|
|
"""Scroll the current tab by count * dx/dy.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
dx: How much to scroll in x-direction.
|
|
|
|
dy: How much to scroll in x-direction.
|
|
|
|
count: multiplier
|
|
|
|
"""
|
2014-05-14 17:28:47 +02:00
|
|
|
dx = int(int(count) * float(dx))
|
|
|
|
dy = int(int(count) * float(dy))
|
2014-05-14 23:29:18 +02:00
|
|
|
cmdutils.check_overflow(dx, 'int')
|
|
|
|
cmdutils.check_overflow(dy, 'int')
|
2014-05-27 16:04:45 +02:00
|
|
|
self._tabs.currentWidget().page().currentFrame().scroll(dx, dy)
|
2014-04-17 09:44:26 +02:00
|
|
|
|
2014-05-17 23:22:10 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
|
|
|
|
def scroll_perc_x(self, perc=None, count=None):
|
2014-04-17 09:44:26 +02:00
|
|
|
"""Scroll the current tab to a specific percent of the page (horiz).
|
|
|
|
|
|
|
|
Args:
|
|
|
|
perc: Percentage to scroll.
|
|
|
|
count: Percentage to scroll.
|
|
|
|
"""
|
|
|
|
self._scroll_percent(perc, count, Qt.Horizontal)
|
|
|
|
|
2014-05-17 23:22:10 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
|
|
|
|
def scroll_perc_y(self, perc=None, count=None):
|
2014-04-17 09:44:26 +02:00
|
|
|
"""Scroll the current tab to a specific percent of the page (vert).
|
|
|
|
|
|
|
|
Args:
|
|
|
|
perc: Percentage to scroll.
|
|
|
|
count: Percentage to scroll.
|
|
|
|
"""
|
|
|
|
self._scroll_percent(perc, count, Qt.Vertical)
|
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
|
2014-04-17 09:44:26 +02:00
|
|
|
def scroll_page(self, mx, my, count=1):
|
|
|
|
"""Scroll the frame page-wise.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
mx: How many pages to scroll to the right.
|
|
|
|
my: How many pages to scroll down.
|
|
|
|
count: multiplier
|
|
|
|
"""
|
2014-05-27 16:04:45 +02:00
|
|
|
frame = self._tabs.currentWidget().page().currentFrame()
|
2014-04-22 14:17:17 +02:00
|
|
|
size = frame.geometry()
|
2014-05-14 23:29:18 +02:00
|
|
|
dx = int(count) * float(mx) * size.width()
|
|
|
|
dy = int(count) * float(my) * size.height()
|
|
|
|
cmdutils.check_overflow(dx, 'int')
|
|
|
|
cmdutils.check_overflow(dy, 'int')
|
|
|
|
frame.scroll(dx, dy)
|
2014-04-17 09:44:26 +02:00
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
2014-04-17 09:44:26 +02:00
|
|
|
def yank(self, sel=False):
|
2014-05-15 16:27:34 +02:00
|
|
|
"""Yank the current URL to the clipboard or primary selection.
|
2014-04-17 09:44:26 +02:00
|
|
|
|
2014-05-18 08:14:11 +02:00
|
|
|
Args:
|
|
|
|
sel: True to use primary selection, False to use clipboard
|
|
|
|
"""
|
2014-06-20 22:57:04 +02:00
|
|
|
urlstr = self._tabs.currentWidget().url().toString(
|
|
|
|
QUrl.FullyEncoded | QUrl.RemovePassword)
|
2014-05-19 11:56:51 +02:00
|
|
|
if sel:
|
|
|
|
mode = QClipboard.Selection
|
|
|
|
target = "primary selection"
|
|
|
|
else:
|
|
|
|
mode = QClipboard.Clipboard
|
|
|
|
target = "clipboard"
|
2014-06-20 16:33:01 +02:00
|
|
|
log.misc.debug("Yanking to {}: '{}'".format(target, urlstr))
|
|
|
|
QApplication.clipboard().setText(urlstr, mode)
|
2014-05-19 11:56:51 +02:00
|
|
|
message.info("URL yanked to {}".format(target))
|
2014-05-18 08:14:11 +02:00
|
|
|
|
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
|
|
def yank_title(self, sel=False):
|
|
|
|
"""Yank the current title to the clipboard or primary selection.
|
|
|
|
|
2014-04-17 09:44:26 +02:00
|
|
|
Args:
|
|
|
|
sel: True to use primary selection, False to use clipboard
|
|
|
|
"""
|
2014-04-17 17:44:27 +02:00
|
|
|
title = self._tabs.tabText(self._tabs.currentIndex())
|
2014-04-17 09:44:26 +02:00
|
|
|
mode = QClipboard.Selection if sel else QClipboard.Clipboard
|
2014-05-19 11:56:51 +02:00
|
|
|
if sel:
|
|
|
|
mode = QClipboard.Selection
|
|
|
|
target = "primary selection"
|
|
|
|
else:
|
|
|
|
mode = QClipboard.Clipboard
|
|
|
|
target = "clipboard"
|
2014-05-23 16:11:55 +02:00
|
|
|
log.misc.debug("Yanking to {}: '{}'".format(target, title))
|
2014-05-19 09:52:58 +02:00
|
|
|
QApplication.clipboard().setText(title, mode)
|
2014-05-19 11:56:51 +02:00
|
|
|
message.info("Title yanked to {}".format(target))
|
2014-04-17 09:44:26 +02:00
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
2014-04-17 09:44:26 +02:00
|
|
|
def zoom_in(self, count=1):
|
2014-05-15 16:27:34 +02:00
|
|
|
"""Increase the zoom level for the current tab.
|
2014-04-17 09:44:26 +02:00
|
|
|
|
|
|
|
Args:
|
|
|
|
count: How many steps to take.
|
|
|
|
"""
|
2014-04-17 17:44:27 +02:00
|
|
|
tab = self._tabs.currentWidget()
|
2014-04-17 09:44:26 +02:00
|
|
|
tab.zoom(count)
|
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
2014-04-17 09:44:26 +02:00
|
|
|
def zoom_out(self, count=1):
|
2014-05-15 16:27:34 +02:00
|
|
|
"""Decrease the zoom level for the current tab.
|
2014-04-17 09:44:26 +02:00
|
|
|
|
|
|
|
Args:
|
|
|
|
count: How many steps to take.
|
|
|
|
"""
|
2014-04-17 17:44:27 +02:00
|
|
|
tab = self._tabs.currentWidget()
|
2014-04-17 09:44:26 +02:00
|
|
|
tab.zoom(-count)
|
2014-04-29 18:00:22 +02:00
|
|
|
|
2014-05-17 23:22:10 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
|
|
def zoom(self, zoom=None, count=None):
|
2014-05-15 16:27:34 +02:00
|
|
|
"""Set the zoom level for the current tab to [count] or 100 percent.
|
2014-05-09 14:20:26 +02:00
|
|
|
|
|
|
|
Args:
|
|
|
|
count: How many steps to take.
|
|
|
|
"""
|
2014-05-09 19:12:08 +02:00
|
|
|
try:
|
|
|
|
level = cmdutils.arg_or_count(zoom, count, default=100)
|
|
|
|
except ValueError as e:
|
2014-05-14 18:00:40 +02:00
|
|
|
raise CommandError(e)
|
2014-05-09 14:20:26 +02:00
|
|
|
tab = self._tabs.currentWidget()
|
|
|
|
tab.zoom_perc(level)
|
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
|
|
def tab_only(self):
|
|
|
|
"""Close all tabs except for the current one."""
|
|
|
|
for tab in self._tabs.widgets:
|
|
|
|
if tab is self._tabs.currentWidget():
|
|
|
|
continue
|
|
|
|
self._tabs.close_tab(tab)
|
|
|
|
|
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd', split=False)
|
2014-06-20 16:33:01 +02:00
|
|
|
def open_tab(self, urlstr):
|
2014-05-17 22:38:07 +02:00
|
|
|
"""Open a new tab with a given url."""
|
2014-06-20 16:33:01 +02:00
|
|
|
try:
|
|
|
|
url = urlutils.fuzzy_url(urlstr)
|
2014-06-20 23:57:52 +02:00
|
|
|
except urlutils.FuzzyUrlError as e:
|
2014-06-20 16:33:01 +02:00
|
|
|
raise CommandError(e)
|
2014-05-17 22:38:07 +02:00
|
|
|
self._tabs.tabopen(url, background=False)
|
|
|
|
|
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd', split=False)
|
2014-06-20 16:33:01 +02:00
|
|
|
def open_tab_bg(self, urlstr):
|
2014-05-17 22:38:07 +02:00
|
|
|
"""Open a new tab in background."""
|
2014-06-20 16:33:01 +02:00
|
|
|
try:
|
|
|
|
url = urlutils.fuzzy_url(urlstr)
|
2014-06-20 23:57:52 +02:00
|
|
|
except urlutils.FuzzyUrlError as e:
|
2014-06-20 16:33:01 +02:00
|
|
|
raise CommandError(e)
|
2014-05-17 22:38:07 +02:00
|
|
|
self._tabs.tabopen(url, background=True)
|
|
|
|
|
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
|
|
|
|
def open_tab_cur(self):
|
|
|
|
"""Set the statusbar to :tabopen and the current URL."""
|
2014-06-20 19:50:44 +02:00
|
|
|
urlstr = self._tabs.currentWidget().url().toDisplayString()
|
2014-06-20 16:33:01 +02:00
|
|
|
message.set_cmd_text(':open-tab ' + urlstr)
|
2014-05-17 22:38:07 +02:00
|
|
|
|
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
|
|
|
|
def open_cur(self):
|
|
|
|
"""Set the statusbar to :open and the current URL."""
|
2014-06-20 19:50:44 +02:00
|
|
|
urlstr = self._tabs.currentWidget().url().toDisplayString()
|
2014-06-20 16:33:01 +02:00
|
|
|
message.set_cmd_text(':open ' + urlstr)
|
2014-05-17 22:38:07 +02:00
|
|
|
|
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
|
|
|
|
def open_tab_bg_cur(self):
|
|
|
|
"""Set the statusbar to :tabopen-bg and the current URL."""
|
2014-06-20 19:50:44 +02:00
|
|
|
urlstr = self._tabs.currentWidget().url().toDisplayString()
|
2014-06-20 16:33:01 +02:00
|
|
|
message.set_cmd_text(':open-tab-bg ' + urlstr)
|
2014-05-17 22:38:07 +02:00
|
|
|
|
2014-05-17 23:22:10 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
|
|
def undo(self):
|
|
|
|
"""Re-open a closed tab (optionally skipping [count] tabs)."""
|
2014-05-17 22:38:07 +02:00
|
|
|
if self._tabs.url_stack:
|
2014-05-18 08:19:27 +02:00
|
|
|
self._tabs.tabopen(self._tabs.url_stack.pop())
|
2014-05-17 22:38:07 +02:00
|
|
|
else:
|
|
|
|
raise CommandError("Nothing to undo!")
|
|
|
|
|
2014-05-17 23:22:10 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
|
|
def tab_prev(self, count=1):
|
2014-05-17 22:38:07 +02:00
|
|
|
"""Switch to the previous tab, or skip [count] tabs.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
count: How many tabs to switch back.
|
|
|
|
"""
|
|
|
|
newidx = self._tabs.currentIndex() - count
|
|
|
|
if newidx >= 0:
|
|
|
|
self._tabs.setCurrentIndex(newidx)
|
|
|
|
elif config.get('tabbar', 'wrap'):
|
|
|
|
self._tabs.setCurrentIndex(newidx % self._tabs.count())
|
|
|
|
else:
|
|
|
|
raise CommandError("First tab")
|
|
|
|
|
2014-05-17 23:22:10 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
|
|
def tab_next(self, count=1):
|
2014-05-17 22:38:07 +02:00
|
|
|
"""Switch to the next tab, or skip [count] tabs.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
count: How many tabs to switch forward.
|
|
|
|
"""
|
|
|
|
newidx = self._tabs.currentIndex() + count
|
|
|
|
if newidx < self._tabs.count():
|
|
|
|
self._tabs.setCurrentIndex(newidx)
|
|
|
|
elif config.get('tabbar', 'wrap'):
|
|
|
|
self._tabs.setCurrentIndex(newidx % self._tabs.count())
|
|
|
|
else:
|
|
|
|
raise CommandError("Last tab")
|
|
|
|
|
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd', nargs=(0, 1))
|
|
|
|
def paste(self, sel=False, tab=False):
|
|
|
|
"""Open a page from the clipboard.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
sel: True to use primary selection, False to use clipboard
|
|
|
|
tab: True to open in a new tab.
|
|
|
|
"""
|
|
|
|
mode = QClipboard.Selection if sel else QClipboard.Clipboard
|
2014-06-20 16:33:01 +02:00
|
|
|
text = QApplication.clipboard().text(mode)
|
|
|
|
if not text:
|
2014-05-17 22:38:07 +02:00
|
|
|
raise CommandError("Clipboard is empty.")
|
2014-06-20 16:33:01 +02:00
|
|
|
log.misc.debug("Clipboard contained: '{}'".format(text))
|
|
|
|
try:
|
|
|
|
url = urlutils.fuzzy_url(text)
|
2014-06-20 23:57:52 +02:00
|
|
|
except urlutils.FuzzyUrlError as e:
|
2014-06-20 16:33:01 +02:00
|
|
|
raise CommandError(e)
|
2014-05-17 22:38:07 +02:00
|
|
|
if tab:
|
|
|
|
self._tabs.tabopen(url)
|
|
|
|
else:
|
2014-06-20 22:31:39 +02:00
|
|
|
widget = self._tabs.currentWidget()
|
|
|
|
widget.openurl(url)
|
2014-05-17 22:38:07 +02:00
|
|
|
|
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
|
|
def paste_tab(self, sel=False):
|
|
|
|
"""Open a page from the clipboard in a new tab.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
sel: True to use primary selection, False to use clipboard
|
|
|
|
"""
|
2014-05-19 15:09:12 +02:00
|
|
|
self.paste(sel, True)
|
2014-05-17 22:38:07 +02:00
|
|
|
|
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
|
|
def tab_focus(self, index=None, count=None):
|
|
|
|
"""Select the tab given as argument/[count].
|
|
|
|
|
|
|
|
Args:
|
|
|
|
index: The tab index to focus, starting with 1.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
idx = cmdutils.arg_or_count(index, count, default=1,
|
|
|
|
countzero=self._tabs.count())
|
|
|
|
except ValueError as e:
|
|
|
|
raise CommandError(e)
|
|
|
|
cmdutils.check_overflow(idx + 1, 'int')
|
|
|
|
if 1 <= idx <= self._tabs.count():
|
|
|
|
self._tabs.setCurrentIndex(idx - 1)
|
|
|
|
else:
|
|
|
|
raise CommandError("There's no tab with index {}!".format(idx))
|
|
|
|
|
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
|
|
def tab_move(self, direction=None, count=None):
|
|
|
|
"""Move the current tab.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
direction: + or - for relative moving, None for absolute.
|
|
|
|
count: If moving absolutely: New position (or first).
|
|
|
|
If moving relatively: Offset.
|
|
|
|
"""
|
|
|
|
if direction is None:
|
|
|
|
new_idx = self._tab_move_absolute(count)
|
|
|
|
elif direction in '+-':
|
|
|
|
try:
|
|
|
|
new_idx = self._tab_move_relative(direction, count)
|
|
|
|
except ValueError:
|
|
|
|
raise CommandError("Count must be given for relative moving!")
|
|
|
|
else:
|
|
|
|
raise CommandError("Invalid direction '{}'!".format(direction))
|
|
|
|
if not 0 <= new_idx < self._tabs.count():
|
|
|
|
raise CommandError("Can't move tab to position {}!".format(
|
|
|
|
new_idx))
|
|
|
|
tab = self._tabs.currentWidget()
|
|
|
|
cur_idx = self._tabs.currentIndex()
|
|
|
|
icon = self._tabs.tabIcon(cur_idx)
|
|
|
|
label = self._tabs.tabText(cur_idx)
|
|
|
|
cmdutils.check_overflow(cur_idx, 'int')
|
|
|
|
cmdutils.check_overflow(new_idx, 'int')
|
|
|
|
self._tabs.removeTab(cur_idx)
|
|
|
|
self._tabs.insertTab(new_idx, tab, icon, label)
|
|
|
|
self._tabs.setCurrentIndex(new_idx)
|
|
|
|
|
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
|
|
def tab_focus_last(self):
|
|
|
|
"""Select the tab which was last focused."""
|
|
|
|
idx = self._tabs.indexOf(self._tabs.last_focused)
|
|
|
|
if idx == -1:
|
|
|
|
raise CommandError("Last focused tab vanished!")
|
|
|
|
self._tabs.setCurrentIndex(idx)
|
|
|
|
|
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd', split=False)
|
2014-05-03 14:25:22 +02:00
|
|
|
def spawn(self, cmd):
|
|
|
|
"""Spawn a command in a shell. {} gets replaced by the current URL.
|
|
|
|
|
|
|
|
The URL will already be quoted correctly, so there's no need to do
|
|
|
|
that.
|
|
|
|
|
|
|
|
The command will be run in a shell, so you can use shell features like
|
|
|
|
redirections.
|
|
|
|
|
2014-05-03 14:27:44 +02:00
|
|
|
We use subprocess rather than Qt's QProcess here because of it's
|
|
|
|
shell=True argument and because we really don't care about the process
|
|
|
|
anymore as soon as it's spawned.
|
|
|
|
|
2014-05-03 14:25:22 +02:00
|
|
|
Args:
|
|
|
|
cmd: The command to execute.
|
|
|
|
"""
|
2014-06-20 22:57:04 +02:00
|
|
|
urlstr = self._tabs.currentWidget().url().toString(
|
|
|
|
QUrl.FullyEncoded | QUrl.RemovePassword)
|
2014-06-20 16:33:01 +02:00
|
|
|
cmd = cmd.replace('{}', shell_escape(urlstr))
|
2014-05-23 16:11:55 +02:00
|
|
|
log.procs.debug("Executing: {}".format(cmd))
|
2014-05-03 14:25:22 +02:00
|
|
|
subprocess.Popen(cmd, shell=True)
|
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
2014-05-09 13:09:37 +02:00
|
|
|
def home(self):
|
|
|
|
"""Open main startpage in current tab."""
|
|
|
|
self.openurl(config.get('general', 'startpage')[0])
|
|
|
|
|
2014-05-21 19:53:58 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
|
|
def run_userscript(self, cmd, *args):
|
|
|
|
"""Run an userscript given as argument."""
|
2014-06-20 22:57:04 +02:00
|
|
|
# We don't remove the password in the URL here, as it's probably safe
|
|
|
|
# to pass via env variable.
|
2014-06-20 16:33:01 +02:00
|
|
|
urlstr = self._tabs.currentWidget().url().toString(QUrl.FullyEncoded)
|
2014-06-17 07:17:21 +02:00
|
|
|
runner = UserscriptRunner(self._tabs)
|
2014-05-21 19:53:58 +02:00
|
|
|
runner.got_cmd.connect(self._tabs.got_cmd)
|
2014-06-20 16:33:01 +02:00
|
|
|
runner.run(cmd, *args, env={'QUTE_URL': urlstr})
|
2014-05-21 19:53:58 +02:00
|
|
|
self._userscript_runners.append(runner)
|
|
|
|
|
2014-05-22 16:44:47 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
|
|
def quickmark_save(self):
|
|
|
|
"""Save the current page as a quickmark."""
|
|
|
|
quickmarks.prompt_save(self._tabs.currentWidget().url())
|
|
|
|
|
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
|
|
def quickmark_load(self, name):
|
|
|
|
"""Load a quickmark."""
|
2014-06-20 22:57:32 +02:00
|
|
|
urlstr = quickmarks.get(name)
|
2014-06-20 23:57:52 +02:00
|
|
|
url = QUrl(urlstr)
|
|
|
|
if not url.isValid():
|
|
|
|
raise CommandError("Invalid URL {}".format(urlstr))
|
|
|
|
self._tabs.currentWidget().openurl(url)
|
2014-05-22 16:44:47 +02:00
|
|
|
|
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
|
|
def quickmark_load_tab(self, name):
|
|
|
|
"""Load a quickmark in a new tab."""
|
|
|
|
url = quickmarks.get(name)
|
|
|
|
self._tabs.tabopen(url, background=False)
|
|
|
|
|
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
|
|
def quickmark_load_tab_bg(self, name):
|
|
|
|
"""Load a quickmark in a new background tab."""
|
|
|
|
url = quickmarks.get(name)
|
|
|
|
self._tabs.tabopen(url, background=True)
|
|
|
|
|
2014-05-26 15:35:05 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd', name='inspector')
|
|
|
|
def toggle_inspector(self):
|
|
|
|
"""Toggle the web inspector."""
|
|
|
|
cur = self._tabs.currentWidget()
|
|
|
|
if cur.inspector is None:
|
2014-06-03 20:28:51 +02:00
|
|
|
if not config.get('general', 'developer-extras'):
|
2014-06-06 11:55:55 +02:00
|
|
|
raise CommandError("Please enable developer-extras before "
|
|
|
|
"using the webinspector!")
|
2014-05-26 15:35:05 +02:00
|
|
|
cur.inspector = QWebInspector()
|
2014-05-27 16:04:45 +02:00
|
|
|
cur.inspector.setPage(cur.page())
|
2014-05-26 15:35:05 +02:00
|
|
|
cur.inspector.show()
|
|
|
|
elif cur.inspector.isVisible():
|
|
|
|
cur.inspector.hide()
|
|
|
|
else:
|
2014-06-03 20:28:51 +02:00
|
|
|
if not config.get('general', 'developer-extras'):
|
2014-06-06 11:55:55 +02:00
|
|
|
raise CommandError("Please enable developer-extras before "
|
|
|
|
"using the webinspector!")
|
2014-05-26 15:35:05 +02:00
|
|
|
else:
|
|
|
|
cur.inspector.show()
|
|
|
|
|
2014-06-19 17:58:46 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
|
|
def download_page(self):
|
|
|
|
"""Download the current page."""
|
2014-06-20 16:33:01 +02:00
|
|
|
url = self._tabs.currentWidget().url()
|
2014-06-19 17:58:46 +02:00
|
|
|
QApplication.instance().downloadmanager.get(url)
|
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@cmdutils.register(instance='mainwindow.tabs.cmd', modes=['insert'],
|
2014-05-17 23:22:10 +02:00
|
|
|
hide=True)
|
|
|
|
def open_editor(self):
|
2014-05-03 14:27:44 +02:00
|
|
|
"""Open an external editor with the current form field.
|
|
|
|
|
|
|
|
We use QProcess rather than subprocess here because it makes it a lot
|
|
|
|
easier to execute some code as soon as the process has been finished
|
|
|
|
and do everything async.
|
|
|
|
"""
|
2014-05-27 16:04:45 +02:00
|
|
|
frame = self._tabs.currentWidget().page().currentFrame()
|
2014-05-06 21:09:09 +02:00
|
|
|
elem = frame.findFirstElement(webelem.SELECTORS[
|
|
|
|
webelem.Group.editable_focused])
|
2014-04-29 18:00:22 +02:00
|
|
|
if elem.isNull():
|
2014-06-06 11:55:55 +02:00
|
|
|
raise CommandError("No editable element focused!")
|
2014-04-29 18:00:22 +02:00
|
|
|
text = elem.evaluateJavaScript('this.value')
|
2014-06-17 07:17:21 +02:00
|
|
|
self._editor = ExternalEditor(self._tabs)
|
2014-05-21 17:29:09 +02:00
|
|
|
self._editor.editing_finished.connect(
|
|
|
|
partial(self.on_editing_finished, elem))
|
2014-05-21 15:37:18 +02:00
|
|
|
self._editor.edit(text)
|
|
|
|
|
|
|
|
def on_editing_finished(self, elem, text):
|
2014-04-30 10:46:20 +02:00
|
|
|
"""Write the editor text into the form field and clean up tempfile.
|
2014-04-29 18:00:22 +02:00
|
|
|
|
2014-04-30 10:46:20 +02:00
|
|
|
Callback for QProcess when the editor was closed.
|
2014-05-21 15:37:18 +02:00
|
|
|
|
|
|
|
Args:
|
|
|
|
elem: The QWebElement which was modified.
|
|
|
|
text: The new text to insert.
|
2014-04-29 18:00:22 +02:00
|
|
|
"""
|
|
|
|
if elem.isNull():
|
2014-06-06 11:55:55 +02:00
|
|
|
raise CommandError("Element vanished while editing!")
|
2014-05-21 15:37:18 +02:00
|
|
|
text = webelem.javascript_escape(text)
|
2014-04-29 18:00:22 +02:00
|
|
|
elem.evaluateJavaScript("this.value='{}'".format(text))
|