Merge remote-tracking branch 'florian/master' into quickmark-completion

This commit is contained in:
Claude 2014-10-17 20:46:00 +02:00
commit 9613cc0eab
17 changed files with 236 additions and 150 deletions

View File

@ -151,7 +151,8 @@ class Application(QApplication):
log.init.debug("Initializing websettings...") log.init.debug("Initializing websettings...")
websettings.init() websettings.init()
log.init.debug("Initializing quickmarks...") log.init.debug("Initializing quickmarks...")
quickmarks.init() quickmark_manager = quickmarks.QuickmarkManager()
objreg.register('quickmark-manager', quickmark_manager)
log.init.debug("Initializing proxy...") log.init.debug("Initializing proxy...")
proxy.init() proxy.init()
log.init.debug("Initializing cookies...") log.init.debug("Initializing cookies...")
@ -313,7 +314,7 @@ class Application(QApplication):
quickstart_done = False quickstart_done = False
if not quickstart_done: if not quickstart_done:
tabbed_browser = objreg.get('tabbed-browser', scope='window', tabbed_browser = objreg.get('tabbed-browser', scope='window',
window='current') window='last-focused')
tabbed_browser.tabopen( tabbed_browser.tabopen(
QUrl('http://www.qutebrowser.org/quickstart.html')) QUrl('http://www.qutebrowser.org/quickstart.html'))
try: try:
@ -341,6 +342,7 @@ class Application(QApplication):
config_obj = objreg.get('config') config_obj = objreg.get('config')
self.lastWindowClosed.connect(self.shutdown) self.lastWindowClosed.connect(self.shutdown)
config_obj.style_changed.connect(style.get_stylesheet.cache_clear) config_obj.style_changed.connect(style.get_stylesheet.cache_clear)
self.focusChanged.connect(self.on_focus_changed)
def _get_widgets(self): def _get_widgets(self):
"""Get a string list of all widgets.""" """Get a string list of all widgets."""
@ -541,7 +543,7 @@ class Application(QApplication):
out = traceback.format_exc() out = traceback.format_exc()
qutescheme.pyeval_output = out qutescheme.pyeval_output = out
tabbed_browser = objreg.get('tabbed-browser', scope='window', tabbed_browser = objreg.get('tabbed-browser', scope='window',
window='current') window='last-focused')
tabbed_browser.openurl(QUrl('qute:pyeval'), newtab=True) tabbed_browser.openurl(QUrl('qute:pyeval'), newtab=True)
@cmdutils.register(instance='app') @cmdutils.register(instance='app')
@ -636,12 +638,8 @@ class Application(QApplication):
self.removeEventFilter(self._event_filter) self.removeEventFilter(self._event_filter)
except AttributeError: except AttributeError:
pass pass
# Close all tabs # Close all windows
for win_id in objreg.window_registry: QApplication.closeAllWindows()
log.destroy.debug("Closing tabs in window {}...".format(win_id))
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
tabbed_browser.shutdown()
# Shut down IPC # Shut down IPC
try: try:
objreg.get('ipc-server').shutdown() objreg.get('ipc-server').shutdown()
@ -663,14 +661,19 @@ class Application(QApplication):
pass pass
else: else:
to_save.append(("keyconfig", key_config.save)) to_save.append(("keyconfig", key_config.save))
to_save += [("window geometry", self._save_geometry), to_save += [("window geometry", self._save_geometry)]
("quickmarks", quickmarks.save)]
try: try:
command_history = objreg.get('command-history') command_history = objreg.get('command-history')
except KeyError: except KeyError:
pass pass
else: else:
to_save.append(("command history", command_history.save)) to_save.append(("command history", command_history.save))
try:
quickmark_manager = objreg.get('quickmark-manager')
except KeyError:
pass
else:
to_save.append(("command history", quickmark_manager.save))
try: try:
state_config = objreg.get('state-config') state_config = objreg.get('state-config')
except KeyError: except KeyError:
@ -700,6 +703,20 @@ class Application(QApplication):
# segfaults. # segfaults.
QTimer.singleShot(0, functools.partial(self.exit, status)) QTimer.singleShot(0, functools.partial(self.exit, status))
def on_focus_changed(self, _old, new):
"""Register currently focused main window in the object registry."""
if new is None:
window = None
else:
window = new.window()
if window is None or not isinstance(window, mainwindow.MainWindow):
try:
objreg.delete('last-focused-main-window')
except KeyError:
pass
else:
objreg.register('last-focused-main-window', window, update=True)
def exit(self, status): def exit(self, status):
"""Extend QApplication::exit to log the event.""" """Extend QApplication::exit to log the event."""
log.destroy.debug("Now calling QApplication::exit.") log.destroy.debug("Now calling QApplication::exit.")

