diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index 2ddbec7f20..68cce6ec5f 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -268,7 +268,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): aus = self.db.author_sort(row) self.author_sort.setText(aus if aus else '') tags = self.db.tags(row) - self.tags.setText(tags if tags else '') + self.tags.setText(', '.join(tags.split(',')) if tags else '') + self.tags.update_tags_cache(self.db.all_tags()) rating = self.db.rating(row) if rating > 0: self.rating.setValue(int(rating/2.)) @@ -389,6 +390,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()) def fetch_cover(self): isbn = unicode(self.isbn.text()).strip() diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui index ff98d22ad3..14191f2851 100644 --- a/src/calibre/gui2/dialogs/metadata_single.ui +++ b/src/calibre/gui2/dialogs/metadata_single.ui @@ -222,7 +222,7 @@ - + Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas. @@ -652,6 +652,11 @@ QComboBox
widgets.h
+ + TagsLineEdit + QLineEdit +
widgets.h
+
title diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index 086d62962c..b7dd91271b 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -19,7 +19,7 @@ from calibre.ptempfile import PersistentTemporaryFile from calibre.library.database2 import FIELD_MAP from calibre.gui2 import NONE, TableView, qstring_to_unicode, config, \ error_dialog -from calibre.gui2.widgets import EnLineEdit +from calibre.gui2.widgets import EnLineEdit, TagsLineEdit from calibre.utils.search_query_parser import SearchQueryParser from calibre.ebooks.metadata.meta import set_metadata as _set_metadata from calibre.ebooks.metadata import string_to_authors, fmt_sidx @@ -118,50 +118,6 @@ class TextDelegate(QStyledItemDelegate): editor = EnLineEdit(parent) return editor -class CompleterLineEdit(EnLineEdit): - - def __init__(self, *args): - EnLineEdit.__init__(self, *args) - - QObject.connect(self, SIGNAL('textChanged(QString)'), self.text_changed) - - def text_changed(self, text): - all_text = qstring_to_unicode(text) - text = all_text[:self.cursorPosition()] - prefix = text.split(',')[-1].strip() - - text_tags = [] - for t in all_text.split(','): - t1 = qstring_to_unicode(t).strip() - if t1 != '': - text_tags.append(t) - text_tags = list(set(text_tags)) - - self.emit(SIGNAL('text_changed(PyQt_PyObject, PyQt_PyObject)'), text_tags, prefix) - - def complete_text(self, text): - cursor_pos = self.cursorPosition() - before_text = qstring_to_unicode(self.text())[:cursor_pos] - after_text = qstring_to_unicode(self.text())[cursor_pos:] - prefix_len = len(before_text.split(',')[-1].strip()) - self.setText('%s%s, %s' % (before_text[:cursor_pos - prefix_len], text, after_text)) - self.setCursorPosition(cursor_pos - prefix_len + len(text) + 2) - -class TagsCompleter(QCompleter): - - def __init__(self, parent, all_tags): - QCompleter.__init__(self, all_tags, parent) - self.all_tags = set(all_tags) - - def update(self, text_tags, completion_prefix): - tags = list(self.all_tags.difference(text_tags)) - model = QStringListModel(tags, self) - self.setModel(model) - - self.setCompletionPrefix(completion_prefix) - if completion_prefix.strip() != '': - self.complete() - class TagsDelegate(QStyledItemDelegate): def __init__(self, parent): @@ -172,18 +128,10 @@ class TagsDelegate(QStyledItemDelegate): self.db = db def createEditor(self, parent, option, index): - editor = CompleterLineEdit(parent) if self.db: - completer = TagsCompleter(editor, self.db.all_tags()) - completer.setCaseSensitivity(Qt.CaseInsensitive) - - QObject.connect(editor, - SIGNAL('text_changed(PyQt_PyObject, PyQt_PyObject)'), - completer.update) - QObject.connect(completer, SIGNAL('activated(QString)'), - editor.complete_text) - - completer.setWidget(editor) + editor = TagsLineEdit(parent, self.db.all_tags()) + else: + editor = EnLineEdit(parent) return editor class BooksModel(QAbstractTableModel): diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index 3bcd1f5d3b..f4b9130fc8 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -11,7 +11,7 @@ from PyQt4.Qt import QListView, QIcon, QFont, QLabel, QListWidget, \ QAbstractListModel, QVariant, Qt, SIGNAL, \ QRegExp, QSettings, QSize, QModelIndex, \ QAbstractButton, QPainter, QLineEdit, QComboBox, \ - QMenu + QMenu, QStringListModel, QCompleter from calibre.gui2 import human_readable, NONE, TableView, \ qstring_to_unicode, error_dialog @@ -500,6 +500,84 @@ class EnLineEdit(LineEditECM, QLineEdit): pass +class TagsCompleter(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 update(self, text_tags, completion_prefix): + tags = list(self.all_tags.difference(text_tags)) + model = QStringListModel(tags, 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) + self.setModel(model) + + +class TagsLineEdit(EnLineEdit): + + ''' + A QLineEdit that can complete parts of text separated by separator. + ''' + + def __init__(self, parent=0, tags=[]): + EnLineEdit.__init__(self, parent) + + self.separator = ',' + + self.connect(self, SIGNAL('textChanged(QString)'), self.text_changed) + + self.completer = TagsCompleter(self, tags) + self.completer.setCaseSensitivity(Qt.CaseInsensitive) + + self.connect(self, + SIGNAL('text_changed(PyQt_PyObject, PyQt_PyObject)'), + self.completer.update) + self.connect(self.completer, SIGNAL('activated(QString)'), + self.complete_text) + + self.completer.setWidget(self) + + def update_tags_cache(self, tags): + self.completer.update_tags_cache(tags) + + def text_changed(self, text): + all_text = qstring_to_unicode(text) + text = all_text[:self.cursorPosition()] + prefix = text.split(',')[-1].strip() + + text_tags = [] + for t in all_text.split(self.separator): + t1 = qstring_to_unicode(t).strip() + if t1 != '': + text_tags.append(t) + text_tags = list(set(text_tags)) + + self.emit(SIGNAL('text_changed(PyQt_PyObject, PyQt_PyObject)'), + text_tags, prefix) + + def complete_text(self, text): + cursor_pos = self.cursorPosition() + before_text = qstring_to_unicode(self.text())[:cursor_pos] + after_text = qstring_to_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) + + class EnComboBox(QComboBox): '''