Fix (I hope) crashes in MultiCompleteComboBox. Also the drop down arrow now only shows the items that start with whatever text is currently entered in the box

This commit is contained in:
Kovid Goyal 2012-07-02 14:12:28 +05:30
parent e6788b5814
commit c8b5db5209
9 changed files with 41 additions and 149 deletions

View File

@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
from PyQt4.Qt import (QLineEdit, QAbstractListModel, Qt, from PyQt4.Qt import (QLineEdit, QAbstractListModel, Qt,
QApplication, QCompleter, pyqtSignal) QApplication, QCompleter)
from calibre.utils.icu import sort_key, lower from calibre.utils.icu import sort_key, lower
from calibre.gui2 import NONE from calibre.gui2 import NONE
@ -56,7 +56,7 @@ class MultiCompleteLineEdit(QLineEdit, LineEditECM):
to complete non multiple fields as well. to complete non multiple fields as well.
''' '''
def __init__(self, parent=None): def __init__(self, parent=None, completer_widget=None):
QLineEdit.__init__(self, parent) QLineEdit.__init__(self, parent)
self.sep = ',' self.sep = ','
@ -66,7 +66,7 @@ class MultiCompleteLineEdit(QLineEdit, LineEditECM):
self._model = CompleteModel(parent=self) self._model = CompleteModel(parent=self)
self._completer = c = QCompleter(self._model, self) self._completer = c = QCompleter(self._model, self)
c.setWidget(self) c.setWidget(self if completer_widget is None else completer_widget)
c.setCompletionMode(QCompleter.PopupCompletion) c.setCompletionMode(QCompleter.PopupCompletion)
c.setCaseSensitivity(Qt.CaseInsensitive) c.setCaseSensitivity(Qt.CaseInsensitive)
c.setModelSorting(self._model.sorting) c.setModelSorting(self._model.sorting)
@ -158,21 +158,13 @@ class MultiCompleteLineEdit(QLineEdit, LineEditECM):
class MultiCompleteComboBox(EnComboBox): class MultiCompleteComboBox(EnComboBox):
clear_edit_text = pyqtSignal()
def __init__(self, *args): def __init__(self, *args):
EnComboBox.__init__(self, *args) EnComboBox.__init__(self, *args)
self.setLineEdit(MultiCompleteLineEdit(self)) self.le = MultiCompleteLineEdit(self, completer_widget=self)
# Needed to allow changing the case of an existing item self.setLineEdit(self.le)
# otherwise on focus out, the text is changed to the
# item that matches case insensitively def showPopup(self):
c = self.lineEdit().completer() self.le._completer.complete()
c.setCaseSensitivity(Qt.CaseSensitive)
self.dummy_model = CompleteModel(self)
c.setModel(self.dummy_model)
self.lineEdit()._completer.setWidget(self)
self.clear_edit_text.connect(self.clearEditText,
type=Qt.QueuedConnection)
def update_items_cache(self, complete_items): def update_items_cache(self, complete_items):
self.lineEdit().update_items_cache(complete_items) self.lineEdit().update_items_cache(complete_items)
@ -187,16 +179,8 @@ class MultiCompleteComboBox(EnComboBox):
self.lineEdit().set_add_separator(what) self.lineEdit().set_add_separator(what)
def show_initial_value(self, what): def show_initial_value(self, what):
''' what = unicode(what) if what else u''
Show an initial value. Handle the case of the initial value being blank
correctly (on Qt 4.8.0 having a blank value causes the first value from
the completer to be shown, when the event loop runs).
'''
what = unicode(what)
le = self.lineEdit() le = self.lineEdit()
if not what.strip():
self.clear_edit_text.emit()
else:
self.setEditText(what) self.setEditText(what)
le.selectAll() le.selectAll()
@ -207,5 +191,8 @@ if __name__ == '__main__':
d.setLayout(QVBoxLayout()) d.setLayout(QVBoxLayout())
le = MultiCompleteComboBox(d) le = MultiCompleteComboBox(d)
d.layout().addWidget(le) d.layout().addWidget(le)
le.all_items = ['one', 'otwo', 'othree', 'ooone', 'ootwo', 'oothree'] items = ['one', 'otwo', 'othree', 'ooone', 'ootwo',
'oothree']
le.update_items_cache(items)
le.show_initial_value('')
d.exec_() d.exec_()