View File

@ -36,7 +36,7 @@ import pygments.formatters
from qutebrowser.commands import userscripts, cmdexc, cmdutils from qutebrowser.commands import userscripts, cmdexc, cmdutils
from qutebrowser.config import config from qutebrowser.config import config
from qutebrowser.browser import quickmarks, webelem from qutebrowser.browser import webelem
from qutebrowser.utils import (message, editor, usertypes, log, qtutils, from qutebrowser.utils import (message, editor, usertypes, log, qtutils,
urlutils, objreg, utils) urlutils, objreg, utils)
@ -815,7 +815,8 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')
def quickmark_save(self): def quickmark_save(self):
"""Save the current page as a quickmark.""" """Save the current page as a quickmark."""
quickmarks.prompt_save(self._win_id, self._current_url()) quickmark_manager = objreg.get('quickmark-manager')
quickmark_manager.prompt_save(self._win_id, self._current_url())
@cmdutils.register(instance='command-dispatcher', scope='window', @cmdutils.register(instance='command-dispatcher', scope='window',
completion=[usertypes.Completion.quickmark]) completion=[usertypes.Completion.quickmark])
@ -828,7 +829,7 @@ class CommandDispatcher:
bg: Load the quickmark in a new background tab. bg: Load the quickmark in a new background tab.
window: Load the quickmark in a new window. window: Load the quickmark in a new window.
""" """
url = quickmarks.get(name) url = objreg.get('quickmark-manager').get(name)
self._open(url, tab, bg, window) self._open(url, tab, bg, window)
@cmdutils.register(instance='command-dispatcher', name='inspector', @cmdutils.register(instance='command-dispatcher', name='inspector',

View File

@ -62,6 +62,7 @@ class DownloadItem(QObject):
_reply: The QNetworkReply associated with this download. _reply: The QNetworkReply associated with this download.
_last_done: The count of bytes which where downloaded when calculating _last_done: The count of bytes which where downloaded when calculating
the speed the last time. the speed the last time.
_error: The current error message, or None
Signals: Signals:
data_changed: The downloads metadata changed. data_changed: The downloads metadata changed.
@ -88,6 +89,7 @@ class DownloadItem(QObject):
self._reply = reply self._reply = reply
self._bytes_total = None self._bytes_total = None
self._speed = 0 self._speed = 0
self._error = None
self.basename = '???' self.basename = '???'
samples = int(self.SPEED_AVG_WINDOW * samples = int(self.SPEED_AVG_WINDOW *
(1000 / self.SPEED_REFRESH_INTERVAL)) (1000 / self.SPEED_REFRESH_INTERVAL))
@ -127,9 +129,13 @@ class DownloadItem(QObject):
down = utils.format_size(self._bytes_done, suffix='B') down = utils.format_size(self._bytes_done, suffix='B')
perc = self._percentage() perc = self._percentage()
remaining = self._remaining_time() remaining = self._remaining_time()
if self._error is None:
errmsg = ""
else:
errmsg = " - {}".format(self._error)
if all(e is None for e in (perc, remaining, self._bytes_total)): if all(e is None for e in (perc, remaining, self._bytes_total)):
return ('{name} [{speed:>10}|{down}]'.format( return ('{name} [{speed:>10}|{down}]{errmsg}'.format(
name=self.basename, speed=speed, down=down)) name=self.basename, speed=speed, down=down, errmsg=errmsg))
if perc is None: if perc is None:
perc = '??' perc = '??'
else: else:
@ -140,9 +146,9 @@ class DownloadItem(QObject):
remaining = utils.format_seconds(remaining) 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}]{errmsg}'.format(
remaining=remaining, perc=perc, name=self.basename, speed=speed, remaining=remaining,
down=down, total=total)) perc=perc, down=down, total=total, errmsg=errmsg))
def _die(self, msg): def _die(self, msg):
"""Abort the download and emit an error.""" """Abort the download and emit an error."""
@ -150,17 +156,19 @@ class DownloadItem(QObject):
self._reply.finished.disconnect() self._reply.finished.disconnect()
self._reply.error.disconnect() self._reply.error.disconnect()
self._reply.readyRead.disconnect() self._reply.readyRead.disconnect()
self._error = msg
self._bytes_done = self._bytes_total self._bytes_done = self._bytes_total
self.timer.stop() self.timer.stop()
self.error.emit(msg) self.error.emit(msg)
self._reply.abort() self._reply.abort()
self._reply.deleteLater() self._reply.deleteLater()
self._reply = None
if self._fileobj is not None: if self._fileobj is not None:
try: try:
self._fileobj.close() self._fileobj.close()
except OSError as e: except OSError as e:
self.error.emit(e.strerror) self.error.emit(e.strerror)
self.finished.emit() self.data_changed.emit()
def _percentage(self): def _percentage(self):
"""The current download percentage, or None if unknown.""" """The current download percentage, or None if unknown."""
@ -187,7 +195,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: error = config.get('colors', 'downloads.bg.error')
if self._error is not None:
return error
elif 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(),
@ -198,6 +209,7 @@ class DownloadItem(QObject):
log.downloads.debug("cancelled") log.downloads.debug("cancelled")
self.cancelled.emit() self.cancelled.emit()
self._is_cancelled = True self._is_cancelled = True
if self._reply is not None:
self._reply.abort() self._reply.abort()
self._reply.deleteLater() self._reply.deleteLater()
if self._fileobj is not None: if self._fileobj is not None:
@ -352,7 +364,7 @@ class DownloadManager(QAbstractListModel):
page: The QWebPage to get the download from. page: The QWebPage to get the download from.
""" """
if not url.isValid(): if not url.isValid():
urlutils.invalid_url_error('current', url, "start download") urlutils.invalid_url_error('last-focused', url, "start download")
return return
req = QNetworkRequest(url) req = QNetworkRequest(url)
reply = page.networkAccessManager().get(req) reply = page.networkAccessManager().get(req)
@ -389,7 +401,8 @@ class DownloadManager(QAbstractListModel):
functools.partial(self.on_finished, download)) functools.partial(self.on_finished, download))
download.data_changed.connect( download.data_changed.connect(
functools.partial(self.on_data_changed, download)) functools.partial(self.on_data_changed, download))
download.error.connect(self.on_error) download.error.connect(
functools.partial(self.on_error, download))
download.basename = suggested_filename download.basename = suggested_filename
idx = len(self.downloads) + 1 idx = len(self.downloads) + 1
self.beginInsertRows(QModelIndex(), idx, idx) self.beginInsertRows(QModelIndex(), idx, idx)
@ -407,7 +420,7 @@ class DownloadManager(QAbstractListModel):
self.questions.append(q) self.questions.append(q)
download.cancelled.connect(q.abort) download.cancelled.connect(q.abort)
message_bridge = objreg.get('message-bridge', scope='window', message_bridge = objreg.get('message-bridge', scope='window',
window='current') window='last-focused')
message_bridge.ask(q, blocking=False) message_bridge.ask(q, blocking=False)
@pyqtSlot(DownloadItem) @pyqtSlot(DownloadItem)
@ -428,10 +441,10 @@ class DownloadManager(QAbstractListModel):
qtutils.ensure_valid(model_idx) qtutils.ensure_valid(model_idx)
self.dataChanged.emit(model_idx, model_idx) self.dataChanged.emit(model_idx, model_idx)
@pyqtSlot(str) @pyqtSlot(DownloadItem, str)
def on_error(self, msg): def on_error(self, download, msg):
"""Display error message on download errors.""" """Display error message on download errors."""
message.error('current', "Download error: {}".format(msg)) message.error('last-focused', "Download error: {}".format(msg))
def last_index(self): def last_index(self):
"""Get the last index in the model. """Get the last index in the model.
@ -465,6 +478,11 @@ class DownloadManager(QAbstractListModel):
data = item.bg_color() data = item.bg_color()
elif role == ModelRole.item: elif role == ModelRole.item:
data = item data = item
elif role == Qt.ToolTipRole:
if item._error is None:
data = QVariant()
else:
return item._error
else: else:
data = QVariant() data = QVariant()
return data return data

