Get rid of most @property's.

They were problematic because they're executing hidden code, and also PyQt
hides exceptions happening inside them.
This commit is contained in:
Florian Bruhin 2014-09-02 21:54:07 +02:00
parent a2e457ccc3
commit ccfc0b3c19
25 changed files with 274 additions and 425 deletions

View File

@ -471,7 +471,7 @@ class Application(QApplication):
return pages return pages
if self.mainwindow.tabs is None: if self.mainwindow.tabs is None:
return pages return pages
for tab in self.mainwindow.tabs.widgets: for tab in self.mainwindow.tabs.widgets():
try: try:
url = tab.cur_url.toString( url = tab.cur_url.toString(
QUrl.RemovePassword | QUrl.FullyEncoded) QUrl.RemovePassword | QUrl.FullyEncoded)
@ -586,7 +586,7 @@ class Application(QApplication):
# exceptions occur. # exceptions occur.
if pages is None: if pages is None:
pages = [] pages = []
for tab in self.mainwindow.tabs.widgets: for tab in self.mainwindow.tabs.widgets():
urlstr = tab.cur_url.toString( urlstr = tab.cur_url.toString(
QUrl.RemovePassword | QUrl.FullyEncoded) QUrl.RemovePassword | QUrl.FullyEncoded)
if urlstr: if urlstr:

View File

@ -491,7 +491,7 @@ class CommandDispatcher:
@cmdutils.register(instance='mainwindow.tabs.cmd') @cmdutils.register(instance='mainwindow.tabs.cmd')
def tab_only(self): def tab_only(self):
"""Close all tabs except for the current one.""" """Close all tabs except for the current one."""
for tab in self._tabs.widgets: for tab in self._tabs.widgets():
if tab is self._tabs.currentWidget(): if tab is self._tabs.currentWidget():
continue continue
self._tabs.close_tab(tab) self._tabs.close_tab(tab)

View File

