From d1e2a03d588d7d024c67621bbd4d3832abd9301f Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 6 Nov 2021 15:03:38 +0000 Subject: [PATCH 1/2] Bug #1950033: Book Details: Missing copy options on composite columns Added the copy options as well as search and remove to composites, bools, numbers, etc --- src/calibre/ebooks/metadata/book/render.py | 29 ++++++++++++++++++++-- src/calibre/gui2/book_details.py | 18 +++++++++++++- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index c4912894f2..6341d1b816 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -60,9 +60,9 @@ def search_action(search_term, value, **k): return action('search', term=search_term, value=value, **k) -def search_action_with_data(search_term, value, book_id, field=None): +def search_action_with_data(search_term, value, book_id, field=None, **k): field = field or search_term - return search_action(search_term, value, field=field, book_id=book_id) + return search_action(search_term, value, field=field, book_id=book_id, **k) DEFAULT_AUTHOR_LINK = 'search-{}'.format(DEFAULT_AUTHOR_SOURCE) @@ -281,6 +281,10 @@ def mi_to_html( aval = getattr(mi, field) if is_date_undefined(aval): continue + val = '%s' % ( + search_action_with_data(field, str(aval), book_id, None, original_value=val), a( + _('Click to see books with {0}: {1} (derived from {2})').format( + metadata['name'] or field, aval, val)), val) elif metadata['datatype'] == 'text' and metadata['is_multiple']: try: st = metadata['search_terms'][0] @@ -303,6 +307,27 @@ def mi_to_html( val = '%s' % ( search_action_with_data(st, val, book_id, field), a( _('Click to see books with {0}: {1}').format(metadata['name'] or field, val)), p(val)) + elif metadata['datatype'] == 'bool': + val = '%s' % ( + search_action_with_data(field, val, book_id, None), a( + _('Click to see books with {0}: {1}').format(metadata['name'] or field, val)), val) + else: + try: + aval = str(getattr(mi, field)) + if not aval: + continue + if val == aval: + val = '%s' % ( + search_action_with_data(field, str(aval), book_id, None, original_value=val), a( + _('Click to see books with {0}: {1}').format(metadata['name'] or field, val)), val) + else: + val = '%s' % ( + search_action_with_data(field, str(aval), book_id, None, original_value=val), a( + _('Click to see books with {0}: {1} (derived from {2})').format( + metadata['name'] or field, aval, val)), val) + except: + import traceback + traceback.print_exc() ans.append((field, row % (name, val))) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index c5177b022b..3239a16484 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -92,6 +92,13 @@ def is_category(field): return field in {x[0] for x in find_categories(fm) if fm.is_custom_field(x[0])} +def is_boolean(field): + from calibre.gui2.ui import get_gui + gui = get_gui() + fm = gui.current_db.field_metadata + return fm.get(field, {}).get('datatype') == 'bool' + + def escape_for_menu(x): return x.replace('&', '&&') @@ -365,10 +372,18 @@ def add_item_specific_entries(menu, data, book_info, copy_menu, search_menu): remove_value = langnames_to_langcodes((value,)).get(value, 'Unknown') init_find_in_tag_browser(search_menu, find_action, field, value) init_find_in_grouped_search(search_menu, field, value, book_info) + else: + v = data.get('original_value') or data.get('value') + copy_menu.addAction(QIcon(I('edit-copy.png')), _('The text: {}').format(v), + lambda: QApplication.instance().clipboard().setText(v)) ac = book_info.remove_item_action ac.data = (field, remove_value, book_id) ac.setText(_('Remove %s from this book') % escape_for_menu(value)) menu.addAction(ac) + else: + v = data.get('original_value') or data.get('value') + copy_menu.addAction(QIcon(I('edit-copy.png')), _('The text: {}').format(v), + lambda: QApplication.instance().clipboard().setText(v)) return search_internet_added @@ -1034,8 +1049,9 @@ class BookDetails(QWidget): # {{{ if mods & Qt.KeyboardModifier.ControlModifier: append = 'AND' if mods & Qt.KeyboardModifier.ShiftModifier else 'OR' + fmt = '{}:{}' if is_boolean(field) else '{}:"={}"' self.search_requested.emit( - '{}:"={}"'.format(field, val.replace('"', '\\"')), + fmt.format(field, val.replace('"', '\\"')), append ) From 4a8f5a226aefea5f7a93b99e33c216f1f19bdc04 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 6 Nov 2021 17:18:47 +0000 Subject: [PATCH 2/2] Add searching for bool, numeric etc values to the content server. --- src/pyj/book_list/book_details.pyj | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/pyj/book_list/book_details.pyj b/src/pyj/book_list/book_details.pyj index a2883de25f..04cbb899bb 100644 --- a/src/pyj/book_list/book_details.pyj +++ b/src/pyj/book_list/book_details.pyj @@ -76,8 +76,8 @@ def field_sorter(field_metadata): return lvl + (fm.name or 'zzzzz') -def href_for_search(name, val): - query = '{}:"={}"'.format(name, str.replace(val, '"', r'\"')) +def href_for_search(name, val, use_quotes=True): + query = ('{}:"={}"' if use_quotes else '{}:{}').format(name, str.replace(val, '"', r'\"')) q = search_query_for(query) return query_as_href(q) @@ -183,7 +183,7 @@ def render_metadata(mi, table, book_id, iframe_css): # {{{ fields = filter(allowed_fields, fields) comments = v'[]' - def add_row(name, val, is_searchable=False, is_html=False, join=None, search_text=None): + def add_row(name, val, is_searchable=False, is_html=False, join=None, search_text=None, use_quotes=True): if val is undefined or val is None: return def add_val(v): @@ -194,7 +194,7 @@ def render_metadata(mi, table, book_id, iframe_css): # {{{ table.lastChild.lastChild.appendChild(E.a( v, title=_('Click to see books with {0}: {1}').format(name, text_rep), class_='blue-link', - href=href_for_search(is_searchable, text_rep) + href=href_for_search(is_searchable, text_rep, use_quotes=use_quotes) )) else: if v.appendChild: @@ -302,9 +302,9 @@ def render_metadata(mi, table, book_id, iframe_css): # {{{ def process_datetime(field, fm, name, val): if val: fmt = interface_data['gui_' + field + '_display_format'] or (fm['display'] or {}).date_format - val = format_date(val, fmt) - if val: - add_row(name, val) + formatted_val = format_date(val, fmt) + if formatted_val: + add_row(name, formatted_val, is_searchable=field, search_text=val) def process_series(field, fm, name, val): if val: @@ -382,15 +382,21 @@ def render_metadata(mi, table, book_id, iframe_css): # {{{ join = fm.is_multiple.list_to_ui if fm.is_multiple else None add_row(name, val, join=join, is_searchable=field) elif datatype is 'bool': - add_row(name, _('Yes') if val else _('No')) + # We are missing checking if the pref bools_are_tristate is False + v = _('Yes') if val else ('' if val is undefined or val is None else _('No')) + if v: + add_row(name, v, is_searchable=field, use_quotes=False) elif datatype is 'int' or datatype is 'float': if val is not undefined and val is not None: fmt = (fm.display or {}).number_format if fmt: - val = fmt.format(val) + formatted_val = fmt.format(val) + if formatted_val: + val += '' + add_row(name, formatted_val, is_searchable=field, search_text=val) else: val += '' - add_row(name, val) + add_row(name, val, is_searchable=field, search_text=val) for field in fields: fm = field_metadata[field]