View File

@ -12,8 +12,8 @@ from PyQt4.Qt import QPixmap, SIGNAL
from calibre.gui2 import choose_images, error_dialog from calibre.gui2 import choose_images, error_dialog
from calibre.gui2.convert.metadata_ui import Ui_Form from calibre.gui2.convert.metadata_ui import Ui_Form
from calibre.ebooks.metadata import (authors_to_string, string_to_authors, from calibre.ebooks.metadata import (string_to_authors, MetaInformation,
MetaInformation, title_sort) title_sort)
from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.ebooks.metadata.opf2 import metadata_to_opf
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.gui2.convert import Widget from calibre.gui2.convert import Widget
@ -74,14 +74,12 @@ class MetadataWidget(Widget, Ui_Form):
mi = self.db.get_metadata(self.book_id, index_is_id=True) mi = self.db.get_metadata(self.book_id, index_is_id=True)
self.title.setText(mi.title) self.title.setText(mi.title)
if mi.publisher: self.publisher.show_initial_value(mi.publisher if mi.publisher else '')
self.publisher.setCurrentIndex(self.publisher.findText(mi.publisher))
self.author_sort.setText(mi.author_sort if mi.author_sort else '') self.author_sort.setText(mi.author_sort if mi.author_sort else '')
self.tags.setText(', '.join(mi.tags if mi.tags else [])) self.tags.setText(', '.join(mi.tags if mi.tags else []))
self.tags.update_items_cache(self.db.all_tags()) self.tags.update_items_cache(self.db.all_tags())
self.comment.html = comments_to_html(mi.comments) if mi.comments else '' self.comment.html = comments_to_html(mi.comments) if mi.comments else ''
if mi.series: self.series.show_initial_value(mi.series if mi.series else '')
self.series.setCurrentIndex(self.series.findText(mi.series))
if mi.series_index is not None: if mi.series_index is not None:
try: try:
self.series_index.setValue(mi.series_index) self.series_index.setValue(mi.series_index)
@ -118,16 +116,11 @@ class MetadataWidget(Widget, Ui_Form):
self.author.set_add_separator(tweaks['authors_completer_append_separator']) self.author.set_add_separator(tweaks['authors_completer_append_separator'])
self.author.update_items_cache(self.db.all_author_names()) self.author.update_items_cache(self.db.all_author_names())
for i in all_authors:
id, name = i
name = authors_to_string([name.strip().replace('|', ',') for n in name.split(',')])
self.author.addItem(name)
au = self.db.authors(self.book_id, True) au = self.db.authors(self.book_id, True)
if not au: if not au:
au = _('Unknown') au = _('Unknown')
au = ' & '.join([a.strip().replace('|', ',') for a in au.split(',')]) au = ' & '.join([a.strip().replace('|', ',') for a in au.split(',')])
self.author.setEditText(au) self.author.show_initial_value(au)
def initialize_series(self): def initialize_series(self):
all_series = self.db.all_series() all_series = self.db.all_series()
@ -135,22 +128,12 @@ class MetadataWidget(Widget, Ui_Form):
self.series.set_separator(None) self.series.set_separator(None)
self.series.update_items_cache([x[1] for x in all_series]) self.series.update_items_cache([x[1] for x in all_series])
for i in all_series:
id, name = i
self.series.addItem(name)
self.series.setCurrentIndex(-1)
def initialize_publisher(self): def initialize_publisher(self):
all_publishers = self.db.all_publishers() all_publishers = self.db.all_publishers()
all_publishers.sort(key=lambda x : sort_key(x[1])) all_publishers.sort(key=lambda x : sort_key(x[1]))
self.publisher.set_separator(None) self.publisher.set_separator(None)
self.publisher.update_items_cache([x[1] for x in all_publishers]) self.publisher.update_items_cache([x[1] for x in all_publishers])
for i in all_publishers:
id, name = i
self.publisher.addItem(name)
self.publisher.setCurrentIndex(-1)
def get_title_and_authors(self): def get_title_and_authors(self):
title = unicode(self.title.text()).strip() title = unicode(self.title.text()).strip()
if not title: if not title:
@ -179,6 +162,7 @@ class MetadataWidget(Widget, Ui_Form):
if tags: if tags:
mi.tags = tags mi.tags = tags
print (mi)
return mi return mi
def select_cover(self): def select_cover(self):