@ -43,14 +43,10 @@ class DownloadItem(QObject):
Attributes: Attributes:
reply: The QNetworkReply associated with this download. reply: The QNetworkReply associated with this download.
percentage: How many percent were downloaded successfully.
None if unknown.
bytes_done: How many bytes there are already downloaded. bytes_done: How many bytes there are already downloaded.
bytes_total: The total count of bytes. bytes_total: The total count of bytes.
None if the total is unknown. None if the total is unknown.
speed: The current download speed, in bytes per second. speed: The current download speed, in bytes per second.
remaining_time: The time remaining for the download.
None if not enough data is available yet.
fileobj: The file object to download the file to. fileobj: The file object to download the file to.
filename: The filename of the download. filename: The filename of the download.
is_cancelled: Whether the download was cancelled. is_cancelled: Whether the download was cancelled.
@ -120,13 +116,19 @@ class DownloadItem(QObject):
""" """
speed = utils.format_size(self.speed, suffix='B/s') speed = utils.format_size(self.speed, suffix='B/s')
down = utils.format_size(self.bytes_done, suffix='B') down = utils.format_size(self.bytes_done, suffix='B')
if all(e is None for e in (self.percentage, self.remaining_time, perc = self._percentage()
self.bytes_total)): remaining = self._remaining_time()
if all(e is None for e in (perc, remaining, self.bytes_total)):
return ('{name} [{speed:>10}|{down}]'.format( return ('{name} [{speed:>10}|{down}]'.format(
name=self.basename, speed=speed, down=down)) name=self.basename, speed=speed, down=down))
perc = '??' if self.percentage is None else round(self.percentage) if perc is None:
remaining = (utils.format_seconds(self.remaining_time) perc = '??'
if self.remaining_time is not None else '?') else:
perc = round(perc)
if remaining is None:
remaining = '?'
else:
remaining = utils.format_seconds(remaining)
total = utils.format_size(self.bytes_total, suffix='B') total = utils.format_size(self.bytes_total, suffix='B')
return ('{name} [{speed:>10}|{remaining:>5}|{perc:>2}%|' return ('{name} [{speed:>10}|{remaining:>5}|{perc:>2}%|'
'{down}/{total}]'.format(name=self.basename, speed=speed, '{down}/{total}]'.format(name=self.basename, speed=speed,
@ -145,17 +147,15 @@ class DownloadItem(QObject):
self.error.emit(e.strerror) self.error.emit(e.strerror)
self.finished.emit() self.finished.emit()
@property def _percentage(self):
def percentage(self): """The current download percentage, or None if unknown."""
"""Property to get the current download percentage."""
if self.bytes_total == 0 or self.bytes_total is None: if self.bytes_total == 0 or self.bytes_total is None:
return None return None
else: else:
return 100 * self.bytes_done / self.bytes_total return 100 * self.bytes_done / self.bytes_total
@property def _remaining_time(self):
def remaining_time(self): """The remaining download time in seconds, or None."""
"""Property to get the remaining download time in seconds."""
if self.bytes_total is None or not self.speed_avg: if self.bytes_total is None or not self.speed_avg:
# No average yet or we don't know the total size. # No average yet or we don't know the total size.
return None return None
@ -172,10 +172,10 @@ class DownloadItem(QObject):
start = config.get('colors', 'downloads.bg.start') start = config.get('colors', 'downloads.bg.start')
stop = config.get('colors', 'downloads.bg.stop') stop = config.get('colors', 'downloads.bg.stop')
system = config.get('colors', 'downloads.bg.system') system = config.get('colors', 'downloads.bg.system')
if self.percentage is None: if self._percentage() is None:
return start return start
else: else:
return utils.interpolate_color(start, stop, self.percentage, return utils.interpolate_color(start, stop, self._percentage(),
system) system)
def cancel(self): def cancel(self):

View File

@ -65,25 +65,13 @@ class HintContext:
def __init__(self): def __init__(self):
self.elems = {} self.elems = {}
self._target = None self.target = None
self.baseurl = None self.baseurl = None
self.to_follow = None self.to_follow = None
self.frames = [] self.frames = []
self.connected_frames = [] self.connected_frames = []
self.args = [] self.args = []
@property
def target(self):
"""Getter for target so we can define a setter."""
return self._target
@target.setter
def target(self, val):
"""Setter for target to do type checking."""
if not isinstance(val, Target):
raise TypeError("Target {} is no Target member!".format(val))
self._target = val
def get_args(self, urlstr): def get_args(self, urlstr):
"""Get the arguments, with {hint-url} replaced by the given URL.""" """Get the arguments, with {hint-url} replaced by the given URL."""
args = [] args = []

View File

@ -79,7 +79,7 @@ class Command:
""" """
# We don't use modeman.instance() here to avoid a circular import # We don't use modeman.instance() here to avoid a circular import
# of qutebrowser.keyinput.modeman. # of qutebrowser.keyinput.modeman.
curmode = QCoreApplication.instance().modeman.mode curmode = QCoreApplication.instance().modeman.mode()
if self.modes is not None and curmode not in self.modes: if self.modes is not None and curmode not in self.modes:
mode_names = '/'.join(mode.name for mode in self.modes) mode_names = '/'.join(mode.name for mode in self.modes)
raise cmdexc.PrerequisitesError( raise cmdexc.PrerequisitesError(

View File

@ -198,7 +198,7 @@ class ConfigManager(QObject):
"""Get the option items as string for sect.""" """Get the option items as string for sect."""
lines = [] lines = []
for optname, option in sect.items(): for optname, option in sect.items():
value = option.get_first_value(startlayer='conf') value = option.value(startlayer='conf')
for c in self.KEY_ESCAPE: for c in self.KEY_ESCAPE:
if optname.startswith(c): if optname.startswith(c):
optname = optname.replace(c, self.ESCAPE_CHAR + c, 1) optname = optname.replace(c, self.ESCAPE_CHAR + c, 1)
@ -243,7 +243,7 @@ class ConfigManager(QObject):
if not raw: if not raw:
raise ValueError("items() with raw=True is not implemented!") raise ValueError("items() with raw=True is not implemented!")
for optname, option in self.sections[sectname].items(): for optname, option in self.sections[sectname].items():
items.append((optname, option.value)) items.append((optname, option.value()))
return items return items
def has_option(self, sectname, optname): def has_option(self, sectname, optname):
@ -325,10 +325,10 @@ class ConfigManager(QObject):
except KeyError: except KeyError:
raise NoOptionError(optname, sectname) raise NoOptionError(optname, sectname)
if raw: if raw:
return val.value return val.value()
mapping = {key: val.value for key, val in sect.values.items()} mapping = {key: val.value() for key, val in sect.values.items()}
newval = self._interpolation.before_get(self, sectname, optname, newval = self._interpolation.before_get(self, sectname, optname,
val.value, mapping) val.value(), mapping)
if transformed: if transformed:
newval = val.typ.transform(newval) newval = val.typ.transform(newval)
return newval return newval
@ -403,7 +403,7 @@ class ConfigManager(QObject):
sect = self.sections[sectname] sect = self.sections[sectname]
except KeyError: except KeyError:
raise NoSectionError(sectname) raise NoSectionError(sectname)
mapping = {key: val.value for key, val in sect.values.items()} mapping = {key: val.value() for key, val in sect.values.items()}
interpolated = self._interpolation.before_get(self, sectname, optname, interpolated = self._interpolation.before_get(self, sectname, optname,
value, mapping) value, mapping)
try: try:
@ -462,27 +462,27 @@ class SectionProxy(collections.abc.MutableMapping):
conf: The Config object. conf: The Config object.
name: The section name. name: The section name.
""" """
self._conf = conf self.conf = conf
self._name = name self.name = name
def __repr__(self): def __repr__(self):
return '<{} {}>'.format(self.__class__.__name__, self._name) return '<{} {}>'.format(self.__class__.__name__, self.name)
def __getitem__(self, key): def __getitem__(self, key):
if not self._conf.has_option(self._name, key): if not self.conf.has_option(self.name, key):
raise KeyError(key) raise KeyError(key)
return self._conf.get(self._name, key) return self.conf.get(self.name, key)
def __setitem__(self, key, value): def __setitem__(self, key, value):
return self._conf.set('conf', self._name, key, value) return self.conf.set('conf', self.name, key, value)
def __delitem__(self, key): def __delitem__(self, key):
if not (self._conf.has_option(self._name, key) and if not (self.conf.has_option(self.name, key) and
self._conf.remove_option(self._name, key)): self.conf.remove_option(self.name, key)):
raise KeyError(key) raise KeyError(key)
def __contains__(self, key): def __contains__(self, key):
return self._conf.has_option(self._name, key) return self.conf.has_option(self.name, key)
def __len__(self): def __len__(self):
return len(self._options()) return len(self._options())
@ -492,7 +492,7 @@ class SectionProxy(collections.abc.MutableMapping):
def _options(self): def _options(self):
"""Get the option keys from this section.""" """Get the option keys from this section."""
return self._conf.sections[self._name].keys() return self.conf.sections[self.name].keys()
def get(self, optname, *, raw=False): # pylint: disable=arguments-differ def get(self, optname, *, raw=False): # pylint: disable=arguments-differ
"""Get a value from this section. """Get a value from this section.
@ -504,14 +504,4 @@ class SectionProxy(collections.abc.MutableMapping):
optname: The option name to get. optname: The option name to get.
raw: Whether to get a raw value or not. raw: Whether to get a raw value or not.
""" """
return self._conf.get(self._name, optname, raw=raw) return self.conf.get(self.name, optname, raw=raw)
@property
def conf(self):
"""The conf object of the proxy is read-only."""
return self._conf
@property
def name(self):
"""The name of the section on a proxy is read-only."""
return self._name

View File

@ -120,12 +120,11 @@ class KeyValue(Section):
def dump_userconfig(self): def dump_userconfig(self):
changed = [] changed = []
for k, v in self.items(): for k, v in self.items():
if (v.values['temp'] is not None and vals = v.values
v.values['temp'] != v.values['default']): if vals['temp'] is not None and vals['temp'] != vals['default']:
changed.append((k, v.values['temp'])) changed.append((k, vals['temp']))
elif (v.values['conf'] is not None and elif vals['conf'] is not None and vals['conf'] != vals['default']:
v.values['conf'] != v.values['default']): changed.append((k, vals['conf']))
changed.append((k, v.values['conf']))
return changed return changed
@ -172,8 +171,7 @@ class ValueList(Section):
self.values = collections.ChainMap( self.values = collections.ChainMap(
self.layers['temp'], self.layers['conf'], self.layers['default']) self.layers['temp'], self.layers['conf'], self.layers['default'])
@property def _ordered_values(self):
def ordered_values(self):
"""Get ordered values in layers. """Get ordered values in layers.
This is more expensive than the ChainMap, but we need this for This is more expensive than the ChainMap, but we need this for
@ -201,20 +199,20 @@ class ValueList(Section):
self.layers['temp'], self.layers['conf']) self.layers['temp'], self.layers['conf'])
for k, v in mapping.items(): for k, v in mapping.items():
try: try:
if v.value != self.layers['default'][k].value: if v.value() != self.layers['default'][k].value():
changed.append((k, v.value)) changed.append((k, v.value()))
except KeyError: except KeyError:
changed.append((k, v.value)) changed.append((k, v.value()))
return changed return changed
def __iter__(self): def __iter__(self):
"""Iterate over all set values.""" """Iterate over all set values."""
return self.ordered_values.__iter__() return self._ordered_values().__iter__()
def items(self): def items(self):
"""Get dict items.""" """Get dict items."""
return self.ordered_values.items() return self._ordered_values().items()
def keys(self): def keys(self):
"""Get value keys.""" """Get value keys."""
return self.ordered_values.keys() return self._ordered_values().keys()

View File

@ -31,7 +31,7 @@ class SettingValue:
Attributes: Attributes:
typ: A BaseType subclass instance. typ: A BaseType subclass instance.
value: (readonly property) The currently valid, most important value. value: (readonly property) The currently valid, most important value.
_values: An OrderedDict with the values on different layers, with the values: An OrderedDict with the values on different layers, with the
most significant layer first. most significant layer first.
""" """
@ -43,28 +43,17 @@ class SettingValue:
default: Raw value to set. default: Raw value to set.
""" """
self.typ = typ self.typ = typ
self._values = collections.OrderedDict.fromkeys( self.values = collections.OrderedDict.fromkeys(
['temp', 'conf', 'default']) ['temp', 'conf', 'default'])
self._values['default'] = default self.values['default'] = default
def __str__(self): def __str__(self):
"""Get raw string value.""" """Get raw string value."""
return self.value return self.value
@property
def value(self):
"""Get the currently valid value."""
return self.get_first_value()
@property
def default(self): def default(self):
"""Get the default value.""" """Get the default value."""
return self._values['default'] return self.values['default']
@property
def values(self):
"""Readonly property for _values."""
return self._values
def getlayers(self, startlayer): def getlayers(self, startlayer):
"""Get a dict of values starting with startlayer. """Get a dict of values starting with startlayer.
@ -72,18 +61,18 @@ class SettingValue:
Args: Args:
startlayer: The first layer to include. startlayer: The first layer to include.
""" """
idx = list(self._values.keys()).index(startlayer) idx = list(self.values.keys()).index(startlayer)
d = collections.OrderedDict(list(self._values.items())[idx:]) d = collections.OrderedDict(list(self.values.items())[idx:])
return d return d
def get_first_value(self, startlayer=None): def value(self, startlayer=None):
"""Get the first valid value starting from startlayer. """Get the first valid value starting from startlayer.
Args: Args:
startlayer: The first layer to include. startlayer: The first layer to include.
""" """
if startlayer is None: if startlayer is None:
d = self._values d = self.values
else: else:
d = self.getlayers(startlayer) d = self.getlayers(startlayer)
for val in d.values(): for val in d.values():
@ -94,8 +83,7 @@ class SettingValue:
def transformed(self): def transformed(self):
"""Get the transformed value.""" """Get the transformed value."""
v = self.value return self.typ.transform(self.value())
return self.typ.transform(v)
def setv(self, layer, value, interpolated): def setv(self, layer, value, interpolated):
"""Set the value on a layer. """Set the value on a layer.
@ -107,4 +95,4 @@ class SettingValue:
interpolated: The interpolated value, for typechecking. interpolated: The interpolated value, for typechecking.
""" """
self.typ.validate(interpolated) self.typ.validate(interpolated)
self._values[layer] = value self.values[layer] = value

View File

@ -73,7 +73,6 @@ class ModeManager(QObject):
"""Manager for keyboard modes. """Manager for keyboard modes.
Attributes: Attributes:
mode: The current mode (readonly property).
passthrough: A list of modes in which to pass through events. passthrough: A list of modes in which to pass through events.
mainwindow: The mainwindow object mainwindow: The mainwindow object
locked: Whether current mode is locked. This means the current mode can locked: Whether current mode is locked. This means the current mode can
@ -109,11 +108,10 @@ class ModeManager(QObject):
'forward-unbound-keys') 'forward-unbound-keys')
def __repr__(self): def __repr__(self):
return '<{} mode={}>'.format(self.__class__.__name__, self.mode) return '<{} mode={}>'.format(self.__class__.__name__, self.mode())
@property
def mode(self): def mode(self):
"""Read-only property for the current mode.""" """Get the current mode.."""
if not self._mode_stack: if not self._mode_stack:
return None return None
return self._mode_stack[-1] return self._mode_stack[-1]
@ -127,17 +125,18 @@ class ModeManager(QObject):
Return: Return:
True if event should be filtered, False otherwise. True if event should be filtered, False otherwise.
""" """
handler = self._handlers[self.mode] curmode = self.mode()
if self.mode != usertypes.KeyMode.insert: handler = self._handlers[curmode]
if curmode != usertypes.KeyMode.insert:
log.modes.debug("got keypress in mode {} - calling handler " log.modes.debug("got keypress in mode {} - calling handler "
"{}".format(self.mode, handler.__qualname__)) "{}".format(curmode, handler.__qualname__))
handled = handler(event) if handler is not None else False handled = handler(event) if handler is not None else False
is_non_alnum = bool(event.modifiers()) or not event.text().strip() is_non_alnum = bool(event.modifiers()) or not event.text().strip()
if handled: if handled:
filter_this = True filter_this = True
elif (self.mode in self.passthrough or elif (curmode in self.passthrough or
self._forward_unbound_keys == 'all' or self._forward_unbound_keys == 'all' or
(self._forward_unbound_keys == 'auto' and is_non_alnum)): (self._forward_unbound_keys == 'auto' and is_non_alnum)):
filter_this = False filter_this = False
@ -147,11 +146,11 @@ class ModeManager(QObject):
if not filter_this: if not filter_this:
self._releaseevents_to_pass.append(event) self._releaseevents_to_pass.append(event)
if self.mode != usertypes.KeyMode.insert: if curmode != usertypes.KeyMode.insert:
log.modes.debug("handled: {}, forward-unbound-keys: {}, " log.modes.debug("handled: {}, forward-unbound-keys: {}, "
"passthrough: {}, is_non_alnum: {} --> filter: " "passthrough: {}, is_non_alnum: {} --> filter: "
"{}".format(handled, self._forward_unbound_keys, "{}".format(handled, self._forward_unbound_keys,
self.mode in self.passthrough, curmode in self.passthrough,
is_non_alnum, filter_this)) is_non_alnum, filter_this))
return filter_this return filter_this
@ -172,7 +171,7 @@ class ModeManager(QObject):
filter_this = False filter_this = False
else: else:
filter_this = True filter_this = True
if self.mode != usertypes.KeyMode.insert: if self.mode() != usertypes.KeyMode.insert:
log.modes.debug("filter: {}".format(filter_this)) log.modes.debug("filter: {}".format(filter_this))
return filter_this return filter_this
@ -205,9 +204,9 @@ class ModeManager(QObject):
raise TypeError("Mode {} is no KeyMode member!".format(mode)) raise TypeError("Mode {} is no KeyMode member!".format(mode))
if self.locked: if self.locked:
log.modes.debug("Not entering mode {} because mode is locked to " log.modes.debug("Not entering mode {} because mode is locked to "
"{}.".format(mode, self.mode)) "{}.".format(mode, self.mode()))
raise ModeLockedError("Mode is currently locked to {}".format( raise ModeLockedError("Mode is currently locked to {}".format(
self.mode)) self.mode()))
log.modes.debug("Entering mode {}{}".format( log.modes.debug("Entering mode {}{}".format(
mode, '' if reason is None else ' (reason: {})'.format(reason))) mode, '' if reason is None else ' (reason: {})'.format(reason)))
if mode not in self._handlers: if mode not in self._handlers:
@ -256,9 +255,9 @@ class ModeManager(QObject):
not_modes=[usertypes.KeyMode.normal], hide=True) not_modes=[usertypes.KeyMode.normal], hide=True)
def leave_current_mode(self): def leave_current_mode(self):
"""Leave the mode we're currently in.""" """Leave the mode we're currently in."""
if self.mode == usertypes.KeyMode.normal: if self.mode() == usertypes.KeyMode.normal:
raise ValueError("Can't leave normal mode!") raise ValueError("Can't leave normal mode!")
self.leave(self.mode, 'leave current') self.leave(self.mode(), 'leave current')
@pyqtSlot(str, str) @pyqtSlot(str, str)
def on_config_changed(self, section, option): def on_config_changed(self, section, option):
@ -278,7 +277,7 @@ class ModeManager(QObject):
Return: Return:
True if event should be filtered, False otherwise. True if event should be filtered, False otherwise.
""" """
if self.mode is None: if self.mode() is None:
# We got events before mode is set, so just pass them through. # We got events before mode is set, so just pass them through.
return False return False
typ = event.type() typ = event.type()