View File

@ -82,9 +82,7 @@ class HintContext:
"""Get the arguments, with {hint-url} replaced by the given URL.""" """Get the arguments, with {hint-url} replaced by the given URL."""
args = [] args = []
for arg in self.args: for arg in self.args:
if arg == '{hint-url}': arg = arg.replace('{hint-url}', urlstr)
args.append(urlstr)
else:
args.append(arg) args.append(arg)
return args return args
@ -274,9 +272,14 @@ class HintManager(QObject):
else: else:
display = 'none' display = 'none'
rect = elem.geometry() rect = elem.geometry()
left = rect.x()
top = rect.y()
if not config.get('ui', 'zoom-text-only'):
zoom = elem.webFrame().zoomFactor()
left /= zoom
top /= zoom
return self.HINT_CSS.format( return self.HINT_CSS.format(
left=rect.x(), top=rect.y(), config=objreg.get('config'), left=left, top=top, config=objreg.get('config'), display=display)
display=display)
def _draw_label(self, elem, string): def _draw_label(self, elem, string):
"""Draw a hint label over an element. """Draw a hint label over an element.

View File

@ -27,38 +27,46 @@ to a file on shutdown, so it makes sense to keep them as strings here.
import functools import functools
import collections import collections
from PyQt5.QtCore import QStandardPaths, QUrl from PyQt5.QtCore import pyqtSignal, QStandardPaths, QUrl, QObject
from qutebrowser.utils import message, usertypes, urlutils, standarddir from qutebrowser.utils import message, usertypes, urlutils, standarddir
from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.config import lineparser from qutebrowser.config import lineparser
marks = collections.OrderedDict() class QuickmarkManager(QObject):
linecp = None
"""Manager for quickmarks.
Attributes:
marks: An OrderedDict of all quickmarks.
_linecp: The LineConfigParser used for the quickmarks.
"""
changed = pyqtSignal()
def __init__(self, parent=None):
"""Initialize and read quickmarks."""
super().__init__(parent)
self.marks = collections.OrderedDict()
def init():
"""Read quickmarks from the config file."""
global linecp
confdir = standarddir.get(QStandardPaths.ConfigLocation) confdir = standarddir.get(QStandardPaths.ConfigLocation)
linecp = lineparser.LineConfigParser(confdir, 'quickmarks') self._linecp = lineparser.LineConfigParser(confdir, 'quickmarks')
for line in linecp: for line in self._linecp:
try: try:
key, url = line.rsplit(maxsplit=1) key, url = line.rsplit(maxsplit=1)
except ValueError: except ValueError:
message.error(0, "Invalid quickmark '{}'".format(line)) message.error(0, "Invalid quickmark '{}'".format(line))
else: else:
marks[key] = url self.marks[key] = url
def save(self):
def save():
"""Save the quickmarks to disk.""" """Save the quickmarks to disk."""
linecp.data = [' '.join(tpl) for tpl in marks.items()] self._linecp.data = [' '.join(tpl) for tpl in self.marks.items()]
linecp.save() self._linecp.save()
def prompt_save(self, win_id, url):
def prompt_save(win_id, url):
"""Prompt for a new quickmark name to be added and add it. """Prompt for a new quickmark name to be added and add it.
Args: Args:
@ -69,12 +77,12 @@ def prompt_save(win_id, url):
urlutils.invalid_url_error(win_id, url, "save quickmark") urlutils.invalid_url_error(win_id, url, "save quickmark")
return return
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded) urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
message.ask_async(win_id, "Add quickmark:", usertypes.PromptMode.text, message.ask_async(
functools.partial(quickmark_add, win_id, urlstr)) win_id, "Add quickmark:", usertypes.PromptMode.text,
functools.partial(self.quickmark_add, win_id, urlstr))
@cmdutils.register(instance='quickmark-manager')
@cmdutils.register() def quickmark_add(self, win_id: {'special': 'win_id'}, url, name):
def quickmark_add(win_id: {'special': 'win_id'}, url, name):
"""Add a new quickmark. """Add a new quickmark.
Args: Args:
@ -82,8 +90,8 @@ def quickmark_add(win_id: {'special': 'win_id'}, url, name):
url: The url to add as quickmark. url: The url to add as quickmark.
name: The name for the new quickmark. name: The name for the new quickmark.
""" """
# We don't raise cmdexc.CommandError here as this can be called async via # We don't raise cmdexc.CommandError here as this can be called async
# prompt_save. # via prompt_save.
if not name: if not name:
message.error(win_id, "Can't set mark with empty name!") message.error(win_id, "Can't set mark with empty name!")
return return
@ -93,25 +101,25 @@ def quickmark_add(win_id: {'special': 'win_id'}, url, name):
def set_mark(): def set_mark():
"""Really set the quickmark.""" """Really set the quickmark."""
marks[name] = url self.marks[name] = url
self.changed.emit()
if name in marks: if name in self.marks:
message.confirm_async(win_id, "Override existing quickmark?", set_mark, message.confirm_async(
default=True) win_id, "Override existing quickmark?", set_mark, default=True)
else: else:
set_mark() set_mark()
def get(self, name):
def get(name):
"""Get the URL of the quickmark named name as a QUrl.""" """Get the URL of the quickmark named name as a QUrl."""
if name not in marks: if name not in self.marks:
raise cmdexc.CommandError( raise cmdexc.CommandError(
"Quickmark '{}' does not exist!".format(name)) "Quickmark '{}' does not exist!".format(name))
urlstr = marks[name] urlstr = self.marks[name]
try: try:
url = urlutils.fuzzy_url(urlstr) url = urlutils.fuzzy_url(urlstr)
except urlutils.FuzzyUrlError: except urlutils.FuzzyUrlError:
raise cmdexc.CommandError( raise cmdexc.CommandError(
"Invalid URL for quickmark {}: {} ({})".format(name, urlstr, "Invalid URL for quickmark {}: {} ({})".format(
url.errorString())) name, urlstr, url.errorString()))
return url return url

