From 5991fecb04bbd05421629ff85dc58d303022b277 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 14 Jun 2022 21:57:32 +0530
Subject: [PATCH] Display individual matches in details panel
---
src/calibre/gui2/fts/search.py | 76 ++++++++++++++++++++++++++++------
1 file changed, 63 insertions(+), 13 deletions(-)
diff --git a/src/calibre/gui2/fts/search.py b/src/calibre/gui2/fts/search.py
index 0987d140b1..4d99bad5b1 100644
--- a/src/calibre/gui2/fts/search.py
+++ b/src/calibre/gui2/fts/search.py
@@ -18,7 +18,7 @@ from qt.core import (
)
from threading import Event, Thread
-from calibre import fit_image
+from calibre import fit_image, prepare_string_for_xml
from calibre.db import FTSQueryError
from calibre.ebooks.metadata import authors_to_string, fmt_sidx
from calibre.gui2 import config, error_dialog, gprefs, safe_open_url
@@ -147,6 +147,7 @@ class ResultsModel(QAbstractItemModel):
matches_found = pyqtSignal(int)
search_complete = pyqtSignal()
query_failed = pyqtSignal(str, str)
+ result_with_context_found = pyqtSignal(object, int)
def __init__(self, parent=None):
super().__init__(parent)
@@ -245,6 +246,7 @@ class ResultsModel(QAbstractItemModel):
self.beginInsertRows(parent_idx, r, r)
parent.add_result_with_text(result)
self.endInsertRows()
+ self.result_with_context_found.emit(parent, r)
def signal_search_complete(self, query_id):
if query_id == self.current_query_id:
@@ -333,6 +335,7 @@ class ResultsView(QTreeView):
matches_found = pyqtSignal(int)
search_complete = pyqtSignal()
current_changed = pyqtSignal(object, object)
+ result_with_context_found = pyqtSignal(object, int)
def __init__(self, parent=None):
super().__init__(parent)
@@ -341,6 +344,7 @@ class ResultsView(QTreeView):
self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.setHeaderHidden(True)
self.m = ResultsModel(self)
+ self.m.result_with_context_found.connect(self.result_with_context_found)
self.m.search_complete.connect(self.search_complete)
self.m.search_started.connect(self.search_started)
self.m.search_started.connect(self.focus_self)
@@ -351,7 +355,10 @@ class ResultsView(QTreeView):
self.setItemDelegate(self.delegate)
def currentChanged(self, current, previous):
- self.current_changed.emit(*self.m.data_for_index(current))
+ results, individual_match = self.m.data_for_index(current)
+ if individual_match is not None:
+ individual_match = current.row()
+ self.current_changed.emit(results, individual_match)
def focus_self(self):
self.setFocus(Qt.FocusReason.OtherFocusReason)
@@ -460,12 +467,19 @@ class ResultDetails(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
+ self.key = None
self.pixmap_label = pl = QLabel(self)
pl.setScaledContents(True)
- self.current_book_id = -1
self.book_info = bi = HTMLDisplay(self)
bi.setDefaultStyleSheet('a { text-decoration: none; }')
bi.anchor_clicked.connect(self.book_info_anchor_clicked)
+ self.results = r = HTMLDisplay(self)
+ r.setDefaultStyleSheet('a { text-decoration: none; }')
+ r.anchor_clicked.connect(self.results_anchor_clicked)
+
+ @property
+ def current_book_id(self):
+ return -1 if self.key is None else self.key[0]
def book_info_anchor_clicked(self, url):
if self.current_book_id > 0:
@@ -474,6 +488,10 @@ class ResultDetails(QWidget):
elif url.host() == 'jump':
jump_to_book(self.current_book_id)
+ def results_anchor_clicked(self, url):
+ if self.current_book_id > 0:
+ print(url)
+
def resizeEvent(self, ev):
self.do_layout()
@@ -492,43 +510,75 @@ class ResultDetails(QWidget):
if self.book_info.horizontalScrollBar().isVisible():
h += self.book_info.horizontalScrollBar().height() + 1
self.book_info.setGeometry(QRect(self.pixmap_label.geometry().right() + 8, 0, w, h))
+ top = max(self.book_info.geometry().bottom(), self.pixmap_label.geometry().bottom())
+ self.results.setGeometry(QRect(0, top, g.width(), g.height() - top))
def show_result(self, results, individual_match=None):
- old_current_book_id, self.current_book_id = self.current_book_id, results.book_id
+ key = results.book_id, len(results.texts), individual_match
+ if key == self.key:
+ return False
+ old_current_book_id = self.current_book_id
+ self.key = key
if old_current_book_id != self.current_book_id:
self.pixmap_label.setPixmap(results.cover)
self.render_book_info(results)
+ self.render_results(results, individual_match)
self.do_layout()
+ return True
+
+ def result_with_context_found(self, results, individual_match):
+ if results.book_id == self.current_book_id:
+ return self.show_result(results, self.key[2])
+ return False
def render_book_info(self, results):
t = results.title
if len(t) > 72:
t = t[:71] + '…'
- text = f'{results.title}
'
+ text = f'
{prepare_string_for_xml(results.title)}
'
au = results.authors
if len(au) > 3:
au = list(au[:3]) + ['…']
- text += f'{authors_to_string(au)}
'
+ text += f'{prepare_string_for_xml(authors_to_string(au))}
'
if results.series:
sidx = fmt_sidx(results.series_index or 0, use_roman=config['use_roman_numerals_for_series_number'])
series = results.series
if len(series) > 60:
series = series[:59] + '…'
+ series = prepare_string_for_xml(series)
text += '' + _('{series_index} of {series}').format(series_index=sidx, series=series) + '
'
text += '
\xa0{0}\xa0\xa0\xa0 '.format(
_('Select'), '
' + _('Scroll to this book in the calibre library book list and select it.'))
- text += '
\xa0{0}
'.format(
+ text += '
\xa0{0}'.format(
_('Mark'), '' + _(
'Put a pin on this book in the calibre library, for future reference.'
' You can search for marked books using the search term: {0}').format('
marked:true'))
self.book_info.setHtml(text)
+ def render_results(self, results, individual_match=None):
+ html = []
+
+ def markup_text(text):
+ count = 0
+
+ def sub(m):
+ nonlocal count
+ count += 1
+ return '' if count % 2 else ''
+
+ return re.sub('\x1d', sub, text)
+
+ for (result, formats) in zip(results.texts, results.formats):
+ text = result['text']
+ text = markup_text(prepare_string_for_xml(text))
+ html.append(f'
{text}
')
+ self.results.setHtml('\n'.join(html))
+
class DetailsPanel(QStackedWidget):
def __init__(self, parent=None):
super().__init__(parent)
- self.currently_showing = None, None
# help panel {{{
self.help_panel = hp = HTMLDisplay(self)
@@ -571,19 +621,18 @@ p { margin: 0; }
return QSize(400, 700)
def show_result(self, results=None, individual_match=None):
- key = results, individual_match
- if key == self.currently_showing:
- return
- self.currently_showing = key
if results is None:
self.setCurrentIndex(0)
else:
self.setCurrentIndex(1)
self.result_details.show_result(results, individual_match)
+ def result_with_context_found(self, results, individual_match):
+ if self.currentIndex() == 1:
+ self.result_details.result_with_context_found(results, individual_match)
+
def clear(self):
self.setCurrentIndex(0)
- self.currently_showing = None, None
class LeftPanel(QWidget):
@@ -627,6 +676,7 @@ class ResultsPanel(QWidget):
self.details = d = DetailsPanel(self)
rv.current_changed.connect(d.show_result)
rv.search_started.connect(d.clear)
+ rv.result_with_context_found.connect(d.result_with_context_found)
s.addWidget(d)
def specialize_button_box(self, bb):