First version of bookmarks.

This commit is contained in:
Antoni Boucher 2015-05-21 18:17:22 -04:00
parent ee0eabc202
commit 28caf45707
7 changed files with 213 additions and 9 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
__pycache__ __pycache__
*.pyc *.pyc
*.swp
/build /build
/dist /dist
/qutebrowser.egg-info /qutebrowser.egg-info

View File

@ -44,7 +44,7 @@ import qutebrowser.resources # pylint: disable=unused-import
from qutebrowser.completion.models import instances as completionmodels from qutebrowser.completion.models import instances as completionmodels
from qutebrowser.commands import cmdutils, runners, cmdexc from qutebrowser.commands import cmdutils, runners, cmdexc
from qutebrowser.config import style, config, websettings, configexc from qutebrowser.config import style, config, websettings, configexc
from qutebrowser.browser import quickmarks, cookies, cache, adblock, history from qutebrowser.browser import bookmarks, quickmarks, cookies, cache, adblock, history
from qutebrowser.browser.network import qutescheme, proxy, networkmanager from qutebrowser.browser.network import qutescheme, proxy, networkmanager
from qutebrowser.mainwindow import mainwindow from qutebrowser.mainwindow import mainwindow
from qutebrowser.misc import readline, ipc, savemanager, sessions, crashsignal from qutebrowser.misc import readline, ipc, savemanager, sessions, crashsignal
@ -413,6 +413,9 @@ def _init_modules(args, crash_handler):
log.init.debug("Initializing quickmarks...") log.init.debug("Initializing quickmarks...")
quickmark_manager = quickmarks.QuickmarkManager(qApp) quickmark_manager = quickmarks.QuickmarkManager(qApp)
objreg.register('quickmark-manager', quickmark_manager) objreg.register('quickmark-manager', quickmark_manager)
log.init.debug("Initializing bookmarks...")
bookmark_manager = bookmarks.BookmarkManager(qApp)
objreg.register('bookmark-manager', bookmark_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...")

View File

@ -0,0 +1,136 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015 Antoni Boucher <bouanto@zoho.com>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Manager for bookmarks.
Note we violate our general QUrl rule by storing url strings in the marks
OrderedDict. This is because we read them from a file at start and write them
to a file on shutdown, so it makes sense to keep them as strings here.
"""
import os.path
import functools
import collections
from PyQt5.QtCore import pyqtSignal, QUrl, QObject
from qutebrowser.utils import message, usertypes, urlutils, standarddir, objreg
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.misc import lineparser
class BookmarkManager(QObject):
"""Manager for bookmarks.
Attributes:
marks: An OrderedDict of all bookmarks.
_lineparser: The LineParser used for the bookmarks, or None
(when qutebrowser is started with -c '').
Signals:
changed: Emitted when anything changed.
added: Emitted when a new bookmark was added.
arg 0: The name of the bookmark.
arg 1: The URL of the bookmark, as string.
removed: Emitted when an existing bookmark was removed.
arg 0: The name of the bookmark.
"""
changed = pyqtSignal()
added = pyqtSignal(str, str)
removed = pyqtSignal(str)
def __init__(self, parent=None):
"""Initialize and read bookmarks."""
super().__init__(parent)
self.marks = collections.OrderedDict()
if standarddir.config() is None:
self._lineparser = None
else:
self._lineparser = lineparser.LineParser(
standarddir.config(), 'bookmarks', parent=self)
for line in self._lineparser:
if not line.strip():
# Ignore empty or whitespace-only lines.
continue
try:
key, url = line.rsplit(maxsplit=1)
except ValueError:
message.error(0, "Invalid bookmark '{}'".format(line))
else:
self.marks[key] = url
filename = os.path.join(standarddir.config(), 'bookmarks')
objreg.get('save-manager').add_saveable(
'bookmark-manager', self.save, self.changed,
filename=filename)
def save(self):
"""Save the bookmarks to disk."""
if self._lineparser is not None:
self._lineparser.data = [' '.join(tpl)
for tpl in self.marks.items()]
self._lineparser.save()
@cmdutils.register(instance='bookmark-manager', win_id='win_id')
def bookmark_add(self, win_id, url, name):
"""Add a new bookmark.
Args:
win_id: The window ID to display the errors in.
url: The url to add as bookmark.
name: The name for the new bookmark.
"""
if not url.isValid():
urlutils.invalid_url_error(win_id, url, "save quickmark")
return
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
# We don't raise cmdexc.CommandError here as this can be called async
# via prompt_save.
if not name:
message.error(win_id, "Can't set mark with empty name!")
return
if not urlstr:
message.error(win_id, "Can't set mark with empty URL!")
return
self.marks[name] = urlstr
self.changed.emit()
self.added.emit(name, urlstr)
message.info(win_id, "Bookmarks added")
@cmdutils.register(instance='bookmark-manager', maxsplit=0,
completion=[usertypes.Completion.bookmark_by_name])
def bookmark_del(self, name):
"""Delete a bookmark.
Args:
name: The name of the bookmark to delete.
"""
try:
del self.marks[name]
except KeyError:
raise cmdexc.CommandError("Bookmark '{}' not found!".format(name))
else:
self.changed.emit()
self.removed.emit(name)

View File

@ -99,6 +99,10 @@ class CommandDispatcher:
msg += "!" msg += "!"
raise cmdexc.CommandError(msg) raise cmdexc.CommandError(msg)
def _current_title(self):
"""Convenience method to get the current title."""
return self._tabbed_browser.page_title(self._current_index())
def _current_widget(self): def _current_widget(self):
"""Get the currently active widget from a command.""" """Get the currently active widget from a command."""
widget = self._tabbed_browser.currentWidget() widget = self._tabbed_browser.currentWidget()
@ -985,6 +989,12 @@ class CommandDispatcher:
url = objreg.get('quickmark-manager').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', scope='window')
def bookmark_save(self):
"""Save the current page as a bookmark."""
bookmark_manager = objreg.get('bookmark-manager')
bookmark_manager.bookmark_add(self._win_id, self._current_url(), self._current_title())
@cmdutils.register(instance='command-dispatcher', name='inspector', @cmdutils.register(instance='command-dispatcher', name='inspector',
scope='window') scope='window')
def toggle_inspector(self): def toggle_inspector(self):

View File

@ -106,6 +106,20 @@ def init_quickmark_completions():
model = _init_model(miscmodels.QuickmarkCompletionModel, 'name') model = _init_model(miscmodels.QuickmarkCompletionModel, 'name')
_instances[usertypes.Completion.quickmark_by_name] = model _instances[usertypes.Completion.quickmark_by_name] = model
@pyqtSlot()
def init_bookmark_completions():
"""Initialize bookmark completion models."""
log.completion.debug("Initializing bookmark completion.")
try:
_instances[usertypes.Completion.bookmark_by_url].deleteLater()
_instances[usertypes.Completion.bookmark_by_name].deleteLater()
except KeyError:
pass
model = _init_model(miscmodels.BookmarkCompletionModel, 'url')
_instances[usertypes.Completion.bookmark_by_url] = model
model = _init_model(miscmodels.BookmarkCompletionModel, 'name')
_instances[usertypes.Completion.bookmark_by_name] = model
@pyqtSlot() @pyqtSlot()
def init_session_completion(): def init_session_completion():
@ -128,6 +142,8 @@ INITIALIZERS = {
usertypes.Completion.value: _init_setting_completions, usertypes.Completion.value: _init_setting_completions,
usertypes.Completion.quickmark_by_url: init_quickmark_completions, usertypes.Completion.quickmark_by_url: init_quickmark_completions,
usertypes.Completion.quickmark_by_name: init_quickmark_completions, usertypes.Completion.quickmark_by_name: init_quickmark_completions,
usertypes.Completion.bookmark_by_url: init_bookmark_completions,
usertypes.Completion.bookmark_by_name: init_bookmark_completions,
usertypes.Completion.sessions: init_session_completion, usertypes.Completion.sessions: init_session_completion,
} }
@ -165,6 +181,10 @@ def init():
quickmark_manager.changed.connect( quickmark_manager.changed.connect(
functools.partial(update, [usertypes.Completion.quickmark_by_url, functools.partial(update, [usertypes.Completion.quickmark_by_url,
usertypes.Completion.quickmark_by_name])) usertypes.Completion.quickmark_by_name]))
bookmark_manager = objreg.get('bookmark-manager')
bookmark_manager.changed.connect(
functools.partial(update, [usertypes.Completion.bookmark_by_url,
usertypes.Completion.bookmark_by_name]))
session_manager = objreg.get('session-manager') session_manager = objreg.get('session-manager')
session_manager.update_completion.connect( session_manager.update_completion.connect(
functools.partial(update, [usertypes.Completion.sessions])) functools.partial(update, [usertypes.Completion.sessions]))

View File

@ -30,7 +30,7 @@ from qutebrowser.config import config
class UrlCompletionModel(base.BaseCompletionModel): class UrlCompletionModel(base.BaseCompletionModel):
"""A model which combines quickmarks and web history URLs. """A model which combines bookmarks, quickmarks and web history URLs.
Used for the `open` command.""" Used for the `open` command."""
@ -40,14 +40,15 @@ class UrlCompletionModel(base.BaseCompletionModel):
super().__init__(parent) super().__init__(parent)
self._quickmark_cat = self.new_category("Quickmarks") self._quickmark_cat = self.new_category("Quickmarks")
self._bookmark_cat = self.new_category("Bookmarks")
self._history_cat = self.new_category("History") self._history_cat = self.new_category("History")
quickmark_manager = objreg.get('quickmark-manager') bookmark_manager = objreg.get('bookmark-manager')
quickmarks = quickmark_manager.marks.items() bookmarks = bookmark_manager.marks.items()
for qm_name, qm_url in quickmarks: for bm_name, bm_url in bookmarks:
self._add_quickmark_entry(qm_name, qm_url) self._add_bookmark_entry(bm_name, bm_url)
quickmark_manager.added.connect(self.on_quickmark_added) bookmark_manager.added.connect(self.on_bookmark_added)
quickmark_manager.removed.connect(self.on_quickmark_removed) bookmark_manager.removed.connect(self.on_bookmark_removed)
self._history = objreg.get('web-history') self._history = objreg.get('web-history')
max_history = config.get('completion', 'web-history-max-items') max_history = config.get('completion', 'web-history-max-items')
@ -81,6 +82,15 @@ class UrlCompletionModel(base.BaseCompletionModel):
""" """
self.new_item(self._quickmark_cat, url, name) self.new_item(self._quickmark_cat, url, name)
def _add_bookmark_entry(self, name, url):
"""Add a new bookmark entry to the completion.
Args:
name: The name of the new bookmark.
url: The URL of the new bookmark.
"""
self.new_item(self._bookmark_cat, url, name)
@config.change_filter('completion', 'timestamp-format') @config.change_filter('completion', 'timestamp-format')
def reformat_timestamps(self): def reformat_timestamps(self):
"""Reformat the timestamps if the config option was changed.""" """Reformat the timestamps if the config option was changed."""
@ -126,3 +136,26 @@ class UrlCompletionModel(base.BaseCompletionModel):
if name_item.data(Qt.DisplayRole) == name: if name_item.data(Qt.DisplayRole) == name:
self._quickmark_cat.removeRow(i) self._quickmark_cat.removeRow(i)
break break
@pyqtSlot(str, str)
def on_bookmark_added(self, name, url):
"""Called when a bookmark has been added by the user.
Args:
name: The name of the new bookmark.
url: The url of the new bookmark, as string.
"""
self._add_bookmark_entry(name, url)
@pyqtSlot(str)
def on_bookmark_removed(self, name):
"""Called when a bookmark has been removed by the user.
Args:
name: The name of the bookmark which has been removed.
"""
for i in range(self._bookmark_cat.rowCount()):
name_item = self._bookmark_cat.child(i, 1)
if name_item.data(Qt.DisplayRole) == name:
self._bookmark_cat.removeRow(i)
break

View File

@ -237,7 +237,8 @@ KeyMode = enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt',
# Available command completions # Available command completions
Completion = enum('Completion', ['command', 'section', 'option', 'value', Completion = enum('Completion', ['command', 'section', 'option', 'value',
'helptopic', 'quickmark_by_url', 'helptopic', 'quickmark_by_url',
'quickmark_by_name', 'url', 'sessions']) 'quickmark_by_name', 'bookmark_by_url',
'bookmark_by_name', 'url', 'sessions'])
# Exit statuses for errors. Needs to be an int for sys.exit. # Exit statuses for errors. Needs to be an int for sys.exit.