Search result highlighting

Disable UI elements when irrelevant
This commit is contained in:
BasioMeusPuga
2019-01-09 13:50:08 +05:30
parent 930a97a8fa
commit f997bc9c9a
5 changed files with 83 additions and 25 deletions

2
TODO
View File

@@ -62,6 +62,7 @@ TODO
✓ Make the bookmark dock float over the reading area ✓ Make the bookmark dock float over the reading area
✓ Spacebar should not cut off lines at the top ✓ Spacebar should not cut off lines at the top
✓ Track open bookmark windows so they can be closed quickly at exit ✓ Track open bookmark windows so they can be closed quickly at exit
✓ Search document using QTextCursor
Double page / column view Double page / column view
✓ For comics ✓ For comics
Caching is currently non fuctional Caching is currently non fuctional
@@ -70,7 +71,6 @@ TODO
Annotation preview in listView Annotation preview in listView
Disable buttons for annotations, search in images Disable buttons for annotations, search in images
Adjust key navigation according to viewport dimensions Adjust key navigation according to viewport dimensions
Search document using QTextCursor
Redo context menu order Redo context menu order
Filetypes: Filetypes:
✓ pdf support ✓ pdf support

View File

@@ -643,6 +643,14 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
if self.bookToolBar.fontButton.isChecked(): if self.bookToolBar.fontButton.isChecked():
self.bookToolBar.customize_view_on() 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_position = current_metadata['position']
current_toc = [i[0] for i in current_metadata['content']] current_toc = [i[0] for i in current_metadata['content']]

View File

@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import os import os
import re
import pathlib import pathlib
from multiprocessing.dummy import Pool from multiprocessing.dummy import Pool
@@ -217,18 +218,25 @@ class BackGroundTextSearch(QtCore.QThread):
surroundingTextCursor = QtGui.QTextCursor(chapterDocument) surroundingTextCursor = QtGui.QTextCursor(chapterDocument)
surroundingTextCursor.setPosition( surroundingTextCursor.setPosition(
result_position, QtGui.QTextCursor.MoveAnchor) result_position, QtGui.QTextCursor.MoveAnchor)
# Get words before and after
surroundingTextCursor.movePosition( surroundingTextCursor.movePosition(
QtGui.QTextCursor.WordLeft, QtGui.QTextCursor.MoveAnchor, 2) QtGui.QTextCursor.WordLeft, QtGui.QTextCursor.MoveAnchor, 3)
surroundingTextCursor.movePosition( 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 = surroundingTextCursor.selection().toPlainText()
surrounding_text = surrounding_text.replace('\n', ' ') 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'<b>{self.search_text}</b>', surrounding_text)
try: try:
self.search_results[chapter].append( self.search_results[chapter].append(
(result_position, surrounding_text)) (result_position, surrounding_text, self.search_text))
except KeyError: 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) new_position = result_position + len(self.search_text)
findResultCursor = chapterDocument.find( findResultCursor = chapterDocument.find(

View File

@@ -74,12 +74,12 @@ class BookToolBar(QtWidgets.QToolBar):
self.fontButton.setCheckable(True) self.fontButton.setCheckable(True)
self.fontButton.triggered.connect(self.toggle_font_settings) self.fontButton.triggered.connect(self.toggle_font_settings)
self.bookSeparator1 = self.addSeparator() self.bookSeparator1 = self.addSeparator()
self.addAction(self.searchButton)
self.bookSeparator2 = self.addSeparator()
self.addAction(self.annotationButton) self.addAction(self.annotationButton)
self.bookSeparator3 = self.addSeparator() self.bookSeparator2 = self.addSeparator()
self.addAction(self.addBookmarkButton) self.addAction(self.addBookmarkButton)
self.addAction(self.bookmarkButton) self.addAction(self.bookmarkButton)
self.bookSeparator3 = self.addSeparator()
self.addAction(self.searchButton)
self.bookSeparator4 = self.addSeparator() self.bookSeparator4 = self.addSeparator()
self.addAction(self.distractionFreeButton) self.addAction(self.distractionFreeButton)
self.addAction(self.fullscreenButton) self.addAction(self.fullscreenButton)
@@ -307,9 +307,11 @@ class BookToolBar(QtWidgets.QToolBar):
self.annotationButton, self.annotationButton,
self.addBookmarkButton, self.addBookmarkButton,
self.bookmarkButton, self.bookmarkButton,
self.searchButton,
self.distractionFreeButton, self.distractionFreeButton,
self.fullscreenButton, self.fullscreenButton,
self.tocBoxAction, self.tocBoxAction,
self.bookSeparator1,
self.bookSeparator2, self.bookSeparator2,
self.bookSeparator3, self.bookSeparator3,
self.bookSeparator4] self.bookSeparator4]

View File

