Use simple enums for constants

This commit is contained in:
Florian Bruhin 2014-05-05 07:45:36 +02:00
parent 91e7565d1e
commit 4ebe643ea6
11 changed files with 215 additions and 176 deletions

1
THANKS
View File

@ -27,6 +27,7 @@ valuable ones:
- Bleeding Fingers
- artyom.stv
- hank
- Alec Thomas
Thanks to these people for helpful bits and pieces in the Qt bugtracker and IRC
channels:

1
TODO
View File

@ -7,7 +7,6 @@ Style
=====
_foo = QApplication.instance().obj.foo for global singletons?
Use py3.4 Enums with a backport to qutebrowser.utils?
initialize completion models at some nicer place (not in widget)
Major features

View File

@ -33,6 +33,7 @@ import qutebrowser.utils.message as message
import qutebrowser.commands.utils as cmdutils
import qutebrowser.utils.webelem as webelem
import qutebrowser.config.config as config
import qutebrowser.browser.hints as hints
from qutebrowser.utils.misc import shell_escape
@ -216,21 +217,31 @@ class CurCommandDispatcher(QObject):
break
@cmdutils.register(instance='mainwindow.tabs.cur')
def hint(self, mode='all', target='normal'):
def hint(self, groupstr='all', targetstr='normal'):
"""Start hinting.
Command handler for :hint.
Args:
mode: The hinting mode to use.
target: Where to open the links.
groupstr: The hinting mode to use.
targetstr: Where to open the links.
"""
widget = self._tabs.currentWidget()
frame = widget.page_.currentFrame()
if frame is None:
message.error("No frame focused!")
else:
widget.hintmanager.start(frame, widget.url(), mode, target)
return
try:
group = getattr(webelem.Group, groupstr)
except AttributeError:
message.error("Unknown hinting group {}!".format(groupstr))
return
try:
target = getattr(hints.Target, targetstr)
except AttributeError:
message.error("Unknown hinting target {}!".format(targetstr))
return
widget.hintmanager.start(frame, widget.url(), group, target)
@cmdutils.register(instance='mainwindow.tabs.cur', hide=True)
def follow_hint(self):

View File