View File

@ -88,7 +88,7 @@ class HintKeyParser(keyparser.CommandKeyParser):
Attributes: Attributes:
_filtertext: The text to filter with. _filtertext: The text to filter with.
last_press: The nature of the last keypress, a LastPress member. _last_press: The nature of the last keypress, a LastPress member.
""" """
fire_hint = pyqtSignal(str) fire_hint = pyqtSignal(str)
@ -97,22 +97,9 @@ class HintKeyParser(keyparser.CommandKeyParser):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent, supports_count=False, supports_chains=True) super().__init__(parent, supports_count=False, supports_chains=True)
self._filtertext = '' self._filtertext = ''
self._last_press = None self._last_press = LastPress.none
self.last_press = LastPress.none
self.read_config('keybind.hint') self.read_config('keybind.hint')
@property
def last_press(self):
"""Getter for last_press so we can define a setter."""
return self._last_press
@last_press.setter
def last_press(self, val):
"""Setter for last_press to do typechecking."""
if not isinstance(val, LastPress):
raise TypeError("Value {} is no LastPress member!".format(val))
self._last_press = val
def _handle_special_key(self, e): def _handle_special_key(self, e):
"""Override _handle_special_key to handle string filtering. """Override _handle_special_key to handle string filtering.
@ -132,14 +119,14 @@ class HintKeyParser(keyparser.CommandKeyParser):
e.key(), e.text())) e.key(), e.text()))
if e.key() == Qt.Key_Backspace: if e.key() == Qt.Key_Backspace:
log.keyboard.debug("Got backspace, mode {}, filtertext '{}', " log.keyboard.debug("Got backspace, mode {}, filtertext '{}', "
"keystring '{}'".format(self.last_press, "keystring '{}'".format(self._last_press,
self._filtertext, self._filtertext,
self._keystring)) self._keystring))
if self.last_press == LastPress.filtertext and self._filtertext: if self._last_press == LastPress.filtertext and self._filtertext:
self._filtertext = self._filtertext[:-1] self._filtertext = self._filtertext[:-1]
self.filter_hints.emit(self._filtertext) self.filter_hints.emit(self._filtertext)
return True return True
elif self.last_press == LastPress.keystring and self._keystring: elif self._last_press == LastPress.keystring and self._keystring:
self._keystring = self._keystring[:-1] self._keystring = self._keystring[:-1]
self.keystring_updated.emit(self._keystring) self.keystring_updated.emit(self._keystring)
return True return True
@ -152,7 +139,7 @@ class HintKeyParser(keyparser.CommandKeyParser):
else: else:
self._filtertext += e.text() self._filtertext += e.text()
self.filter_hints.emit(self._filtertext) self.filter_hints.emit(self._filtertext)
self.last_press = LastPress.filtertext self._last_press = LastPress.filtertext
return True return True
def handle(self, e): def handle(self, e):
@ -168,12 +155,12 @@ class HintKeyParser(keyparser.CommandKeyParser):
if handled and self._keystring: if handled and self._keystring:
# A key has been added to the keystring (Match.partial) # A key has been added to the keystring (Match.partial)
self.keystring_updated.emit(self._keystring) self.keystring_updated.emit(self._keystring)
self.last_press = LastPress.keystring self._last_press = LastPress.keystring
return handled return handled
elif handled: elif handled:
# We handled the key but the keystring is empty. This happens when # We handled the key but the keystring is empty. This happens when
# match is Match.definitive, so a keychain has been completed. # match is Match.definitive, so a keychain has been completed.
self.last_press = LastPress.none self._last_press = LastPress.none
return handled return handled
else: else:
# We couldn't find a keychain so we check if it's a special key. # We couldn't find a keychain so we check if it's a special key.

View File

@ -43,7 +43,6 @@ class History:
"""Command history. """Command history.
Attributes: Attributes:
browsing: If we're currently browsing the history (property).
history: A list of executed commands, with newer commands at the end. history: A list of executed commands, with newer commands at the end.
_tmphist: Temporary history for history browsing (as NeighborList) _tmphist: Temporary history for history browsing (as NeighborList)
""" """
@ -63,8 +62,7 @@ class History:
def __getitem__(self, idx): def __getitem__(self, idx):
return self.history[idx] return self.history[idx]
@property def is_browsing(self):
def browsing(self):
"""Check _tmphist to see if we're browsing.""" """Check _tmphist to see if we're browsing."""
return self._tmphist is not None return self._tmphist is not None
@ -101,7 +99,7 @@ class History:
ValueError if start() wasn't called. ValueError if start() wasn't called.
HistoryEndReachedError if the first item was reached. HistoryEndReachedError if the first item was reached.
""" """
if not self.browsing: if not self.is_browsing():
raise ValueError("Currently not browsing history") raise ValueError("Currently not browsing history")
try: try:
return self._tmphist.previtem() return self._tmphist.previtem()
@ -117,7 +115,7 @@ class History:
ValueError if start() wasn't called. ValueError if start() wasn't called.
HistoryEndReachedError if the last item was reached. HistoryEndReachedError if the last item was reached.
""" """
if not self.browsing: if not self.is_browsing():
raise ValueError("Currently not browsing history") raise ValueError("Currently not browsing history")
try: try:
return self._tmphist.nextitem() return self._tmphist.nextitem()

View File