View File

@ -314,14 +314,7 @@ class Text(Base):
if self.col_metadata['is_multiple']: if self.col_metadata['is_multiple']:
self.setter(val) self.setter(val)
else: else:
idx = None self.widgets[1].show_initial_value(val)
for i, c in enumerate(values):
if c == val:
idx = i
self.widgets[1].addItem(c)
self.widgets[1].setEditText('')
if idx is not None:
self.widgets[1].setCurrentIndex(idx)
def setter(self, val): def setter(self, val):
if self.col_metadata['is_multiple']: if self.col_metadata['is_multiple']:
@ -396,16 +389,8 @@ class Series(Base):
self.initial_index = s_index self.initial_index = s_index
self.initial_val = val self.initial_val = val
val = self.normalize_db_val(val) val = self.normalize_db_val(val)
idx = None
self.name_widget.clear()
for i, c in enumerate(values):
if c == val:
idx = i
self.name_widget.addItem(c)
self.name_widget.update_items_cache(values) self.name_widget.update_items_cache(values)
self.name_widget.setEditText('') self.name_widget.show_initial_value(val)
if idx is not None:
self.widgets[1].setCurrentIndex(idx)
def getter(self): def getter(self):
n = unicode(self.name_widget.currentText()).strip() n = unicode(self.name_widget.currentText()).strip()
@ -860,8 +845,6 @@ class BulkSeries(BulkBase):
self.idx_widget.setChecked(False) self.idx_widget.setChecked(False)
self.main_widget.set_separator(None) self.main_widget.set_separator(None)
self.main_widget.update_items_cache(self.all_values) self.main_widget.update_items_cache(self.all_values)
for c in self.all_values:
self.main_widget.addItem(c)
self.main_widget.setEditText('') self.main_widget.setEditText('')
self.a_c_checkbox.setChecked(False) self.a_c_checkbox.setChecked(False)
@ -1005,15 +988,8 @@ class BulkText(BulkBase):
if not self.col_metadata['is_multiple']: if not self.col_metadata['is_multiple']:
val = self.get_initial_value(book_ids) val = self.get_initial_value(book_ids)
self.initial_val = val = self.normalize_db_val(val) self.initial_val = val = self.normalize_db_val(val)
idx = None
self.main_widget.blockSignals(True) self.main_widget.blockSignals(True)
for i, c in enumerate(self.all_values): self.main_widget.show_initial_value(val)
if c == val:
idx = i
self.main_widget.addItem(c)
self.main_widget.setEditText('')
if idx is not None:
self.main_widget.setCurrentIndex(idx)
self.main_widget.blockSignals(False) self.main_widget.blockSignals(False)
def commit(self, book_ids, notify=False): def commit(self, book_ids, notify=False):

View File

@ -6,8 +6,7 @@ __license__ = 'GPL v3'
from PyQt4.Qt import QDialog, QGridLayout, QLabel, QDialogButtonBox, \ from PyQt4.Qt import QDialog, QGridLayout, QLabel, QDialogButtonBox, \
QApplication, QSpinBox, QToolButton, QIcon QApplication, QSpinBox, QToolButton, QIcon
from calibre.ebooks.metadata import authors_to_string, string_to_authors from calibre.ebooks.metadata import string_to_authors
from calibre.utils.icu import sort_key
from calibre.gui2.complete import MultiCompleteComboBox from calibre.gui2.complete import MultiCompleteComboBox
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
@ -56,17 +55,10 @@ class AddEmptyBookDialog(QDialog):
self.authors_combo.setEditText(_('Unknown')) self.authors_combo.setEditText(_('Unknown'))
def initialize_authors(self, db, author): def initialize_authors(self, db, author):
all_authors = db.all_authors()
all_authors.sort(key=lambda x : sort_key(x[1]))
for i in all_authors:
id, name = i
name = [name.strip().replace('|', ',') for n in name.split(',')]
self.authors_combo.addItem(authors_to_string(name))
au = author au = author
if not au: if not au:
au = _('Unknown') au = _('Unknown')
self.authors_combo.setEditText(au.replace('|', ',')) self.authors_combo.show_initial_value(au.replace('|', ','))
self.authors_combo.set_separator('&') self.authors_combo.set_separator('&')
self.authors_combo.set_space_before_sep(True) self.authors_combo.set_space_before_sep(True)

