Fix #8405 (Completion on multiple authors)

This commit is contained in:
Kovid Goyal 2011-01-16 16:27:44 -07:00
commit 799b0f6033
13 changed files with 150 additions and 60 deletions

View File

@ -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))

View File

@ -190,7 +190,7 @@
</widget>
</item>
<item row="4" column="1">
<widget class="TagsLineEdit" name="tags">
<widget class="CompleteLineEdit" name="tags">
<property name="toolTip">
<string>Tags categorize the book. This is particularly useful while searching. &lt;br&gt;&lt;br&gt;They can be any words or phrases, separated by commas.</string>
</property>
@ -255,7 +255,7 @@
</widget>
</item>
<item row="1" column="1">
<widget class="EnComboBox" name="author">
<widget class="CompleteComboBox" name="author">
<property name="editable">
<bool>true</bool>
</property>
@ -310,7 +310,12 @@
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>TagsLineEdit</class>
<class>CompleteComboBox</class>
<extends>QComboBox</extends>
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>CompleteLineEdit</class>
<extends>QLineEdit</extends>
<header>widgets.h</header>
</customwidget>

View File

@ -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)

View File

@ -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:

View File

@ -76,7 +76,7 @@
</widget>
</item>
<item row="0" column="1">
<widget class="EnComboBox" name="authors">
<widget class="CompleteComboBox" name="authors">
<property name="editable">
<bool>true</bool>
</property>
@ -195,7 +195,7 @@
</widget>
</item>
<item row="5" column="1">
<widget class="TagsLineEdit" name="tags">
<widget class="CompleteLineEdit" name="tags">
<property name="toolTip">
<string>Tags categorize the book. This is particularly useful while searching. &lt;br&gt;&lt;br&gt;They can be any words or phrases, separated by commas.</string>
</property>
@ -229,7 +229,7 @@
</widget>
</item>
<item row="6" column="1">
<widget class="TagsLineEdit" name="remove_tags">
<widget class="CompleteLineEdit" name="remove_tags">
<property name="toolTip">
<string>Comma separated list of tags to remove from the books. </string>
</property>
@ -955,7 +955,12 @@ not multiple and the destination field is multiple</string>
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>TagsLineEdit</class>
<class>CompleteComboBox</class>
<extends>QComboBox</extends>
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>CompleteLineEdit</class>
<extends>QLineEdit</extends>
<header>widgets.h</header>
</customwidget>

View File

@ -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):

View File

@ -240,7 +240,7 @@ Using this button to create author sort will change author sort from red to gree
</widget>
</item>
<item row="2" column="1">
<widget class="EnComboBox" name="authors">
<widget class="CompleteComboBox" name="authors">
<property name="editable">
<bool>true</bool>
</property>
@ -335,7 +335,7 @@ If the box is colored green, then text matches the individual author's sort stri
<item row="6" column="1">
<layout class="QHBoxLayout" name="_2">
<item>
<widget class="TagsLineEdit" name="tags">
<widget class="CompleteLineEdit" name="tags">
<property name="toolTip">
<string>Tags categorize the book. This is particularly useful while searching. &lt;br&gt;&lt;br&gt;They can be any words or phrases, separated by commas.</string>
</property>
@ -842,10 +842,15 @@ If the box is colored green, then text matches the individual author's sort stri
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>TagsLineEdit</class>
<class>CompleteLineEdit</class>
<extends>QLineEdit</extends>
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>CompleteComboBox</class>
<extends>QComboBox</extends>
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>FormatList</class>
<extends>QListWidget</extends>

View File

@ -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:

View File

@ -265,7 +265,7 @@
</widget>
</item>
<item row="2" column="1">
<widget class="EnComboBox" name="authors_box">
<widget class="CompleteComboBox" name="authors_box">
<property name="toolTip">
<string>Enter an author's name. Only one author can be used.</string>
</property>
@ -279,7 +279,7 @@
</widget>
</item>
<item row="4" column="1">
<widget class="TagsLineEdit" name="tags_box">
<widget class="CompleteLineEdit" name="tags_box">
<property name="toolTip">
<string>Enter tags separated by spaces</string>
</property>
@ -360,10 +360,15 @@
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>TagsLineEdit</class>
<class>CompleteLineEdit</class>
<extends>QLineEdit</extends>
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>CompleteComboBox</class>
<extends>QComboBox</extends>
<header>widgets.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>all</tabstop>

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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')]