diff --git a/src/calibre/gui2/library/annotations.py b/src/calibre/gui2/library/annotations.py index 3fde7e9ab1..15212edf1a 100644 --- a/src/calibre/gui2/library/annotations.py +++ b/src/calibre/gui2/library/annotations.py @@ -7,12 +7,12 @@ from __future__ import absolute_import, division, print_function, unicode_litera import os from PyQt5.Qt import ( - QApplication, QCursor, QHBoxLayout, QIcon, QListWidget, QSize, QSplitter, Qt, - QToolButton, QVBoxLayout, QWidget + QApplication, QCursor, QFont, QHBoxLayout, QIcon, QSize, QSplitter, Qt, + QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget ) from calibre.gui2 import Application -from calibre.gui2.viewer.search import SearchBox +from calibre.gui2.viewer.search import SearchBox, ResultsDelegate from calibre.gui2.widgets2 import Dialog @@ -30,15 +30,52 @@ class BusyCursor(object): QApplication.restoreOverrideCursor() -class ResultsList(QListWidget): +class AnnotsResultsDelegate(ResultsDelegate): + + def result_data(self, result): + if not isinstance(result, dict): + return None, None, None, None + full_text = result['text'].replace('0x1f', ' ') + parts = full_text.split('0x1d', 2) + before = after = '' + if len(parts) == 3: + before, text, after = parts + elif len(parts) == 2: + before, text = parts + else: + text = parts[0] + return False, before, text, after + + +class ResultsList(QTreeWidget): def __init__(self, parent): - QListWidget.__init__(self, parent) + QTreeWidget.__init__(self, parent) + self.setHeaderHidden(True) + self.delegate = AnnotsResultsDelegate(self) + self.setItemDelegate(self.delegate) + self.section_font = QFont(self.font()) + self.section_font.setItalic(True) def set_results(self, results): self.clear() + book_id_map = {} + db = current_db() for result in results: - print(result) + book_id = result['book_id'] + if book_id not in book_id_map: + book_id_map[book_id] = {'title': db.field_for('title', book_id), 'matches': []} + book_id_map[book_id]['matches'].append(result) + for book_id, entry in book_id_map.items(): + section = QTreeWidgetItem([entry['title']], 1) + section.setFlags(Qt.ItemIsEnabled) + section.setFont(0, self.section_font) + self.addTopLevelItem(section) + section.setExpanded(True) + for result in entry['matches']: + item = QTreeWidgetItem(section, [' '], 2) + item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemNeverHasChildren) + item.setData(0, Qt.UserRole, result) class BrowsePanel(QWidget): @@ -97,8 +134,9 @@ class BrowsePanel(QWidget): return with BusyCursor(): db = current_db() - results = db.search_annotations(**q) + results = db.search_annotations(highlight_start='0x1d', highlight_end='0x1d', snippet_size=64, **q) self.results_list.set_results(results) + self.current_query = q def show_next(self): self.do_find() diff --git a/src/calibre/gui2/viewer/search.py b/src/calibre/gui2/viewer/search.py index 19738a5a83..f8587b10ae 100644 --- a/src/calibre/gui2/viewer/search.py +++ b/src/calibre/gui2/viewer/search.py @@ -449,10 +449,16 @@ class SearchInput(QWidget): # {{{ class ResultsDelegate(QStyledItemDelegate): # {{{ + def result_data(self, result): + if not isinstance(result, SearchResult): + return None, None, None, None + return result.is_hidden, result.before, result.text, result.text + def paint(self, painter, option, index): QStyledItemDelegate.paint(self, painter, option, index) result = index.data(Qt.UserRole) - if not isinstance(result, SearchResult): + is_hidden, result_before, result_text, result_after = self.result_data(result) + if result_text is None: return painter.save() try: @@ -465,19 +471,19 @@ class ResultsDelegate(QStyledItemDelegate): # {{{ emphasis_font = QFont(font) emphasis_font.setBold(True) flags = Qt.AlignTop | Qt.TextSingleLine | Qt.TextIncludeTrailingSpaces - rect = option.rect.adjusted(option.decorationSize.width() + 4 if result.is_hidden else 0, 0, 0, 0) + rect = option.rect.adjusted(option.decorationSize.width() + 4 if is_hidden else 0, 0, 0, 0) painter.setClipRect(rect) - before = re.sub(r'\s+', ' ', result.before) + before = re.sub(r'\s+', ' ', result_before) before_width = 0 if before: before_width = painter.boundingRect(rect, flags, before).width() - after = re.sub(r'\s+', ' ', result.after.rstrip()) + after = re.sub(r'\s+', ' ', result_after.rstrip()) after_width = 0 if after: after_width = painter.boundingRect(rect, flags, after).width() ellipsis_width = painter.boundingRect(rect, flags, '...').width() painter.setFont(emphasis_font) - text = re.sub(r'\s+', ' ', result.text) + text = re.sub(r'\s+', ' ', result_text) match_width = painter.boundingRect(rect, flags, text).width() if match_width >= rect.width() - 3 * ellipsis_width: efm = QFontMetrics(emphasis_font)