From f997bc9c9a6847b0b0b7441963ce340474e280aa Mon Sep 17 00:00:00 2001 From: BasioMeusPuga Date: Wed, 9 Jan 2019 13:50:08 +0530 Subject: [PATCH] Search result highlighting Disable UI elements when irrelevant --- TODO | 2 +- lector/__main__.py | 8 +++++ lector/threaded.py | 16 +++++++--- lector/toolbars.py | 8 +++-- lector/widgets.py | 74 +++++++++++++++++++++++++++++++++++----------- 5 files changed, 83 insertions(+), 25 deletions(-) diff --git a/TODO b/TODO index da9eeb8..5c1b1f3 100644 --- a/TODO +++ b/TODO @@ -62,6 +62,7 @@ TODO ✓ Make the bookmark dock float over the reading area ✓ Spacebar should not cut off lines at the top ✓ Track open bookmark windows so they can be closed quickly at exit + ✓ Search document using QTextCursor Double page / column view ✓ For comics Caching is currently non fuctional @@ -70,7 +71,6 @@ TODO Annotation preview in listView Disable buttons for annotations, search in images Adjust key navigation according to viewport dimensions - Search document using QTextCursor Redo context menu order Filetypes: ✓ pdf support diff --git a/lector/__main__.py b/lector/__main__.py index 7f0f80d..7c14d8a 100755 --- a/lector/__main__.py +++ b/lector/__main__.py @@ -643,6 +643,14 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): if self.bookToolBar.fontButton.isChecked(): self.bookToolBar.customize_view_on() + # Disable irrelevant buttons in image view mode + if current_tab.are_we_doing_images_only: + self.bookToolBar.searchButton.setEnabled(False) + self.bookToolBar.annotationButton.setEnabled(False) + else: + self.bookToolBar.searchButton.setEnabled(True) + self.bookToolBar.annotationButton.setEnabled(True) + current_position = current_metadata['position'] current_toc = [i[0] for i in current_metadata['content']] diff --git a/lector/threaded.py b/lector/threaded.py index 270f972..79669ac 100644 --- a/lector/threaded.py +++ b/lector/threaded.py @@ -15,6 +15,7 @@ # along with this program. If not, see . import os +import re import pathlib from multiprocessing.dummy import Pool @@ -217,18 +218,25 @@ class BackGroundTextSearch(QtCore.QThread): surroundingTextCursor = QtGui.QTextCursor(chapterDocument) surroundingTextCursor.setPosition( result_position, QtGui.QTextCursor.MoveAnchor) + # Get words before and after surroundingTextCursor.movePosition( - QtGui.QTextCursor.WordLeft, QtGui.QTextCursor.MoveAnchor, 2) + QtGui.QTextCursor.WordLeft, QtGui.QTextCursor.MoveAnchor, 3) surroundingTextCursor.movePosition( - QtGui.QTextCursor.NextWord, QtGui.QTextCursor.KeepAnchor, 5) # 2n + 1 + QtGui.QTextCursor.NextWord, QtGui.QTextCursor.KeepAnchor, 6) surrounding_text = surroundingTextCursor.selection().toPlainText() surrounding_text = surrounding_text.replace('\n', ' ') + # Case insensitive replace for find results + replace_pattern = re.compile(re.escape(self.search_text), re.IGNORECASE) + surrounding_text = replace_pattern.sub( + f'{self.search_text}', surrounding_text) + try: self.search_results[chapter].append( - (result_position, surrounding_text)) + (result_position, surrounding_text, self.search_text)) except KeyError: - self.search_results[chapter] = [(result_position, surrounding_text)] + self.search_results[chapter] = [ + (result_position, surrounding_text, self.search_text)] new_position = result_position + len(self.search_text) findResultCursor = chapterDocument.find( diff --git a/lector/toolbars.py b/lector/toolbars.py index d34468d..76beee0 100644 --- a/lector/toolbars.py +++ b/lector/toolbars.py @@ -74,12 +74,12 @@ class BookToolBar(QtWidgets.QToolBar): self.fontButton.setCheckable(True) self.fontButton.triggered.connect(self.toggle_font_settings) self.bookSeparator1 = self.addSeparator() - self.addAction(self.searchButton) - self.bookSeparator2 = self.addSeparator() self.addAction(self.annotationButton) - self.bookSeparator3 = self.addSeparator() + self.bookSeparator2 = self.addSeparator() self.addAction(self.addBookmarkButton) self.addAction(self.bookmarkButton) + self.bookSeparator3 = self.addSeparator() + self.addAction(self.searchButton) self.bookSeparator4 = self.addSeparator() self.addAction(self.distractionFreeButton) self.addAction(self.fullscreenButton) @@ -307,9 +307,11 @@ class BookToolBar(QtWidgets.QToolBar): self.annotationButton, self.addBookmarkButton, self.bookmarkButton, + self.searchButton, self.distractionFreeButton, self.fullscreenButton, self.tocBoxAction, + self.bookSeparator1, self.bookSeparator2, self.bookSeparator3, self.bookSeparator4] diff --git a/lector/widgets.py b/lector/widgets.py index 1c3e9b4..a93ef04 100644 --- a/lector/widgets.py +++ b/lector/widgets.py @@ -130,18 +130,18 @@ class Tab(QtWidgets.QWidget): self.sideDock.setWidget(self.sideDockTabWidget) # Annotation list view and model - self.annotationListView = QtWidgets.QListView(self.sideDockTabWidget) - # self.annotationListView.setResizeMode(QtWidgets.QListWidget.Adjust) + self.annotationListView = QtWidgets.QListView() self.annotationListView.setEditTriggers(QtWidgets.QListView.NoEditTriggers) self.annotationListView.doubleClicked.connect(self.contentView.toggle_annotation_mode) annotations_string = self._translate('Tab', 'Annotations') - self.sideDockTabWidget.addTab(self.annotationListView, annotations_string) + if not self.are_we_doing_images_only: + self.sideDockTabWidget.addTab(self.annotationListView, annotations_string) self.annotationModel = QtGui.QStandardItemModel(self) self.generate_annotation_model() # Bookmark tree view and model - self.bookmarkTreeView = QtWidgets.QTreeView(self.sideDockTabWidget) + self.bookmarkTreeView = QtWidgets.QTreeView() self.bookmarkTreeView.setHeaderHidden(True) self.bookmarkTreeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.bookmarkTreeView.customContextMenuRequested.connect( @@ -155,14 +155,14 @@ class Tab(QtWidgets.QWidget): self.generate_bookmark_model() # Search view and model - self.searchLineEdit = QtWidgets.QLineEdit(self.sideDockTabWidget) + self.searchLineEdit = QtWidgets.QLineEdit() self.searchLineEdit.setFocusPolicy(QtCore.Qt.StrongFocus) self.searchLineEdit.setClearButtonEnabled(True) search_string = self._translate('Tab', 'Search') self.searchLineEdit.setPlaceholderText(search_string) search_book_string = self._translate('Tab', 'Search entire book') - self.searchBookButton = QtWidgets.QToolButton(self.sideDockTabWidget) + self.searchBookButton = QtWidgets.QToolButton() self.searchBookButton.setIcon( self.main_window.QImageFactory.get_image('view-readermode')) self.searchBookButton.setToolTip(search_book_string) @@ -170,7 +170,7 @@ class Tab(QtWidgets.QWidget): self.searchBookButton.setAutoRaise(True) case_sensitive_string = self._translate('Tab', 'Match case') - self.caseSensitiveSearchButton = QtWidgets.QToolButton(self.sideDockTabWidget) + self.caseSensitiveSearchButton = QtWidgets.QToolButton() self.caseSensitiveSearchButton.setIcon( self.main_window.QImageFactory.get_image('search-case')) self.caseSensitiveSearchButton.setToolTip(case_sensitive_string) @@ -178,7 +178,7 @@ class Tab(QtWidgets.QWidget): self.caseSensitiveSearchButton.setAutoRaise(True) match_word_string = self._translate('Tab', 'Match word') - self.matchWholeWordButton = QtWidgets.QToolButton(self.sideDockTabWidget) + self.matchWholeWordButton = QtWidgets.QToolButton() self.matchWholeWordButton.setIcon( self.main_window.QImageFactory.get_image('search-word')) self.matchWholeWordButton.setToolTip(match_word_string) @@ -192,19 +192,20 @@ class Tab(QtWidgets.QWidget): self.searchOptionsLayout.addWidget(self.caseSensitiveSearchButton) self.searchOptionsLayout.addWidget(self.matchWholeWordButton) - self.searchResultsTreeView = QtWidgets.QTreeView(self.sideDockTabWidget) + self.searchResultsTreeView = QtWidgets.QTreeView() self.searchResultsTreeView.setHeaderHidden(True) self.searchResultsTreeView.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) self.searchResultsTreeView.clicked.connect(self.navigate_to_search_result) - self.searchTabLayout = QtWidgets.QVBoxLayout(self.sideDockTabWidget) + self.searchTabLayout = QtWidgets.QVBoxLayout() self.searchTabLayout.addLayout(self.searchOptionsLayout) self.searchTabLayout.addWidget(self.searchResultsTreeView) self.searchTabLayout.setContentsMargins(0, 0, 0, 0) - self.searchTabWidget = QtWidgets.QWidget(self.sideDockTabWidget) + self.searchTabWidget = QtWidgets.QWidget() self.searchTabWidget.setLayout(self.searchTabLayout) - self.sideDockTabWidget.addTab(self.searchTabWidget, search_string) + if not self.are_we_doing_images_only: + self.sideDockTabWidget.addTab(self.searchTabWidget, search_string) # Create the annotation notes dock self.annotationNoteDock = PliantDockWidget(self.main_window, True, self.contentView) @@ -241,6 +242,8 @@ class Tab(QtWidgets.QWidget): self.searchTimer.setSingleShot(True) self.searchTimer.timeout.connect(self.set_search_options) + self.searchLineEdit.textChanged.connect( + lambda: self.searchLineEdit.setStyleSheet(QtWidgets.QLineEdit.styleSheet(self))) self.searchLineEdit.textChanged.connect(lambda: self.searchTimer.start(500)) self.searchBookButton.clicked.connect(lambda: self.searchTimer.start(100)) self.caseSensitiveSearchButton.clicked.connect(lambda: self.searchTimer.start(100)) @@ -705,23 +708,46 @@ class Tab(QtWidgets.QWidget): search_results = self.searchThread.search_results for i in search_results: parentItem = QtGui.QStandardItem() - parentItem.setText(i) - parentItem.setData(True, QtCore.Qt.UserRole) + parentItem.setData(True, QtCore.Qt.UserRole) # Is parent? + parentItem.setData(i, QtCore.Qt.UserRole + 3) # Display text for label chapter_index = self.main_window.bookToolBar.tocBox.findText( i, QtCore.Qt.MatchExactly) for j in search_results[i]: - childItem = QtGui.QStandardItem() - childItem.setText(j[1]) + childItem = QtGui.QStandardItem(parentItem) childItem.setData(False, QtCore.Qt.UserRole) # Is parent? childItem.setData(chapter_index, QtCore.Qt.UserRole + 1) # Chapter index childItem.setData(j[0], QtCore.Qt.UserRole + 2) # Cursor Position + childItem.setData(j[1], QtCore.Qt.UserRole + 3) # Display text for label + childItem.setData(j[2], QtCore.Qt.UserRole + 4) # Search term parentItem.appendRow(childItem) self.searchResultsModel.appendRow(parentItem) self.searchResultsTreeView.setModel(self.searchResultsModel) self.searchResultsTreeView.expandToDepth(1) + if not search_results and len(self.searchLineEdit.text()) > 2: + self.searchLineEdit.setStyleSheet("QLineEdit {color: red;}") + + # We'll be putting in labels instead of making a delegate + # QLabels can understand RTF, and they also have the somewhat + # distinct advantage of being a lot less work than a delegate + + def generate_label(index): + label_text = self.searchResultsModel.data(index, QtCore.Qt.UserRole + 3) + labelWidget = PliantLabelWidget(index, self.navigate_to_search_result) + labelWidget.setText(label_text) + self.searchResultsTreeView.setIndexWidget(index, labelWidget) + + for parent_iter in range(self.searchResultsModel.rowCount()): + parentItem = self.searchResultsModel.item(parent_iter) + parentIndex = self.searchResultsModel.index(parent_iter, 0) + generate_label(parentIndex) + + for child_iter in range(parentItem.rowCount()): + childIndex = self.searchResultsModel.index(child_iter, 0, parentIndex) + generate_label(childIndex) + def navigate_to_search_result(self, index): if not index.isValid(): return @@ -732,11 +758,12 @@ class Tab(QtWidgets.QWidget): chapter_index = self.searchResultsModel.data(index, QtCore.Qt.UserRole + 1) cursor_position = self.searchResultsModel.data(index, QtCore.Qt.UserRole + 2) + search_term = self.searchResultsModel.data(index, QtCore.Qt.UserRole + 4) self.main_window.bookToolBar.tocBox.setCurrentIndex(chapter_index) if not self.are_we_doing_images_only: self.set_cursor_position( - cursor_position, len(self.searchLineEdit.text())) + cursor_position, len(search_term)) def hide_mouse(self): self.contentView.viewport().setCursor(QtCore.Qt.BlankCursor) @@ -754,6 +781,19 @@ class Tab(QtWidgets.QWidget): self.main_window.closeEvent() +class PliantLabelWidget(QtWidgets.QLabel): + # This is a hack to get clickable / editable appearance + # search results in the tree view. + + def __init__(self, index, navigate_to_search_result): + super(PliantLabelWidget, self).__init__() + self.index = index + self.navigate_to_search_result = navigate_to_search_result + + def mousePressEvent(self, QMouseEvent): + self.navigate_to_search_result(self.index) + QtWidgets.QLabel.mousePressEvent(self, QMouseEvent) + class PliantDockWidget(QtWidgets.QDockWidget): def __init__(self, main_window, notes_only, contentView, parent=None): super(PliantDockWidget, self).__init__()