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'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
from qt.core import (
|
from qt.core import (
|
||||||
QAbstractItemView,
|
QAbstractItemView,
|
||||||
@ -38,6 +39,10 @@ def containsq(x, prefix):
|
|||||||
return primary_contains(prefix, x)
|
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): # {{{
|
class CompleteModel(QAbstractListModel): # {{{
|
||||||
|
|
||||||
def __init__(self, parent=None, sort_func=sort_key, strip_completion_entries=True):
|
def __init__(self, parent=None, sort_func=sort_key, strip_completion_entries=True):
|
||||||
@ -58,7 +63,7 @@ class CompleteModel(QAbstractListModel): # {{{
|
|||||||
self.current_prefix = ''
|
self.current_prefix = ''
|
||||||
self.endResetModel()
|
self.endResetModel()
|
||||||
|
|
||||||
def set_completion_prefix(self, prefix):
|
def set_completion_prefix(self, prefix, hierarchy_separator: str = ''):
|
||||||
old_prefix = self.current_prefix
|
old_prefix = self.current_prefix
|
||||||
self.current_prefix = prefix
|
self.current_prefix = prefix
|
||||||
if prefix == old_prefix:
|
if prefix == old_prefix:
|
||||||
@ -71,6 +76,8 @@ class CompleteModel(QAbstractListModel): # {{{
|
|||||||
subset = prefix.startswith(old_prefix)
|
subset = prefix.startswith(old_prefix)
|
||||||
universe = self.current_items if subset else self.all_items
|
universe = self.current_items if subset else self.all_items
|
||||||
func = primary_startswith if tweaks['completion_mode'] == 'prefix' else containsq
|
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.beginResetModel()
|
||||||
self.current_items = tuple(x for x in universe if func(x, prefix))
|
self.current_items = tuple(x for x in universe if func(x, prefix))
|
||||||
self.endResetModel()
|
self.endResetModel()
|
||||||
@ -143,8 +150,8 @@ class Completer(QListView): # {{{
|
|||||||
if self.isVisible():
|
if self.isVisible():
|
||||||
self.relayout_needed.emit()
|
self.relayout_needed.emit()
|
||||||
|
|
||||||
def set_completion_prefix(self, prefix):
|
def set_completion_prefix(self, prefix, hierarchy_separator: str = ''):
|
||||||
self.model().set_completion_prefix(prefix)
|
self.model().set_completion_prefix(prefix, hierarchy_separator=hierarchy_separator)
|
||||||
if self.isVisible():
|
if self.isVisible():
|
||||||
self.relayout_needed.emit()
|
self.relayout_needed.emit()
|
||||||
|
|
||||||
@ -327,6 +334,7 @@ class LineEdit(QLineEdit, LineEditECM):
|
|||||||
self.sep = ','
|
self.sep = ','
|
||||||
self.space_before_sep = False
|
self.space_before_sep = False
|
||||||
self.add_separator = True
|
self.add_separator = True
|
||||||
|
self.hierarchy_separator = ''
|
||||||
self.original_cursor_pos = None
|
self.original_cursor_pos = None
|
||||||
completer_widget = (self if completer_widget is None else
|
completer_widget = (self if completer_widget is None else
|
||||||
completer_widget)
|
completer_widget)
|
||||||
@ -351,6 +359,9 @@ class LineEdit(QLineEdit, LineEditECM):
|
|||||||
def set_separator(self, sep):
|
def set_separator(self, sep):
|
||||||
self.sep = sep
|
self.sep = sep
|
||||||
|
|
||||||
|
def set_hierarchy_separator(self, sep: str = '') -> None:
|
||||||
|
self.hierarchy_separator = sep
|
||||||
|
|
||||||
def set_space_before_sep(self, space_before):
|
def set_space_before_sep(self, space_before):
|
||||||
self.space_before_sep = space_before
|
self.space_before_sep = space_before
|
||||||
|
|
||||||
@ -392,7 +403,7 @@ class LineEdit(QLineEdit, LineEditECM):
|
|||||||
orig = None
|
orig = None
|
||||||
if show_all:
|
if show_all:
|
||||||
orig = self.mcompleter.model().current_prefix
|
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:
|
if not self.mcompleter.model().current_items:
|
||||||
self.mcompleter.hide()
|
self.mcompleter.hide()
|
||||||
return
|
return
|
||||||
@ -421,7 +432,7 @@ class LineEdit(QLineEdit, LineEditECM):
|
|||||||
complete_prefix = prefix.lstrip()
|
complete_prefix = prefix.lstrip()
|
||||||
if self.sep:
|
if self.sep:
|
||||||
complete_prefix = prefix.split(self.sep)[-1].lstrip()
|
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):
|
def get_completed_text(self, text):
|
||||||
'Get completed text in before and after parts'
|
'Get completed text in before and after parts'
|
||||||
@ -500,6 +511,9 @@ class EditWithComplete(EnComboBox):
|
|||||||
def set_separator(self, sep):
|
def set_separator(self, sep):
|
||||||
self.lineEdit().set_separator(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):
|
def set_space_before_sep(self, space_before):
|
||||||
self.lineEdit().set_space_before_sep(space_before)
|
self.lineEdit().set_space_before_sep(space_before)
|
||||||
|
|
||||||
|
@ -66,6 +66,11 @@ class MetadataWidget(Widget, Ui_Form):
|
|||||||
self.comment.hide_toolbars()
|
self.comment.hide_toolbars()
|
||||||
self.cover.cover_changed.connect(self.change_cover)
|
self.cover.cover_changed.connect(self.change_cover)
|
||||||
self.series.currentTextChanged.connect(self.series_changed)
|
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
|
self.cover.draw_border = False
|
||||||
|
|
||||||
def change_cover(self, data):
|
def change_cover(self, data):
|
||||||
|
@ -101,6 +101,14 @@ class Base:
|
|||||||
except:
|
except:
|
||||||
pass
|
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):
|
def finish_ui_setup(self, parent, edit_widget):
|
||||||
self.was_none = False
|
self.was_none = False
|
||||||
w = QWidget(parent)
|
w = QWidget(parent)
|
||||||
@ -568,6 +576,10 @@ class MultipleWidget(QWidget):
|
|||||||
def set_separator(self, sep):
|
def set_separator(self, sep):
|
||||||
self.edit_widget.set_separator(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):
|
def set_add_separator(self, sep):
|
||||||
self.edit_widget.set_add_separator(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 = MultipleWidget(parent, only_manage_items=True, name=self.col_metadata['name'])
|
||||||
w.set_separator(None)
|
w.set_separator(None)
|
||||||
w.get_editor_button().clicked.connect(super().edit)
|
w.get_editor_button().clicked.connect(super().edit)
|
||||||
|
w.set_hierarchy_separator(self.hierarchy_separator)
|
||||||
w.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
w.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||||
self.set_to_undefined = w.clear
|
self.set_to_undefined = w.clear
|
||||||
self.widgets = [QLabel(label_string(self.col_metadata['name']), parent)]
|
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)
|
w.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||||
self.set_to_undefined = w.clear
|
self.set_to_undefined = w.clear
|
||||||
w.set_separator(None)
|
w.set_separator(None)
|
||||||
|
w.set_hierarchy_separator(self.hierarchy_separator)
|
||||||
self.name_widget = w.edit_widget
|
self.name_widget = w.edit_widget
|
||||||
self.widgets = [QLabel(label_string(self.col_metadata['name']), parent)]
|
self.widgets = [QLabel(label_string(self.col_metadata['name']), parent)]
|
||||||
self.finish_ui_setup(parent, lambda parent: w)
|
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'],
|
self.key = self.db.field_metadata.label_to_key(self.col_metadata['label'],
|
||||||
prefer_custom=True)
|
prefer_custom=True)
|
||||||
w = MultipleWidget(parent, only_manage_items=True, widget=QComboBox, name=self.col_metadata['name'])
|
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.get_editor_button().clicked.connect(self.edit)
|
||||||
w.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
w.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||||
self.widgets = [QLabel(label_string(self.col_metadata['name']), parent)]
|
self.widgets = [QLabel(label_string(self.col_metadata['name']), parent)]
|
||||||
@ -1085,6 +1100,8 @@ class BulkBase(Base):
|
|||||||
l.setContentsMargins(0, 0, 0, 0)
|
l.setContentsMargins(0, 0, 0, 0)
|
||||||
w.setLayout(l)
|
w.setLayout(l)
|
||||||
self.main_widget = main_widget_class(w)
|
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.addWidget(self.main_widget)
|
||||||
l.setStretchFactor(self.main_widget, 10)
|
l.setStretchFactor(self.main_widget, 10)
|
||||||
self.a_c_checkbox = QCheckBox(_('Apply changes'), w)
|
self.a_c_checkbox = QCheckBox(_('Apply changes'), w)
|
||||||
|
@ -624,6 +624,12 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
|||||||
self.adddate.setSpecialValueText(_('Undefined'))
|
self.adddate.setSpecialValueText(_('Undefined'))
|
||||||
self.clear_adddate_button.clicked.connect(self.clear_adddate)
|
self.clear_adddate_button.clicked.connect(self.clear_adddate)
|
||||||
self.adddate.dateTimeChanged.connect(self.do_apply_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([
|
self.casing_algorithm.addItems([
|
||||||
_('Title case'), _('Capitalize'), _('Upper case'), _('Lower case'), _('Swap case')
|
_('Title case'), _('Capitalize'), _('Upper case'), _('Lower case'), _('Swap case')
|
||||||
])
|
])
|
||||||
|
@ -353,6 +353,10 @@ class TextDelegate(StyledItemDelegate, UpdateEditorGeometry, EditableTextDelegat
|
|||||||
editor = EditWithComplete(parent)
|
editor = EditWithComplete(parent)
|
||||||
editor.set_separator(None)
|
editor.set_separator(None)
|
||||||
editor.set_clear_button_enabled(False)
|
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()]
|
complete_items = [i[1] for i in f()]
|
||||||
editor.update_items_cache(complete_items)
|
editor.update_items_cache(complete_items)
|
||||||
else:
|
else:
|
||||||
@ -403,6 +407,8 @@ class CompleteDelegate(StyledItemDelegate, UpdateEditorGeometry, EditableTextDel
|
|||||||
editor = EditWithComplete(parent)
|
editor = EditWithComplete(parent)
|
||||||
if col == 'tags':
|
if col == 'tags':
|
||||||
editor.set_elide_mode(Qt.TextElideMode.ElideMiddle)
|
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_separator(self.sep)
|
||||||
editor.set_clear_button_enabled(False)
|
editor.set_clear_button_enabled(False)
|
||||||
editor.set_space_before_sep(self.space_before_sep)
|
editor.set_space_before_sep(self.space_before_sep)
|
||||||
@ -518,6 +524,8 @@ class CcTextDelegate(StyledItemDelegate, UpdateEditorGeometry, EditableTextDeleg
|
|||||||
editor = EditWithComplete(parent)
|
editor = EditWithComplete(parent)
|
||||||
editor.set_separator(None)
|
editor.set_separator(None)
|
||||||
editor.set_clear_button_enabled(False)
|
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)
|
complete_items = sorted(m.db.all_custom(label=key), key=sort_key)
|
||||||
editor.update_items_cache(complete_items)
|
editor.update_items_cache(complete_items)
|
||||||
else:
|
else:
|
||||||
|
@ -660,6 +660,8 @@ class SeriesEdit(EditWithComplete, ToMetadataMixin):
|
|||||||
|
|
||||||
def initialize(self, db, id_):
|
def initialize(self, db, id_):
|
||||||
self.books_to_refresh = set()
|
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'))
|
self.update_items_cache(db.new_api.all_field_names('series'))
|
||||||
series = db.new_api.field_for('series', id_)
|
series = db.new_api.field_for('series', id_)
|
||||||
self.current_val = self.original_val = series or ''
|
self.current_val = self.original_val = series or ''
|
||||||
@ -1502,6 +1504,8 @@ class TagsEdit(EditWithComplete, ToMetadataMixin): # {{{
|
|||||||
|
|
||||||
def initialize(self, db, id_):
|
def initialize(self, db, id_):
|
||||||
self.books_to_refresh = set()
|
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 = db.tags(id_, index_is_id=True)
|
||||||
tags = tags.split(',') if tags else []
|
tags = tags.split(',') if tags else []
|
||||||
self.current_val = tags
|
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['fts_enabled'] = db.is_fts_enabled()
|
||||||
ans['book_details_vertical_categories'] = db._pref('book_details_vertical_categories', ())
|
ans['book_details_vertical_categories'] = db._pref('book_details_vertical_categories', ())
|
||||||
ans['fields_that_support_notes'] = tuple(db._field_supports_notes())
|
ans['fields_that_support_notes'] = tuple(db._field_supports_notes())
|
||||||
|
ans['categories_using_hierarchy'] = db._pref('categories_using_hierarchy', ())
|
||||||
mdata = ans['metadata'] = {}
|
mdata = ans['metadata'] = {}
|
||||||
try:
|
try:
|
||||||
extra_books = {
|
extra_books = {
|
||||||
|
@ -282,8 +282,13 @@ def query_contains(haystack, needle):
|
|||||||
return haystack.toLowerCase().indexOf(needle) is not -1
|
return haystack.toLowerCase().indexOf(needle) is not -1
|
||||||
|
|
||||||
|
|
||||||
def query_startswitch(haystack, needle):
|
def query_startswith(haystack, needle):
|
||||||
return haystack.toLowerCase().indexOf(needle) is 0
|
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):
|
def update_removals(container_id):
|
||||||
@ -337,7 +342,12 @@ def update_completions(container_id, ok, field, names):
|
|||||||
if needle:
|
if needle:
|
||||||
interface_data = get_interface_data()
|
interface_data = get_interface_data()
|
||||||
universe = update_completions.names if update_completions.prefix and needle.startswith(update_completions.prefix.toLowerCase()) else names
|
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]
|
matching_names = [x for x in universe if q(x, needle) and x is not prefix]
|
||||||
else:
|
else:
|
||||||
matching_names = []
|
matching_names = []
|
||||||
|
@ -83,7 +83,7 @@ def update_library_data(data):
|
|||||||
if library_data.for_library is not current_library_id():
|
if library_data.for_library is not current_library_id():
|
||||||
library_data.field_names = {}
|
library_data.field_names = {}
|
||||||
library_data.for_library = current_library_id()
|
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]
|
library_data[key] = data[key]
|
||||||
sr = library_data.search_result
|
sr = library_data.search_result
|
||||||
if sr:
|
if sr:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user