@ -35,7 +35,7 @@ class CompletionFilterModel(QSortFilterProxyModel):
Attributes: Attributes:
srcmodel: The source model of this QSFPM. srcmodel: The source model of this QSFPM.
_pattern: The pattern to filter with, used in pattern property. _pattern: The pattern to filter with.
""" """
def __init__(self, source, parent=None): def __init__(self, source, parent=None):
@ -44,13 +44,7 @@ class CompletionFilterModel(QSortFilterProxyModel):
self.srcmodel = source self.srcmodel = source
self._pattern = '' self._pattern = ''
@property def set_pattern(self, val):
def pattern(self):
"""Getter for pattern."""
return self._pattern
@pattern.setter
def pattern(self, val):
"""Setter for pattern. """Setter for pattern.
Invalidates the filter and re-sorts the model. Invalidates the filter and re-sorts the model.
@ -70,8 +64,7 @@ class CompletionFilterModel(QSortFilterProxyModel):
self.sort(sortcol) self.sort(sortcol)
self.invalidate() self.invalidate()
@property def count(self):
def item_count(self):
"""Get the count of non-toplevel items currently visible. """Get the count of non-toplevel items currently visible.
Note this only iterates one level deep, as we only need root items Note this only iterates one level deep, as we only need root items
@ -121,7 +114,7 @@ class CompletionFilterModel(QSortFilterProxyModel):
def setSourceModel(self, model): def setSourceModel(self, model):
"""Override QSortFilterProxyModel's setSourceModel to clear pattern.""" """Override QSortFilterProxyModel's setSourceModel to clear pattern."""
log.completion.debug("Setting source model: {}".format(model)) log.completion.debug("Setting source model: {}".format(model))
self.pattern = '' self.set_pattern('')
self.srcmodel = model self.srcmodel = model
super().setSourceModel(model) super().setSourceModel(model)
@ -135,7 +128,7 @@ class CompletionFilterModel(QSortFilterProxyModel):
parent: The parent item QModelIndex. parent: The parent item QModelIndex.
Return: Return:
True if self.pattern is contained in item, or if it's a root item True if self._pattern is contained in item, or if it's a root item
(category). False in all other cases (category). False in all other cases
""" """
if parent == QModelIndex(): if parent == QModelIndex():
@ -144,14 +137,14 @@ class CompletionFilterModel(QSortFilterProxyModel):
qtutils.ensure_valid(idx) qtutils.ensure_valid(idx)
data = self.srcmodel.data(idx) data = self.srcmodel.data(idx)
# TODO more sophisticated filtering # TODO more sophisticated filtering
if not self.pattern: if not self._pattern:
return True return True
return self.pattern in data return self._pattern in data
def lessThan(self, lindex, rindex): def lessThan(self, lindex, rindex):
"""Custom sorting implementation. """Custom sorting implementation.
Prefers all items which start with self.pattern. Other than that, uses Prefers all items which start with self._pattern. Other than that, uses
normal Python string sorting. normal Python string sorting.
Args: Args:
@ -173,8 +166,8 @@ class CompletionFilterModel(QSortFilterProxyModel):
left = self.srcmodel.data(lindex) left = self.srcmodel.data(lindex)
right = self.srcmodel.data(rindex) right = self.srcmodel.data(rindex)
leftstart = left.startswith(self.pattern) leftstart = left.startswith(self._pattern)
rightstart = right.startswith(self.pattern) rightstart = right.startswith(self._pattern)
if leftstart and rightstart: if leftstart and rightstart:
return left < right return left < right

View File

@ -148,8 +148,7 @@ class Completer(QObject):
data = model.data(indexes[0]) data = model.data(indexes[0])
if data is None: if data is None:
return return
if model.item_count == 1 and config.get('completion', if model.count() == 1 and config.get('completion', 'quick-complete'):
'quick-complete'):
# If we only have one item, we want to apply it immediately # If we only have one item, we want to apply it immediately
# and go on to the next part. # and go on to the next part.
self.change_completed_part.emit(data, True) self.change_completed_part.emit(data, True)
@ -192,13 +191,13 @@ class Completer(QObject):
return return
pattern = parts[cursor_part] if parts else '' pattern = parts[cursor_part] if parts else ''
self.view.model().pattern = pattern self.view.model().set_pattern(pattern)
log.completion.debug( log.completion.debug(
"New completion for {}: {}, with pattern '{}'".format( "New completion for {}: {}, with pattern '{}'".format(
parts, model.srcmodel.__class__.__name__, pattern)) parts, model.srcmodel.__class__.__name__, pattern))
if self.view.model().item_count == 0: if self.view.model().count() == 0:
self.view.hide() self.view.hide()
return return

View File

@ -46,13 +46,13 @@ def parse_content_disposition(reply):
try: try:
content_disposition = rfc6266.parse_headers( content_disposition = rfc6266.parse_headers(
bytes(reply.rawHeader('Content-Disposition'))) bytes(reply.rawHeader('Content-Disposition')))
filename = content_disposition.filename_unsafe filename = content_disposition.filename()
except UnicodeDecodeError as e: except UnicodeDecodeError as e:
log.misc.warning("Error while getting filename: {}: {}".format( log.misc.warning("Error while getting filename: {}: {}".format(
e.__class__.__name__, e)) e.__class__.__name__, e))
filename = None filename = None
else: else:
is_inline = content_disposition.is_inline is_inline = content_disposition.is_inline()
# Then try to get filename from url # Then try to get filename from url
if not filename: if not filename:
filename = reply.url().path() filename = reply.url().path()

View File

