Search result highlighting
Disable UI elements when irrelevant
This commit is contained in:
2
TODO
2
TODO
@@ -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
|
||||||
|
@@ -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']]
|
||||||
|
|
||||||
|
@@ -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(
|
||||||
|
@@ -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]
|
||||||
|
@@ -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__()
|
||||||
|
Reference in New Issue
Block a user