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 - Bleeding Fingers
- artyom.stv - artyom.stv
- hank - hank
- Alec Thomas
Thanks to these people for helpful bits and pieces in the Qt bugtracker and IRC Thanks to these people for helpful bits and pieces in the Qt bugtracker and IRC
channels: channels:

1
TODO
View File

@ -7,7 +7,6 @@ Style
===== =====
_foo = QApplication.instance().obj.foo for global singletons? _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) initialize completion models at some nicer place (not in widget)
Major features Major features

View File

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

View File

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

View File

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

View File

@ -127,9 +127,9 @@ class HintKeyParser(CommandKeyParser):
"""Handle a completed keychain. """Handle a completed keychain.
Emit: 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) self.fire_hint.emit(cmdstr)
else: else:
# execute as command # execute as command

View File

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

View File

@ -26,12 +26,31 @@ import logging
_UNSET = object() _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: class NeighborList:
"""A list of items which saves it current position. """A list of items which saves it current position.
Class attributes: Class attributes:
BLOCK/WRAP/RAISE: Modes, see constructor documentation. Modes: Different modes, see constructor documentation.
Attributes: Attributes:
idx: The current position in the list. idx: The current position in the list.
@ -39,20 +58,18 @@ class NeighborList:
_mode: The current mode. _mode: The current mode.
""" """
BLOCK = 0 Modes = enum('block', 'wrap', 'exception')
WRAP = 1
RAISE = 2
def __init__(self, items=None, default=_UNSET, mode=RAISE): def __init__(self, items=None, default=_UNSET, mode=Modes.exception):
"""Constructor. """Constructor.
Args: Args:
items: The list of items to iterate in. items: The list of items to iterate in.
_default: The initially selected value. _default: The initially selected value.
_mode: Behaviour when the first/last item is reached. _mode: Behaviour when the first/last item is reached.
BLOCK: Stay on the selected item Modes.block: Stay on the selected item
WRAP: Wrap around to the other end Modes.wrap: Wrap around to the other end
RAISE: Raise an IndexError. Modes.exception: Raise an IndexError.
""" """
if items is None: if items is None:
self._items = [] self._items = []
@ -80,7 +97,8 @@ class NeighborList:
The new item. The new item.
Raise: 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), logging.debug("{} items, idx {}, offset {}".format(len(self._items),
self.idx, offset)) self.idx, offset))
@ -92,13 +110,13 @@ class NeighborList:
else: else:
raise IndexError raise IndexError
except IndexError: except IndexError:
if self._mode == self.BLOCK: if self._mode == self.Modes.block:
new = self.curitem() new = self.curitem()
elif self._mode == self.WRAP: elif self._mode == self.Modes.wrap:
self.idx += offset self.idx += offset
self.idx %= len(self.items) self.idx %= len(self.items)
new = self.curitem() new = self.curitem()
elif self._mode == self.RAISE: elif self._mode == self.Modes.exception:
raise raise
else: else:
self.idx += offset self.idx += offset

View File