@ -36,8 +36,7 @@ class ReadlineBridge:
def __init__(self): def __init__(self):
self.deleted = {} self.deleted = {}
@property def _widget(self):
def widget(self):
"""Get the currently active QLineEdit.""" """Get the currently active QLineEdit."""
w = QApplication.instance().focusWidget() w = QApplication.instance().focusWidget()
if isinstance(w, QLineEdit): if isinstance(w, QLineEdit):
@ -52,9 +51,10 @@ class ReadlineBridge:
This acts like readline's backward-char. This acts like readline's backward-char.
""" """
if self.widget is None: widget = self._widget()
if widget is None:
return return
self.widget.cursorBackward(False) widget.cursorBackward(False)
@cmdutils.register(instance='rl_bridge', hide=True, @cmdutils.register(instance='rl_bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt]) modes=[typ.KeyMode.command, typ.KeyMode.prompt])
@ -63,9 +63,10 @@ class ReadlineBridge:
This acts like readline's forward-char. This acts like readline's forward-char.
""" """
if self.widget is None: widget = self._widget()
if widget is None:
return return
self.widget.cursorForward(False) widget.cursorForward(False)
@cmdutils.register(instance='rl_bridge', hide=True, @cmdutils.register(instance='rl_bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt]) modes=[typ.KeyMode.command, typ.KeyMode.prompt])
@ -74,9 +75,10 @@ class ReadlineBridge:
This acts like readline's backward-word. This acts like readline's backward-word.
""" """
if self.widget is None: widget = self._widget()
if widget is None:
return return
self.widget.cursorWordBackward(False) widget.cursorWordBackward(False)
@cmdutils.register(instance='rl_bridge', hide=True, @cmdutils.register(instance='rl_bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt]) modes=[typ.KeyMode.command, typ.KeyMode.prompt])
@ -85,9 +87,10 @@ class ReadlineBridge:
This acts like readline's forward-word. This acts like readline's forward-word.
""" """
if self.widget is None: widget = self._widget()
if widget is None:
return return
self.widget.cursorWordForward(False) widget.cursorWordForward(False)
@cmdutils.register(instance='rl_bridge', hide=True, @cmdutils.register(instance='rl_bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt]) modes=[typ.KeyMode.command, typ.KeyMode.prompt])
@ -96,9 +99,10 @@ class ReadlineBridge:
This acts like readline's beginning-of-line. This acts like readline's beginning-of-line.
""" """
if self.widget is None: widget = self._widget()
if widget is None:
return return
self.widget.home(False) widget.home(False)
@cmdutils.register(instance='rl_bridge', hide=True, @cmdutils.register(instance='rl_bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt]) modes=[typ.KeyMode.command, typ.KeyMode.prompt])
@ -107,9 +111,10 @@ class ReadlineBridge:
This acts like readline's end-of-line. This acts like readline's end-of-line.
""" """
if self.widget is None: widget = self._widget()
if widget is None:
return return
self.widget.end(False) widget.end(False)
@cmdutils.register(instance='rl_bridge', hide=True, @cmdutils.register(instance='rl_bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt]) modes=[typ.KeyMode.command, typ.KeyMode.prompt])
@ -118,11 +123,12 @@ class ReadlineBridge:
This acts like readline's unix-line-discard. This acts like readline's unix-line-discard.
""" """
if self.widget is None: widget = self._widget()
if widget is None:
return return
self.widget.home(True) widget.home(True)
self.deleted[self.widget] = self.widget.selectedText() self.deleted[widget] = widget.selectedText()
self.widget.del_() widget.del_()
@cmdutils.register(instance='rl_bridge', hide=True, @cmdutils.register(instance='rl_bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt]) modes=[typ.KeyMode.command, typ.KeyMode.prompt])
@ -131,11 +137,12 @@ class ReadlineBridge:
This acts like readline's kill-line. This acts like readline's kill-line.
""" """
if self.widget is None: widget = self._widget()
if widget is None:
return return
self.widget.end(True) widget.end(True)
self.deleted[self.widget] = self.widget.selectedText() self.deleted[widget] = widget.selectedText()
self.widget.del_() widget.del_()
@cmdutils.register(instance='rl_bridge', hide=True, @cmdutils.register(instance='rl_bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt]) modes=[typ.KeyMode.command, typ.KeyMode.prompt])
@ -144,11 +151,12 @@ class ReadlineBridge:
This acts like readline's unix-word-rubout. This acts like readline's unix-word-rubout.
""" """
if self.widget is None: widget = self._widget()
if widget is None:
return return
self.widget.cursorWordBackward(True) widget.cursorWordBackward(True)
self.deleted[self.widget] = self.widget.selectedText() self.deleted[widget] = widget.selectedText()
self.widget.del_() widget.del_()
@cmdutils.register(instance='rl_bridge', hide=True, @cmdutils.register(instance='rl_bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt]) modes=[typ.KeyMode.command, typ.KeyMode.prompt])
@ -157,11 +165,12 @@ class ReadlineBridge:
This acts like readline's kill-word. This acts like readline's kill-word.
""" """
if self.widget is None: widget = self._widget()
if widget is None:
return return
self.widget.cursorWordForward(True) widget.cursorWordForward(True)
self.deleted[self.widget] = self.widget.selectedText() self.deleted[widget] = widget.selectedText()
self.widget.del_() widget.del_()
@cmdutils.register(instance='rl_bridge', hide=True, @cmdutils.register(instance='rl_bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt]) modes=[typ.KeyMode.command, typ.KeyMode.prompt])
@ -170,9 +179,10 @@ class ReadlineBridge:
This acts like readline's yank. This acts like readline's yank.
""" """
if self.widget is None or self.widget not in self.deleted: widget = self._widget()
if widget is None or widget not in self.deleted:
return return
self.widget.insert(self.deleted[self.widget]) widget.insert(self.deleted[widget])
@cmdutils.register(instance='rl_bridge', hide=True, @cmdutils.register(instance='rl_bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt]) modes=[typ.KeyMode.command, typ.KeyMode.prompt])
@ -181,9 +191,10 @@ class ReadlineBridge:
This acts like readline's delete-char. This acts like readline's delete-char.
""" """
if self.widget is None: widget = self._widget()
if widget is None:
return return
self.widget.del_() widget.del_()
@cmdutils.register(instance='rl_bridge', hide=True, @cmdutils.register(instance='rl_bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt]) modes=[typ.KeyMode.command, typ.KeyMode.prompt])
@ -192,6 +203,7 @@ class ReadlineBridge:
This acts like readline's backward-delete-char. This acts like readline's backward-delete-char.
""" """
if self.widget is None: widget = self._widget()
if widget is None:
return return
self.widget.backspace() widget.backspace()

View File

@ -250,18 +250,17 @@ class ContentDisposition:
assert isinstance(param, ExtDispositionParm) assert isinstance(param, ExtDispositionParm)
self.assocs['filename*'] = parse_ext_value(param.value).string self.assocs['filename*'] = parse_ext_value(param.value).string
@property def filename(self):
def filename_unsafe(self):
"""The filename from the Content-Disposition header or None. """The filename from the Content-Disposition header or None.
On safety: On safety:
This property records the intent of the sender. This property records the intent of the sender.
You shouldn't use this sender-controlled value as a filesystem You shouldn't use this sender-controlled value as a filesystem path, it
path, it can be insecure. Serving files with this filename can be can be insecure. Serving files with this filename can be dangerous as
dangerous as well, due to a certain browser using the part after the well, due to a certain browser using the part after the dot for
dot for mime-sniffing. mime-sniffing. Saving it to a database is fine by itself though.
Saving it to a database is fine by itself though.
""" """
if 'filename*' in self.assocs: if 'filename*' in self.assocs:
@ -270,11 +269,10 @@ class ContentDisposition:
# XXX Reject non-ascii (parsed via qdtext) here? # XXX Reject non-ascii (parsed via qdtext) here?
return self.assocs['filename'] return self.assocs['filename']
@property
def is_inline(self): def is_inline(self):
"""If this property is true, the file should be handled inline. """Return if the file should be handled inline.
Otherwise, and unless your application supports other dispositions If not, and unless your application supports other dispositions
than the standard inline and attachment, it should be handled than the standard inline and attachment, it should be handled
as an attachment. as an attachment.
""" """

View File

@ -60,8 +60,7 @@ class ConsoleLineEdit(misc.CommandLineEdit):
self.returnPressed.connect(self.execute) self.returnPressed.connect(self.execute)
self.setText('') self.setText('')
@property def _curprompt(self):
def curprompt(self):
"""Get the prompt which is visible currently.""" """Get the prompt which is visible currently."""
return sys.ps2 if self._more else sys.ps1 return sys.ps2 if self._more else sys.ps1
@ -79,7 +78,7 @@ class ConsoleLineEdit(misc.CommandLineEdit):
"""Push a line to the interpreter.""" """Push a line to the interpreter."""
self._buffer.append(line) self._buffer.append(line)
source = '\n'.join(self._buffer) source = '\n'.join(self._buffer)
self.write.emit(self.curprompt + line) self.write.emit(self._curprompt() + line)
# We do two special things with the contextmanagers here: # We do two special things with the contextmanagers here:
# - We replace stdout/stderr to capture output. Even if we could # - We replace stdout/stderr to capture output. Even if we could
# override InteractiveInterpreter's write method, most things are # override InteractiveInterpreter's write method, most things are
@ -89,14 +88,14 @@ class ConsoleLineEdit(misc.CommandLineEdit):
# printed and don't ooen a crashdialog. # printed and don't ooen a crashdialog.
with utils.fake_io(self.write.emit), utils.disabled_excepthook(): with utils.fake_io(self.write.emit), utils.disabled_excepthook():
self._more = self._interpreter.runsource(source, '<console>') self._more = self._interpreter.runsource(source, '<console>')
self.set_prompt(self.curprompt) self.set_prompt(self._curprompt())
if not self._more: if not self._more:
self._buffer = [] self._buffer = []
def history_prev(self): def history_prev(self):
"""Go back in the history.""" """Go back in the history."""
try: try:
if not self.history.browsing: if not self.history.is_browsing():
item = self.history.start(self.text().strip()) item = self.history.start(self.text().strip())
else: else:
item = self.history.previtem() item = self.history.previtem()
@ -107,7 +106,7 @@ class ConsoleLineEdit(misc.CommandLineEdit):
def history_next(self): def history_next(self):
"""Go forward in the history.""" """Go forward in the history."""
if not self.history.browsing: if not self.history.is_browsing():
return return
try: try:
item = self.history.nextitem() item = self.history.nextitem()
@ -117,12 +116,12 @@ class ConsoleLineEdit(misc.CommandLineEdit):
def setText(self, text): def setText(self, text):
"""Override setText to always prepend the prompt.""" """Override setText to always prepend the prompt."""
super().setText(self.curprompt + text) super().setText(self._curprompt() + text)
def text(self): def text(self):
"""Override text to strip the prompt.""" """Override text to strip the prompt."""
text = super().text() text = super().text()
return text[len(self.curprompt):] return text[len(self._curprompt()):]
def keyPressEvent(self, e): def keyPressEvent(self, e):
"""Override keyPressEvent to handle up/down keypresses.""" """Override keyPressEvent to handle up/down keypresses."""

View File

@ -65,14 +65,12 @@ class StatusBar(QWidget):
For some reason we need to have this as class attribute so For some reason we need to have this as class attribute so
pyqtProperty works correctly. pyqtProperty works correctly.
_prompt_active: If we're currently in prompt-mode, accessed through the _prompt_active: If we're currently in prompt-mode.
prompt_active property.
For some reason we need to have this as class attribute For some reason we need to have this as class attribute
so pyqtProperty works correctly. so pyqtProperty works correctly.
_insert_active: If we're currently in insert mode, accessed through the _insert_active: If we're currently in insert mode.
insert_active property.
For some reason we need to have this as class attribute For some reason we need to have this as class attribute
so pyqtProperty works correctly. so pyqtProperty works correctly.
@ -180,8 +178,7 @@ class StatusBar(QWidget):
# pylint: disable=method-hidden # pylint: disable=method-hidden
return self._error return self._error
@error.setter def _set_error(self, val):
def error(self, val):
"""Setter for self.error, so it can be used as Qt property. """Setter for self.error, so it can be used as Qt property.
Re-set the stylesheet after setting the value, so everything gets Re-set the stylesheet after setting the value, so everything gets
@ -206,9 +203,8 @@ class StatusBar(QWidget):
# pylint: disable=method-hidden # pylint: disable=method-hidden
return self._prompt_active return self._prompt_active
@prompt_active.setter def _set_prompt_active(self, val):
def prompt_active(self, val): """Setter for self.prompt_active.
"""Setter for self.prompt_active, so it can be used as Qt property.
Re-set the stylesheet after setting the value, so everything gets Re-set the stylesheet after setting the value, so everything gets
updated by Qt properly. updated by Qt properly.
@ -223,9 +219,8 @@ class StatusBar(QWidget):
# pylint: disable=method-hidden # pylint: disable=method-hidden
return self._insert_active return self._insert_active
@insert_active.setter def _set_insert_active(self, val):
def insert_active(self, val): """Setter for self.insert_active.
"""Setter for self.insert_active, so it can be used as Qt property.
Re-set the stylesheet after setting the value, so everything gets Re-set the stylesheet after setting the value, so everything gets
updated by Qt properly. updated by Qt properly.
@ -239,8 +234,8 @@ class StatusBar(QWidget):
try: try:
error, text = self._text_queue.popleft() error, text = self._text_queue.popleft()
except IndexError: except IndexError:
self.error = False self._set_error(False)
self.txt.temptext = '' self.txt.set_text(self.txt.Text.temp, '')
self._text_pop_timer.stop() self._text_pop_timer.stop()
# If a previous widget was interrupted by an error, restore it. # If a previous widget was interrupted by an error, restore it.
if self._previous_widget == PreviousWidget.prompt: if self._previous_widget == PreviousWidget.prompt:
@ -255,12 +250,12 @@ class StatusBar(QWidget):
log.statusbar.debug("Displaying {} message: {}".format( log.statusbar.debug("Displaying {} message: {}".format(
'error' if error else 'text', text)) 'error' if error else 'text', text))
log.statusbar.debug("Remaining: {}".format(self._text_queue)) log.statusbar.debug("Remaining: {}".format(self._text_queue))
self.error = error self._set_error(error)
self.txt.temptext = text self.txt.set_text(self.txt.Text.temp, text)
def _show_cmd_widget(self): def _show_cmd_widget(self):
"""Show command widget instead of temporary text.""" """Show command widget instead of temporary text."""
self.error = False self._set_error(False)
self._previous_widget = PreviousWidget.prompt self._previous_widget = PreviousWidget.prompt
if self._text_pop_timer.isActive(): if self._text_pop_timer.isActive():
self._timer_was_active = True self._timer_was_active = True
@ -281,8 +276,8 @@ class StatusBar(QWidget):
def _show_prompt_widget(self): def _show_prompt_widget(self):
"""Show prompt widget instead of temporary text.""" """Show prompt widget instead of temporary text."""
self.error = False self._set_error(False)
self.prompt_active = True self._set_prompt_active(True)
self._previous_widget = PreviousWidget.prompt self._previous_widget = PreviousWidget.prompt
if self._text_pop_timer.isActive(): if self._text_pop_timer.isActive():
self._timer_was_active = True self._timer_was_active = True
@ -291,7 +286,7 @@ class StatusBar(QWidget):
def _hide_prompt_widget(self): def _hide_prompt_widget(self):
"""Show temporary text instead of prompt widget.""" """Show temporary text instead of prompt widget."""
self.prompt_active = False self._set_prompt_active(False)
self._previous_widget = PreviousWidget.none self._previous_widget = PreviousWidget.none
log.statusbar.debug("Hiding prompt widget, queue: {}".format( log.statusbar.debug("Hiding prompt widget, queue: {}".format(
self._text_queue)) self._text_queue))
@ -328,8 +323,8 @@ class StatusBar(QWidget):
# immediately. We then start the pop_timer only to restore the # immediately. We then start the pop_timer only to restore the
# normal state in 2 seconds. # normal state in 2 seconds.
log.statusbar.debug("Displaying immediately") log.statusbar.debug("Displaying immediately")
self.error = error self._set_error(error)
self.txt.temptext = text self.txt.set_text(self.txt.Text.temp, text)
self._text_pop_timer.start() self._text_pop_timer.start()
elif self._text_queue and self._text_queue[-1] == (error, text): elif self._text_queue and self._text_queue[-1] == (error, text):
# If we get the same message multiple times in a row and we're # If we get the same message multiple times in a row and we're
@ -341,8 +336,8 @@ class StatusBar(QWidget):
# We display this immediately and restart the timer.to clear it and # We display this immediately and restart the timer.to clear it and
# display the rest of the queue later. # display the rest of the queue later.
log.statusbar.debug("Moving to beginning of queue") log.statusbar.debug("Moving to beginning of queue")
self.error = error self._set_error(error)
self.txt.temptext = text self.txt.set_text(self.txt.Text.temp, text)
self._text_pop_timer.start() self._text_pop_timer.start()
else: else:
# There are still some messages to be displayed, so we queue this # There are still some messages to be displayed, so we queue this
@ -376,23 +371,24 @@ class StatusBar(QWidget):
@pyqtSlot(str) @pyqtSlot(str)
def set_text(self, val): def set_text(self, val):
"""Set a normal (persistent) text in the status bar.""" """Set a normal (persistent) text in the status bar."""
self.txt.normaltext = val self.txt.set_text(self.txt.Text.normal, val)
@pyqtSlot(usertypes.KeyMode) @pyqtSlot(usertypes.KeyMode)
def on_mode_entered(self, mode): def on_mode_entered(self, mode):
"""Mark certain modes in the commandline.""" """Mark certain modes in the commandline."""
if mode in modeman.instance().passthrough: if mode in modeman.instance().passthrough:
self.txt.normaltext = "-- {} MODE --".format(mode.name.upper()) text = "-- {} MODE --".format(mode.name.upper())
self.txt.set_text(self.txt.Text.normal, text)
if mode == usertypes.KeyMode.insert: if mode == usertypes.KeyMode.insert:
self.insert_active = True self._set_insert_active(True)
@pyqtSlot(usertypes.KeyMode) @pyqtSlot(usertypes.KeyMode)
def on_mode_left(self, mode): def on_mode_left(self, mode):
"""Clear marked mode.""" """Clear marked mode."""
if mode in modeman.instance().passthrough: if mode in modeman.instance().passthrough:
self.txt.normaltext = "" self.txt.set_text(self.txt.Text.normal, '')
if mode == usertypes.KeyMode.insert: if mode == usertypes.KeyMode.insert:
self.insert_active = False self._set_insert_active(False)
@pyqtSlot(str, str) @pyqtSlot(str, str)
def on_config_changed(self, section, option): def on_config_changed(self, section, option):

View File

@ -81,9 +81,8 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
self.cursorPositionChanged.connect(self.on_cursor_position_changed) self.cursorPositionChanged.connect(self.on_cursor_position_changed)
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored)
@property
def prefix(self): def prefix(self):
"""Property to get the current command prefix entered.""" """Get the currently entered command prefix."""
text = self.text() text = self.text()
if not text: if not text:
return '' return ''
@ -92,10 +91,9 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
else: else:
return '' return ''
@property def split(self):
def parts(self): """Get the text split up in parts."""
"""Property to get the text split up in parts.""" text = self.text()[len(self.prefix()):]
text = self.text()[len(self.prefix):]
if not text: if not text:
# When only ":" is entered, we already have one imaginary part, # When only ":" is entered, we already have one imaginary part,
# which just is empty at the moment. # which just is empty at the moment.
@ -122,8 +120,8 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
spaces = True spaces = True
else: else:
spaces = False spaces = False
cursor_pos -= len(self.prefix) cursor_pos -= len(self.prefix())
for i, part in enumerate(self.parts): for i, part in enumerate(self.split()):
if cursor_pos <= len(part): if cursor_pos <= len(part):
# foo| bar # foo| bar
self.cursor_part = i self.cursor_part = i
@ -140,7 +138,8 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
@pyqtSlot() @pyqtSlot()
def on_cursor_position_changed(self): def on_cursor_position_changed(self):
"""Update completion when the cursor position changed.""" """Update completion when the cursor position changed."""
self.update_completion.emit(self.prefix, self.parts, self.cursor_part) self.update_completion.emit(self.prefix(), self.split(),
self.cursor_part)
@pyqtSlot(str) @pyqtSlot(str)
def set_cmd_text(self, text): def set_cmd_text(self, text):
@ -156,7 +155,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
self.setText(text) self.setText(text)
if old_text != text: if old_text != text:
# We want the completion to pop out here. # We want the completion to pop out here.
self.update_completion.emit(self.prefix, self.parts, self.update_completion.emit(self.prefix(), self.split(),
self.cursor_part) self.cursor_part)
self.setFocus() self.setFocus()
self.show_cmd.emit() self.show_cmd.emit()
@ -189,17 +188,17 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
including a trailing space and we shouldn't continue including a trailing space and we shouldn't continue
completing the current item. completing the current item.
""" """
parts = self.parts[:] parts = self.split()
log.completion.debug("changing part {} to '{}'".format( log.completion.debug("changing part {} to '{}'".format(
self.cursor_part, newtext)) self.cursor_part, newtext))
parts[self.cursor_part] = newtext parts[self.cursor_part] = newtext
# We want to place the cursor directly after the part we just changed. # We want to place the cursor directly after the part we just changed.
cursor_str = self.prefix + ' '.join(parts[:self.cursor_part + 1]) cursor_str = self.prefix() + ' '.join(parts[:self.cursor_part + 1])
if immediate: if immediate:
# If we should complete immediately, we want to move the cursor by # If we should complete immediately, we want to move the cursor by
# one more char, to get to the next field. # one more char, to get to the next field.
cursor_str += ' ' cursor_str += ' '
text = self.prefix + ' '.join(parts) text = self.prefix() + ' '.join(parts)
if immediate and self.cursor_part == len(parts) - 1: if immediate and self.cursor_part == len(parts) - 1:
# If we should complete immediately and we're completing the last # If we should complete immediately and we're completing the last
# part in the commandline, we automatically add a space. # part in the commandline, we automatically add a space.
@ -215,7 +214,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
def command_history_prev(self): def command_history_prev(self):
"""Go back in the commandline history.""" """Go back in the commandline history."""
try: try:
if not self.history.browsing: if not self.history.is_browsing():
item = self.history.start(self.text().strip()) item = self.history.start(self.text().strip())
else: else:
item = self.history.previtem() item = self.history.previtem()
@ -229,7 +228,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
modes=[usertypes.KeyMode.command]) modes=[usertypes.KeyMode.command])
def command_history_next(self): def command_history_next(self):
"""Go forward in the commandline history.""" """Go forward in the commandline history."""
if not self.history.browsing: if not self.history.is_browsing():
return return
try: try:
item = self.history.nextitem() item = self.history.nextitem()

View File

@ -283,7 +283,7 @@ class Prompter:
try: try:
modeman.enter(mode, 'question asked') modeman.enter(mode, 'question asked')
except modeman.ModeLockedError: except modeman.ModeLockedError:
if modeman.instance().mode != usertypes.KeyMode.prompt: if modeman.instance().mode() != usertypes.KeyMode.prompt:
question.abort() question.abort()
return None return None
modeman.instance().locked = True modeman.instance().locked = True

View File

@ -23,6 +23,7 @@ from PyQt5.QtCore import pyqtSlot
from qutebrowser.config import config from qutebrowser.config import config
from qutebrowser.widgets.statusbar import textbase from qutebrowser.widgets.statusbar import textbase
from qutebrowser.utils import usertypes
class Text(textbase.TextBase): class Text(textbase.TextBase):
@ -31,80 +32,63 @@ class Text(textbase.TextBase):
Attributes: Attributes:
_normaltext: The "permanent" text. Never automatically cleared. _normaltext: The "permanent" text. Never automatically cleared.
Accessed via normaltext property.
_temptext: The temporary text to display. _temptext: The temporary text to display.
Accessed via temptext property.
_jstext: The text javascript wants to display. _jstext: The text javascript wants to display.
Accessed via jstext property.
The temptext is shown from StatusBar when a temporary text or error is The temptext is shown from StatusBar when a temporary text or error is
available. If not, the permanent text is shown. available. If not, the permanent text is shown.
""" """
Text = usertypes.enum('Text', 'normal', 'temp', 'js')
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self._normaltext = '' self._normaltext = ''
self._temptext = '' self._temptext = ''
self._jstext = '' self._jstext = ''
@property def set_text(self, which, text):
def normaltext(self): """Set a text.
"""Getter for normaltext so we can define a setter."""
return self._normaltext
@normaltext.setter Args:
def normaltext(self, val): which: Which text to set, a self.Text instance.
"""Setter for normaltext to update text display after setting.""" text: The text to set.
self._normaltext = val """
self._update_text() if which is self.Text.normal:
self._normaltext = text
@property elif which is self.Text.temp:
def temptext(self): self._temptext = text
"""Getter for temptext so we can define a setter.""" elif which is self.Text.js:
return self._temptext self._jstext = text
else:
@temptext.setter raise ValueError("Invalid value {} for which!".format(which))
def temptext(self, val):
"""Setter for temptext to update text display after setting."""
self._temptext = val
self._update_text()
@property
def jstext(self):
"""Getter for jstext so we can define a setter."""
return self._jstext
@jstext.setter
def jstext(self, val):
"""Setter for jstext to update text display after setting."""
self._jstext = val
self._update_text() self._update_text()
def _update_text(self): def _update_text(self):
"""Update QLabel text when needed.""" """Update QLabel text when needed."""
if self.temptext: if self._temptext:
self.setText(self.temptext) self.setText(self._temptext)
elif self.jstext and config.get('ui', 'display-statusbar-messages'): elif self._jstext and config.get('ui', 'display-statusbar-messages'):
self.setText(self.jstext) self.setText(self._jstext)
elif self.normaltext: elif self._normaltext:
self.setText(self.normaltext) self.setText(self._normaltext)
else: else:
self.setText('') self.setText('')
@pyqtSlot(str) @pyqtSlot(str)
def on_statusbar_message(self, val): def on_statusbar_message(self, val):
"""Called when javascript tries to set a statusbar message.""" """Called when javascript tries to set a statusbar message."""
self.jstext = val self._jstext = val
@pyqtSlot() @pyqtSlot()
def on_load_started(self): def on_load_started(self):
"""Clear jstext when page loading started.""" """Clear jstext when page loading started."""
self.jstext = '' self._jstext = ''
@pyqtSlot(int) @pyqtSlot(int)
def on_tab_changed(self, tab): def on_tab_changed(self, tab):
"""Set the correct jstext when the current tab changed.""" """Set the correct jstext when the current tab changed."""
self.jstext = tab.statusbar_message self._jstext = tab.statusbar_message
@pyqtSlot(str, str) @pyqtSlot(str, str)
def on_config_changed(self, section, option): def on_config_changed(self, section, option):

View File

@ -37,9 +37,9 @@ class UrlText(textbase.TextBase):
"""URL displayed in the statusbar. """URL displayed in the statusbar.
Attributes: Attributes:
normal_url: The normal URL to be displayed as a UrlType instance. _normal_url: The normal URL to be displayed as a UrlType instance.
normal_url_type: The type of the normal URL as a UrlType instance. _normal_url_type: The type of the normal URL as a UrlType instance.
hover_url: The URL we're currently hovering over. _hover_url: The URL we're currently hovering over.
_ssl_errors: Whether SSL errors occured while loading. _ssl_errors: Whether SSL errors occured while loading.
Class attributes: Class attributes:
@ -96,64 +96,18 @@ class UrlText(textbase.TextBase):
else: else:
return self._urltype.name return self._urltype.name
@urltype.setter
def urltype(self, val):
"""Setter for self.urltype to update stylesheets after it is set."""
if not isinstance(val, UrlType):
raise TypeError("Type {} is no UrlType member!".format(val))
self._urltype = val
self.setStyleSheet(style.get_stylesheet(self.STYLESHEET))
@property
def hover_url(self):
"""Getter so we can define a setter."""
return self._hover_url
@hover_url.setter
def hover_url(self, val):
"""Setter to update displayed URL when hover_url was set."""
self._hover_url = val
self._update_url()
@property
def normal_url(self):
"""Getter so we can define a setter."""
return self._normal_url
@normal_url.setter
def normal_url(self, val):
"""Setter to update displayed URL when normal_url was set."""
self._normal_url = val
self._update_url()
@property
def normal_url_type(self):
"""Getter so we can define a setter."""
return self._normal_url_type
@normal_url_type.setter
def normal_url_type(self, val):
"""Setter to update displayed URL when normal_url_type was set.
Args:
val: The value as an UrlType instance.
"""
if not isinstance(val, UrlType):
raise TypeError("Type {} is no UrlType member!".format(val))
self._normal_url_type = val
self._update_url()
def _update_url(self): def _update_url(self):
"""Update the displayed URL if the url or the hover url changed.""" """Update the displayed URL if the url or the hover url changed."""
if self.hover_url is not None: if self._hover_url is not None:
self.setText(self.hover_url) self.setText(self._hover_url)
self.urltype = UrlType.hover self._urltype = UrlType.hover
elif self.normal_url is not None: elif self._normal_url is not None:
self.setText(self.normal_url) self.setText(self._normal_url)
self.urltype = self.normal_url_type self._urltype = self._normal_url_type
else: else:
self.setText('') self.setText('')
self.urltype = UrlType.normal self._urltype = UrlType.normal
self.setStyleSheet(style.get_stylesheet(self.STYLESHEET))
@pyqtSlot(str) @pyqtSlot(str)
def on_load_status_changed(self, status_str): def on_load_status_changed(self, status_str):
@ -165,9 +119,10 @@ class UrlText(textbase.TextBase):
status = webview.LoadStatus[status_str] status = webview.LoadStatus[status_str]
if status in (webview.LoadStatus.success, webview.LoadStatus.error, if status in (webview.LoadStatus.success, webview.LoadStatus.error,
webview.LoadStatus.warn): webview.LoadStatus.warn):
self.normal_url_type = UrlType[status_str] self._normal_url_type = UrlType[status_str]
else: else:
self.normal_url_type = UrlType.normal self._normal_url_type = UrlType.normal
self._update_url()
@pyqtSlot(str) @pyqtSlot(str)
def set_url(self, s): def set_url(self, s):
@ -176,8 +131,9 @@ class UrlText(textbase.TextBase):
Args: Args:
s: The URL to set as string. s: The URL to set as string.
""" """
self.normal_url = s self._normal_url = s
self.normal_url_type = UrlType.normal self._normal_url_type = UrlType.normal
self._update_url()
@pyqtSlot(str, str, str) @pyqtSlot(str, str, str)
def set_hover_url(self, link, _title, _text): def set_hover_url(self, link, _title, _text):
@ -192,13 +148,15 @@ class UrlText(textbase.TextBase):
_text: The text of the hovered link (string) _text: The text of the hovered link (string)
""" """
if link: if link:
self.hover_url = link self._hover_url = link
else: else:
self.hover_url = None self._hover_url = None
self._update_url()
@pyqtSlot(int) @pyqtSlot(int)
def on_tab_changed(self, tab): def on_tab_changed(self, tab):
"""Update URL if the tab changed.""" """Update URL if the tab changed."""
self.hover_url = None self._hover_url = None
self.normal_url = tab.cur_url.toDisplayString() self._normal_url = tab.cur_url.toDisplayString()
self.on_load_status_changed(tab.load_status.name) self.on_load_status_changed(tab.load_status.name)
self._update_url()

View File

@ -120,12 +120,12 @@ class TabbedBrowser(tabwidget.TabWidget):
return '<{} with {} tabs>'.format(self.__class__.__name__, return '<{} with {} tabs>'.format(self.__class__.__name__,
self.count()) self.count())
@property
def widgets(self): def widgets(self):
"""Get a list of open tab widgets. """Get a list of open tab widgets.
We don't implement this as generator so we can delete tabs while We don't implement this as generator so we can delete tabs while
iterating over the list.""" iterating over the list.
"""
w = [] w = []
for i in range(self.count()): for i in range(self.count()):
w.append(self.widget(i)) w.append(self.widget(i))
@ -227,7 +227,7 @@ class TabbedBrowser(tabwidget.TabWidget):
except TypeError as e: except TypeError as e:
log.destroy.debug("Error while shutting down tabs: {}: {}".format( log.destroy.debug("Error while shutting down tabs: {}: {}".format(
e.__class__.__name__, e)) e.__class__.__name__, e))
for tab in self.widgets: for tab in self.widgets():
self._remove_tab(tab) self._remove_tab(tab)
def close_tab(self, tab): def close_tab(self, tab):
@ -413,7 +413,7 @@ class TabbedBrowser(tabwidget.TabWidget):
tab.on_config_changed(section, option) tab.on_config_changed(section, option)
if (section, option) == ('tabbar', 'show-favicons'): if (section, option) == ('tabbar', 'show-favicons'):
show = config.get('tabs', 'show-favicons') show = config.get('tabs', 'show-favicons')
for i, tab in enumerate(self.widgets): for i, tab in enumerate(self.widgets()):
if show: if show:
self.setTabIcon(i, tab.icon()) self.setTabIcon(i, tab.icon())
else: else:

View File

@ -50,15 +50,14 @@ class WebView(QWebView):
scroll_pos: The current scroll position as (x%, y%) tuple. scroll_pos: The current scroll position as (x%, y%) tuple.
statusbar_message: The current javscript statusbar message. statusbar_message: The current javscript statusbar message.
inspector: The QWebInspector used for this webview. inspector: The QWebInspector used for this webview.
load_status: loading status of this page (index into LoadStatus)
open_target: Where to open the next tab ("normal", "tab", "tab_bg") open_target: Where to open the next tab ("normal", "tab", "tab_bg")
_page: The QWebPage behind the view _page: The QWebPage behind the view
_cur_url: The current URL (accessed via cur_url property). _cur_url: The current URL (accessed via cur_url property).
_load_status: loading status of this page (index into LoadStatus)
Accessed via load_status property.
_has_ssl_errors: Whether SSL errors occured during loading. _has_ssl_errors: Whether SSL errors occured during loading.
_zoom: A NeighborList with the zoom levels. _zoom: A NeighborList with the zoom levels.
_old_scroll_pos: The old scroll position. _old_scroll_pos: The old scroll position.
_force_open_target: Override for _open_target. _force_open_target: Override for open_target.
_check_insertmode: If True, in mouseReleaseEvent we should check if we _check_insertmode: If True, in mouseReleaseEvent we should check if we
need to enter/leave insert mode. need to enter/leave insert mode.
@ -78,7 +77,6 @@ class WebView(QWebView):
def __init__(self, parent): def __init__(self, parent):
super().__init__(parent) super().__init__(parent)
self._load_status = None
self.load_status = LoadStatus.none self.load_status = LoadStatus.none
self._check_insertmode = False self._check_insertmode = False
self.tabbedbrowser = parent self.tabbedbrowser = parent
@ -86,7 +84,6 @@ class WebView(QWebView):
self.scroll_pos = (-1, -1) self.scroll_pos = (-1, -1)
self.statusbar_message = '' self.statusbar_message = ''
self._old_scroll_pos = (-1, -1) self._old_scroll_pos = (-1, -1)
self._open_target = None
self.open_target = usertypes.ClickTarget.normal self.open_target = usertypes.ClickTarget.normal
self._force_open_target = None self._force_open_target = None
self._zoom = None self._zoom = None
@ -116,25 +113,7 @@ class WebView(QWebView):
url = self.url().toDisplayString() url = self.url().toDisplayString()
return "WebView(url='{}')".format(utils.elide(url, 50)) return "WebView(url='{}')".format(utils.elide(url, 50))
@property def _set_load_status(self, val):
def open_target(self):
"""Getter for open_target so we can define a setter."""
return self._open_target
@open_target.setter
def open_target(self, val):
"""Setter for open_target to do type checking."""
if not isinstance(val, usertypes.ClickTarget):
raise TypeError("Target {} is no ClickTarget member!".format(val))
self._open_target = val
@property
def load_status(self):
"""Getter for load_status."""
return self._load_status
@load_status.setter
def load_status(self, val):
"""Setter for load_status. """Setter for load_status.
Emit: Emit:
@ -143,27 +122,9 @@ class WebView(QWebView):
if not isinstance(val, LoadStatus): if not isinstance(val, LoadStatus):
raise TypeError("Type {} is no LoadStatus member!".format(val)) raise TypeError("Type {} is no LoadStatus member!".format(val))
log.webview.debug("load status for {}: {}".format(repr(self), val)) log.webview.debug("load status for {}: {}".format(repr(self), val))
self._load_status = val self.load_status = val
self.load_status_changed.emit(val.name) self.load_status_changed.emit(val.name)
@property
def cur_url(self):
"""Getter for cur_url so we can define a setter."""
return self._cur_url
@cur_url.setter
def cur_url(self, url):
"""Setter for cur_url to emit a signal..
Arg:
url: The new URL as a QUrl.
Emit:
url_text_changed: Always emitted.
"""
self._cur_url = url
self.url_text_changed.emit(url.toDisplayString())
def _init_neighborlist(self): def _init_neighborlist(self):
"""Initialize the _zoom neighborlist.""" """Initialize the _zoom neighborlist."""
levels = config.get('ui', 'zoom-levels') levels = config.get('ui', 'zoom-levels')
@ -302,6 +263,7 @@ class WebView(QWebView):
log.webview.debug("New title: {}".format(urlstr)) log.webview.debug("New title: {}".format(urlstr))
self.titleChanged.emit(urlstr) self.titleChanged.emit(urlstr)
self.cur_url = url self.cur_url = url
self.url_text_changed.emit(url.toDisplayString())
return self.load(url) return self.load(url)
def zoom_perc(self, perc, fuzzyval=True): def zoom_perc(self, perc, fuzzyval=True):
@ -356,6 +318,7 @@ class WebView(QWebView):
"""Update cur_url when URL has changed.""" """Update cur_url when URL has changed."""
qtutils.ensure_valid(url) qtutils.ensure_valid(url)
self.cur_url = url self.cur_url = url
self.url_text_changed.emit(url.toDisplayString())
@pyqtSlot(str, str) @pyqtSlot(str, str)
def on_config_changed(self, section, option): def on_config_changed(self, section, option):
@ -374,20 +337,20 @@ class WebView(QWebView):
"""Leave insert/hint mode and set vars when a new page is loading.""" """Leave insert/hint mode and set vars when a new page is loading."""
self.progress = 0 self.progress = 0
self._has_ssl_errors = False self._has_ssl_errors = False
self.load_status = LoadStatus.loading self._set_load_status(LoadStatus.loading)
@pyqtSlot(bool) @pyqtSlot(bool)
def on_load_finished(self, ok): def on_load_finished(self, ok):
"""Handle auto-insert-mode after loading finished.""" """Handle auto-insert-mode after loading finished."""
if ok and not self._has_ssl_errors: if ok and not self._has_ssl_errors:
self.load_status = LoadStatus.success self._set_load_status(LoadStatus.success)
elif ok: elif ok:
self.load_status = LoadStatus.warn self._set_load_status(LoadStatus.warn)
else: else:
self.load_status = LoadStatus.error self._set_load_status(LoadStatus.error)
if not config.get('input', 'auto-insert-mode'): if not config.get('input', 'auto-insert-mode'):
return return
if modeman.instance().mode == usertypes.KeyMode.insert or not ok: if modeman.instance().mode() == usertypes.KeyMode.insert or not ok:
return return
frame = self.page().currentFrame() frame = self.page().currentFrame()
elem = frame.findFirstElement(':focus') elem = frame.findFirstElement(':focus')
@ -467,7 +430,7 @@ class WebView(QWebView):
This does the following things: This does the following things:
- Check if a link was clicked with the middle button or Ctrl and - Check if a link was clicked with the middle button or Ctrl and
set the _open_target attribute accordingly. set the open_target attribute accordingly.
- Emit the editable_elem_selected signal if an editable element was - Emit the editable_elem_selected signal if an editable element was
clicked. clicked.

View File

@ -376,9 +376,9 @@ def generate_settings(f):
except KeyError: except KeyError:
f.write(" * +{}+".format(val) + "\n") f.write(" * +{}+".format(val) + "\n")
f.write("\n") f.write("\n")
if option.default: if option.default():
f.write("Default: +pass:[{}]+\n".format(html.escape( f.write("Default: +pass:[{}]+\n".format(html.escape(
option.default))) option.default())))
else: else:
f.write("Default: empty\n") f.write("Default: empty\n")