qutebrowser/qutebrowser/browser/commands.py

822 lines
30 KiB
Python
Raw Normal View History

# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
2014-06-19 09:04:37 +02:00
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-25 10:03:13 +02:00
from PyQt5.QtCore import Qt, QUrl
2014-04-17 09:44:26 +02:00
from PyQt5.QtGui import QClipboard
from PyQt5.QtPrintSupport import QPrintDialog, QPrintPreviewDialog
from PyQt5.QtWebKitWidgets import QWebInspector
2014-09-15 17:59:54 +02:00
import pygments
import pygments.lexers
import pygments.formatters
2014-04-17 09:44:26 +02:00
from qutebrowser.commands import userscripts, cmdexc, cmdutils
2014-08-26 19:10:14 +02:00
from qutebrowser.config import config
2014-09-08 10:30:05 +02:00
from qutebrowser.browser import hints, quickmarks, webelem
from qutebrowser.utils import (message, editor, usertypes, log, qtutils,
urlutils)
2014-04-17 09:44:26 +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.
_editor: The ExternalEditor object.
2014-04-17 09:44:26 +02:00
"""
def __init__(self, parent):
"""Constructor.
Args:
parent: The TabbedBrowser for this dispatcher.
"""
2014-04-17 17:44:27 +02:00
self._tabs = parent
self._editor = None
2014-04-17 09:44:26 +02:00
def _current_widget(self):
"""Get the currently active widget from a command."""
widget = self._tabs.currentWidget()
if widget is None:
raise cmdexc.CommandError("No WebView available yet!")
return widget
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-08-26 19:10:14 +02:00
perc = qtutils.check_overflow(perc, 'int', fatal=False)
frame = self._current_widget().page().currentFrame()
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._current_widget()
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-08-26 19:10:14 +02:00
raise cmdexc.CommandError("No frame focused!")
widget.hintmanager.follow_prevnext(frame, self._tabs.current_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:
2014-08-25 15:40:48 +02:00
# We don't set delta to 1 in the function arguments because this
# gets called from tab_move which has delta set to None by default.
delta = 1
2014-05-17 22:38:07 +02:00
if direction == '-':
return self._tabs.currentIndex() - delta
elif direction == '+':
return self._tabs.currentIndex() + delta
2014-08-03 00:39:39 +02:00
def _tab_focus_last(self):
"""Select the tab which was last focused."""
if self._tabs.last_focused is None:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("No last focused tab!")
2014-08-03 00:39:39 +02:00
idx = self._tabs.indexOf(self._tabs.last_focused)
if idx == -1:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Last focused tab vanished!")
2014-08-03 00:39:39 +02:00
self._tabs.setCurrentIndex(idx)
2014-05-17 22:38:07 +02:00
def _editor_cleanup(self, oshandle, filename):
"""Clean up temporary file when the editor was closed."""
os.close(oshandle)
try:
os.remove(filename)
except PermissionError:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Failed to delete tempfile...")
2014-05-17 22:38:07 +02:00
@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',
split=False)
def openurl(self, urlstr, count=None):
"""Open a URL in the current/[count]th tab.
2014-04-17 09:44:26 +02:00
Args:
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)
try:
url = urlutils.fuzzy_url(urlstr)
2014-06-20 23:57:52 +02:00
except urlutils.FuzzyUrlError as e:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError(e)
2014-04-17 09:44:26 +02:00
if tab is None:
if count is None:
# 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-08-26 19:10:14 +02:00
if not qtutils.check_print_compat():
2014-09-02 20:44:58 +02:00
# WORKAROUND (remove this when we bump the requirements to 5.3.0)
2014-08-26 19:10:14 +02:00
raise cmdexc.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-08-26 19:10:14 +02:00
if not qtutils.check_print_compat():
2014-09-02 20:44:58 +02:00
# WORKAROUND (remove this when we bump the requirements to 5.3.0)
2014-08-26 19:10:14 +02:00
raise cmdexc.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):
self._current_widget().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):
self._current_widget().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-07-29 02:24:04 +02:00
def hint(self, group='all', target='normal', *args):
2014-04-19 17:50:11 +02:00
"""Start hinting.
Args:
2014-07-29 02:24:04 +02:00
group: The hinting mode to use.
2014-08-02 23:46:27 +02:00
- `all`: All clickable elements.
- `links`: Only links.
- `images`: Only images.
target: What to do with the selected element.
- `normal`: Open the link in the current tab.
- `tab`: Open the link in a new tab.
- `tab-bg`: Open the link in a new background tab.
- `yank`: Yank the link to the clipboard.
- `yank-primary`: Yank the link to the primary selection.
- `fill`: Fill the commandline with the command given as
argument.
2014-08-03 00:36:35 +02:00
- `cmd-tab`: Fill the commandline with `:open-tab` and the
2014-08-03 00:40:28 +02:00
link.
2014-08-03 00:36:35 +02:00
- `cmd-tag-bg`: Fill the commandline with `:open-tab-bg` and
2014-08-03 00:40:28 +02:00
the link.
2014-08-02 23:46:27 +02:00
- `rapid`: Open the link in a new tab and stay in hinting mode.
- `download`: Download the link.
2014-08-02 23:58:52 +02:00
- `userscript`: Call an userscript with `$QUTE_URL` set to the
2014-08-03 00:40:28 +02:00
link.
- `spawn`: Spawn a command.
2014-08-02 23:46:27 +02:00
*args: Arguments for spawn/userscript/fill.
- With `spawn`: The executable and arguments to spawn.
`{hint-url}` will get replaced by the selected
URL.
- With `userscript`: The userscript to execute.
- With `fill`: The command to fill the statusbar with.
`{hint-url}` will get replaced by the selected
URL.
2014-04-19 17:50:11 +02:00
"""
widget = self._current_widget()
2014-05-27 16:04:45 +02:00
frame = widget.page().mainFrame()
if frame is None:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("No frame focused!")
2014-05-05 07:45:36 +02:00
try:
2014-07-29 02:24:04 +02:00
group_enum = webelem.Group[group.replace('-', '_')]
except KeyError:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Unknown hinting group {}!".format(
group))
2014-05-05 07:45:36 +02:00
try:
2014-07-29 02:24:04 +02:00
target_enum = hints.Target[target.replace('-', '_')]
except KeyError:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Unknown hinting target {}!".format(
target))
2014-07-29 02:24:04 +02:00
widget.hintmanager.start(frame, self._tabs.current_url(), group_enum,
target_enum, *args)
2014-05-17 22:38:07 +02:00
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
def follow_hint(self):
"""Follow the currently selected hint."""
self._current_widget().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-08-03 00:33:39 +02:00
"""Open a "previous" link.
This tries to automaticall click on typical "Previous Page" links using
some heuristics.
"""
2014-05-01 16:35:26 +02:00
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-08-03 00:33:39 +02:00
"""Open a "next" link.
This tries to automatically click on typical "Next Page" links using
some heuristics.
"""
2014-05-01 16:35:26 +02:00
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-08-03 00:33:39 +02:00
"""Open a "previous" link in a new tab.
2014-08-03 00:36:35 +02:00
This tries to automatically click on typical "Previous Page" links
using some heuristics.
2014-08-03 00:33:39 +02:00
"""
2014-05-01 16:35:26 +02:00
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-08-03 00:33:39 +02:00
"""Open a "next" link in a new tab.
2014-08-03 00:36:35 +02:00
This tries to automatically click on typical "Previous Page" links
using some heuristics.
2014-08-03 00:33:39 +02:00
"""
2014-05-01 16:35:26 +02:00
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):
2014-08-03 00:33:39 +02:00
"""Scroll the current tab by 'count * dx/dy'.
2014-04-17 09:44:26 +02:00
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))
cmdutils.check_overflow(dx, 'int')
cmdutils.check_overflow(dy, 'int')
self._current_widget().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-08-03 00:33:39 +02:00
"""Scroll horizontally to a specific percentage of the page.
The percentage can be given either as argument or as count.
If no percentage is given, the page is scrolled to the end.
2014-04-17 09:44:26 +02:00
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-08-03 00:33:39 +02:00
"""Scroll vertically to a specific percentage of the page.
The percentage can be given either as argument or as count.
If no percentage is given, the page is scrolled to the end.
2014-04-17 09:44:26 +02:00
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-08-03 00:33:39 +02:00
def scroll_page(self, x, y, count=1):
2014-04-17 09:44:26 +02:00
"""Scroll the frame page-wise.
Args:
2014-08-03 00:33:39 +02:00
x: How many pages to scroll to the right.
y: How many pages to scroll down.
2014-04-17 09:44:26 +02:00
count: multiplier
"""
frame = self._current_widget().page().currentFrame()
size = frame.geometry()
2014-08-03 00:33:39 +02:00
dx = int(count) * float(x) * size.width()
dy = int(count) * float(y) * 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):
"""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
"""
clipboard = QApplication.clipboard()
urlstr = self._tabs.current_url().toString(
QUrl.FullyEncoded | QUrl.RemovePassword)
if sel and clipboard.supportsSelection():
2014-05-19 11:56:51 +02:00
mode = QClipboard.Selection
target = "primary selection"
else:
mode = QClipboard.Clipboard
target = "clipboard"
log.misc.debug("Yanking to {}: '{}'".format(target, urlstr))
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
"""
clipboard = QApplication.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
if sel and clipboard.supportsSelection():
2014-05-19 11:56:51 +02:00
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))
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):
"""Increase the zoom level for the current tab.
2014-04-17 09:44:26 +02:00
Args:
2014-08-03 00:33:39 +02:00
count: How many steps to zoom in.
2014-04-17 09:44:26 +02:00
"""
tab = self._current_widget()
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):
"""Decrease the zoom level for the current tab.
2014-04-17 09:44:26 +02:00
Args:
2014-08-03 00:33:39 +02:00
count: How many steps to zoom out.
2014-04-17 09:44:26 +02:00
"""
tab = self._current_widget()
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-08-03 00:33:39 +02:00
"""Set the zoom level for the current tab.
The zoom can be given as argument or as [count]. If neither of both is
given, the zoom is set to 100%.
2014-05-09 14:20:26 +02:00
Args:
2014-08-03 00:33:39 +02:00
zoom: The zoom percentage to set.
count: The zoom percentage to set.
2014-05-09 14:20:26 +02:00
"""
2014-05-09 19:12:08 +02:00
try:
level = cmdutils.arg_or_count(zoom, count, default=100)
except ValueError as e:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError(e)
tab = self._current_widget()
2014-05-09 14:20:26 +02:00
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._current_widget():
2014-05-17 22:38:07 +02:00
continue
self._tabs.close_tab(tab)
@cmdutils.register(instance='mainwindow.tabs.cmd', split=False)
def open_tab(self, urlstr):
2014-05-17 22:38:07 +02:00
"""Open a new tab with a given url."""
try:
url = urlutils.fuzzy_url(urlstr)
2014-06-20 23:57:52 +02:00
except urlutils.FuzzyUrlError as e:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError(e)
self._tabs.tabopen(url, background=False, explicit=True)
2014-05-17 22:38:07 +02:00
@cmdutils.register(instance='mainwindow.tabs.cmd', split=False)
def open_tab_bg(self, urlstr):
2014-05-17 22:38:07 +02:00
"""Open a new tab in background."""
try:
url = urlutils.fuzzy_url(urlstr)
2014-06-20 23:57:52 +02:00
except urlutils.FuzzyUrlError as e:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError(e)
self._tabs.tabopen(url, background=True, explicit=True)
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):
2014-08-03 00:33:39 +02:00
"""Re-open a closed tab (optionally skipping [count] closed 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:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Nothing to undo!")
2014-05-17 22:38:07 +02:00
2014-05-17 23:22:10 +02:00
@cmdutils.register(instance='mainwindow.tabs.cmd')
def tab_prev(self, count=1):
2014-08-03 00:33:39 +02:00
"""Switch to the previous tab, or switch [count] tabs back.
2014-05-17 22:38:07 +02:00
Args:
count: How many tabs to switch back.
"""
newidx = self._tabs.currentIndex() - count
if newidx >= 0:
self._tabs.setCurrentIndex(newidx)
2014-08-06 08:10:32 +02:00
elif config.get('tabs', 'wrap'):
2014-05-17 22:38:07 +02:00
self._tabs.setCurrentIndex(newidx % self._tabs.count())
else:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("First tab")
2014-05-17 22:38:07 +02:00
2014-05-17 23:22:10 +02:00
@cmdutils.register(instance='mainwindow.tabs.cmd')
def tab_next(self, count=1):
2014-08-03 00:33:39 +02:00
"""Switch to the next tab, or switch [count] tabs forward.
2014-05-17 22:38:07 +02:00
Args:
count: How many tabs to switch forward.
"""
newidx = self._tabs.currentIndex() + count
if newidx < self._tabs.count():
self._tabs.setCurrentIndex(newidx)
2014-08-06 08:10:32 +02:00
elif config.get('tabs', 'wrap'):
2014-05-17 22:38:07 +02:00
self._tabs.setCurrentIndex(newidx % self._tabs.count())
else:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Last tab")
2014-05-17 22:38:07 +02:00
@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.
"""
clipboard = QApplication.clipboard()
if sel and clipboard.supportsSelection():
mode = QClipboard.Selection
target = "Primary selection"
else:
mode = QClipboard.Clipboard
target = "Clipboard"
text = clipboard.text(mode)
if not text:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("{} is empty.".format(target))
log.misc.debug("{} contained: '{}'".format(target, text))
try:
url = urlutils.fuzzy_url(text)
2014-06-20 23:57:52 +02:00
except urlutils.FuzzyUrlError as e:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError(e)
2014-05-17 22:38:07 +02:00
if tab:
self._tabs.tabopen(url, explicit=True)
2014-05-17 22:38:07 +02:00
else:
widget = self._current_widget()
2014-06-20 22:31:39 +02:00
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:
2014-08-03 00:39:39 +02:00
index: The tab index to focus, starting with 1. The special value
`last` focuses the last focused tab.
2014-08-03 00:33:39 +02:00
count: The tab index to focus, starting with 1.
2014-05-17 22:38:07 +02:00
"""
2014-08-03 00:39:39 +02:00
if index == 'last':
self._tab_focus_last()
return
2014-05-17 22:38:07 +02:00
try:
idx = cmdutils.arg_or_count(index, count, default=1,
countzero=self._tabs.count())
except ValueError as e:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError(e)
2014-05-17 22:38:07 +02:00
cmdutils.check_overflow(idx + 1, 'int')
if 1 <= idx <= self._tabs.count():
self._tabs.setCurrentIndex(idx - 1)
else:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("There's no tab with index {}!".format(
idx))
2014-05-17 22:38:07 +02:00
@cmdutils.register(instance='mainwindow.tabs.cmd')
def tab_move(self, direction=None, count=None):
"""Move the current tab.
Args:
2014-08-03 00:33:39 +02:00
direction: + or - for relative moving, none for absolute.
count: If moving absolutely: New position (default: 0)
2014-05-17 22:38:07 +02:00
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:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Count must be given for relative "
"moving!")
2014-05-17 22:38:07 +02:00
else:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Invalid direction '{}'!".format(
direction))
2014-05-17 22:38:07 +02:00
if not 0 <= new_idx < self._tabs.count():
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Can't move tab to position {}!".format(
2014-05-17 22:38:07 +02:00
new_idx))
tab = self._current_widget()
2014-05-17 22:38:07 +02:00
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.setUpdatesEnabled(False)
try:
self._tabs.removeTab(cur_idx)
self._tabs.insertTab(new_idx, tab, icon, label)
self._tabs.setCurrentIndex(new_idx)
finally:
self._tabs.setUpdatesEnabled(True)
2014-05-17 22:38:07 +02:00
@cmdutils.register(instance='mainwindow.tabs.cmd', split=False)
def spawn(self, *args):
"""Spawn a command in a shell.
2014-05-03 14:25:22 +02:00
Note the {url} variable which gets replaced by the current URL might be
useful here.
2014-05-03 14:25:22 +02:00
//
We use subprocess rather than Qt's QProcess here 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:
2014-08-03 00:33:39 +02:00
*args: The commandline to execute.
2014-05-03 14:25:22 +02:00
"""
log.procs.debug("Executing: {}".format(args))
subprocess.Popen(args)
2014-05-03 14:25:22 +02:00
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')
2014-07-29 02:05:15 +02:00
def run_userscript(self, cmd, *args):
2014-07-29 01:45:42 +02:00
"""Run an userscript given as argument.
Args:
cmd: The userscript to run.
args: Arguments to pass to the userscript.
"""
2014-07-29 02:05:15 +02:00
url = self._tabs.current_url()
userscripts.run(cmd, *args, url=url)
2014-05-21 19:53:58 +02:00
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.current_url())
2014-05-22 16:44:47 +02:00
@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():
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Invalid URL {} ({})".format(
2014-06-21 16:42:58 +02:00
urlstr, url.errorString()))
self._current_widget().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, explicit=True)
2014-05-22 16:44:47 +02:00
@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, explicit=True)
2014-05-22 16:44:47 +02:00
@cmdutils.register(instance='mainwindow.tabs.cmd', name='inspector')
def toggle_inspector(self):
"""Toggle the web inspector."""
cur = self._current_widget()
if cur.inspector is None:
2014-06-03 20:28:51 +02:00
if not config.get('general', 'developer-extras'):
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError(
"Please enable developer-extras before using the "
"webinspector!")
cur.inspector = QWebInspector()
2014-05-27 16:04:45 +02:00
cur.inspector.setPage(cur.page())
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-08-26 19:10:14 +02:00
raise cmdexc.CommandError(
"Please enable developer-extras before using the "
"webinspector!")
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."""
page = self._current_widget().page()
self._tabs.download_get.emit(self._tabs.current_url(), page)
2014-06-19 17:58:46 +02:00
2014-09-15 17:59:54 +02:00
@cmdutils.register(instance='mainwindow.tabs.cmd')
def view_source(self):
"""Show the source of the current page."""
widget = self._current_widget()
if widget.viewing_source:
raise cmdexc.CommandError("Already viewing source!")
frame = widget.page().currentFrame()
url = self._tabs.current_url()
html = frame.toHtml()
lexer = pygments.lexers.HtmlLexer()
2014-09-15 18:19:56 +02:00
formatter = pygments.formatters.HtmlFormatter(
full=True, linenos='table')
2014-09-15 17:59:54 +02:00
highlighted = pygments.highlight(html, lexer, formatter)
tab = self._tabs.tabopen(explicit=True)
tab.setHtml(highlighted, url)
tab.viewing_source = True
2014-08-26 19:10:14 +02:00
@cmdutils.register(instance='mainwindow.tabs.cmd',
modes=[usertypes.KeyMode.insert],
2014-05-17 23:22:10 +02:00
hide=True)
def open_editor(self):
2014-08-03 00:33:39 +02:00
"""Open an external editor with the currently selected form field.
The editor which should be launched can be configured via the
`general -> editor` config option.
//
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.
"""
frame = self._current_widget().page().currentFrame()
2014-09-04 08:00:05 +02:00
try:
elem = webelem.focus_elem(frame)
except webelem.IsNullError:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("No element focused!")
2014-09-04 08:00:05 +02:00
if not elem.is_editable(strict=True):
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Focused element is not editable!")
2014-09-04 08:00:05 +02:00
if elem.is_content_editable():
text = str(elem)
else:
text = elem.evaluateJavaScript('this.value')
2014-08-26 19:10:14 +02:00
self._editor = editor.ExternalEditor(self._tabs)
2014-05-21 17:29:09 +02:00
self._editor.editing_finished.connect(
partial(self.on_editing_finished, elem))
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.
Args:
2014-09-04 08:00:05 +02:00
elem: The WebElementWrapper which was modified.
text: The new text to insert.
2014-04-29 18:00:22 +02:00
"""
2014-09-04 08:00:05 +02:00
try:
if elem.is_content_editable():
log.misc.debug("Filling element {} via setPlainText.".format(
elem.debug_text()))
elem.setPlainText(text)
else:
log.misc.debug("Filling element {} via javascript.".format(
elem.debug_text()))
text = webelem.javascript_escape(text)
elem.evaluateJavaScript("this.value='{}'".format(text))
except webelem.IsNullError:
2014-08-26 19:10:14 +02:00
raise cmdexc.CommandError("Element vanished while editing!")