@ -18,6 +18,7 @@
"""Utilities related to QWebElements. """Utilities related to QWebElements.
Module attributes: Module attributes:
Group: Enum for different kinds of groups.
SELECTORS: CSS selectors for different groups of elements. SELECTORS: CSS selectors for different groups of elements.
FILTERS: A dictionary of filter functions for the modes. FILTERS: A dictionary of filter functions for the modes.
The filter for "links" filters javascript:-links and a-tags The filter for "links" filters javascript:-links and a-tags
@ -25,26 +26,32 @@ Module attributes:
""" """
import qutebrowser.utils.url as urlutils 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 = { SELECTORS = {
'all': ('a, textarea, select, input:not([type=hidden]), button, ' Group.all: ('a, textarea, select, input:not([type=hidden]), button, '
'frame, iframe, [onclick], [onmousedown], [role=link], ' 'frame, iframe, [onclick], [onmousedown], [role=link], '
'[role=option], [role=button], img'), '[role=option], [role=button], img'),
'links': 'a', Group.links: 'a',
'images': 'img', Group.images: 'img',
'editable': ('input[type=text], input[type=email], input[type=url], ' Group.editable: ('input[type=text], input[type=email], input[type=url], '
'input[type=tel], input[type=number], ' 'input[type=tel], input[type=number], '
'input[type=password], input[type=search], textarea'), 'input[type=password], input[type=search], textarea'),
'url': '[src], [href]', Group.url: '[src], [href]',
'prevnext_rel': 'link, [role=link]', Group.prevnext_rel: 'link, [role=link]',
'prevnext': 'a, button, [role=button]', Group.prevnext: 'a, button, [role=button]',
} }
SELECTORS['editable_focused'] = ', '.join( SELECTORS[Group.editable_focused] = ', '.join(
[sel.strip() + ':focus' for sel in SELECTORS['editable'].split(',')]) [sel.strip() + ':focus' for sel in SELECTORS[Group.editable].split(',')])
FILTERS = { FILTERS = {
'links': (lambda e: e.hasAttribute('href') and Group.links: (lambda e: e.hasAttribute('href') and
urlutils.qurl(e.attribute('href')).scheme() != 'javascript'), 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.webpage import BrowserPage
from qutebrowser.browser.hints import HintManager from qutebrowser.browser.hints import HintManager
from qutebrowser.utils.signals import SignalCache 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): class WebView(QWebView):
@ -72,7 +75,7 @@ class WebView(QWebView):
super().__init__(parent) super().__init__(parent)
self._scroll_pos = (-1, -1) self._scroll_pos = (-1, -1)
self._shutdown_callback = None self._shutdown_callback = None
self._open_target = 'normal' self._open_target = Target.normal
self._force_open_target = None self._force_open_target = None
self._destroyed = {} self._destroyed = {}
self._zoom = None self._zoom = None
@ -95,7 +98,7 @@ class WebView(QWebView):
self._zoom = NeighborList( self._zoom = NeighborList(
config.get('general', 'zoom-levels'), config.get('general', 'zoom-levels'),
default=config.get('general', 'default-zoom'), default=config.get('general', 'default-zoom'),
mode=NeighborList.BLOCK) mode=NeighborList.Modes.block)
def _on_destroyed(self, sender): def _on_destroyed(self, sender):
"""Called when a subsystem has been destroyed during shutdown. """Called when a subsystem has been destroyed during shutdown.
@ -226,9 +229,9 @@ class WebView(QWebView):
Emit: Emit:
open_tab: Emitted if window should be opened in a new tab. 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) self.open_tab.emit(url, False)
elif self._open_target == 'bgtab': elif self._open_target == Target.bgtab:
self.open_tab.emit(url, True) self.open_tab.emit(url, True)
else: else:
self.openurl(url) self.openurl(url)
@ -252,7 +255,7 @@ class WebView(QWebView):
return return
frame = self.page_.currentFrame() frame = self.page_.currentFrame()
elem = frame.findFirstElement( elem = frame.findFirstElement(
webelem.SELECTORS['editable_focused']) webelem.SELECTORS[webelem.Group.editable_focused])
logging.debug("focus element: {}".format(not elem.isNull())) logging.debug("focus element: {}".format(not elem.isNull()))
if elem.isNull(): if elem.isNull():
modeman.maybe_leave("insert") modeman.maybe_leave("insert")
@ -266,7 +269,7 @@ class WebView(QWebView):
Args: Args:
target: A string to set self._force_open_target to. 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): def paintEvent(self, e):
"""Extend paintEvent to emit a signal if the scroll position changed. """Extend paintEvent to emit a signal if the scroll position changed.
@ -339,10 +342,10 @@ class WebView(QWebView):
elif (e.button() == Qt.MidButton or elif (e.button() == Qt.MidButton or
e.modifiers() & Qt.ControlModifier): e.modifiers() & Qt.ControlModifier):
if config.get('general', 'background-tabs'): if config.get('general', 'background-tabs'):
self._open_target = "bgtab" self._open_target = Target.bgtab
else: else:
self._open_target = "tab" self._open_target = Target.tab
logging.debug("Setting target: {}".format(self._open_target)) logging.debug("Setting target: {}".format(self._open_target))
else: else:
self._open_target = "normal" self._open_target = Target.normal
return super().mousePressEvent(e) return super().mousePressEvent(e)