Permalink
Browse files

Merge branch 'webengine-hints'

  • Loading branch information...
The-Compiler committed Aug 18, 2016
2 parents 713201a + 9226e3e commit 2b6f4f06986b95b2f520d7ddd484925837d5b890
@@ -21,9 +21,9 @@
import itertools
-from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF
+from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, QTimer
from PyQt5.QtGui import QIcon
-from PyQt5.QtWidgets import QWidget, QApplication
+from PyQt5.QtWidgets import QWidget
from qutebrowser.keyinput import modeman
from qutebrowser.config import config
@@ -60,7 +60,7 @@ class WebTabError(Exception):
"""Base class for various errors."""
-class TabData(QObject):
+class TabData:
"""A simple namespace with a fixed set of attributes.
@@ -73,8 +73,7 @@ class TabData(QObject):
hint_target: Override for open_target for hints.
"""
- def __init__(self, parent=None):
- super().__init__(parent)
+ def __init__(self):
self.keep_icon = False
self.viewing_source = False
self.inspector = None
@@ -87,21 +86,6 @@ def combined_target(self):
else:
return self.open_target
- @pyqtSlot(usertypes.ClickTarget)
- def _on_start_hinting(self, hint_target):
- """Emitted before a hinting-click takes place.
-
- Args:
- hint_target: A ClickTarget member to set self.hint_target to.
- """
- log.webview.debug("Setting force target to {}".format(hint_target))
- self.hint_target = hint_target
-
- @pyqtSlot()
- def _on_stop_hinting(self):
- log.webview.debug("Finishing hinting.")
- self.hint_target = None
-
class AbstractPrinting:
@@ -489,7 +473,7 @@ def __init__(self, win_id, parent=None):
# self.search = AbstractSearch(parent=self)
# self.printing = AbstractPrinting()
- self.data = TabData(parent=self)
+ self.data = TabData()
self._layout = miscwidgets.WrapperLayout(self)
self._widget = None
self._progress = 0
@@ -501,11 +485,7 @@ def __init__(self, win_id, parent=None):
# FIXME:qtwebengine Should this be public api via self.hints?
# Also, should we get it out of objreg?
hintmanager = hints.HintManager(win_id, self.tab_id, parent=self)
- hintmanager.mouse_event.connect(self._on_hint_mouse_event)
- # pylint: disable=protected-access
- hintmanager.start_hinting.connect(self.data._on_start_hinting)
- hintmanager.stop_hinting.connect(self.data._on_stop_hinting)
- # pylint: enable=protected-access
+ hintmanager.hint_events.connect(self._on_hint_events)
objreg.register('hintmanager', hintmanager, scope='tab',
window=self.win_id, tab=self.tab_id)
@@ -532,15 +512,24 @@ def _set_load_status(self, val):
self._load_status = val
self.load_status_changed.emit(val.name)
- @pyqtSlot('QMouseEvent')
- def _on_hint_mouse_event(self, evt):
+ def post_event(self, evt):
+ """Send the given event to the underlying widget."""
+ raise NotImplementedError
+
+ @pyqtSlot(usertypes.ClickTarget, list)
+ def _on_hint_events(self, target, events):
"""Post a new mouse event from a hintmanager."""
- # FIXME:qtwebengine Will this implementation work for QtWebEngine?
- # We probably need to send the event to the
- # focusProxy()?
- log.modes.debug("Hint triggered, focusing {!r}".format(self))
+ log.modes.debug("Sending hint events to {!r} with target {}".format(
+ self, target))
self._widget.setFocus()
- QApplication.postEvent(self._widget, evt)
+ self.data.hint_target = target
+
+ for evt in events:
+ self.post_event(evt)
+
+ def reset_target():
+ self.data.hint_target = None
+ QTimer.singleShot(0, reset_target)
@pyqtSlot(QUrl)
def _on_link_clicked(self, url):
@@ -30,7 +30,6 @@
QTimer)
from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QLabel
-from PyQt5.QtWebKitWidgets import QWebPage
from qutebrowser.config import config, style
from qutebrowser.keyinput import modeman, modeparsers
@@ -118,7 +117,7 @@ def update_text(self, matched, unmatched):
@pyqtSlot()
def _move_to_elem(self):
"""Reposition the label to its element."""
- if self.elem.frame() is None:
+ if not self.elem.has_frame():
# This sometimes happens for some reason...
log.hints.debug("Frame for {!r} vanished!".format(self))
self.hide()
@@ -186,16 +185,10 @@ class HintActions(QObject):
"""Actions which can be done after selecting a hint.
Signals:
- mouse_event: Mouse event to be posted in the web view.
- arg: A QMouseEvent
- start_hinting: Emitted when hinting starts, before a link is clicked.
- arg: The ClickTarget to use.
- stop_hinting: Emitted after a link was clicked.
+ hint_events: Emitted with a ClickTarget and a list of hint event.s
"""
- mouse_event = pyqtSignal('QMouseEvent')
- start_hinting = pyqtSignal(usertypes.ClickTarget)
- stop_hinting = pyqtSignal()
+ hint_events = pyqtSignal(usertypes.ClickTarget, list) # QMouseEvent list
def __init__(self, win_id, parent=None):
super().__init__(parent)
@@ -236,7 +229,6 @@ def click(self, elem, context):
log.hints.debug("{} on '{}' at position {}".format(
action, elem.debug_text(), pos))
- self.start_hinting.emit(target_mapping[context.target])
if context.target in [Target.tab, Target.tab_fg, Target.tab_bg,
Target.window]:
modifiers = Qt.ControlModifier
@@ -262,13 +254,10 @@ def click(self, elem, context):
if context.target == Target.current:
elem.remove_blank_target()
- for evt in events:
- self.mouse_event.emit(evt)
+
+ self.hint_events.emit(target_mapping[context.target], events)
if elem.is_text_input() and elem.is_editable():
- QTimer.singleShot(0, functools.partial(
- elem.frame().page().triggerAction,
- QWebPage.MoveToEndOfDocument))
- QTimer.singleShot(0, self.stop_hinting.emit)
+ QTimer.singleShot(0, context.tab.caret.move_to_end_of_document)
def yank(self, url, context):
"""Yank an element to the clipboard or primary selection.
@@ -311,11 +300,8 @@ def preset_cmd_text(self, url, context):
args = context.get_args(urlstr)
text = ' '.join(args)
if text[0] not in modeparsers.STARTCHARS:
- message.error(self._win_id,
- "Invalid command text '{}'.".format(text),
- immediately=True)
- else:
- message.set_cmd_text(self._win_id, text)
+ raise HintingError("Invalid command text '{}'.".format(text))
+ message.set_cmd_text(self._win_id, text)
def download(self, elem, context):
"""Download a hint URL.
@@ -326,16 +312,20 @@ def download(self, elem, context):
"""
url = elem.resolve_url(context.baseurl)
if url is None:
- raise HintingError
+ raise HintingError("No suitable link found for this element.")
if context.rapid:
prompt = False
else:
prompt = None
+ # FIXME:qtwebengine get a proper API for this
+ # pylint: disable=protected-access
+ page = elem._elem.webFrame().page()
+ # pylint: enable=protected-access
+
download_manager = objreg.get('download-manager', scope='window',
window=self._win_id)
- download_manager.get(url, page=elem.frame().page(),
- prompt_download_directory=prompt)
+ download_manager.get(url, page=page, prompt_download_directory=prompt)
def call_userscript(self, elem, context):
"""Call a userscript from a hint.
@@ -359,7 +349,7 @@ def call_userscript(self, elem, context):
userscripts.run_async(context.tab, cmd, *args, win_id=self._win_id,
env=env)
except userscripts.UnsupportedError as e:
- message.error(self._win_id, str(e), immediately=True)
+ raise HintingError(str(e))
def spawn(self, url, context):
"""Spawn a simple command from a hint.
@@ -407,9 +397,7 @@ class HintManager(QObject):
Target.spawn: "Spawn command via hint",
}
- mouse_event = pyqtSignal('QMouseEvent')
- start_hinting = pyqtSignal(usertypes.ClickTarget)
- stop_hinting = pyqtSignal()
+ hint_events = pyqtSignal(usertypes.ClickTarget, list) # QMouseEvent list
def __init__(self, win_id, tab_id, parent=None):
"""Constructor."""
@@ -420,9 +408,7 @@ def __init__(self, win_id, tab_id, parent=None):
self._word_hinter = WordHinter()
self._actions = HintActions(win_id)
- self._actions.start_hinting.connect(self.start_hinting)
- self._actions.stop_hinting.connect(self.stop_hinting)
- self._actions.mouse_event.connect(self.mouse_event)
+ self._actions.hint_events.connect(self.hint_events)
mode_manager = objreg.get('mode-manager', scope='window',
window=win_id)
@@ -580,11 +566,6 @@ def _number_to_hint_str(self, number, chars, digits=0):
hintstr.insert(0, chars[0])
return ''.join(hintstr)
- def _show_url_error(self):
- """Show an error because no link was found."""
- message.error(self._win_id, "No suitable link found for this element.",
- immediately=True)
-
def _check_args(self, target, *args):
"""Check the arguments passed to start() and raise if they're wrong.
@@ -654,8 +635,7 @@ def _start_cb(self, elems):
self._handle_auto_follow()
@cmdutils.register(instance='hintmanager', scope='tab', name='hint',
- star_args_optional=True, maxsplit=2,
- backend=usertypes.Backend.QtWebKit)
+ star_args_optional=True, maxsplit=2)
@cmdutils.argument('win_id', win_id=True)
def start(self, rapid=False, group=webelem.Group.all, target=Target.normal,
*args, win_id, mode=None):
@@ -719,6 +699,12 @@ def start(self, rapid=False, group=webelem.Group.all, target=Target.normal,
tab = tabbed_browser.currentWidget()
if tab is None:
raise cmdexc.CommandError("No WebView available yet!")
+ if (tab.backend == usertypes.Backend.QtWebEngine and
+ target == Target.download):
+ message.error(self._win_id, "The download target is not available "
+ "yet with QtWebEngine.", immediately=True)
+ return
+
mode_manager = objreg.get('mode-manager', scope='window',
window=self._win_id)
if mode_manager.mode == usertypes.KeyMode.hint:
@@ -901,7 +887,7 @@ def _fire(self, keystr):
}
elem = self._context.labels[keystr].elem
- if elem.frame() is None:
+ if not elem.has_frame():
message.error(self._win_id,
"This element has no webframe.",
immediately=True)
@@ -913,7 +899,9 @@ def _fire(self, keystr):
elif self._context.target in url_handlers:
url = elem.resolve_url(self._context.baseurl)
if url is None:
- self._show_url_error()
+ message.error(self._win_id,
+ "No suitable link found for this element.",
+ immediately=True)
return
handler = functools.partial(url_handlers[self._context.target],
url, self._context)
@@ -932,8 +920,8 @@ def _fire(self, keystr):
try:
handler()
- except HintingError:
- self._show_url_error()
+ except HintingError as e:
+ message.error(self._win_id, str(e), immediately=True)
@cmdutils.register(instance='hintmanager', scope='tab', hide=True,
modes=[usertypes.KeyMode.hint])
@@ -103,9 +103,8 @@ def __repr__(self):
html = None
return utils.get_repr(self, html=html)
- def frame(self):
- """Get the main frame of this element."""
- # FIXME:qtwebengine get rid of this?
+ def has_frame(self):
+ """Check if this element has a valid frame attached."""
raise NotImplementedError
def geometry(self):
@@ -32,10 +32,10 @@ class WebEngineElement(webelem.AbstractWebElement):
"""A web element for QtWebEngine, using JS under the hood."""
- def __init__(self, js_dict, run_js_callable):
+ def __init__(self, js_dict, tab):
self._id = js_dict['id']
self._js_dict = js_dict
- self._run_js = run_js_callable
+ self._tab = tab
def __eq__(self, other):
if not isinstance(other, WebEngineElement):
@@ -58,9 +58,8 @@ def __iter__(self):
def __len__(self):
return len(self._js_dict['attributes'])
- def frame(self):
- log.stub()
- return None
+ def has_frame(self):
+ return True
def geometry(self):
log.stub()
@@ -107,7 +106,7 @@ def set_text(self, text, *, use_js=False):
"""
# FIXME:qtwebengine what to do about use_js with WebEngine?
js_code = javascript.assemble('webelem', 'set_text', self._id, text)
- self._run_js(js_code)
+ self._tab.run_js_async(js_code)
def run_js_async(self, code, callback=None):
"""Run the given JS snippet async on the element."""
@@ -123,11 +122,6 @@ def parent(self):
def rect_on_view(self, *, elem_geometry=None, no_js=False):
"""Get the geometry of the element relative to the webview.
- Uses the getClientRects() JavaScript method to obtain the collection of
- rectangles containing the element and returns the first rectangle which
- is large enough (larger than 1px times 1px). If all rectangles returned
- by getClientRects() are too small, falls back to elem.rect_on_view().
-
Skipping of small rectangles is due to <a> elements containing other
elements with "display:block" style, see
https://github.com/The-Compiler/qutebrowser/issues/1298
@@ -138,7 +132,33 @@ def rect_on_view(self, *, elem_geometry=None, no_js=False):
we want to avoid doing it twice.
no_js: Fall back to the Python implementation
"""
- log.stub()
+ rects = self._js_dict['rects']
+ for rect in rects:
+ # FIXME:qtwebengine
+ # width = rect.get("width", 0)
+ # height = rect.get("height", 0)
+ width = rect['width']
+ height = rect['height']
+ if width > 1 and height > 1:
+ # Fix coordinates according to zoom level
+ # We're not checking for zoom-text-only here as that doesn't
+ # exist for QtWebEngine.
+ zoom = self._tab.zoom.factor()
+ rect["left"] *= zoom
+ rect["top"] *= zoom
+ width *= zoom
+ height *= zoom
+ rect = QRect(rect["left"], rect["top"], width, height)
+ # FIXME:qtwebengine
+ # frame = self._elem.webFrame()
+ # while frame is not None:
+ # # Translate to parent frames' position (scroll position
+ # # is taken care of inside getClientRects)
+ # rect.translate(frame.geometry().topLeft())
+ # frame = frame.parentFrame()
+ return rect
+ log.webview.debug("Couldn't find rectangle for {!r} ({})".format(
+ self, rects))
return QRect()
def is_visible(self, mainframe):
Oops, something went wrong.

0 comments on commit 2b6f4f0

Please sign in to comment.