2014-06-19 09:04:37 +02:00
|
|
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
|
|
|
2014-03-03 21:35:13 +01: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/>.
|
|
|
|
|
|
|
|
"""The main browser widgets."""
|
|
|
|
|
2014-09-26 16:44:41 +02:00
|
|
|
import itertools
|
|
|
|
|
2014-09-30 08:07:45 +02:00
|
|
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl, QEventLoop
|
2014-04-21 16:59:03 +02:00
|
|
|
from PyQt5.QtWidgets import QApplication
|
2014-08-01 23:23:31 +02:00
|
|
|
from PyQt5.QtWebKit import QWebSettings
|
2014-03-03 21:35:13 +01:00
|
|
|
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
|
|
|
|
|
2014-08-26 19:10:14 +02:00
|
|
|
from qutebrowser.config import config
|
|
|
|
from qutebrowser.keyinput import modeman
|
2014-09-24 07:06:45 +02:00
|
|
|
from qutebrowser.utils import message, log, usertypes, utils, qtutils, objreg
|
2014-09-08 10:30:05 +02:00
|
|
|
from qutebrowser.browser import webpage, hints, webelem
|
2014-08-26 20:38:10 +02:00
|
|
|
from qutebrowser.commands import cmdexc
|
2014-05-05 07:45:36 +02:00
|
|
|
|
|
|
|
|
2014-09-28 00:41:08 +02:00
|
|
|
LoadStatus = usertypes.enum('LoadStatus', ['none', 'success', 'error', 'warn',
|
|
|
|
'loading'])
|
2014-03-03 21:35:13 +01:00
|
|
|
|
|
|
|
|
2014-09-26 16:44:41 +02:00
|
|
|
tab_id_gen = itertools.count(0)
|
|
|
|
|
|
|
|
|
2014-04-25 12:24:26 +02:00
|
|
|
class WebView(QWebView):
|
2014-03-03 21:35:13 +01:00
|
|
|
|
|
|
|
"""One browser tab in TabbedBrowser.
|
|
|
|
|
|
|
|
Our own subclass of a QWebView with some added bells and whistles.
|
|
|
|
|
|
|
|
Attributes:
|
2014-04-21 00:24:08 +02:00
|
|
|
hintmanager: The HintManager instance for this view.
|
2014-05-15 17:57:08 +02:00
|
|
|
progress: loading progress of this page.
|
2014-05-19 09:50:56 +02:00
|
|
|
scroll_pos: The current scroll position as (x%, y%) tuple.
|
2014-06-03 17:59:15 +02:00
|
|
|
statusbar_message: The current javscript statusbar message.
|
2014-05-26 15:35:05 +02:00
|
|
|
inspector: The QWebInspector used for this webview.
|
2014-09-02 21:54:07 +02:00
|
|
|
load_status: loading status of this page (index into LoadStatus)
|
2014-07-02 22:17:36 +02:00
|
|
|
open_target: Where to open the next tab ("normal", "tab", "tab_bg")
|
2014-09-15 17:59:54 +02:00
|
|
|
viewing_source: Whether the webview is currently displaying source
|
|
|
|
code.
|
2014-09-25 07:43:50 +02:00
|
|
|
registry: The ObjectRegistry associated with this tab.
|
2014-08-02 02:35:38 +02:00
|
|
|
_cur_url: The current URL (accessed via cur_url property).
|
2014-05-15 17:57:08 +02:00
|
|
|
_has_ssl_errors: Whether SSL errors occured during loading.
|
2014-03-03 21:35:13 +01:00
|
|
|
_zoom: A NeighborList with the zoom levels.
|
2014-05-19 09:50:56 +02:00
|
|
|
_old_scroll_pos: The old scroll position.
|
2014-09-02 21:54:07 +02:00
|
|
|
_force_open_target: Override for open_target.
|
2014-07-16 09:09:28 +02:00
|
|
|
_check_insertmode: If True, in mouseReleaseEvent we should check if we
|
|
|
|
need to enter/leave insert mode.
|
2014-03-03 21:35:13 +01:00
|
|
|
|
|
|
|
Signals:
|
2014-05-19 09:50:56 +02:00
|
|
|
scroll_pos_changed: Scroll percentage of current tab changed.
|
2014-03-03 21:35:13 +01:00
|
|
|
arg 1: x-position in %.
|
|
|
|
arg 2: y-position in %.
|
|
|
|
linkHovered: QWebPages linkHovered signal exposed.
|
2014-05-15 17:57:08 +02:00
|
|
|
load_status_changed: The loading status changed
|
2014-05-15 22:31:01 +02:00
|
|
|
url_text_changed: Current URL string changed.
|
2014-03-03 21:35:13 +01:00
|
|
|
"""
|
|
|
|
|
2014-05-19 09:50:56 +02:00
|
|
|
scroll_pos_changed = pyqtSignal(int, int)
|
2014-03-03 21:35:13 +01:00
|
|
|
linkHovered = pyqtSignal(str, str, str)
|
2014-05-15 17:57:08 +02:00
|
|
|
load_status_changed = pyqtSignal(str)
|
2014-05-15 22:31:01 +02:00
|
|
|
url_text_changed = pyqtSignal(str)
|
2014-03-03 21:35:13 +01:00
|
|
|
|
2014-09-24 20:27:05 +02:00
|
|
|
def __init__(self, parent=None):
|
2014-03-03 21:35:13 +01:00
|
|
|
super().__init__(parent)
|
2014-07-29 00:23:20 +02:00
|
|
|
self.load_status = LoadStatus.none
|
2014-07-16 09:09:28 +02:00
|
|
|
self._check_insertmode = False
|
2014-05-26 15:35:05 +02:00
|
|
|
self.inspector = None
|
2014-05-19 09:50:56 +02:00
|
|
|
self.scroll_pos = (-1, -1)
|
2014-06-03 17:59:15 +02:00
|
|
|
self.statusbar_message = ''
|
2014-05-19 09:50:56 +02:00
|
|
|
self._old_scroll_pos = (-1, -1)
|
2014-08-26 19:10:14 +02:00
|
|
|
self.open_target = usertypes.ClickTarget.normal
|
2014-04-21 19:29:11 +02:00
|
|
|
self._force_open_target = None
|
2014-04-10 18:01:16 +02:00
|
|
|
self._zoom = None
|
2014-05-15 22:02:43 +02:00
|
|
|
self._has_ssl_errors = False
|
2014-09-28 11:27:52 +02:00
|
|
|
self.init_neighborlist()
|
|
|
|
config.on_change(self.init_neighborlist, 'ui', 'zoom-levels')
|
|
|
|
config.on_change(self.init_neighborlist, 'ui', 'default-zoom')
|
2014-08-02 02:35:38 +02:00
|
|
|
self._cur_url = None
|
|
|
|
self.cur_url = QUrl()
|
2014-05-15 17:57:08 +02:00
|
|
|
self.progress = 0
|
2014-09-25 07:43:50 +02:00
|
|
|
self.registry = objreg.ObjectRegistry()
|
2014-09-26 16:44:41 +02:00
|
|
|
self.tab_id = next(tab_id_gen)
|
2014-09-25 07:43:50 +02:00
|
|
|
objreg.register('webview', self, registry=self.registry)
|
2014-09-30 08:07:45 +02:00
|
|
|
# QNetworkAccessManager init will hang for over a second, so
|
|
|
|
# we make sure the GUI is refreshed here, so the start seems faster.
|
|
|
|
app = objreg.get('app')
|
|
|
|
app.processEvents(QEventLoop.ExcludeUserInputEvents |
|
|
|
|
QEventLoop.ExcludeSocketNotifiers)
|
2014-09-24 20:29:38 +02:00
|
|
|
page = webpage.BrowserPage(self)
|
|
|
|
self.setPage(page)
|
2014-09-25 07:44:11 +02:00
|
|
|
hintmanager = hints.HintManager(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)
|
2014-09-26 16:44:41 +02:00
|
|
|
objreg.register('tab-{}'.format(self.tab_id),
|
|
|
|
self.registry, scope='meta')
|
2014-09-24 20:29:38 +02:00
|
|
|
page.linkHovered.connect(self.linkHovered)
|
|
|
|
page.mainFrame().loadStarted.connect(self.on_load_started)
|
2014-05-15 22:31:01 +02:00
|
|
|
self.urlChanged.connect(self.on_url_changed)
|
2014-09-24 20:29:38 +02:00
|
|
|
page.mainFrame().loadFinished.connect(self.on_load_finished)
|
2014-05-15 19:02:20 +02:00
|
|
|
self.loadProgress.connect(lambda p: setattr(self, 'progress', p))
|
2014-06-03 17:59:15 +02:00
|
|
|
self.page().statusBarMessage.connect(
|
|
|
|
lambda msg: setattr(self, 'statusbar_message', msg))
|
2014-05-27 16:04:45 +02:00
|
|
|
self.page().networkAccessManager().sslErrors.connect(
|
2014-05-15 19:02:20 +02:00
|
|
|
lambda *args: setattr(self, '_has_ssl_errors', True))
|
2014-09-15 17:59:54 +02:00
|
|
|
self.viewing_source = False
|
2014-03-03 21:35:13 +01:00
|
|
|
|
2014-05-16 07:47:06 +02:00
|
|
|
def __repr__(self):
|
2014-09-26 15:48:24 +02:00
|
|
|
url = utils.elide(self.url().toDisplayString(), 50)
|
2014-09-26 16:44:41 +02:00
|
|
|
return utils.get_repr(self, tab_id=self.tab_id, url=url)
|
2014-05-16 07:47:06 +02:00
|
|
|
|
2014-09-02 21:54:07 +02:00
|
|
|
def _set_load_status(self, val):
|
2014-05-15 17:57:08 +02:00
|
|
|
"""Setter for load_status.
|
|
|
|
|
|
|
|
Emit:
|
|
|
|
load_status_changed
|
|
|
|
"""
|
2014-07-29 00:23:20 +02:00
|
|
|
if not isinstance(val, LoadStatus):
|
|
|
|
raise TypeError("Type {} is no LoadStatus member!".format(val))
|
2014-07-28 20:41:42 +02:00
|
|
|
log.webview.debug("load status for {}: {}".format(repr(self), val))
|
2014-09-02 21:54:07 +02:00
|
|
|
self.load_status = val
|
2014-07-28 20:41:42 +02:00
|
|
|
self.load_status_changed.emit(val.name)
|
2014-05-15 17:57:08 +02:00
|
|
|
|
2014-09-28 11:27:52 +02:00
|
|
|
def init_neighborlist(self):
|
2014-04-10 18:01:16 +02:00
|
|
|
"""Initialize the _zoom neighborlist."""
|
2014-08-26 19:10:14 +02:00
|
|
|
levels = config.get('ui', 'zoom-levels')
|
|
|
|
default = config.get('ui', 'default-zoom')
|
|
|
|
self._zoom = usertypes.NeighborList(
|
|
|
|
levels, default, mode=usertypes.NeighborList.Modes.block)
|
2014-04-10 18:01:16 +02:00
|
|
|
|
2014-05-14 20:59:21 +02:00
|
|
|
def _mousepress_backforward(self, e):
|
|
|
|
"""Handle back/forward mouse button presses.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
e: The QMouseEvent.
|
|
|
|
"""
|
|
|
|
if e.button() == Qt.XButton1:
|
|
|
|
# Back button on mice which have it.
|
|
|
|
try:
|
|
|
|
self.go_back()
|
2014-08-26 19:10:14 +02:00
|
|
|
except cmdexc.CommandError as ex:
|
2014-06-26 07:58:00 +02:00
|
|
|
message.error(ex, immediately=True)
|
2014-05-14 20:59:21 +02:00
|
|
|
elif e.button() == Qt.XButton2:
|
|
|
|
# Forward button on mice which have it.
|
|
|
|
try:
|
|
|
|
self.go_forward()
|
2014-08-26 19:10:14 +02:00
|
|
|
except cmdexc.CommandError as ex:
|
2014-06-26 07:58:00 +02:00
|
|
|
message.error(ex, immediately=True)
|
2014-05-14 20:59:21 +02:00
|
|
|
|
|
|
|
def _mousepress_insertmode(self, e):
|
|
|
|
"""Switch to insert mode when an editable element was clicked.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
e: The QMouseEvent.
|
|
|
|
"""
|
|
|
|
pos = e.pos()
|
2014-05-27 16:04:45 +02:00
|
|
|
frame = self.page().frameAt(pos)
|
2014-05-14 20:59:21 +02:00
|
|
|
if frame is None:
|
|
|
|
# This happens when we click inside the webview, but not actually
|
|
|
|
# on the QWebPage - for example when clicking the scrollbar
|
|
|
|
# sometimes.
|
2014-05-23 16:11:55 +02:00
|
|
|
log.mouse.debug("Clicked at {} but frame is None!".format(pos))
|
2014-05-14 20:59:21 +02:00
|
|
|
return
|
|
|
|
# You'd think we have to subtract frame.geometry().topLeft() from the
|
|
|
|
# position, but it seems QWebFrame::hitTestContent wants a position
|
|
|
|
# relative to the QWebView, not to the frame. This makes no sense to
|
|
|
|
# me, but it works this way.
|
|
|
|
hitresult = frame.hitTestContent(pos)
|
2014-06-22 23:35:01 +02:00
|
|
|
if hitresult.isNull():
|
2014-08-02 16:55:11 +02:00
|
|
|
# For some reason, the whole hitresult can be null sometimes (e.g.
|
|
|
|
# on doodle menu links). If this is the case, we schedule a check
|
|
|
|
# later (in mouseReleaseEvent) which uses webelem.focus_elem.
|
2014-06-22 23:35:01 +02:00
|
|
|
log.mouse.debug("Hitresult is null!")
|
2014-08-02 16:55:11 +02:00
|
|
|
self._check_insertmode = True
|
|
|
|
return
|
2014-09-04 08:00:05 +02:00
|
|
|
try:
|
|
|
|
elem = webelem.WebElementWrapper(hitresult.element())
|
|
|
|
except webelem.IsNullError:
|
2014-07-16 09:09:28 +02:00
|
|
|
# For some reason, the hitresult element can be a null element
|
|
|
|
# sometimes (e.g. when clicking the timetable fields on
|
|
|
|
# http://www.sbb.ch/ ). If this is the case, we schedule a check
|
|
|
|
# later (in mouseReleaseEvent) which uses webelem.focus_elem.
|
|
|
|
log.mouse.debug("Hitresult element is null!")
|
|
|
|
self._check_insertmode = True
|
|
|
|
return
|
2014-09-04 08:00:05 +02:00
|
|
|
if ((hitresult.isContentEditable() and elem.is_writable()) or
|
2014-09-08 10:23:18 +02:00
|
|
|
elem.is_editable()):
|
2014-05-23 16:11:55 +02:00
|
|
|
log.mouse.debug("Clicked editable element!")
|
2014-08-26 19:10:14 +02:00
|
|
|
modeman.maybe_enter(usertypes.KeyMode.insert, 'click')
|
2014-05-14 20:59:21 +02:00
|
|
|
else:
|
2014-05-23 16:11:55 +02:00
|
|
|
log.mouse.debug("Clicked non-editable element!")
|
2014-06-23 08:05:52 +02:00
|
|
|
if config.get('input', 'auto-leave-insert-mode'):
|
2014-08-26 19:10:14 +02:00
|
|
|
modeman.maybe_leave(usertypes.KeyMode.insert, 'click')
|
2014-05-14 20:59:21 +02:00
|
|
|
|
2014-07-16 09:09:28 +02:00
|
|
|
def mouserelease_insertmode(self):
|
|
|
|
"""If we have an insertmode check scheduled, handle it."""
|
|
|
|
if not self._check_insertmode:
|
|
|
|
return
|
|
|
|
self._check_insertmode = False
|
2014-09-04 08:00:05 +02:00
|
|
|
try:
|
|
|
|
elem = webelem.focus_elem(self.page().currentFrame())
|
|
|
|
except webelem.IsNullError:
|
|
|
|
log.mouse.warning("Element vanished!")
|
|
|
|
return
|
|
|
|
if elem.is_editable():
|
2014-07-16 09:09:28 +02:00
|
|
|
log.mouse.debug("Clicked editable element (delayed)!")
|
2014-08-26 19:10:14 +02:00
|
|
|
modeman.maybe_enter(usertypes.KeyMode.insert, 'click-delayed')
|
2014-07-16 09:09:28 +02:00
|
|
|
else:
|
|
|
|
log.mouse.debug("Clicked non-editable element (delayed)!")
|
|
|
|
if config.get('input', 'auto-leave-insert-mode'):
|
2014-08-26 19:10:14 +02:00
|
|
|
modeman.maybe_leave(usertypes.KeyMode.insert, 'click-delayed')
|
2014-07-16 09:09:28 +02:00
|
|
|
|
2014-05-14 20:59:21 +02:00
|
|
|
def _mousepress_opentarget(self, e):
|
|
|
|
"""Set the open target when something was clicked.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
e: The QMouseEvent.
|
|
|
|
"""
|
|
|
|
if self._force_open_target is not None:
|
2014-07-02 22:17:36 +02:00
|
|
|
self.open_target = self._force_open_target
|
2014-05-14 20:59:21 +02:00
|
|
|
self._force_open_target = None
|
2014-05-23 16:11:55 +02:00
|
|
|
log.mouse.debug("Setting force target: {}".format(
|
2014-07-28 20:41:42 +02:00
|
|
|
self.open_target))
|
2014-05-14 20:59:21 +02:00
|
|
|
elif (e.button() == Qt.MidButton or
|
|
|
|
e.modifiers() & Qt.ControlModifier):
|
2014-08-06 08:10:32 +02:00
|
|
|
if config.get('tabs', 'background-tabs'):
|
2014-08-26 19:10:14 +02:00
|
|
|
self.open_target = usertypes.ClickTarget.tab_bg
|
2014-05-14 20:59:21 +02:00
|
|
|
else:
|
2014-08-26 19:10:14 +02:00
|
|
|
self.open_target = usertypes.ClickTarget.tab
|
2014-05-23 16:11:55 +02:00
|
|
|
log.mouse.debug("Middle click, setting target: {}".format(
|
2014-07-28 20:41:42 +02:00
|
|
|
self.open_target))
|
2014-05-14 20:59:21 +02:00
|
|
|
else:
|
2014-08-26 19:10:14 +02:00
|
|
|
self.open_target = usertypes.ClickTarget.normal
|
2014-05-23 16:11:55 +02:00
|
|
|
log.mouse.debug("Normal click, setting normal target")
|
2014-05-14 20:59:21 +02:00
|
|
|
|
2014-08-01 23:23:31 +02:00
|
|
|
def shutdown(self):
|
|
|
|
"""Shut down the webview."""
|
|
|
|
# We disable javascript because that prevents some segfaults when
|
|
|
|
# quitting it seems.
|
|
|
|
settings = self.settings()
|
|
|
|
settings.setAttribute(QWebSettings.JavascriptEnabled, False)
|
|
|
|
self.stop()
|
2014-09-02 07:01:23 +02:00
|
|
|
self.page().networkAccessManager().shutdown()
|
2014-08-01 23:23:31 +02:00
|
|
|
# Explicitely releasing the page here seems to prevent some segfaults
|
|
|
|
# when quitting.
|
|
|
|
# Copied from:
|
|
|
|
# https://code.google.com/p/webscraping/source/browse/webkit.py#325
|
|
|
|
self.setPage(None)
|
|
|
|
|
2014-03-03 21:35:13 +01:00
|
|
|
def openurl(self, url):
|
2014-05-15 16:27:34 +02:00
|
|
|
"""Open a URL in the browser.
|
2014-03-03 21:35:13 +01:00
|
|
|
|
|
|
|
Args:
|
2014-06-20 16:33:01 +02:00
|
|
|
url: The URL to load as QUrl
|
2014-03-03 21:35:13 +01:00
|
|
|
|
|
|
|
Return:
|
|
|
|
Return status of self.load
|
|
|
|
|
|
|
|
Emit:
|
2014-05-15 22:31:01 +02:00
|
|
|
titleChanged
|
2014-03-03 21:35:13 +01:00
|
|
|
"""
|
2014-08-26 19:23:06 +02:00
|
|
|
qtutils.ensure_valid(url)
|
2014-06-20 19:50:44 +02:00
|
|
|
urlstr = url.toDisplayString()
|
2014-06-20 16:33:01 +02:00
|
|
|
log.webview.debug("New title: {}".format(urlstr))
|
|
|
|
self.titleChanged.emit(urlstr)
|
2014-08-02 02:35:38 +02:00
|
|
|
self.cur_url = url
|
2014-09-02 21:54:07 +02:00
|
|
|
self.url_text_changed.emit(url.toDisplayString())
|
2014-06-20 16:33:01 +02:00
|
|
|
return self.load(url)
|
2014-03-03 21:35:13 +01:00
|
|
|
|
2014-05-09 14:20:26 +02:00
|
|
|
def zoom_perc(self, perc, fuzzyval=True):
|
|
|
|
"""Zoom to a given zoom percentage.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
perc: The zoom percentage as int.
|
|
|
|
fuzzyval: Whether to set the NeighborLists fuzzyval.
|
|
|
|
"""
|
|
|
|
if fuzzyval:
|
|
|
|
self._zoom.fuzzyval = int(perc)
|
2014-05-15 15:41:54 +02:00
|
|
|
if perc < 0:
|
2014-08-26 19:10:14 +02:00
|
|
|
raise cmdexc.CommandError("Can't zoom {}%!".format(perc))
|
2014-05-09 14:20:26 +02:00
|
|
|
self.setZoomFactor(float(perc) / 100)
|
2014-05-19 03:40:10 +02:00
|
|
|
message.info("Zoom level: {}%".format(perc))
|
2014-05-09 14:20:26 +02:00
|
|
|
|
2014-03-03 21:35:13 +01:00
|
|
|
def zoom(self, offset):
|
|
|
|
"""Increase/Decrease the zoom level.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
offset: The offset in the zoom level list.
|
|
|
|
"""
|
|
|
|
level = self._zoom.getitem(offset)
|
2014-05-09 14:20:26 +02:00
|
|
|
self.zoom_perc(level, fuzzyval=False)
|
2014-03-03 21:35:13 +01:00
|
|
|
|
2014-05-17 22:38:07 +02:00
|
|
|
@pyqtSlot(str, int)
|
|
|
|
def search(self, text, flags):
|
|
|
|
"""Search for text in the current page.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
text: The text to search for.
|
|
|
|
flags: The QWebPage::FindFlags.
|
|
|
|
"""
|
|
|
|
self._tabs.currentWidget().findText(text, flags)
|
|
|
|
|
2014-05-13 09:53:29 +02:00
|
|
|
def go_back(self):
|
2014-05-14 18:00:40 +02:00
|
|
|
"""Go back a page in the history."""
|
2014-05-27 16:04:45 +02:00
|
|
|
if self.page().history().canGoBack():
|
2014-05-13 09:53:29 +02:00
|
|
|
self.back()
|
|
|
|
else:
|
2014-08-26 19:10:14 +02:00
|
|
|
raise cmdexc.CommandError("At beginning of history.")
|
2014-05-13 09:53:29 +02:00
|
|
|
|
|
|
|
def go_forward(self):
|
2014-05-14 18:00:40 +02:00
|
|
|
"""Go forward a page in the history."""
|
2014-05-27 16:04:45 +02:00
|
|
|
if self.page().history().canGoForward():
|
2014-05-13 09:53:29 +02:00
|
|
|
self.forward()
|
|
|
|
else:
|
2014-08-26 19:10:14 +02:00
|
|
|
raise cmdexc.CommandError("At end of history.")
|
2014-05-13 09:53:29 +02:00
|
|
|
|
2014-05-15 22:31:01 +02:00
|
|
|
@pyqtSlot('QUrl')
|
|
|
|
def on_url_changed(self, url):
|
2014-10-03 16:58:30 +02:00
|
|
|
"""Update cur_url when URL has changed.
|
|
|
|
|
|
|
|
If the URL is invalid, we just ignore it here.
|
|
|
|
"""
|
|
|
|
if url.isValid():
|
|
|
|
self.cur_url = url
|
|
|
|
self.url_text_changed.emit(url.toDisplayString())
|
2014-05-15 22:31:01 +02:00
|
|
|
|
2014-04-21 16:59:03 +02:00
|
|
|
@pyqtSlot('QMouseEvent')
|
|
|
|
def on_mouse_event(self, evt):
|
|
|
|
"""Post a new mouseevent from a hintmanager."""
|
2014-04-21 17:23:22 +02:00
|
|
|
self.setFocus()
|
2014-04-21 16:59:03 +02:00
|
|
|
QApplication.postEvent(self, evt)
|
|
|
|
|
2014-05-13 11:17:36 +02:00
|
|
|
@pyqtSlot()
|
|
|
|
def on_load_started(self):
|
2014-05-15 17:57:08 +02:00
|
|
|
"""Leave insert/hint mode and set vars when a new page is loading."""
|
|
|
|
self.progress = 0
|
2014-09-15 17:59:54 +02:00
|
|
|
self.viewing_source = False
|
2014-05-15 17:57:08 +02:00
|
|
|
self._has_ssl_errors = False
|
2014-09-02 21:54:07 +02:00
|
|
|
self._set_load_status(LoadStatus.loading)
|
2014-05-13 11:17:36 +02:00
|
|
|
|
2014-04-24 16:03:16 +02:00
|
|
|
@pyqtSlot(bool)
|
2014-05-09 16:43:08 +02:00
|
|
|
def on_load_finished(self, ok):
|
2014-04-27 21:21:14 +02:00
|
|
|
"""Handle auto-insert-mode after loading finished."""
|
2014-05-15 17:57:08 +02:00
|
|
|
if ok and not self._has_ssl_errors:
|
2014-09-02 21:54:07 +02:00
|
|
|
self._set_load_status(LoadStatus.success)
|
2014-05-15 17:57:08 +02:00
|
|
|
elif ok:
|
2014-09-02 21:54:07 +02:00
|
|
|
self._set_load_status(LoadStatus.warn)
|
2014-05-15 17:57:08 +02:00
|
|
|
else:
|
2014-09-02 21:54:07 +02:00
|
|
|
self._set_load_status(LoadStatus.error)
|
2014-04-27 21:21:14 +02:00
|
|
|
if not config.get('input', 'auto-insert-mode'):
|
2014-04-24 17:59:35 +02:00
|
|
|
return
|
2014-09-24 07:06:45 +02:00
|
|
|
cur_mode = objreg.get('mode-manager').mode()
|
2014-09-23 23:17:36 +02:00
|
|
|
if cur_mode == usertypes.KeyMode.insert or not ok:
|
2014-05-09 16:43:08 +02:00
|
|
|
return
|
2014-05-27 16:04:45 +02:00
|
|
|
frame = self.page().currentFrame()
|
2014-09-04 08:00:05 +02:00
|
|
|
try:
|
|
|
|
elem = webelem.WebElementWrapper(frame.findFirstElement(':focus'))
|
|
|
|
except webelem.IsNullError:
|
2014-06-23 14:19:56 +02:00
|
|
|
log.webview.debug("Focused element is null!")
|
2014-09-04 08:00:05 +02:00
|
|
|
return
|
|
|
|
log.modes.debug("focus element: {}".format(repr(elem)))
|
|
|
|
if elem.is_editable():
|
2014-08-26 19:10:14 +02:00
|
|
|
modeman.maybe_enter(usertypes.KeyMode.insert, 'load finished')
|
2014-04-24 16:03:16 +02:00
|
|
|
|
2014-04-21 19:29:11 +02:00
|
|
|
@pyqtSlot(str)
|
|
|
|
def set_force_open_target(self, target):
|
|
|
|
"""Change the forced link target. Setter for _force_open_target.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
target: A string to set self._force_open_target to.
|
|
|
|
"""
|
2014-08-26 19:10:14 +02:00
|
|
|
t = getattr(usertypes.ClickTarget, target)
|
2014-05-23 16:11:55 +02:00
|
|
|
log.webview.debug("Setting force target to {}/{}".format(target, t))
|
2014-05-06 09:09:23 +02:00
|
|
|
self._force_open_target = t
|
2014-04-21 19:29:11 +02:00
|
|
|
|
2014-05-08 20:36:05 +02:00
|
|
|
def createWindow(self, wintype):
|
|
|
|
"""Called by Qt when a page wants to create a new window.
|
|
|
|
|
|
|
|
This function is called from the createWindow() method of the
|
|
|
|
associated QWebPage, each time the page wants to create a new window of
|
|
|
|
the given type. This might be the result, for example, of a JavaScript
|
|
|
|
request to open a document in a new window.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
wintype: This enum describes the types of window that can be
|
|
|
|
created by the createWindow() function.
|
|
|
|
|
|
|
|
QWebPage::WebBrowserWindow: The window is a regular web
|
|
|
|
browser window.
|
|
|
|
QWebPage::WebModalDialog: The window acts as modal dialog.
|
|
|
|
|
|
|
|
Return:
|
|
|
|
The new QWebView object.
|
|
|
|
"""
|
|
|
|
if wintype == QWebPage.WebModalDialog:
|
2014-05-23 16:11:55 +02:00
|
|
|
log.webview.warning("WebModalDialog requested, but we don't "
|
|
|
|
"support that!")
|
2014-09-24 20:27:05 +02:00
|
|
|
return objreg.get('tabbed-browser').tabopen()
|
2014-05-08 20:36:05 +02:00
|
|
|
|
2014-03-03 21:35:13 +01:00
|
|
|
def paintEvent(self, e):
|
|
|
|
"""Extend paintEvent to emit a signal if the scroll position changed.
|
|
|
|
|
|
|
|
This is a bit of a hack: We listen to repaint requests here, in the
|
|
|
|
hope a repaint will always be requested when scrolling, and if the
|
|
|
|
scroll position actually changed, we emit a signal.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
e: The QPaintEvent.
|
|
|
|
|
|
|
|
Emit:
|
2014-05-19 09:50:56 +02:00
|
|
|
scroll_pos_changed; If the scroll position changed.
|
2014-04-17 17:44:27 +02:00
|
|
|
|
|
|
|
Return:
|
|
|
|
The superclass event return value.
|
2014-03-03 21:35:13 +01:00
|
|
|
"""
|
2014-05-27 16:04:45 +02:00
|
|
|
frame = self.page().mainFrame()
|
2014-05-19 09:50:56 +02:00
|
|
|
new_pos = (frame.scrollBarValue(Qt.Horizontal),
|
|
|
|
frame.scrollBarValue(Qt.Vertical))
|
|
|
|
if self._old_scroll_pos != new_pos:
|
|
|
|
self._old_scroll_pos = new_pos
|
|
|
|
m = (frame.scrollBarMaximum(Qt.Horizontal),
|
|
|
|
frame.scrollBarMaximum(Qt.Vertical))
|
|
|
|
perc = (round(100 * new_pos[0] / m[0]) if m[0] != 0 else 0,
|
|
|
|
round(100 * new_pos[1] / m[1]) if m[1] != 0 else 0)
|
|
|
|
self.scroll_pos = perc
|
|
|
|
self.scroll_pos_changed.emit(*perc)
|
2014-03-03 21:35:13 +01:00
|
|
|
# Let superclass handle the event
|
2014-06-22 23:37:16 +02:00
|
|
|
super().paintEvent(e)
|
2014-03-03 21:35:13 +01:00
|
|
|
|
2014-04-23 07:32:27 +02:00
|
|
|
def mousePressEvent(self, e):
|
2014-04-23 17:57:59 +02:00
|
|
|
"""Extend QWidget::mousePressEvent().
|
2014-03-03 21:35:13 +01:00
|
|
|
|
2014-04-23 17:57:59 +02:00
|
|
|
This does the following things:
|
|
|
|
- Check if a link was clicked with the middle button or Ctrl and
|
2014-09-02 21:54:07 +02:00
|
|
|
set the open_target attribute accordingly.
|
2014-04-23 17:57:59 +02:00
|
|
|
- Emit the editable_elem_selected signal if an editable element was
|
|
|
|
clicked.
|
2014-03-03 21:35:13 +01:00
|
|
|
|
|
|
|
Args:
|
|
|
|
e: The arrived event.
|
|
|
|
|
|
|
|
Return:
|
2014-04-23 07:32:27 +02:00
|
|
|
The superclass return value.
|
2014-03-03 21:35:13 +01:00
|
|
|
"""
|
2014-06-06 17:12:54 +02:00
|
|
|
if e.button() in (Qt.XButton1, Qt.XButton2):
|
2014-05-14 20:59:21 +02:00
|
|
|
self._mousepress_backforward(e)
|
2014-06-22 23:37:16 +02:00
|
|
|
super().mousePressEvent(e)
|
2014-06-23 10:18:27 +02:00
|
|
|
return
|
2014-05-14 20:59:21 +02:00
|
|
|
self._mousepress_insertmode(e)
|
|
|
|
self._mousepress_opentarget(e)
|
2014-06-22 23:37:16 +02:00
|
|
|
super().mousePressEvent(e)
|
2014-07-16 09:09:28 +02:00
|
|
|
|
|
|
|
def mouseReleaseEvent(self, e):
|
2014-07-16 10:40:11 +02:00
|
|
|
"""Extend mouseReleaseEvent to enter insert mode if needed."""
|
2014-07-16 09:09:28 +02:00
|
|
|
super().mouseReleaseEvent(e)
|
|
|
|
# We want to make sure we check the focus element after the WebView is
|
|
|
|
# updated completely.
|
|
|
|
QTimer.singleShot(0, self.mouserelease_insertmode)
|