mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
When completing names for fields that contain hierarchical data in prefix mode match prefixes after every period. Fixes #2099780 [completion_mode setting](https://bugs.launchpad.net/calibre/+bug/2099780)
This commit is contained in:
parent
3e35e2db76
commit
eb1e62a047
@ -6,6 +6,7 @@ __copyright__ = '2012, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from contextlib import suppress
|
||||
from functools import partial
|
||||
|
||||
from qt.core import (
|
||||
QAbstractItemView,
|
||||
@ -38,6 +39,10 @@ def containsq(x, prefix):
|
||||
return primary_contains(prefix, x)
|
||||
|
||||
|
||||
def hierarchy_startswith(x, prefix, sep='.'):
|
||||
return primary_startswith(x, prefix) or primary_contains(sep + prefix, x)
|
||||
|
||||
|
||||
class CompleteModel(QAbstractListModel): # {{{
|
||||
|
||||
def __init__(self, parent=None, sort_func=sort_key, strip_completion_entries=True):
|
||||
@ -58,7 +63,7 @@ class CompleteModel(QAbstractListModel): # {{{
|
||||
self.current_prefix = ''
|
||||
self.endResetModel()
|
||||
|
||||
def set_completion_prefix(self, prefix):
|
||||
def set_completion_prefix(self, prefix, hierarchy_separator: str = ''):
|
||||
old_prefix = self.current_prefix
|
||||
self.current_prefix = prefix
|
||||
if prefix == old_prefix:
|
||||
@ -71,6 +76,8 @@ class CompleteModel(QAbstractListModel): # {{{
|
||||
subset = prefix.startswith(old_prefix)
|
||||
universe = self.current_items if subset else self.all_items
|
||||
func = primary_startswith if tweaks['completion_mode'] == 'prefix' else containsq
|
||||
if func is primary_startswith and hierarchy_separator:
|
||||
func = partial(hierarchy_startswith, sep=hierarchy_separator)
|
||||
self.beginResetModel()
|
||||
self.current_items = tuple(x for x in universe if func(x, prefix))
|
||||
self.endResetModel()
|
||||
@ -143,8 +150,8 @@ class Completer(QListView): # {{{
|
||||
if self.isVisible():
|
||||
self.relayout_needed.emit()
|
||||
|
||||
def set_completion_prefix(self, prefix):
|
||||
self.model().set_completion_prefix(prefix)
|
||||
def set_completion_prefix(self, prefix, hierarchy_separator: str = ''):
|
||||
self.model().set_completion_prefix(prefix, hierarchy_separator=hierarchy_separator)
|
||||
if self.isVisible():
|
||||
self.relayout_needed.emit()
|
||||
|
||||
@ -327,6 +334,7 @@ class LineEdit(QLineEdit, LineEditECM):
|
||||
self.sep = ','
|
||||
self.space_before_sep = False
|
||||
self.add_separator = True
|
||||
self.hierarchy_separator = ''
|
||||
self.original_cursor_pos = None
|
||||
completer_widget = (self if completer_widget is None else
|
||||
completer_widget)
|
||||
@ -351,6 +359,9 @@ class LineEdit(QLineEdit, LineEditECM):
|
||||
def set_separator(self, sep):
|
||||
self.sep = sep
|
||||
|
||||
def set_hierarchy_separator(self, sep: str = '') -> None:
|
||||
self.hierarchy_separator = sep
|
||||
|
||||
def set_space_before_sep(self, space_before):
|
||||
self.space_before_sep = space_before
|
||||
|
||||
@ -392,7 +403,7 @@ class LineEdit(QLineEdit, LineEditECM):
|
||||
orig = None
|
||||
if show_all:
|
||||
orig = self.mcompleter.model().current_prefix
|
||||
self.mcompleter.set_completion_prefix('')
|
||||
self.mcompleter.set_completion_prefix('', self.hierarchy_separator)
|
||||
if not self.mcompleter.model().current_items:
|
||||
self.mcompleter.hide()
|
||||
return
|
||||
@ -421,7 +432,7 @@ class LineEdit(QLineEdit, LineEditECM):
|
||||
complete_prefix = prefix.lstrip()
|
||||
if self.sep:
|
||||
complete_prefix = prefix.split(self.sep)[-1].lstrip()
|
||||
self.mcompleter.set_completion_prefix(complete_prefix)
|
||||
self.mcompleter.set_completion_prefix(complete_prefix, self.hierarchy_separator)
|
||||
|
||||
def get_completed_text(self, text):
|
||||
'Get completed text in before and after parts'
|
||||
@ -500,6 +511,9 @@ class EditWithComplete(EnComboBox):
|
||||
def set_separator(self, sep):
|
||||
self.lineEdit().set_separator(sep)
|
||||
|
||||
def set_hierarchy_separator(self, sep):
|
||||
self.lineEdit().set_hierarchy_separator(sep)
|
||||
|
||||
def set_space_before_sep(self, space_before):
|
||||
self.lineEdit().set_space_before_sep(space_before)
|
||||
|
||||
|
@ -66,6 +66,11 @@ class MetadataWidget(Widget, Ui_Form):
|
||||
self.comment.hide_toolbars()
|
||||
self.cover.cover_changed.connect(self.change_cover)
|
||||
self.series.currentTextChanged.connect(self.series_changed)
|
||||
cuh = self.db.new_api.pref('categories_using_hierarchy', default=())
|
||||
if 'series' in cuh:
|
||||
self.series.set_hierarchy_separator('.')
|
||||
if 'tags' in cuh:
|
||||
self.tags.set_hierarchy_separator('.')
|
||||
self.cover.draw_border = False
|
||||
|
||||
def change_cover(self, data):
|
||||
|
@ -101,6 +101,14 @@ class Base:
|
||||
except:
|
||||
pass
|
||||
|
||||
@property
|
||||
def field_name(self) -> str:
|
||||
return self.db.field_metadata.label_to_key(self.col_metadata['label'], prefer_custom=True)
|
||||
|
||||
@property
|
||||
def hierarchy_separator(self) -> str:
|
||||
return '.' if self.field_name in self.db.new_api.pref('categories_using_hierarchy', default=()) else ''
|
||||
|
||||
def finish_ui_setup(self, parent, edit_widget):
|
||||
self.was_none = False
|
||||
w = QWidget(parent)
|
||||
@ -568,6 +576,10 @@ class MultipleWidget(QWidget):
|
||||
def set_separator(self, sep):
|
||||
self.edit_widget.set_separator(sep)
|
||||
|
||||
def set_hierarchy_separator(self, sep):
|
||||
if hasattr(self.edit_widget, 'set_hierarchy_separator'):
|
||||
self.edit_widget.set_hierarchy_separator(sep)
|
||||
|
||||
def set_add_separator(self, sep):
|
||||
self.edit_widget.set_add_separator(sep)
|
||||
|
||||
@ -611,6 +623,7 @@ class Text(Base):
|
||||
w = MultipleWidget(parent, only_manage_items=True, name=self.col_metadata['name'])
|
||||
w.set_separator(None)
|
||||
w.get_editor_button().clicked.connect(super().edit)
|
||||
w.set_hierarchy_separator(self.hierarchy_separator)
|
||||
w.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||
self.set_to_undefined = w.clear
|
||||
self.widgets = [QLabel(label_string(self.col_metadata['name']), parent)]
|
||||
@ -693,6 +706,7 @@ class Series(Base):
|
||||
w.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||
self.set_to_undefined = w.clear
|
||||
w.set_separator(None)
|
||||
w.set_hierarchy_separator(self.hierarchy_separator)
|
||||
self.name_widget = w.edit_widget
|
||||
self.widgets = [QLabel(label_string(self.col_metadata['name']), parent)]
|
||||
self.finish_ui_setup(parent, lambda parent: w)
|
||||
@ -784,6 +798,7 @@ class Enumeration(Base):
|
||||
self.key = self.db.field_metadata.label_to_key(self.col_metadata['label'],
|
||||
prefer_custom=True)
|
||||
w = MultipleWidget(parent, only_manage_items=True, widget=QComboBox, name=self.col_metadata['name'])
|
||||
w.set_hierarchy_separator(self.hierarchy_separator)
|
||||
w.get_editor_button().clicked.connect(self.edit)
|
||||
w.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||
self.widgets = [QLabel(label_string(self.col_metadata['name']), parent)]
|
||||
@ -1085,6 +1100,8 @@ class BulkBase(Base):
|
||||
l.setContentsMargins(0, 0, 0, 0)
|
||||
w.setLayout(l)
|
||||
self.main_widget = main_widget_class(w)
|
||||
if (hs := self.hierarchy_separator) and hasattr(self.main_widget, 'set_hierarchy_separator'):
|
||||
self.main_widget.set_hierarchy_separator(hs)
|
||||
l.addWidget(self.main_widget)
|
||||
l.setStretchFactor(self.main_widget, 10)
|
||||
self.a_c_checkbox = QCheckBox(_('Apply changes'), w)
|
||||
|
@ -624,6 +624,12 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
||||
self.adddate.setSpecialValueText(_('Undefined'))
|
||||
self.clear_adddate_button.clicked.connect(self.clear_adddate)
|
||||
self.adddate.dateTimeChanged.connect(self.do_apply_adddate)
|
||||
cuh = self.db.new_api.pref('categories_using_hierarchy', default=())
|
||||
if 'series' in cuh:
|
||||
self.series.set_hierarchy_separator('.')
|
||||
if 'tags' in cuh:
|
||||
self.tags.set_hierarchy_separator('.')
|
||||
self.remove_tags.set_hierarchy_separator('.')
|
||||
self.casing_algorithm.addItems([
|
||||
_('Title case'), _('Capitalize'), _('Upper case'), _('Lower case'), _('Swap case')
|
||||
])
|
||||
|
@ -353,6 +353,10 @@ class TextDelegate(StyledItemDelegate, UpdateEditorGeometry, EditableTextDelegat
|
||||
editor = EditWithComplete(parent)
|
||||
editor.set_separator(None)
|
||||
editor.set_clear_button_enabled(False)
|
||||
if self.auto_complete_function_name.startswith('all_'):
|
||||
field = self.auto_complete_function_name[4:]
|
||||
if field in db.new_api.pref('categories_using_hierarchy', default=()):
|
||||
editor.set_hierarchy_separator('.')
|
||||
complete_items = [i[1] for i in f()]
|
||||
editor.update_items_cache(complete_items)
|
||||
else:
|
||||
@ -403,6 +407,8 @@ class CompleteDelegate(StyledItemDelegate, UpdateEditorGeometry, EditableTextDel
|
||||
editor = EditWithComplete(parent)
|
||||
if col == 'tags':
|
||||
editor.set_elide_mode(Qt.TextElideMode.ElideMiddle)
|
||||
if col in db.new_api.pref('categories_using_hierarchy', default=()):
|
||||
editor.set_hierarchy_separator('.')
|
||||
editor.set_separator(self.sep)
|
||||
editor.set_clear_button_enabled(False)
|
||||
editor.set_space_before_sep(self.space_before_sep)
|
||||
@ -518,6 +524,8 @@ class CcTextDelegate(StyledItemDelegate, UpdateEditorGeometry, EditableTextDeleg
|
||||
editor = EditWithComplete(parent)
|
||||
editor.set_separator(None)
|
||||
editor.set_clear_button_enabled(False)
|
||||
if col in m.db.new_api.pref('categories_using_hierarchy', default=()):
|
||||
editor.set_hierarchy_separator('.')
|
||||
complete_items = sorted(m.db.all_custom(label=key), key=sort_key)
|
||||
editor.update_items_cache(complete_items)
|
||||
else:
|
||||
|
@ -660,6 +660,8 @@ class SeriesEdit(EditWithComplete, ToMetadataMixin):
|
||||
|
||||
def initialize(self, db, id_):
|
||||
self.books_to_refresh = set()
|
||||
if 'series' in db.new_api.pref('categories_using_hierarchy', default=()):
|
||||
self.set_hierarchy_separator('.')
|
||||
self.update_items_cache(db.new_api.all_field_names('series'))
|
||||
series = db.new_api.field_for('series', id_)
|
||||
self.current_val = self.original_val = series or ''
|
||||
@ -1502,6 +1504,8 @@ class TagsEdit(EditWithComplete, ToMetadataMixin): # {{{
|
||||
|
||||
def initialize(self, db, id_):
|
||||
self.books_to_refresh = set()
|
||||
if 'tags' in db.new_api.pref('categories_using_hierarchy', default=()):
|
||||
self.set_hierarchy_separator('.')
|
||||
tags = db.tags(id_, index_is_id=True)
|
||||
tags = tags.split(',') if tags else []
|
||||
self.current_val = tags
|
||||
|
@ -232,6 +232,7 @@ def get_library_init_data(ctx, rd, db, num, sorts, orders, vl):
|
||||
ans['fts_enabled'] = db.is_fts_enabled()
|
||||
ans['book_details_vertical_categories'] = db._pref('book_details_vertical_categories', ())
|
||||
ans['fields_that_support_notes'] = tuple(db._field_supports_notes())
|
||||
ans['categories_using_hierarchy'] = db._pref('categories_using_hierarchy', ())
|
||||
mdata = ans['metadata'] = {}
|
||||
try:
|
||||
extra_books = {
|
||||
|
@ -282,8 +282,13 @@ def query_contains(haystack, needle):
|
||||
return haystack.toLowerCase().indexOf(needle) is not -1
|
||||
|
||||
|
||||
def query_startswitch(haystack, needle):
|
||||
return haystack.toLowerCase().indexOf(needle) is 0
|
||||
def query_startswith(haystack, needle):
|
||||
return haystack.toLowerCase().startsWith(needle)
|
||||
|
||||
|
||||
def query_startswith_hierarchical(haystack, needle):
|
||||
haystack = haystack.toLowerCase()
|
||||
return haystack.startsWith(needle) or haystack.indexOf('.' + needle) is not -1
|
||||
|
||||
|
||||
def update_removals(container_id):
|
||||
@ -337,7 +342,12 @@ def update_completions(container_id, ok, field, names):
|
||||
if needle:
|
||||
interface_data = get_interface_data()
|
||||
universe = update_completions.names if update_completions.prefix and needle.startswith(update_completions.prefix.toLowerCase()) else names
|
||||
q = query_contains if interface_data.completion_mode is 'contains' else query_startswitch
|
||||
if interface_data.completion_mode is 'contains':
|
||||
q = query_contains
|
||||
else:
|
||||
q = query_startswith
|
||||
if library_data.categories_using_hierarchy and library_data.categories_using_hierarchy.indexOf(field) is not -1:
|
||||
q = query_startswith_hierarchical
|
||||
matching_names = [x for x in universe if q(x, needle) and x is not prefix]
|
||||
else:
|
||||
matching_names = []
|
||||
|
@ -83,7 +83,7 @@ def update_library_data(data):
|
||||
if library_data.for_library is not current_library_id():
|
||||
library_data.field_names = {}
|
||||
library_data.for_library = current_library_id()
|
||||
for key in 'search_result sortable_fields field_metadata metadata virtual_libraries book_display_fields bools_are_tristate book_details_vertical_categories fts_enabled fields_that_support_notes'.split(' '):
|
||||
for key in 'search_result sortable_fields field_metadata metadata virtual_libraries book_display_fields bools_are_tristate book_details_vertical_categories fts_enabled fields_that_support_notes categories_using_hierarchy'.split(' '):
|
||||
library_data[key] = data[key]
|
||||
sr = library_data.search_result
|
||||
if sr:
|
||||
|
Loading…
x
Reference in New Issue
Block a user