View File

@ -876,38 +876,25 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
all_authors = self.db.all_authors() all_authors = self.db.all_authors()
all_authors.sort(key=lambda x : sort_key(x[1])) all_authors.sort(key=lambda x : sort_key(x[1]))
for i in all_authors:
id, name = i
name = name.strip().replace('|', ',')
self.authors.addItem(name)
self.authors.setEditText('')
self.authors.set_separator('&') self.authors.set_separator('&')
self.authors.set_space_before_sep(True) self.authors.set_space_before_sep(True)
self.authors.set_add_separator(tweaks['authors_completer_append_separator']) self.authors.set_add_separator(tweaks['authors_completer_append_separator'])
self.authors.update_items_cache(self.db.all_author_names()) self.authors.update_items_cache(self.db.all_author_names())
self.authors.show_initial_value('')
def initialize_series(self): def initialize_series(self):
all_series = self.db.all_series() all_series = self.db.all_series()
all_series.sort(key=lambda x : sort_key(x[1])) all_series.sort(key=lambda x : sort_key(x[1]))
self.series.set_separator(None) self.series.set_separator(None)
self.series.update_items_cache([x[1] for x in all_series]) self.series.update_items_cache([x[1] for x in all_series])
self.series.show_initial_value('')
for i in all_series:
id, name = i
self.series.addItem(name)
self.series.setEditText('')
def initialize_publisher(self): def initialize_publisher(self):
all_publishers = self.db.all_publishers() all_publishers = self.db.all_publishers()
all_publishers.sort(key=lambda x : sort_key(x[1])) all_publishers.sort(key=lambda x : sort_key(x[1]))
self.publisher.set_separator(None) self.publisher.set_separator(None)
self.publisher.update_items_cache([x[1] for x in all_publishers]) self.publisher.update_items_cache([x[1] for x in all_publishers])
self.publisher.show_initial_value('')
for i in all_publishers:
id, name = i
self.publisher.addItem(name)
self.publisher.setEditText('')
def tag_editor(self, *args): def tag_editor(self, *args):
d = TagEditor(self, self.db, None) d = TagEditor(self, self.db, None)

View File

@ -25,10 +25,6 @@ class SearchDialog(QDialog, Ui_Dialog):
all_authors = db.all_authors() all_authors = db.all_authors()
all_authors.sort(key=lambda x : sort_key(x[1])) all_authors.sort(key=lambda x : sort_key(x[1]))
for i in all_authors:
id, name = i
name = name.strip().replace('|', ',')
self.authors_box.addItem(name)
self.authors_box.setEditText('') self.authors_box.setEditText('')
self.authors_box.set_separator('&') self.authors_box.set_separator('&')
self.authors_box.set_space_before_sep(True) self.authors_box.set_space_before_sep(True)
@ -39,10 +35,7 @@ class SearchDialog(QDialog, Ui_Dialog):
all_series.sort(key=lambda x : sort_key(x[1])) all_series.sort(key=lambda x : sort_key(x[1]))
self.series_box.set_separator(None) self.series_box.set_separator(None)
self.series_box.update_items_cache([x[1] for x in all_series]) self.series_box.update_items_cache([x[1] for x in all_series])
for i in all_series: self.series_box.show_initial_value('')
id, name = i
self.series_box.addItem(name)
self.series_box.setEditText('')
all_tags = db.all_tags() all_tags = db.all_tags()
self.tags_box.update_items_cache(all_tags) self.tags_box.update_items_cache(all_tags)

View File

@ -32,8 +32,6 @@ class LanguagesEdit(MultiCompleteComboBox):
all_items = sorted(self._lang_map.itervalues(), all_items = sorted(self._lang_map.itervalues(),
key=lambda x: (-pmap.get(x, 0), sort_key(x))) key=lambda x: (-pmap.get(x, 0), sort_key(x)))
self.update_items_cache(all_items) self.update_items_cache(all_items)
for item in all_items:
self.addItem(item)
@property @property
def vals(self): def vals(self):

