diff --git a/src/calibre/gui2/convert/metadata.py b/src/calibre/gui2/convert/metadata.py
index d3744bb614..5f39202e26 100644
--- a/src/calibre/gui2/convert/metadata.py
+++ b/src/calibre/gui2/convert/metadata.py
@@ -68,6 +68,9 @@ class MetadataWidget(Widget, Ui_Form):
def initialize_metadata_options(self):
self.initialize_combos()
self.author.editTextChanged.connect(self.deduce_author_sort)
+ self.author.set_separator('&')
+ self.author.set_space_before_sep(True)
+ self.author.update_items_cache(self.db.all_author_names())
mi = self.db.get_metadata(self.book_id, index_is_id=True)
self.title.setText(mi.title)
@@ -75,7 +78,7 @@ class MetadataWidget(Widget, Ui_Form):
self.publisher.setCurrentIndex(self.publisher.findText(mi.publisher))
self.author_sort.setText(mi.author_sort if mi.author_sort else '')
self.tags.setText(', '.join(mi.tags if mi.tags else []))
- self.tags.update_tags_cache(self.db.all_tags())
+ self.tags.update_items_cache(self.db.all_tags())
self.comment.setPlainText(mi.comments if mi.comments else '')
if mi.series:
self.series.setCurrentIndex(self.series.findText(mi.series))
diff --git a/src/calibre/gui2/convert/metadata.ui b/src/calibre/gui2/convert/metadata.ui
index a594f47b5d..8db4cfa2a1 100644
--- a/src/calibre/gui2/convert/metadata.ui
+++ b/src/calibre/gui2/convert/metadata.ui
@@ -190,7 +190,7 @@
-
-
+
Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.
@@ -255,7 +255,7 @@
-
-
+
true
@@ -310,7 +310,12 @@
- TagsLineEdit
+ CompleteComboBox
+ QComboBox
+
+
+
+ CompleteLineEdit
QLineEdit
diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py
index ec18675359..d80909c4bb 100644
--- a/src/calibre/gui2/custom_column_widgets.py
+++ b/src/calibre/gui2/custom_column_widgets.py
@@ -14,7 +14,7 @@ from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \
QPushButton
from calibre.utils.date import qt_to_dt, now
-from calibre.gui2.widgets import TagsLineEdit, EnComboBox
+from calibre.gui2.widgets import CompleteLineEdit, EnComboBox
from calibre.gui2.comments_editor import Editor as CommentsEditor
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
from calibre.utils.config import tweaks
@@ -212,7 +212,7 @@ class Text(Base):
values = self.all_values = list(self.db.all_custom(num=self.col_id))
values.sort(key=sort_key)
if self.col_metadata['is_multiple']:
- w = TagsLineEdit(parent, values)
+ w = CompleteLineEdit(parent, values)
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
else:
w = EnComboBox(parent)
@@ -226,7 +226,7 @@ class Text(Base):
val = self.normalize_db_val(val)
if self.col_metadata['is_multiple']:
self.setter(val)
- self.widgets[1].update_tags_cache(self.all_values)
+ self.widgets[1].update_items_cache(self.all_values)
else:
idx = None
for i, c in enumerate(self.all_values):
@@ -656,7 +656,7 @@ class RemoveTags(QWidget):
layout.setSpacing(5)
layout.setContentsMargins(0, 0, 0, 0)
- self.tags_box = TagsLineEdit(parent, values)
+ self.tags_box = CompleteLineEdit(parent, values)
layout.addWidget(self.tags_box, stretch = 1)
# self.tags_box.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
@@ -678,7 +678,7 @@ class BulkText(BulkBase):
values = self.all_values = list(self.db.all_custom(num=self.col_id))
values.sort(key=sort_key)
if self.col_metadata['is_multiple']:
- w = TagsLineEdit(parent, values)
+ w = CompleteLineEdit(parent, values)
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
self.widgets = [QLabel('&'+self.col_metadata['name']+': ' +
_('tags to add'), parent), w]
@@ -697,7 +697,7 @@ class BulkText(BulkBase):
def initialize(self, book_ids):
if self.col_metadata['is_multiple']:
- self.widgets[1].update_tags_cache(self.all_values)
+ self.widgets[1].update_items_cache(self.all_values)
else:
val = self.get_initial_value(book_ids)
self.initial_val = val = self.normalize_db_val(val)
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index 302766a92d..2b3a319663 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -279,8 +279,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
self.changed = False
all_tags = self.db.all_tags()
- self.tags.update_tags_cache(all_tags)
- self.remove_tags.update_tags_cache(all_tags)
+ self.tags.update_items_cache(all_tags)
+ self.remove_tags.update_items_cache(all_tags)
self.initialize_combos()
@@ -726,6 +726,10 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
name = name.strip().replace('|', ',')
self.authors.addItem(name)
self.authors.setEditText('')
+
+ self.authors.set_separator('&')
+ self.authors.set_space_before_sep(True)
+ self.authors.update_items_cache(self.db.all_author_names())
def initialize_series(self):
all_series = self.db.all_series()
@@ -751,8 +755,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
if d.result() == QDialog.Accepted:
tag_string = ', '.join(d.tags)
self.tags.setText(tag_string)
- self.tags.update_tags_cache(self.db.all_tags())
- self.remove_tags.update_tags_cache(self.db.all_tags())
+ self.tags.update_items_cache(self.db.all_tags())
+ self.remove_tags.update_items_cache(self.db.all_tags())
def auto_number_changed(self, state):
if state:
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui
index 5690a8e555..8db74b343d 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.ui
+++ b/src/calibre/gui2/dialogs/metadata_bulk.ui
@@ -76,7 +76,7 @@
-
-
+
true
@@ -195,7 +195,7 @@
-
-
+
Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.
@@ -229,7 +229,7 @@
-
-
+
Comma separated list of tags to remove from the books.
@@ -955,7 +955,12 @@ not multiple and the destination field is multiple
- TagsLineEdit
+ CompleteComboBox
+ QComboBox
+
+
+
+ CompleteLineEdit
QLineEdit
diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py
index e4efdf0470..4ca2072317 100644
--- a/src/calibre/gui2/dialogs/metadata_single.py
+++ b/src/calibre/gui2/dialogs/metadata_single.py
@@ -556,7 +556,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
tags = self.db.tags(row)
self.original_tags = ', '.join(tags.split(',')) if tags else ''
self.tags.setText(self.original_tags)
- self.tags.update_tags_cache(self.db.all_tags())
+ self.tags.update_items_cache(self.db.all_tags())
rating = self.db.rating(row)
if rating > 0:
self.rating.setValue(int(rating/2.))
@@ -724,6 +724,10 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
au = _('Unknown')
au = ' & '.join([a.strip().replace('|', ',') for a in au.split(',')])
self.authors.setEditText(au)
+
+ self.authors.set_separator('&')
+ self.authors.set_space_before_sep(True)
+ self.authors.update_items_cache(self.db.all_author_names())
def initialize_series(self):
self.series.setSizeAdjustPolicy(self.series.AdjustToContentsOnFirstShow)
@@ -776,7 +780,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
if d.result() == QDialog.Accepted:
tag_string = ', '.join(d.tags)
self.tags.setText(tag_string)
- self.tags.update_tags_cache(self.db.all_tags())
+ self.tags.update_items_cache(self.db.all_tags())
def fetch_metadata(self):
diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui
index 60c221be1a..23efc45399 100644
--- a/src/calibre/gui2/dialogs/metadata_single.ui
+++ b/src/calibre/gui2/dialogs/metadata_single.ui
@@ -240,7 +240,7 @@ Using this button to create author sort will change author sort from red to gree
-
-
+
true
@@ -335,7 +335,7 @@ If the box is colored green, then text matches the individual author's sort stri
-
-
-
+
Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.
@@ -842,10 +842,15 @@ If the box is colored green, then text matches the individual author's sort stri
- TagsLineEdit
+ CompleteLineEdit
QLineEdit
+
+ CompleteComboBox
+ QComboBox
+
+
FormatList
QListWidget
diff --git a/src/calibre/gui2/dialogs/search.py b/src/calibre/gui2/dialogs/search.py
index 62a0f8a9f1..ab3fd3ec4e 100644
--- a/src/calibre/gui2/dialogs/search.py
+++ b/src/calibre/gui2/dialogs/search.py
@@ -31,6 +31,9 @@ class SearchDialog(QDialog, Ui_Dialog):
self.authors_box.setEditText('')
self.authors_box.completer().setCompletionMode(QCompleter.PopupCompletion)
self.authors_box.setAutoCompletionCaseSensitivity(Qt.CaseInsensitive)
+ self.authors_box.set_separator('&')
+ self.authors_box.set_space_before_sep(True)
+ self.authors_box.update_items_cache(db.all_author_names())
all_series = db.all_series()
all_series.sort(key=lambda x : sort_key(x[1]))
@@ -42,7 +45,7 @@ class SearchDialog(QDialog, Ui_Dialog):
self.series_box.setAutoCompletionCaseSensitivity(Qt.CaseInsensitive)
all_tags = db.all_tags()
- self.tags_box.update_tags_cache(all_tags)
+ self.tags_box.update_items_cache(all_tags)
self.box_last_values = copy.deepcopy(box_values)
if self.box_last_values:
diff --git a/src/calibre/gui2/dialogs/search.ui b/src/calibre/gui2/dialogs/search.ui
index 6848a45506..1d013a1e9f 100644
--- a/src/calibre/gui2/dialogs/search.ui
+++ b/src/calibre/gui2/dialogs/search.ui
@@ -265,7 +265,7 @@
-
-
+
Enter an author's name. Only one author can be used.
@@ -279,7 +279,7 @@
-
-
+
Enter tags separated by spaces
@@ -360,10 +360,15 @@
- TagsLineEdit
+ CompleteLineEdit
QLineEdit
+
+ CompleteComboBox
+ QComboBox
+
+
all
diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py
index b41fd78dc3..ea614aa817 100644
--- a/src/calibre/gui2/library/delegates.py
+++ b/src/calibre/gui2/library/delegates.py
@@ -16,7 +16,7 @@ from PyQt4.Qt import QColor, Qt, QModelIndex, QSize, \
QComboBox, QTextDocument
from calibre.gui2 import UNDEFINED_QDATE, error_dialog
-from calibre.gui2.widgets import EnLineEdit, TagsLineEdit
+from calibre.gui2.widgets import EnLineEdit, CompleteLineEdit
from calibre.utils.date import now, format_date
from calibre.utils.config import tweaks
from calibre.utils.formatter import validation_formatter
@@ -173,9 +173,9 @@ class TagsDelegate(QStyledItemDelegate): # {{{
if self.db:
col = index.model().column_map[index.column()]
if not index.model().is_custom_column(col):
- editor = TagsLineEdit(parent, self.db.all_tags())
+ editor = CompleteLineEdit(parent, self.db.all_tags())
else:
- editor = TagsLineEdit(parent,
+ editor = CompleteLineEdit(parent,
sorted(list(self.db.all_custom(label=self.db.field_metadata.key_to_label(col))),
key=sort_key))
return editor
@@ -184,6 +184,31 @@ class TagsDelegate(QStyledItemDelegate): # {{{
return editor
# }}}
+class CompleteDelegate(QStyledItemDelegate): # {{{
+ def __init__(self, parent, sep, items_func_name, space_before_sep=False):
+ QStyledItemDelegate.__init__(self, parent)
+ self.sep = sep
+ self.items_func_name = items_func_name
+ self.space_before_sep = space_before_sep
+
+ def set_database(self, db):
+ self.db = db
+
+ def createEditor(self, parent, option, index):
+ if self.db and hasattr(self.db, self.items_func_name):
+ col = index.model().column_map[index.column()]
+ if not index.model().is_custom_column(col):
+ editor = CompleteLineEdit(parent, getattr(self.db, self.items_func_name)(),
+ self.sep, self.space_before_sep)
+ else:
+ editor = CompleteLineEdit(parent,
+ sorted(list(self.db.all_custom(label=self.db.field_metadata.key_to_label(col))),
+ key=sort_key), self.sep, self.space_before_sep)
+ else:
+ editor = EnLineEdit(parent)
+ return editor
+# }}}
+
class CcDateDelegate(QStyledItemDelegate): # {{{
'''
Delegate for custom columns dates. Because this delegate stores the
diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py
index 3ff0fc3cd7..61161cd5e6 100644
--- a/src/calibre/gui2/library/views.py
+++ b/src/calibre/gui2/library/views.py
@@ -13,7 +13,7 @@ from PyQt4.Qt import QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal, \
QPoint, QPixmap, QUrl, QImage, QPainter, QColor, QRect
from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \
- TextDelegate, DateDelegate, TagsDelegate, CcTextDelegate, \
+ TextDelegate, DateDelegate, CompleteDelegate, CcTextDelegate, \
CcBoolDelegate, CcCommentsDelegate, CcDateDelegate, CcTemplateDelegate, \
CcEnumDelegate
from calibre.gui2.library.models import BooksModel, DeviceBooksModel
@@ -76,8 +76,8 @@ class BooksView(QTableView): # {{{
self.rating_delegate = RatingDelegate(self)
self.timestamp_delegate = DateDelegate(self)
self.pubdate_delegate = PubDateDelegate(self)
- self.tags_delegate = TagsDelegate(self)
- self.authors_delegate = TextDelegate(self)
+ self.tags_delegate = CompleteDelegate(self, ',', 'all_tags')
+ self.authors_delegate = CompleteDelegate(self, '&', 'all_author_names', True)
self.series_delegate = TextDelegate(self)
self.publisher_delegate = TextDelegate(self)
self.text_delegate = TextDelegate(self)
@@ -410,8 +410,7 @@ class BooksView(QTableView): # {{{
self.save_state()
self._model.set_database(db)
self.tags_delegate.set_database(db)
- self.authors_delegate.set_auto_complete_function(
- lambda: [(x, y.replace('|', ',')) for (x, y) in db.all_authors()])
+ self.authors_delegate.set_database(db)
self.series_delegate.set_auto_complete_function(db.all_series)
self.publisher_delegate.set_auto_complete_function(db.all_publishers)
diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py
index f2ff783a76..0bb5ee7634 100644
--- a/src/calibre/gui2/widgets.py
+++ b/src/calibre/gui2/widgets.py
@@ -426,46 +426,47 @@ class EnLineEdit(LineEditECM, QLineEdit):
pass
-class TagsCompleter(QCompleter):
+class ItemsCompleter(QCompleter):
'''
A completer object that completes a list of tags. It is used in conjunction
with a CompleterLineEdit.
'''
- def __init__(self, parent, all_tags):
- QCompleter.__init__(self, all_tags, parent)
- self.all_tags = set(all_tags)
+ def __init__(self, parent, all_items):
+ QCompleter.__init__(self, all_items, parent)
+ self.all_items = set(all_items)
- def update(self, text_tags, completion_prefix):
- tags = list(self.all_tags.difference(text_tags))
- model = QStringListModel(tags, self)
+ def update(self, text_items, completion_prefix):
+ items = list(self.all_items.difference(text_items))
+ model = QStringListModel(items, self)
self.setModel(model)
self.setCompletionPrefix(completion_prefix)
if completion_prefix.strip() != '':
self.complete()
- def update_tags_cache(self, tags):
- self.all_tags = set(tags)
- model = QStringListModel(tags, self)
+ def update_items_cache(self, items):
+ self.all_items = set(items)
+ model = QStringListModel(items, self)
self.setModel(model)
-class TagsLineEdit(EnLineEdit):
+class CompleteLineEdit(EnLineEdit):
'''
A QLineEdit that can complete parts of text separated by separator.
'''
- def __init__(self, parent=0, tags=[]):
+ def __init__(self, parent=0, complete_items=[], sep=',', space_before_sep=False):
EnLineEdit.__init__(self, parent)
- self.separator = ','
+ self.separator = sep
+ self.space_before_sep = space_before_sep
self.connect(self, SIGNAL('textChanged(QString)'), self.text_changed)
- self.completer = TagsCompleter(self, tags)
+ self.completer = ItemsCompleter(self, complete_items)
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
self.connect(self,
@@ -476,32 +477,43 @@ class TagsLineEdit(EnLineEdit):
self.completer.setWidget(self)
- def update_tags_cache(self, tags):
- self.completer.update_tags_cache(tags)
+ def update_items_cache(self, complete_items):
+ self.completer.update_items_cache(complete_items)
+
+ def set_separator(self, sep):
+ self.separator = sep
+
+ def set_space_before_sep(self, space_before):
+ self.space_before_sep = space_before
def text_changed(self, text):
all_text = unicode(text)
text = all_text[:self.cursorPosition()]
- prefix = text.split(',')[-1].strip()
+ prefix = text.split(self.separator)[-1].strip()
- text_tags = []
+ text_items = []
for t in all_text.split(self.separator):
t1 = unicode(t).strip()
if t1 != '':
- text_tags.append(t)
- text_tags = list(set(text_tags))
+ text_items.append(t)
+ text_items = list(set(text_items))
self.emit(SIGNAL('text_changed(PyQt_PyObject, PyQt_PyObject)'),
- text_tags, prefix)
+ text_items, prefix)
def complete_text(self, text):
cursor_pos = self.cursorPosition()
before_text = unicode(self.text())[:cursor_pos]
after_text = unicode(self.text())[cursor_pos:]
- prefix_len = len(before_text.split(',')[-1].strip())
- self.setText('%s%s%s %s' % (before_text[:cursor_pos - prefix_len],
- text, self.separator, after_text))
- self.setCursorPosition(cursor_pos - prefix_len + len(text) + 2)
+ prefix_len = len(before_text.split(self.separator)[-1].strip())
+ if self.space_before_sep:
+ complete_text_pat = '%s%s %s %s'
+ len_extra = 3
+ else:
+ complete_text_pat = '%s%s%s %s'
+ len_extra = 2
+ self.setText(complete_text_pat % (before_text[:cursor_pos - prefix_len], text, self.separator, after_text))
+ self.setCursorPosition(cursor_pos - prefix_len + len(text) + len_extra)
class EnComboBox(QComboBox):
@@ -528,6 +540,22 @@ class EnComboBox(QComboBox):
idx = 0
self.setCurrentIndex(idx)
+class CompleteComboBox(EnComboBox):
+
+ def __init__(self, *args):
+ EnComboBox.__init__(self, *args)
+ self.setLineEdit(CompleteLineEdit(self))
+
+ def update_items_cache(self, complete_items):
+ self.lineEdit().update_items_cache(complete_items)
+
+ def set_separator(self, sep):
+ self.lineEdit().set_separator(sep)
+
+ def set_space_before_sep(self, space_before):
+ self.lineEdit().set_space_before_sep(space_before)
+
+
class HistoryLineEdit(QComboBox):
lost_focus = pyqtSignal()
diff --git a/src/calibre/library/database.py b/src/calibre/library/database.py
index 6016dbd03e..2138b2f1eb 100644
--- a/src/calibre/library/database.py
+++ b/src/calibre/library/database.py
@@ -1060,6 +1060,10 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
return [ (i[0], i[1]) for i in \
self.conn.get('SELECT id, name FROM authors')]
+ def all_author_names(self):
+ return filter(None, [i[0].strip().replace('|', ',') for i in self.conn.get(
+ 'SELECT name FROM authors')])
+
def all_publishers(self):
return [ (i[0], i[1]) for i in \
self.conn.get('SELECT id, name FROM publishers')]