From 4eebe2dc57012828796137087ae292b8e16ce589 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 12 May 2014 07:49:44 +0200 Subject: [PATCH] First try at hinting with frames --- TODO | 5 +-- qutebrowser/browser/curcommand.py | 2 +- qutebrowser/browser/hints.py | 51 ++++++++++++++------------- qutebrowser/utils/webelem.py | 57 ++++++++++++++++++++++++++----- 4 files changed, 77 insertions(+), 38 deletions(-) diff --git a/TODO b/TODO index 0ec9c608f..2ad90cad7 100644 --- a/TODO +++ b/TODO @@ -75,8 +75,9 @@ Bugs - Pasting highlighted text (to mrxvt) does not work (iggy). -- Hinting problems on https://bugreports.qt-project.org/secure/Dashboard.jspa - http://wiki.gentoo.org/wiki/Lenovo_ThinkPad_T440s (top navbar) +- Hinting problems on http://wiki.gentoo.org/wiki/Lenovo_ThinkPad_T440s (top navbar, e.g. lists, etc.) + +- clicking on text fields at https://bugreports.qt-project.org/secure/Dashboard.jspa doesn't go into insert mode. (because of iframes?) - scroll_page doesn't care about always visible bars, so content gets hidden e.g. http://www.mtb-news.de/forum/t/welcher-schuh-five-ten-vs-oneal.529148/ diff --git a/qutebrowser/browser/curcommand.py b/qutebrowser/browser/curcommand.py index d118419ca..af69e50bd 100644 --- a/qutebrowser/browser/curcommand.py +++ b/qutebrowser/browser/curcommand.py @@ -226,7 +226,7 @@ class CurCommandDispatcher(QObject): targetstr: Where to open the links. """ widget = self._tabs.currentWidget() - frame = widget.page_.currentFrame() + frame = widget.page_.mainFrame() if frame is None: message.error("No frame focused!") return diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index e86cf5ceb..047c3f9d8 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -48,7 +48,7 @@ class HintManager(QObject): HINT_CSS: The CSS template to use for hints. Attributes: - _frame: The QWebFrame to use. + _frames: The QWebFrames to use. _elems: A mapping from keystrings to (elem, label) namedtuples. _baseurl: The URL of the current page. _target: What to do with the opened links. @@ -96,10 +96,10 @@ class HintManager(QObject): """ super().__init__(parent) self._elems = {} - self._frame = None self._target = None self._baseurl = None self._to_follow = None + self._frames = [] modeman.instance().left.connect(self.on_mode_left) def _hint_strings(self, elems): @@ -223,7 +223,7 @@ class HintManager(QObject): The newly created label elment """ css = self._get_hint_css(elem) - doc = self._frame.documentElement() + doc = elem.webFrame().documentElement() # It seems impossible to create an empty QWebElement for which isNull() # is false so we can work with it. # As a workaround, we use appendInside() with markup as argument, and @@ -244,21 +244,15 @@ class HintManager(QObject): else: target = self._target self.set_open_target.emit(Target[target]) - # FIXME Instead of clicking the center, we could have nicer heuristics. - # e.g. parse (-webkit-)border-radius correctly and click text fields at - # the bottom right, and everything else on the top left or so. - point = elem.geometry().center() - scrollpos = self._frame.scrollPosition() - logging.debug("Clicking on \"{}\" at {}/{} - {}/{}".format( - elem.toPlainText(), point.x(), point.y(), scrollpos.x(), - scrollpos.y())) - point -= scrollpos + pos = webelem.pos_on_screen(elem) + logging.debug("Clicking on \"{}\" at {}/{}".format( + elem.toPlainText(), pos.x(), pos.y())) events = [ - QMouseEvent(QEvent.MouseMove, point, Qt.NoButton, Qt.NoButton, + QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton, Qt.NoModifier), - QMouseEvent(QEvent.MouseButtonPress, point, Qt.LeftButton, + QMouseEvent(QEvent.MouseButtonPress, pos, Qt.LeftButton, Qt.NoButton, Qt.NoModifier), - QMouseEvent(QEvent.MouseButtonRelease, point, Qt.LeftButton, + QMouseEvent(QEvent.MouseButtonRelease, pos, Qt.LeftButton, Qt.NoButton, Qt.NoModifier), ] for evt in events: @@ -365,7 +359,6 @@ class HintManager(QObject): Emit: hint_strings_updated: Emitted to update keypraser. """ - elems = frame.findAllElements(webelem.SELECTORS[group]) self._target = target self._baseurl = baseurl if frame is None: @@ -373,11 +366,14 @@ class HintManager(QObject): # start. But since we had a bug where frame is None in # on_mode_left, we are extra careful here. raise ValueError("start() was called with frame=None") - self._frame = frame + self._frames = webelem.get_child_frames(frame) + elems = [] + for f in self._frames: + elems += f.findAllElements(webelem.SELECTORS[group]) filterfunc = webelem.FILTERS.get(group, lambda e: True) visible_elems = [] for e in elems: - if filterfunc(e) and webelem.is_visible(e, self._frame): + if filterfunc(e) and webelem.is_visible(e): visible_elems.append(e) if not visible_elems: message.error("No elements found.") @@ -398,7 +394,8 @@ class HintManager(QObject): for e, string in zip(visible_elems, strings): label = self._draw_label(e, string) self._elems[string] = ElemTuple(e, label) - frame.contentsSizeChanged.connect(self.on_contents_size_changed) + for f in self._frames: + f.contentsSizeChanged.connect(self.on_contents_size_changed) self.hint_strings_updated.emit(strings) modeman.enter('hint', 'HintManager.start') @@ -508,14 +505,16 @@ class HintManager(QObject): for elem in self._elems.values(): if not elem.label.isNull(): elem.label.removeFromDocument() - if self._frame is not None: - # The frame which was focused in start() might not be available - # anymore, since Qt might already have deleted it (e.g. when a new - # page is loaded). - self._frame.contentsSizeChanged.disconnect( - self.on_contents_size_changed) + if self._frames is not None: + for frame in self._frames: + if frame is not None: + # The frame which was focused in start() might not be + # available anymore, since Qt might already have deleted it + # (e.g. when a new page is loaded). + frame.contentsSizeChanged.disconnect( + self.on_contents_size_changed) self._elems = {} self._to_follow = None self._target = None - self._frame = None + self._frames = [] message.clear() diff --git a/qutebrowser/utils/webelem.py b/qutebrowser/utils/webelem.py index f3e876ff8..01bcd9f40 100644 --- a/qutebrowser/utils/webelem.py +++ b/qutebrowser/utils/webelem.py @@ -25,6 +25,8 @@ Module attributes: without "href". """ +import logging + import qutebrowser.utils.url as urlutils from qutebrowser.utils.usertypes import enum @@ -56,33 +58,48 @@ FILTERS = { } -def is_visible(e, frame=None): +def is_visible(elem): """Check whether the element is currently visible in its frame. Args: - e: The QWebElement to check. - frame: The QWebFrame in which the element should be visible in. - If None, the element's frame is used. + elem: The QWebElement to check. Return: True if the element is visible, False otherwise. """ - if e.isNull(): + # FIXME we should also check if the frame is visible + if elem.isNull(): raise ValueError("Element is a null-element!") - if frame is None: - frame = e.webFrame() - rect = e.geometry() + frame = elem.webFrame() + rect = elem.geometry() if (not rect.isValid()) and rect.x() == 0: # Most likely an invisible link return False framegeom = frame.geometry() + framegeom.moveTo(0, 0) framegeom.translate(frame.scrollPosition()) - if not framegeom.contains(rect.topLeft()): + if not framegeom.intersects(rect): # out of screen return False return True +def pos_on_screen(elem): + """Get the position of the element on the screen.""" + # FIXME Instead of clicking the center, we could have nicer heuristics. + # e.g. parse (-webkit-)border-radius correctly and click text fields at + # the bottom right, and everything else on the top left or so. + frame = elem.webFrame() + pos = elem.geometry().center() + while frame is not None: + pos += frame.geometry().topLeft() + logging.debug("After adding frame pos: {}".format(pos)) + pos -= frame.scrollPosition() + logging.debug("After removing frame scrollpos: {}".format(pos)) + frame = frame.parentFrame() + return pos + + def javascript_escape(text): """Escape values special to javascript in strings. @@ -102,3 +119,25 @@ def javascript_escape(text): for orig, repl in replacements: text = text.replace(orig, repl) return text + + +def get_child_frames(startframe): + """Get all children recursively of a given QWebFrame. + + Loosly based on http://blog.nextgenetics.net/?e=64 + + Args: + startframe: The QWebFrame to start with. + + Return: + A list of children QWebFrame, or an empty list. + """ + results = [] + frames = [startframe] + while frames: + new_frames = [] + for frame in frames: + results.append(frame) + new_frames += frame.childFrames() + frames = new_frames + return results