View File

@ -59,7 +59,7 @@ class HelpAction(argparse.Action):
def __call__(self, parser, _namespace, _values, _option_string=None): def __call__(self, parser, _namespace, _values, _option_string=None):
tabbed_browser = objreg.get('tabbed-browser', scope='window', tabbed_browser = objreg.get('tabbed-browser', scope='window',
window='current') window='last-focused')
tabbed_browser.tabopen( tabbed_browser.tabopen(
QUrl('qute://help/commands.html#{}'.format(parser.name))) QUrl('qute://help/commands.html#{}'.format(parser.name)))
parser.exit() parser.exit()

View File

@ -714,6 +714,10 @@ DATA = collections.OrderedDict([
('downloads.bg.system', ('downloads.bg.system',
SettingValue(typ.ColorSystem(), 'rgb'), SettingValue(typ.ColorSystem(), 'rgb'),
"Color gradient interpolation system for downloads."), "Color gradient interpolation system for downloads."),
('downloads.bg.error',
SettingValue(typ.QtColor(), 'red'),
"Background color for downloads with errors."),
)), )),
('fonts', sect.KeyValue( ('fonts', sect.KeyValue(

View File

@ -35,7 +35,7 @@ class CompletionFilterModel(QSortFilterProxyModel):
Attributes: Attributes:
_pattern: The pattern to filter with. _pattern: The pattern to filter with.
_srcmodel: The current source model. srcmodel: The current source model.
Kept as attribute because calling `sourceModel` takes quite Kept as attribute because calling `sourceModel` takes quite
a long time for some reason. a long time for some reason.
""" """
@ -43,7 +43,7 @@ class CompletionFilterModel(QSortFilterProxyModel):
def __init__(self, source, parent=None): def __init__(self, source, parent=None):
super().__init__(parent) super().__init__(parent)
super().setSourceModel(source) super().setSourceModel(source)
self._srcmodel = source self.srcmodel = source
self._pattern = '' self._pattern = ''
def set_pattern(self, val): def set_pattern(self, val):
@ -61,7 +61,7 @@ class CompletionFilterModel(QSortFilterProxyModel):
self.invalidateFilter() self.invalidateFilter()
sortcol = 0 sortcol = 0
try: try:
self._srcmodel.sort(sortcol) self.srcmodel.sort(sortcol)
except NotImplementedError: except NotImplementedError:
self.sort(sortcol) self.sort(sortcol)
self.invalidate() self.invalidate()
@ -111,14 +111,14 @@ class CompletionFilterModel(QSortFilterProxyModel):
qtutils.ensure_valid(index) qtutils.ensure_valid(index)
index = self.mapToSource(index) index = self.mapToSource(index)
qtutils.ensure_valid(index) qtutils.ensure_valid(index)
self._srcmodel.mark_item(index, text) self.srcmodel.mark_item(index, text)
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.set_pattern('') self.set_pattern('')
super().setSourceModel(model) super().setSourceModel(model)
self._srcmodel = model self.srcmodel = model
def filterAcceptsRow(self, row, parent): def filterAcceptsRow(self, row, parent):
"""Custom filter implementation. """Custom filter implementation.
@ -135,9 +135,9 @@ class CompletionFilterModel(QSortFilterProxyModel):
""" """
if parent == QModelIndex(): if parent == QModelIndex():
return True return True
idx = self._srcmodel.index(row, 0, parent) idx = self.srcmodel.index(row, 0, parent)
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
@ -159,14 +159,14 @@ class CompletionFilterModel(QSortFilterProxyModel):
qtutils.ensure_valid(lindex) qtutils.ensure_valid(lindex)
qtutils.ensure_valid(rindex) qtutils.ensure_valid(rindex)
left_sort = self._srcmodel.data(lindex, role=completion.Role.sort) left_sort = self.srcmodel.data(lindex, role=completion.Role.sort)
right_sort = self._srcmodel.data(rindex, role=completion.Role.sort) right_sort = self.srcmodel.data(rindex, role=completion.Role.sort)
if left_sort is not None and right_sort is not None: if left_sort is not None and right_sort is not None:
return left_sort < right_sort return left_sort < right_sort
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)

View File

@ -181,7 +181,6 @@ class Completer(QObject):
else: else:
self._ignore_change = True self._ignore_change = True
self.change_completed_part.emit(data, False) self.change_completed_part.emit(data, False)
self._ignore_change = False
@pyqtSlot(str, list, int) @pyqtSlot(str, list, int)
def on_update_completion(self, prefix, parts, cursor_part): def on_update_completion(self, prefix, parts, cursor_part):
@ -204,6 +203,7 @@ class Completer(QObject):
cursor_part: The part the cursor is currently over. cursor_part: The part the cursor is currently over.
""" """
if self._ignore_change: if self._ignore_change:
self._ignore_change = False
log.completion.debug("Ignoring completion update") log.completion.debug("Ignoring completion update")
return return
@ -235,7 +235,7 @@ class Completer(QObject):
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._model().count() == 0: if self._model().count() == 0:
completion.hide() completion.hide()

View File

@ -136,6 +136,15 @@ def _get_window_registry(window):
win = app.activeWindow() win = app.activeWindow()
if win is None or not hasattr(win, 'win_id'): if win is None or not hasattr(win, 'win_id'):
raise RegistryUnavailableError('window') raise RegistryUnavailableError('window')
elif window == 'last-focused':
try:
win = get('last-focused-main-window')
except KeyError:
try:
win = get('last-main-window')
except KeyError:
raise RegistryUnavailableError('window')
assert hasattr(win, 'registry')
else: else:
try: try:
win = window_registry[window] win = window_registry[window]

View File

@ -22,7 +22,7 @@
import functools import functools
import sip import sip
from PyQt5.QtCore import pyqtSlot, QSize, Qt from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer
from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu
from qutebrowser.browser import downloads from qutebrowser.browser import downloads
@ -44,10 +44,17 @@ def update_geometry(obj):
Original bug: https://github.com/The-Compiler/qutebrowser/issues/167 Original bug: https://github.com/The-Compiler/qutebrowser/issues/167
Workaround bug: https://github.com/The-Compiler/qutebrowser/issues/171 Workaround bug: https://github.com/The-Compiler/qutebrowser/issues/171
""" """
def _update_geometry():
"""Actually update the geometry if the object still exists."""
if sip.isdeleted(obj): if sip.isdeleted(obj):
return return
obj.updateGeometry() obj.updateGeometry()
# If we don't use a singleShot QTimer, the geometry isn't updated correctly
# and won't include the new item.
QTimer.singleShot(0, _update_geometry)
class DownloadView(QListView): class DownloadView(QListView):

View File

@ -87,7 +87,6 @@ class MainWindow(QWidget):
self._downloadview.show() self._downloadview.show()
self._tabbed_browser = tabbedbrowser.TabbedBrowser(win_id) self._tabbed_browser = tabbedbrowser.TabbedBrowser(win_id)
self._tabbed_browser.title_changed.connect(self.setWindowTitle)
objreg.register('tabbed-browser', self._tabbed_browser, scope='window', objreg.register('tabbed-browser', self._tabbed_browser, scope='window',
window=win_id) window=win_id)
self._vbox.addWidget(self._tabbed_browser) self._vbox.addWidget(self._tabbed_browser)
@ -333,3 +332,4 @@ class MainWindow(QWidget):
e.accept() e.accept()
objreg.get('app').geometry = bytes(self.saveGeometry()) objreg.get('app').geometry = bytes(self.saveGeometry())
log.destroy.debug("Closing window {}".format(self.win_id)) log.destroy.debug("Closing window {}".format(self.win_id))
self._tabbed_browser.shutdown()

View File

@ -20,8 +20,8 @@
"""Misc. widgets used at different places.""" """Misc. widgets used at different places."""
from PyQt5.QtCore import pyqtSlot, Qt from PyQt5.QtCore import pyqtSlot, Qt
from PyQt5.QtWidgets import QLineEdit from PyQt5.QtWidgets import QLineEdit, QApplication
from PyQt5.QtGui import QValidator from PyQt5.QtGui import QValidator, QClipboard
from qutebrowser.models import cmdhistory from qutebrowser.models import cmdhistory
from qutebrowser.utils import utils from qutebrowser.utils import utils
@ -64,6 +64,9 @@ class CommandLineEdit(QLineEdit):
self.cursorPositionChanged.connect(self.__on_cursor_position_changed) self.cursorPositionChanged.connect(self.__on_cursor_position_changed)
self._promptlen = 0 self._promptlen = 0
def __repr__(self):
return utils.get_repr(self, text=self.text())
@pyqtSlot(str) @pyqtSlot(str)
def on_text_edited(self, _text): def on_text_edited(self, _text):
"""Slot for textEdited. Stop history browsing.""" """Slot for textEdited. Stop history browsing."""
@ -94,8 +97,16 @@ class CommandLineEdit(QLineEdit):
if mark: if mark:
self.setSelection(self._promptlen, oldpos - self._promptlen) self.setSelection(self._promptlen, oldpos - self._promptlen)
def __repr__(self): def keyPressEvent(self, e):
return utils.get_repr(self, text=self.text()) """Override keyPressEvent to paste primary selection on Shift + Ins."""
if e.key() == Qt.Key_Insert and e.modifiers() == Qt.ShiftModifier:
clipboard = QApplication.clipboard()
if clipboard.supportsSelection():
e.accept()
text = clipboard.text(QClipboard.Selection)
self.insert(text)
return
super().keyPressEvent(e)
class _CommandValidator(QValidator): class _CommandValidator(QValidator):

View File

@ -79,8 +79,6 @@ class TabbedBrowser(tabwidget.TabWidget):
start_download: Emitted when any tab wants to start downloading start_download: Emitted when any tab wants to start downloading
something. something.
current_tab_changed: The current tab changed to the emitted WebView. current_tab_changed: The current tab changed to the emitted WebView.
title_changed: Emitted when the application title should be changed.
arg: The new title as string.
""" """
cur_progress = pyqtSignal(int) cur_progress = pyqtSignal(int)
@ -96,7 +94,6 @@ class TabbedBrowser(tabwidget.TabWidget):
resized = pyqtSignal('QRect') resized = pyqtSignal('QRect')
got_cmd = pyqtSignal(str) got_cmd = pyqtSignal(str)
current_tab_changed = pyqtSignal(webview.WebView) current_tab_changed = pyqtSignal(webview.WebView)
title_changed = pyqtSignal(str)
def __init__(self, win_id, parent=None): def __init__(self, win_id, parent=None):
super().__init__(win_id, parent) super().__init__(win_id, parent)
@ -138,9 +135,10 @@ class TabbedBrowser(tabwidget.TabWidget):
def _change_app_title(self, text): def _change_app_title(self, text):
"""Change the window title based on the tab text.""" """Change the window title based on the tab text."""
if not text: if not text:
self.title_changed.emit('qutebrowser') title = 'qutebrowser'
else: else:
self.title_changed.emit('{} - qutebrowser'.format(text)) title = '{} - qutebrowser'.format(text)
self.window().setWindowTitle(title)
def _connect_tab_signals(self, tab): def _connect_tab_signals(self, tab):
"""Set up the needed signals for tab.""" """Set up the needed signals for tab."""

View File

@ -127,6 +127,13 @@ class WebView(QWebView):
url = utils.elide(self.url().toDisplayString(), 50) url = utils.elide(self.url().toDisplayString(), 50)
return utils.get_repr(self, tab_id=self.tab_id, url=url) return utils.get_repr(self, tab_id=self.tab_id, url=url)
def __del__(self):
# Explicitely releasing the page here seems to prevent some segfaults
# when quitting.
# Copied from:
# https://code.google.com/p/webscraping/source/browse/webkit.py#325
self.setPage(None)
def _set_load_status(self, val): def _set_load_status(self, val):
"""Setter for load_status.""" """Setter for load_status."""
if not isinstance(val, LoadStatus): if not isinstance(val, LoadStatus):
@ -257,15 +264,11 @@ class WebView(QWebView):
"""Shut down the webview.""" """Shut down the webview."""
# We disable javascript because that prevents some segfaults when # We disable javascript because that prevents some segfaults when
# quitting it seems. # quitting it seems.
log.destroy.debug("Shutting down {!r}.".format(self))
settings = self.settings() settings = self.settings()
settings.setAttribute(QWebSettings.JavascriptEnabled, False) settings.setAttribute(QWebSettings.JavascriptEnabled, False)
self.stop() self.stop()
self.page().networkAccessManager().shutdown() self.page().networkAccessManager().shutdown()
# Explicitely releasing the page here seems to prevent some segfaults
# when quitting.
# Copied from:
# https://code.google.com/p/webscraping/source/browse/webkit.py#325
self.setPage(None)
def openurl(self, url): def openurl(self, url):
"""Open a URL in the browser. """Open a URL in the browser.

View File

@ -1,4 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Claude (longneck) <longneck@scratchbook.ch> # Copyright 2014 Claude (longneck) <longneck@scratchbook.ch>
@ -49,8 +50,8 @@ def get_args():
def import_chromium(bookmarks_file): def import_chromium(bookmarks_file):
"""Import bookmarks from a HTML file generated by Chromium.""" """Import bookmarks from a HTML file generated by Chromium."""
import bs4 import bs4
with open(bookmarks_file, encoding='utf-8') as f:
soup = bs4.BeautifulSoup(open(bookmarks_file, encoding='utf-8')) soup = bs4.BeautifulSoup(f)
html_tags = soup.findAll('a') html_tags = soup.findAll('a')

View File

@ -52,10 +52,16 @@ class OpenEncodingChecker(checkers.BaseChecker):
keyword='mode') keyword='mode')
except utils.NoSuchArgumentError: except utils.NoSuchArgumentError:
mode_arg = None mode_arg = None
_encoding = None
try: try:
_encoding = utils.get_argument_from_call(node, position=2, _encoding = utils.get_argument_from_call(node, position=2)
except utils.NoSuchArgumentError:
try:
_encoding = utils.get_argument_from_call(node,
keyword='encoding') keyword='encoding')
except utils.NoSuchArgumentError: except utils.NoSuchArgumentError:
pass
if _encoding is None:
if mode_arg is not None: if mode_arg is not None:
mode = utils.safe_infer(mode_arg) mode = utils.safe_infer(mode_arg)
if (mode_arg is not None and isinstance(mode, astroid.Const) and if (mode_arg is not None and isinstance(mode, astroid.Const) and