Display individual matches in details panel

This commit is contained in:
Kovid Goyal 2022-06-14 21:57:32 +05:30
parent e1e490b66e
commit 5991fecb04
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C

View File

@ -18,7 +18,7 @@ from qt.core import (
) )
from threading import Event, Thread 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.db import FTSQueryError
from calibre.ebooks.metadata import authors_to_string, fmt_sidx from calibre.ebooks.metadata import authors_to_string, fmt_sidx
from calibre.gui2 import config, error_dialog, gprefs, safe_open_url from calibre.gui2 import config, error_dialog, gprefs, safe_open_url
@ -147,6 +147,7 @@ class ResultsModel(QAbstractItemModel):
matches_found = pyqtSignal(int) matches_found = pyqtSignal(int)
search_complete = pyqtSignal() search_complete = pyqtSignal()
query_failed = pyqtSignal(str, str) query_failed = pyqtSignal(str, str)
result_with_context_found = pyqtSignal(object, int)
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
@ -245,6 +246,7 @@ class ResultsModel(QAbstractItemModel):
self.beginInsertRows(parent_idx, r, r) self.beginInsertRows(parent_idx, r, r)
parent.add_result_with_text(result) parent.add_result_with_text(result)
self.endInsertRows() self.endInsertRows()
self.result_with_context_found.emit(parent, r)
def signal_search_complete(self, query_id): def signal_search_complete(self, query_id):
if query_id == self.current_query_id: if query_id == self.current_query_id:
@ -333,6 +335,7 @@ class ResultsView(QTreeView):
matches_found = pyqtSignal(int) matches_found = pyqtSignal(int)
search_complete = pyqtSignal() search_complete = pyqtSignal()
current_changed = pyqtSignal(object, object) current_changed = pyqtSignal(object, object)
result_with_context_found = pyqtSignal(object, int)
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
@ -341,6 +344,7 @@ class ResultsView(QTreeView):
self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.setHeaderHidden(True) self.setHeaderHidden(True)
self.m = ResultsModel(self) 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_complete.connect(self.search_complete)
self.m.search_started.connect(self.search_started) self.m.search_started.connect(self.search_started)
self.m.search_started.connect(self.focus_self) self.m.search_started.connect(self.focus_self)
@ -351,7 +355,10 @@ class ResultsView(QTreeView):
self.setItemDelegate(self.delegate) self.setItemDelegate(self.delegate)
def currentChanged(self, current, previous): 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): def focus_self(self):
self.setFocus(Qt.FocusReason.OtherFocusReason) self.setFocus(Qt.FocusReason.OtherFocusReason)
@ -460,12 +467,19 @@ class ResultDetails(QWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.key = None
self.pixmap_label = pl = QLabel(self) self.pixmap_label = pl = QLabel(self)
pl.setScaledContents(True) pl.setScaledContents(True)
self.current_book_id = -1
self.book_info = bi = HTMLDisplay(self) self.book_info = bi = HTMLDisplay(self)
bi.setDefaultStyleSheet('a { text-decoration: none; }') bi.setDefaultStyleSheet('a { text-decoration: none; }')
bi.anchor_clicked.connect(self.book_info_anchor_clicked) 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): def book_info_anchor_clicked(self, url):
if self.current_book_id > 0: if self.current_book_id > 0:
@ -474,6 +488,10 @@ class ResultDetails(QWidget):
elif url.host() == 'jump': elif url.host() == 'jump':
jump_to_book(self.current_book_id) 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): def resizeEvent(self, ev):
self.do_layout() self.do_layout()
@ -492,43 +510,75 @@ class ResultDetails(QWidget):
if self.book_info.horizontalScrollBar().isVisible(): if self.book_info.horizontalScrollBar().isVisible():
h += self.book_info.horizontalScrollBar().height() + 1 h += self.book_info.horizontalScrollBar().height() + 1
self.book_info.setGeometry(QRect(self.pixmap_label.geometry().right() + 8, 0, w, h)) 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): 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: if old_current_book_id != self.current_book_id:
self.pixmap_label.setPixmap(results.cover) self.pixmap_label.setPixmap(results.cover)
self.render_book_info(results) self.render_book_info(results)
self.render_results(results, individual_match)
self.do_layout() 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): def render_book_info(self, results):
t = results.title t = results.title
if len(t) > 72: if len(t) > 72:
t = t[:71] + '' t = t[:71] + ''
text = f'<p><b>{results.title}</b><br>' text = f'<p><b>{prepare_string_for_xml(results.title)}</b><br>'
au = results.authors au = results.authors
if len(au) > 3: if len(au) > 3:
au = list(au[:3]) + [''] au = list(au[:3]) + ['']
text += f'{authors_to_string(au)}</p>' text += f'{prepare_string_for_xml(authors_to_string(au))}</p>'
if results.series: if results.series:
sidx = fmt_sidx(results.series_index or 0, use_roman=config['use_roman_numerals_for_series_number']) sidx = fmt_sidx(results.series_index or 0, use_roman=config['use_roman_numerals_for_series_number'])
series = results.series series = results.series
if len(series) > 60: if len(series) > 60:
series = series[:59] + '' series = series[:59] + ''
series = prepare_string_for_xml(series)
text += '<p>' + _('{series_index} of {series}').format(series_index=sidx, series=series) + '</p>' text += '<p>' + _('{series_index} of {series}').format(series_index=sidx, series=series) + '</p>'
text += '<p><a href="calibre://jump" title="{1}"><img valign="bottom" src="calibre-icon:///lt.png" width=16 height=16>\xa0{0}</a>\xa0\xa0\xa0 '.format( text += '<p><a href="calibre://jump" title="{1}"><img valign="bottom" src="calibre-icon:///lt.png" width=16 height=16>\xa0{0}</a>\xa0\xa0\xa0 '.format(
_('Select'), '<p>' + _('Scroll to this book in the calibre library book list and select it.')) _('Select'), '<p>' + _('Scroll to this book in the calibre library book list and select it.'))
text += '<a href="calibre://mark" title="{1}"><img valig="bottom" src="calibre-icon:///marked.png" width=16 height=16>\xa0{0}</a></p>'.format( text += '<a href="calibre://mark" title="{1}"><img valign="bottom" src="calibre-icon:///marked.png" width=16 height=16>\xa0{0}</a></p>'.format(
_('Mark'), '<p>' + _( _('Mark'), '<p>' + _(
'Put a pin on this book in the calibre library, for future reference.' '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('<p>marked:true')) ' You can search for marked books using the search term: {0}').format('<p>marked:true'))
self.book_info.setHtml(text) 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 '<b><i>' if count % 2 else '</i></b>'
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'<hr><p>{text}</p>')
self.results.setHtml('\n'.join(html))
class DetailsPanel(QStackedWidget): class DetailsPanel(QStackedWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.currently_showing = None, None
# help panel {{{ # help panel {{{
self.help_panel = hp = HTMLDisplay(self) self.help_panel = hp = HTMLDisplay(self)
@ -571,19 +621,18 @@ p { margin: 0; }
return QSize(400, 700) return QSize(400, 700)
def show_result(self, results=None, individual_match=None): 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: if results is None:
self.setCurrentIndex(0) self.setCurrentIndex(0)
else: else:
self.setCurrentIndex(1) self.setCurrentIndex(1)
self.result_details.show_result(results, individual_match) 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): def clear(self):
self.setCurrentIndex(0) self.setCurrentIndex(0)
self.currently_showing = None, None
class LeftPanel(QWidget): class LeftPanel(QWidget):
@ -627,6 +676,7 @@ class ResultsPanel(QWidget):
self.details = d = DetailsPanel(self) self.details = d = DetailsPanel(self)
rv.current_changed.connect(d.show_result) rv.current_changed.connect(d.show_result)
rv.search_started.connect(d.clear) rv.search_started.connect(d.clear)
rv.result_with_context_found.connect(d.result_with_context_found)
s.addWidget(d) s.addWidget(d)
def specialize_button_box(self, bb): def specialize_button_box(self, bb):