@@ -130,18 +130,18 @@ class Tab(QtWidgets.QWidget):
self.sideDock.setWidget(self.sideDockTabWidget) self.sideDock.setWidget(self.sideDockTabWidget)
# Annotation list view and model # Annotation list view and model
self.annotationListView = QtWidgets.QListView(self.sideDockTabWidget) self.annotationListView = QtWidgets.QListView()
# self.annotationListView.setResizeMode(QtWidgets.QListWidget.Adjust)
self.annotationListView.setEditTriggers(QtWidgets.QListView.NoEditTriggers) self.annotationListView.setEditTriggers(QtWidgets.QListView.NoEditTriggers)
self.annotationListView.doubleClicked.connect(self.contentView.toggle_annotation_mode) self.annotationListView.doubleClicked.connect(self.contentView.toggle_annotation_mode)
annotations_string = self._translate('Tab', 'Annotations') 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.annotationModel = QtGui.QStandardItemModel(self)
self.generate_annotation_model() self.generate_annotation_model()
# Bookmark tree view and model # Bookmark tree view and model
self.bookmarkTreeView = QtWidgets.QTreeView(self.sideDockTabWidget) self.bookmarkTreeView = QtWidgets.QTreeView()
self.bookmarkTreeView.setHeaderHidden(True) self.bookmarkTreeView.setHeaderHidden(True)
self.bookmarkTreeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.bookmarkTreeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.bookmarkTreeView.customContextMenuRequested.connect( self.bookmarkTreeView.customContextMenuRequested.connect(
@@ -155,14 +155,14 @@ class Tab(QtWidgets.QWidget):
self.generate_bookmark_model() self.generate_bookmark_model()
# Search view and model # Search view and model
self.searchLineEdit = QtWidgets.QLineEdit(self.sideDockTabWidget) self.searchLineEdit = QtWidgets.QLineEdit()
self.searchLineEdit.setFocusPolicy(QtCore.Qt.StrongFocus) self.searchLineEdit.setFocusPolicy(QtCore.Qt.StrongFocus)
self.searchLineEdit.setClearButtonEnabled(True) self.searchLineEdit.setClearButtonEnabled(True)
search_string = self._translate('Tab', 'Search') search_string = self._translate('Tab', 'Search')
self.searchLineEdit.setPlaceholderText(search_string) self.searchLineEdit.setPlaceholderText(search_string)
search_book_string = self._translate('Tab', 'Search entire book') search_book_string = self._translate('Tab', 'Search entire book')
self.searchBookButton = QtWidgets.QToolButton(self.sideDockTabWidget) self.searchBookButton = QtWidgets.QToolButton()
self.searchBookButton.setIcon( self.searchBookButton.setIcon(
self.main_window.QImageFactory.get_image('view-readermode')) self.main_window.QImageFactory.get_image('view-readermode'))
self.searchBookButton.setToolTip(search_book_string) self.searchBookButton.setToolTip(search_book_string)
@@ -170,7 +170,7 @@ class Tab(QtWidgets.QWidget):
self.searchBookButton.setAutoRaise(True) self.searchBookButton.setAutoRaise(True)
case_sensitive_string = self._translate('Tab', 'Match case') case_sensitive_string = self._translate('Tab', 'Match case')
self.caseSensitiveSearchButton = QtWidgets.QToolButton(self.sideDockTabWidget) self.caseSensitiveSearchButton = QtWidgets.QToolButton()
self.caseSensitiveSearchButton.setIcon( self.caseSensitiveSearchButton.setIcon(
self.main_window.QImageFactory.get_image('search-case')) self.main_window.QImageFactory.get_image('search-case'))
self.caseSensitiveSearchButton.setToolTip(case_sensitive_string) self.caseSensitiveSearchButton.setToolTip(case_sensitive_string)
@@ -178,7 +178,7 @@ class Tab(QtWidgets.QWidget):
self.caseSensitiveSearchButton.setAutoRaise(True) self.caseSensitiveSearchButton.setAutoRaise(True)
match_word_string = self._translate('Tab', 'Match word') match_word_string = self._translate('Tab', 'Match word')
self.matchWholeWordButton = QtWidgets.QToolButton(self.sideDockTabWidget) self.matchWholeWordButton = QtWidgets.QToolButton()
self.matchWholeWordButton.setIcon( self.matchWholeWordButton.setIcon(
self.main_window.QImageFactory.get_image('search-word')) self.main_window.QImageFactory.get_image('search-word'))
self.matchWholeWordButton.setToolTip(match_word_string) self.matchWholeWordButton.setToolTip(match_word_string)
@@ -192,19 +192,20 @@ class Tab(QtWidgets.QWidget):
self.searchOptionsLayout.addWidget(self.caseSensitiveSearchButton) self.searchOptionsLayout.addWidget(self.caseSensitiveSearchButton)
self.searchOptionsLayout.addWidget(self.matchWholeWordButton) self.searchOptionsLayout.addWidget(self.matchWholeWordButton)
self.searchResultsTreeView = QtWidgets.QTreeView(self.sideDockTabWidget) self.searchResultsTreeView = QtWidgets.QTreeView()
self.searchResultsTreeView.setHeaderHidden(True) self.searchResultsTreeView.setHeaderHidden(True)
self.searchResultsTreeView.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) self.searchResultsTreeView.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers)
self.searchResultsTreeView.clicked.connect(self.navigate_to_search_result) 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.addLayout(self.searchOptionsLayout)
self.searchTabLayout.addWidget(self.searchResultsTreeView) self.searchTabLayout.addWidget(self.searchResultsTreeView)
self.searchTabLayout.setContentsMargins(0, 0, 0, 0) self.searchTabLayout.setContentsMargins(0, 0, 0, 0)
self.searchTabWidget = QtWidgets.QWidget(self.sideDockTabWidget) self.searchTabWidget = QtWidgets.QWidget()
self.searchTabWidget.setLayout(self.searchTabLayout) 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 # Create the annotation notes dock
self.annotationNoteDock = PliantDockWidget(self.main_window, True, self.contentView) self.annotationNoteDock = PliantDockWidget(self.main_window, True, self.contentView)
@@ -241,6 +242,8 @@ class Tab(QtWidgets.QWidget):
self.searchTimer.setSingleShot(True) self.searchTimer.setSingleShot(True)
self.searchTimer.timeout.connect(self.set_search_options) 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.searchLineEdit.textChanged.connect(lambda: self.searchTimer.start(500))
self.searchBookButton.clicked.connect(lambda: self.searchTimer.start(100)) self.searchBookButton.clicked.connect(lambda: self.searchTimer.start(100))
self.caseSensitiveSearchButton.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 search_results = self.searchThread.search_results
for i in search_results: for i in search_results:
parentItem = QtGui.QStandardItem() parentItem = QtGui.QStandardItem()
parentItem.setText(i) parentItem.setData(True, QtCore.Qt.UserRole) # Is parent?
parentItem.setData(True, QtCore.Qt.UserRole) parentItem.setData(i, QtCore.Qt.UserRole + 3) # Display text for label
chapter_index = self.main_window.bookToolBar.tocBox.findText( chapter_index = self.main_window.bookToolBar.tocBox.findText(
i, QtCore.Qt.MatchExactly) i, QtCore.Qt.MatchExactly)
for j in search_results[i]: for j in search_results[i]:
childItem = QtGui.QStandardItem() childItem = QtGui.QStandardItem(parentItem)
childItem.setText(j[1])
childItem.setData(False, QtCore.Qt.UserRole) # Is parent? childItem.setData(False, QtCore.Qt.UserRole) # Is parent?
childItem.setData(chapter_index, QtCore.Qt.UserRole + 1) # Chapter index childItem.setData(chapter_index, QtCore.Qt.UserRole + 1) # Chapter index
childItem.setData(j[0], QtCore.Qt.UserRole + 2) # Cursor Position 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) parentItem.appendRow(childItem)
self.searchResultsModel.appendRow(parentItem) self.searchResultsModel.appendRow(parentItem)
self.searchResultsTreeView.setModel(self.searchResultsModel) self.searchResultsTreeView.setModel(self.searchResultsModel)
self.searchResultsTreeView.expandToDepth(1) 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): def navigate_to_search_result(self, index):
if not index.isValid(): if not index.isValid():
return return
@@ -732,11 +758,12 @@ class Tab(QtWidgets.QWidget):
chapter_index = self.searchResultsModel.data(index, QtCore.Qt.UserRole + 1) chapter_index = self.searchResultsModel.data(index, QtCore.Qt.UserRole + 1)
cursor_position = self.searchResultsModel.data(index, QtCore.Qt.UserRole + 2) 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) self.main_window.bookToolBar.tocBox.setCurrentIndex(chapter_index)
if not self.are_we_doing_images_only: if not self.are_we_doing_images_only:
self.set_cursor_position( self.set_cursor_position(
cursor_position, len(self.searchLineEdit.text())) cursor_position, len(search_term))
def hide_mouse(self): def hide_mouse(self):
self.contentView.viewport().setCursor(QtCore.Qt.BlankCursor) self.contentView.viewport().setCursor(QtCore.Qt.BlankCursor)
@@ -754,6 +781,19 @@ class Tab(QtWidgets.QWidget):
self.main_window.closeEvent() 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): class PliantDockWidget(QtWidgets.QDockWidget):
def __init__(self, main_window, notes_only, contentView, parent=None): def __init__(self, main_window, notes_only, contentView, parent=None):
super(PliantDockWidget, self).__init__() super(PliantDockWidget, self).__init__()