View File

@ -125,8 +125,6 @@ class TextDelegate(QStyledItemDelegate): # {{{
editor.set_separator(None) editor.set_separator(None)
complete_items = [i[1] for i in self.auto_complete_function()] complete_items = [i[1] for i in self.auto_complete_function()]
editor.update_items_cache(complete_items) editor.update_items_cache(complete_items)
for item in sorted(complete_items, key=sort_key):
editor.addItem(item)
ct = index.data(Qt.DisplayRole).toString() ct = index.data(Qt.DisplayRole).toString()
editor.show_initial_value(ct) editor.show_initial_value(ct)
else: else:
@ -166,8 +164,6 @@ class CompleteDelegate(QStyledItemDelegate): # {{{
all_items = list(self.db.all_custom( all_items = list(self.db.all_custom(
label=self.db.field_metadata.key_to_label(col))) label=self.db.field_metadata.key_to_label(col)))
editor.update_items_cache(all_items) editor.update_items_cache(all_items)
for item in sorted(all_items, key=sort_key):
editor.addItem(item)
ct = index.data(Qt.DisplayRole).toString() ct = index.data(Qt.DisplayRole).toString()
editor.show_initial_value(ct) editor.show_initial_value(ct)
else: else:

View File

@ -246,14 +246,6 @@ class AuthorsEdit(MultiCompleteComboBox):
def initialize(self, db, id_): def initialize(self, db, id_):
self.books_to_refresh = set([]) self.books_to_refresh = set([])
all_authors = db.all_authors()
all_authors.sort(key=lambda x : sort_key(x[1]))
self.clear()
for i in all_authors:
id, name = i
name = name.strip().replace('|', ',')
self.addItem(name)
self.set_separator('&') self.set_separator('&')
self.set_space_before_sep(True) self.set_space_before_sep(True)
self.set_add_separator(tweaks['authors_completer_append_separator']) self.set_add_separator(tweaks['authors_completer_append_separator'])
@ -299,7 +291,6 @@ class AuthorsEdit(MultiCompleteComboBox):
self.setEditText(' & '.join([x.strip() for x in val])) self.setEditText(' & '.join([x.strip() for x in val]))
self.lineEdit().setCursorPosition(0) self.lineEdit().setCursorPosition(0)
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
def break_cycles(self): def break_cycles(self):
@ -488,19 +479,12 @@ class SeriesEdit(MultiCompleteComboBox):
all_series.sort(key=lambda x : sort_key(x[1])) all_series.sort(key=lambda x : sort_key(x[1]))
self.update_items_cache([x[1] for x in all_series]) self.update_items_cache([x[1] for x in all_series])
series_id = db.series_id(id_, index_is_id=True) series_id = db.series_id(id_, index_is_id=True)
idx, c = None, 0 inval = ''
self.clear()
for i in all_series: for i in all_series:
id, name = i if i[0] == series_id:
if id == series_id: inval = i[1]
idx = c break
self.addItem(name) self.original_val = self.current_val = inval
c += 1
self.lineEdit().setText('')
if idx is not None:
self.setCurrentIndex(idx)
self.original_val = self.current_val
def commit(self, db, id_): def commit(self, db, id_):
series = self.current_val series = self.current_val
@ -1373,17 +1357,12 @@ class PublisherEdit(MultiCompleteComboBox): # {{{
all_publishers.sort(key=lambda x : sort_key(x[1])) all_publishers.sort(key=lambda x : sort_key(x[1]))
self.update_items_cache([x[1] for x in all_publishers]) self.update_items_cache([x[1] for x in all_publishers])
publisher_id = db.publisher_id(id_, index_is_id=True) publisher_id = db.publisher_id(id_, index_is_id=True)
idx = None inval = ''
self.clear() for pid, name in all_publishers:
for i, x in enumerate(all_publishers): if pid == publisher_id:
id_, name = x inval = name
if id_ == publisher_id: break
idx = i self.original_val = self.current_val = inval
self.addItem(name)
self.setEditText('')
if idx is not None:
self.setCurrentIndex(idx)
def commit(self, db, id_): def commit(self, db, id_):
self.books_to_refresh |= db.set_publisher(id_, self.current_val, self.books_to_refresh |= db.set_publisher(id_, self.current_val,