@ -30,11 +30,16 @@ import qutebrowser.keyinput.modeman as modeman
import qutebrowser.utils.message as message
import qutebrowser.utils.url as urlutils
import qutebrowser.utils.webelem as webelem
from qutebrowser.utils.usertypes import enum
ElemTuple = namedtuple('ElemTuple', 'elem, label')
Target = enum('normal', 'tab', 'bgtab', 'yank', 'yank_primary', 'cmd',
'cmd_tab', 'cmd_bgtab', 'rapid')
class HintManager(QObject):
"""Manage drawing hints over links or other elements.
@ -47,10 +52,10 @@ class HintManager(QObject):
_elems: A mapping from keystrings to (elem, label) namedtuples.
_baseurl: The URL of the current page.
_target: What to do with the opened links.
'normal'/'tab'/'bgtab': Get passed to BrowserTab.
'yank'/'yank_primary': Yank to clipboard/primary selection
'cmd'/'cmd_tab'/'cmd_bgtab': Enter link to commandline
'rapid': Rapid mode with background tabs
normal/tab/bgtab: Get passed to BrowserTab.
yank/yank_primary: Yank to clipboard/primary selection
cmd/cmd_tab/cmd_bgtab: Enter link to commandline
rapid: Rapid mode with background tabs
_to_follow: The link to follow when enter is pressed.
Signals:
@ -217,11 +222,11 @@ class HintManager(QObject):
Args:
elem: The QWebElement to click.
"""
if self._target == 'rapid':
target = 'bgtab'
if self._target == Target.rapid:
target = Target.bgtab
else:
target = self._target
self.set_open_target.emit(target)
self.set_open_target.emit(Target.reverse_mapping[target])
point = elem.geometry().topLeft()
scrollpos = self._frame.scrollPosition()
logging.debug("Clicking on \"{}\" at {}/{} - {}/{}".format(
@ -245,7 +250,7 @@ class HintManager(QObject):
Args:
link: The URL to open.
"""
sel = self._target == 'yank_primary'
sel = self._target == Target.yank_primary
mode = QClipboard.Selection if sel else QClipboard.Clipboard
QApplication.clipboard().setText(urlutils.urlstring(link), mode)
message.info("URL yanked to {}".format("primary selection" if sel
@ -258,9 +263,9 @@ class HintManager(QObject):
link: The link to open.
"""
commands = {
'cmd': 'open',
'cmd_tab': 'tabopen',
'cmd_bgtab': 'backtabopen',
Target.cmd: 'open',
Target.cmd_tab: 'tabopen',
Target.cmd_bgtab: 'backtabopen',
}
message.set_cmd_text(':{} {}'.format(commands[self._target],
urlutils.urlstring(link)))
@ -325,23 +330,20 @@ class HintManager(QObject):
return
self.openurl.emit(link, newtab)
def start(self, frame, baseurl, mode='all', target='normal'):
def start(self, frame, baseurl, group=webelem.Group.all,
target=Target.normal):
"""Start hinting.
Args:
frame: The QWebFrame to place hints in.
baseurl: URL of the current page.
mode: The mode to be used.
group: Which group of elements to hint.
target: What to do with the link. See attribute docstring.
Emit:
hint_strings_updated: Emitted to update keypraser.
"""
try:
elems = frame.findAllElements(webelem.SELECTORS[mode])
except KeyError:
message.error("Hinting mode '{}' does not exist!".format(mode))
return
elems = frame.findAllElements(webelem.SELECTORS[group])
self._target = target
self._baseurl = baseurl
if frame is None:
@ -350,7 +352,7 @@ class HintManager(QObject):
# on_mode_left, we are extra careful here.
raise ValueError("start() was called with frame=None")
self._frame = frame
filterfunc = webelem.FILTERS.get(mode, lambda e: True)
filterfunc = webelem.FILTERS.get(group, lambda e: True)
visible_elems = []
for e in elems:
if filterfunc(e) and webelem.is_visible(e, self._frame):
@ -359,21 +361,17 @@ class HintManager(QObject):
message.error("No elements found.")
return
texts = {
'normal': "Follow hint...",
'tab': "Follow hint in new tab...",
'bgtab': "Follow hint in background tab...",
'yank': "Yank hint to clipboard...",
'yank_primary': "Yank hint to primary selection...",
'cmd': "Set hint in commandline...",
'cmd_tab': "Set hint in commandline as new tab...",
'cmd_bgtab': "Set hint in commandline as background tab...",
'rapid': "Follow hint (rapid mode)...",
Target.normal: "Follow hint...",
Target.tab: "Follow hint in new tab...",
Target.bgtab: "Follow hint in background tab...",
Target.yank: "Yank hint to clipboard...",
Target.yank_primary: "Yank hint to primary selection...",
Target.cmd: "Set hint in commandline...",
Target.cmd_tab: "Set hint in commandline as new tab...",
Target.cmd_bgtab: "Set hint in commandline as background tab...",
Target.rapid: "Follow hint (rapid mode)...",
}
try:
message.text(texts[target])
except KeyError:
message.error("Hinting target '{}' does not exist!".format(target))
return
message.text(texts[target])
strings = self._hint_strings(visible_elems)
for e, string in zip(visible_elems, strings):
label = self._draw_label(e, string)
@ -426,18 +424,18 @@ class HintManager(QObject):
return
# Handlers which take a QWebElement
elem_handlers = {
'normal': self._click,
'tab': self._click,
'bgtab': self._click,
'rapid': self._click,
Target.normal: self._click,
Target.tab: self._click,
Target.bgtab: self._click,
Target.rapid: self._click,
}
# Handlers which take a link string
link_handlers = {
'yank': self._yank,
'yank_primary': self._yank,
'cmd': self._preset_cmd_text,
'cmd_tab': self._preset_cmd_text,
'cmd_bgtab': self._preset_cmd_text,
Target.yank: self._yank,
Target.yank_primary: self._yank,
Target.cmd: self._preset_cmd_text,
Target.cmd_tab: self._preset_cmd_text,
Target.cmd_bgtab: self._preset_cmd_text,
}
elem = self._elems[keystr].elem
if self._target in elem_handlers:
@ -448,7 +446,9 @@ class HintManager(QObject):
message.error("No suitable link found for this element.")
return
link_handlers[self._target](link)
if self._target != 'rapid':
else:
raise ValueError("No suitable handler found!")
if self._target != Target.rapid:
modeman.leave('hint')
def follow_hint(self):

View File

@ -30,95 +30,96 @@ from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWebKit import QWebSettings
import qutebrowser.config.config as config
from qutebrowser.utils.usertypes import enum
ATTRIBUTE = 0
SETTER = 1
STATIC_SETTER = 2
MapType = enum('attribute', 'setter', 'static_setter')
MAPPINGS = {
# noqa
'auto-load-images':
(ATTRIBUTE, QWebSettings.AutoLoadImages),
(MapType.attribute, QWebSettings.AutoLoadImages),
'dns-prefetch-enabled':
(ATTRIBUTE, QWebSettings.DnsPrefetchEnabled),
(MapType.attribute, QWebSettings.DnsPrefetchEnabled),
'javascript-enabled':
(ATTRIBUTE, QWebSettings.JavascriptEnabled),
(MapType.attribute, QWebSettings.JavascriptEnabled),
#'java-enabled':
# (ATTRIBUTE, QWebSettings.JavaEnabled),
# (MapType.attribute, QWebSettings.JavaEnabled),
'plugins-enabled':
(ATTRIBUTE, QWebSettings.PluginsEnabled),
(MapType.attribute, QWebSettings.PluginsEnabled),
'private-browsing-enabled':
(ATTRIBUTE, QWebSettings.PrivateBrowsingEnabled),
(MapType.attribute, QWebSettings.PrivateBrowsingEnabled),
'javascript-can-open-windows':
(ATTRIBUTE, QWebSettings.JavascriptCanOpenWindows),
(MapType.attribute, QWebSettings.JavascriptCanOpenWindows),
'javascript-can-close-windows':
(ATTRIBUTE, QWebSettings.JavascriptCanCloseWindows),
(MapType.attribute, QWebSettings.JavascriptCanCloseWindows),
'javascript-can-access-clipboard':
(ATTRIBUTE, QWebSettings.JavascriptCanAccessClipboard),
(MapType.attribute, QWebSettings.JavascriptCanAccessClipboard),
'developer-extras-enabled':
(ATTRIBUTE, QWebSettings.DeveloperExtrasEnabled),
(MapType.attribute, QWebSettings.DeveloperExtrasEnabled),
'spatial-navigation-enabled':
(ATTRIBUTE, QWebSettings.SpatialNavigationEnabled),
(MapType.attribute, QWebSettings.SpatialNavigationEnabled),
'links-included-in-focus-chain':
(ATTRIBUTE, QWebSettings.LinksIncludedInFocusChain),
(MapType.attribute, QWebSettings.LinksIncludedInFocusChain),
'zoom-text-only':
(ATTRIBUTE, QWebSettings.ZoomTextOnly),
(MapType.attribute, QWebSettings.ZoomTextOnly),
'print-element-backgrounds':
(ATTRIBUTE, QWebSettings.PrintElementBackgrounds),
(MapType.attribute, QWebSettings.PrintElementBackgrounds),
'offline-storage-database-enabled':
(ATTRIBUTE, QWebSettings.OfflineStorageDatabaseEnabled),
(MapType.attribute, QWebSettings.OfflineStorageDatabaseEnabled),
'offline-web-application-storage-enabled':
(ATTRIBUTE, QWebSettings.OfflineWebApplicationCacheEnabled),
(MapType.attribute, QWebSettings.OfflineWebApplicationCacheEnabled),
'local-storage-enabled':
(ATTRIBUTE, QWebSettings.LocalStorageEnabled),
(MapType.attribute, QWebSettings.LocalStorageEnabled),
'local-content-can-access-remote-urls':
(ATTRIBUTE, QWebSettings.LocalContentCanAccessRemoteUrls),
(MapType.attribute, QWebSettings.LocalContentCanAccessRemoteUrls),
'local-content-can-access-file-urls':
(ATTRIBUTE, QWebSettings.LocalContentCanAccessFileUrls),
(MapType.attribute, QWebSettings.LocalContentCanAccessFileUrls),
'xss-auditing-enabled':
(ATTRIBUTE, QWebSettings.XSSAuditingEnabled),
(MapType.attribute, QWebSettings.XSSAuditingEnabled),
#'accelerated-compositing-enabled':
# (ATTRIBUTE, QWebSettings.AcceleratedCompositingEnabled),
# (MapType.attribute, QWebSettings.AcceleratedCompositingEnabled),
#'tiled-backing-store-enabled':
# (ATTRIBUTE, QWebSettings.TiledBackingStoreEnabled),
# (MapType.attribute, QWebSettings.TiledBackingStoreEnabled),
'frame-flattening-enabled':
(ATTRIBUTE, QWebSettings.FrameFlatteningEnabled),
(MapType.attribute, QWebSettings.FrameFlatteningEnabled),
'site-specific-quirks-enabled':
(ATTRIBUTE, QWebSettings.SiteSpecificQuirksEnabled),
(MapType.attribute, QWebSettings.SiteSpecificQuirksEnabled),
'user-stylesheet':
(SETTER, lambda qws, v: qws.setUserStyleSheetUrl(v)),
(MapType.setter, lambda qws, v: qws.setUserStyleSheetUrl(v)),
'css-media-type':
(SETTER, lambda qws, v: qws.setCSSMediaType(v)),
(MapType.setter, lambda qws, v: qws.setCSSMediaType(v)),
'default-encoding':
(SETTER, lambda qws, v: qws.setDefaultTextEncoding(v)),
(MapType.setter, lambda qws, v: qws.setDefaultTextEncoding(v)),
'font-family-standard':
(SETTER, lambda qws, v:
(MapType.setter, lambda qws, v:
qws.setFontFamily(QWebSettings.StandardFont, v)),
'font-family-fixed':
(SETTER, lambda qws, v:
(MapType.setter, lambda qws, v:
qws.setFontFamily(QWebSettings.FixedFont, v)),
'font-family-serif':
(SETTER, lambda qws, v:
(MapType.setter, lambda qws, v:
qws.setFontFamily(QWebSettings.SerifFont, v)),
'font-family-sans-serif':
(SETTER, lambda qws, v:
(MapType.setter, lambda qws, v:
qws.setFontFamily(QWebSettings.SansSerifFont, v)),
'font-family-cursive':
(SETTER, lambda qws, v:
(MapType.setter, lambda qws, v:
qws.setFontFamily(QWebSettings.CursiveFont, v)),
'font-family-fantasy':
(SETTER, lambda qws, v:
(MapType.setter, lambda qws, v:
qws.setFontFamily(QWebSettings.FantasyFont, v)),
'maximum-pages-in-cache':
(STATIC_SETTER, lambda v: QWebSettings.setMaximumPagesInCache(v)),
(MapType.static_setter, lambda v:
QWebSettings.setMaximumPagesInCache(v)),
'object-cache-capacities':
(STATIC_SETTER, lambda v: QWebSettings.setObjectCacheCapacities(*v)),
(MapType.static_setter, lambda v:
QWebSettings.setObjectCacheCapacities(*v)),
'offline-storage-default-quota':
(STATIC_SETTER, lambda v:
(MapType.static_setter, lambda v:
QWebSettings.setOfflineStorageDefaultQuota(v)),
'offline-web-application-cache-quota':
(STATIC_SETTER, lambda v:
(MapType.static_setter, lambda v:
QWebSettings.setOfflineWebApplicationCacheQuota(v)),
}
@ -130,15 +131,16 @@ def _set_setting(typ, arg, value):
"""Set a QWebSettings setting.
Args:
typ: The type of the item (ATTRIBUTE/SETTER/STATIC_SETTER)
typ: The type of the item
(MapType.attribute/MapType.setter/MapType.static_setter)
arg: The argument (attribute/handler)
value: The value to set.
"""
if typ == ATTRIBUTE:
if typ == MapType.attribute:
settings.setAttribute(arg, value)
elif typ == SETTER and value is not None:
elif typ == MapType.setter and value is not None:
arg(settings, value)
elif typ == STATIC_SETTER and value is not None:
elif typ == MapType.static_setter and value is not None:
arg(value)

View File

@ -25,6 +25,7 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QObject, QTimer
from PyQt5.QtGui import QKeySequence
import qutebrowser.config.config as config
from qutebrowser.utils.usertypes import enum
class BaseKeyParser(QObject):
@ -35,14 +36,16 @@ class BaseKeyParser(QObject):
execute() to do whatever they want to.
Class Attributes:
MATCH_PARTIAL: Constant for a partial match (no keychain matched yet,
but it's still possible in the future.
MATCH_DEFINITIVE: Constant for a full match (keychain matches exactly).
MATCH_AMBIGUOUS: There are both a partial and a definitive match.
MATCH_NONE: Constant for no match (no more matches possible).
Match: types of a match between a binding and the keystring.
partial: No keychain matched yet, but it's still possible in the
future.
definitive: Keychain matches exactly.
ambiguous: There are both a partial and a definitive match.
none: No more matches possible.
TYPE_CHAIN: execute() was called via a chain-like keybinding
TYPE_SPECIAL: execute() was called via a special keybinding
Types: type of a keybinding.
chain: execute() was called via a chain-like keybinding
special: execute() was called via a special keybinding
Attributes:
bindings: Bound keybindings
@ -60,13 +63,8 @@ class BaseKeyParser(QObject):
keystring_updated = pyqtSignal(str)
MATCH_PARTIAL = 0
MATCH_DEFINITIVE = 1
MATCH_AMBIGUOUS = 2
MATCH_NONE = 3
TYPE_CHAIN = 0
TYPE_SPECIAL = 1
Match = enum('partial', 'definitive', 'ambiguous', 'none')
Type = enum('chain', 'special')
def __init__(self, parent=None, supports_count=None,
supports_chains=False):
@ -134,7 +132,7 @@ class BaseKeyParser(QObject):
except KeyError:
logging.debug("No binding found for {}.".format(modstr + keystr))
return False
self.execute(cmdstr, self.TYPE_SPECIAL)
self.execute(cmdstr, self.Type.special)
return True
def _handle_single_key(self, e):
@ -173,15 +171,15 @@ class BaseKeyParser(QObject):
(match, binding) = self._match_key(cmd_input)
if match == self.MATCH_DEFINITIVE:
if match == self.Match.definitive:
self._keystring = ''
self.execute(binding, self.TYPE_CHAIN, count)
elif match == self.MATCH_AMBIGUOUS:
self.execute(binding, self.Type.chain, count)
elif match == self.Match.ambiguous:
self._handle_ambiguous_match(binding, count)
elif match == self.MATCH_PARTIAL:
elif match == self.Match.partial:
logging.debug("No match for \"{}\" (added {})".format(
self._keystring, txt))
elif match == self.MATCH_NONE:
elif match == self.Match.none:
logging.debug("Giving up with \"{}\", no matches".format(
self._keystring))
self._keystring = ''
@ -196,11 +194,11 @@ class BaseKeyParser(QObject):
Return:
A tuple (matchtype, binding).
matchtype: MATCH_DEFINITIVE, MATCH_AMBIGUOUS, MATCH_PARTIAL or
MATCH_NONE
binding: - None with MATCH_PARTIAL/MATCH_NONE
- The found binding with MATCH_DEFINITIVE/
MATCH_AMBIGUOUS
matchtype: Match.definitive, Match.ambiguous, Match.partial or
Match.none
binding: - None with Match.partial/Match.none
- The found binding with Match.definitive/
Match.ambiguous
"""
# A (cmd_input, binding) tuple (k, v of bindings) or None.
definitive_match = None
@ -219,13 +217,13 @@ class BaseKeyParser(QObject):
partial_match = True
break
if definitive_match is not None and partial_match:
return (self.MATCH_AMBIGUOUS, definitive_match[1])
return (self.Match.ambiguous, definitive_match[1])
elif definitive_match is not None:
return (self.MATCH_DEFINITIVE, definitive_match[1])
return (self.Match.definitive, definitive_match[1])
elif partial_match:
return (self.MATCH_PARTIAL, None)
return (self.Match.partial, None)
else:
return (self.MATCH_NONE, None)
return (self.Match.none, None)
def _stop_delayed_exec(self):
"""Stop a delayed execution if any is running."""
@ -246,7 +244,7 @@ class BaseKeyParser(QObject):
if time == 0:
# execute immediately
self._keystring = ''
self.execute(binding, self.TYPE_CHAIN, count)
self.execute(binding, self.Type.chain, count)
else:
# execute in `time' ms
logging.debug("Scheduling execution of {} in {}ms".format(binding,
@ -271,7 +269,7 @@ class BaseKeyParser(QObject):
self._timer = None
self._keystring = ''
self.keystring_updated.emit(self._keystring)
self.execute(command, self.TYPE_CHAIN, count)
self.execute(command, self.Type.chain, count)
def handle(self, e):
"""Handle a new keypress and call the respective handlers.
@ -329,7 +327,7 @@ class BaseKeyParser(QObject):
Args:
cmdstr: The command to execute as a string.
keytype: TYPE_CHAIN or TYPE_SPECIAL
keytype: Type.chain or Type.special
count: The count if given.
"""
raise NotImplementedError

View File

@ -127,9 +127,9 @@ class HintKeyParser(CommandKeyParser):
"""Handle a completed keychain.
Emit:
fire_hint: Emitted if keytype is TYPE_CHAIN
fire_hint: Emitted if keytype is chain
"""
if keytype == self.TYPE_CHAIN:
if keytype == self.Type.chain:
self.fire_hint.emit(cmdstr)
else:
# execute as command

View File

@ -133,21 +133,21 @@ class OneTests(TestCase):
self.nl = NeighborList([1], default=1)
def test_first_wrap(self):
self.nl._mode = NeighborList.WRAP
self.nl._mode = NeighborList.Modes.wrap
self.nl.firstitem()
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl.previtem(), 1)
self.assertEqual(self.nl.idx, 0)
def test_first_block(self):
self.nl._mode = NeighborList.BLOCK
self.nl._mode = NeighborList.Modes.block
self.nl.firstitem()
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl.previtem(), 1)
self.assertEqual(self.nl.idx, 0)
def test_first_raise(self):
self.nl._mode = NeighborList.RAISE
self.nl._mode = NeighborList.Modes.exception
self.nl.firstitem()
self.assertEqual(self.nl.idx, 0)
with self.assertRaises(IndexError):
@ -155,21 +155,21 @@ class OneTests(TestCase):
self.assertEqual(self.nl.idx, 0)
def test_last_wrap(self):
self.nl._mode = NeighborList.WRAP
self.nl._mode = NeighborList.Modes.wrap
self.nl.lastitem()
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl.nextitem(), 1)
self.assertEqual(self.nl.idx, 0)
def test_last_block(self):
self.nl._mode = NeighborList.BLOCK
self.nl._mode = NeighborList.Modes.block
self.nl.lastitem()
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl.nextitem(), 1)
self.assertEqual(self.nl.idx, 0)
def test_last_raise(self):
self.nl._mode = NeighborList.RAISE
self.nl._mode = NeighborList.Modes.exception
self.nl.lastitem()
self.assertEqual(self.nl.idx, 0)
with self.assertRaises(IndexError):
@ -179,11 +179,11 @@ class OneTests(TestCase):
class BlockTests(TestCase):
"""Tests with mode=BLOCK."""
"""Tests with mode=block."""
def setUp(self):
self.nl = NeighborList([1, 2, 3, 4, 5], default=3,
mode=NeighborList.BLOCK)
mode=NeighborList.Modes.block)
def test_first(self):
self.nl.firstitem()
@ -200,11 +200,11 @@ class BlockTests(TestCase):
class WrapTests(TestCase):
"""Tests with mode=WRAP."""
"""Tests with mode=wrap."""
def setUp(self):
self.nl = NeighborList([1, 2, 3, 4, 5], default=3,
mode=NeighborList.WRAP)
mode=NeighborList.Modes.wrap)
def test_first(self):
self.nl.firstitem()
@ -221,11 +221,11 @@ class WrapTests(TestCase):
class RaiseTests(TestCase):
"""Tests with mode=RAISE."""
"""Tests with mode=exception."""
def setUp(self):
self.nl = NeighborList([1, 2, 3, 4, 5], default=3,
mode=NeighborList.RAISE)
mode=NeighborList.Modes.exception)
def test_first(self):
self.nl.firstitem()

View File

@ -26,12 +26,31 @@ import logging
_UNSET = object()
def enum(*items, **named):
"""Factory for simple enumerations.
We really don't need more complex things here, so we don't use python3.4's
enum, because we'd have to backport things to 3.3 and maybe even 3.2.
Based on: http://stackoverflow.com/a/1695250/2085149
Args:
*items: Items to be sequentally enumerated.
**named: Items to have a given position/number.
"""
enums = dict(zip(items, range(len(items))), **named)
reverse = dict((v, k) for k, v in enums.items())
enums['reverse_mapping'] = reverse
return type('Enum', (), enums)
class NeighborList:
"""A list of items which saves it current position.
Class attributes:
BLOCK/WRAP/RAISE: Modes, see constructor documentation.
Modes: Different modes, see constructor documentation.
Attributes:
idx: The current position in the list.
@ -39,20 +58,18 @@ class NeighborList:
_mode: The current mode.
"""
BLOCK = 0
WRAP = 1
RAISE = 2
Modes = enum('block', 'wrap', 'exception')
def __init__(self, items=None, default=_UNSET, mode=RAISE):
def __init__(self, items=None, default=_UNSET, mode=Modes.exception):
"""Constructor.
Args:
items: The list of items to iterate in.
_default: The initially selected value.
_mode: Behaviour when the first/last item is reached.
BLOCK: Stay on the selected item
WRAP: Wrap around to the other end
RAISE: Raise an IndexError.
Modes.block: Stay on the selected item
Modes.wrap: Wrap around to the other end
Modes.exception: Raise an IndexError.
"""
if items is None:
self._items = []
@ -80,7 +97,8 @@ class NeighborList:
The new item.
Raise:
IndexError if the border of the list is reached and mode is RAISE.
IndexError if the border of the list is reached and mode is
exception.
"""
logging.debug("{} items, idx {}, offset {}".format(len(self._items),
self.idx, offset))
@ -92,13 +110,13 @@ class NeighborList:
else:
raise IndexError
except IndexError:
if self._mode == self.BLOCK:
if self._mode == self.Modes.block:
new = self.curitem()
elif self._mode == self.WRAP:
elif self._mode == self.Modes.wrap:
self.idx += offset
self.idx %= len(self.items)
new = self.curitem()
elif self._mode == self.RAISE:
elif self._mode == self.Modes.exception:
raise
else:
self.idx += offset

View File

@ -18,6 +18,7 @@
"""Utilities related to QWebElements.
Module attributes:
Group: Enum for different kinds of groups.
SELECTORS: CSS selectors for different groups of elements.
FILTERS: A dictionary of filter functions for the modes.
The filter for "links" filters javascript:-links and a-tags
@ -25,27 +26,33 @@ Module attributes:
"""
import qutebrowser.utils.url as urlutils
from qutebrowser.utils.usertypes import enum
Group = enum('all', 'links', 'images', 'editable', 'url', 'prevnext_rel',
'prevnext', 'editable_focused')
SELECTORS = {
'all': ('a, textarea, select, input:not([type=hidden]), button, '
'frame, iframe, [onclick], [onmousedown], [role=link], '
'[role=option], [role=button], img'),
'links': 'a',
'images': 'img',
'editable': ('input[type=text], input[type=email], input[type=url], '
'input[type=tel], input[type=number], '
'input[type=password], input[type=search], textarea'),
'url': '[src], [href]',
'prevnext_rel': 'link, [role=link]',
'prevnext': 'a, button, [role=button]',
Group.all: ('a, textarea, select, input:not([type=hidden]), button, '
'frame, iframe, [onclick], [onmousedown], [role=link], '
'[role=option], [role=button], img'),
Group.links: 'a',
Group.images: 'img',
Group.editable: ('input[type=text], input[type=email], input[type=url], '
'input[type=tel], input[type=number], '
'input[type=password], input[type=search], textarea'),
Group.url: '[src], [href]',
Group.prevnext_rel: 'link, [role=link]',
Group.prevnext: 'a, button, [role=button]',
}
SELECTORS['editable_focused'] = ', '.join(
[sel.strip() + ':focus' for sel in SELECTORS['editable'].split(',')])
SELECTORS[Group.editable_focused] = ', '.join(
[sel.strip() + ':focus' for sel in SELECTORS[Group.editable].split(',')])
FILTERS = {
'links': (lambda e: e.hasAttribute('href') and
urlutils.qurl(e.attribute('href')).scheme() != 'javascript'),
Group.links: (lambda e: e.hasAttribute('href') and
urlutils.qurl(e.attribute('href')).scheme() != 'javascript'),
}

View File

@ -33,7 +33,10 @@ import qutebrowser.utils.webelem as webelem
from qutebrowser.browser.webpage import BrowserPage
from qutebrowser.browser.hints import HintManager
from qutebrowser.utils.signals import SignalCache
from qutebrowser.utils.usertypes import NeighborList
from qutebrowser.utils.usertypes import NeighborList, enum
Target = enum('normal', 'tab', 'bgtab')
class WebView(QWebView):
@ -72,7 +75,7 @@ class WebView(QWebView):
super().__init__(parent)
self._scroll_pos = (-1, -1)
self._shutdown_callback = None
self._open_target = 'normal'
self._open_target = Target.normal
self._force_open_target = None
self._destroyed = {}
self._zoom = None
@ -95,7 +98,7 @@ class WebView(QWebView):
self._zoom = NeighborList(
config.get('general', 'zoom-levels'),
default=config.get('general', 'default-zoom'),
mode=NeighborList.BLOCK)
mode=NeighborList.Modes.block)
def _on_destroyed(self, sender):
"""Called when a subsystem has been destroyed during shutdown.
@ -226,9 +229,9 @@ class WebView(QWebView):
Emit:
open_tab: Emitted if window should be opened in a new tab.
"""
if self._open_target == 'tab':
if self._open_target == Target.tab:
self.open_tab.emit(url, False)
elif self._open_target == 'bgtab':
elif self._open_target == Target.bgtab:
self.open_tab.emit(url, True)
else:
self.openurl(url)
@ -252,7 +255,7 @@ class WebView(QWebView):
return
frame = self.page_.currentFrame()
elem = frame.findFirstElement(
webelem.SELECTORS['editable_focused'])
webelem.SELECTORS[webelem.Group.editable_focused])
logging.debug("focus element: {}".format(not elem.isNull()))
if elem.isNull():
modeman.maybe_leave("insert")
@ -266,7 +269,7 @@ class WebView(QWebView):
Args:
target: A string to set self._force_open_target to.
"""
self._force_open_target = target
self._force_open_target = getattr(Target, target)
def paintEvent(self, e):
"""Extend paintEvent to emit a signal if the scroll position changed.
@ -339,10 +342,10 @@ class WebView(QWebView):
elif (e.button() == Qt.MidButton or
e.modifiers() & Qt.ControlModifier):
if config.get('general', 'background-tabs'):
self._open_target = "bgtab"
self._open_target = Target.bgtab
else:
self._open_target = "tab"
self._open_target = Target.tab
logging.debug("Setting target: {}".format(self._open_target))
else:
self._open_target = "normal"
self._open_target = Target.normal
return super().mousePressEvent(e)