Implement search
This commit is contained in:
@@ -398,10 +398,6 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
if not file_paths:
|
||||
return
|
||||
|
||||
def finishing_touches():
|
||||
self.profile_functions.format_contentView()
|
||||
self.start_culling_timer()
|
||||
|
||||
print('Attempting to open: ' + ', '.join(file_paths))
|
||||
|
||||
contents = sorter.BookSorter(
|
||||
@@ -434,11 +430,9 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
if self.settings['last_open_tab'] == this_path:
|
||||
self.tabWidget.setCurrentIndex(i)
|
||||
self.settings['last_open_tab'] = None
|
||||
finishing_touches()
|
||||
return
|
||||
|
||||
self.tabWidget.setCurrentIndex(self.tabWidget.count() - 1)
|
||||
finishing_touches()
|
||||
|
||||
def start_culling_timer(self):
|
||||
if self.settings['perform_culling']:
|
||||
@@ -457,7 +451,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
|
||||
# The hackiness of this hack is just...
|
||||
default_size = 170 # This is size of the QIcon (160 by default) +
|
||||
# minimum margin is needed between thumbnails
|
||||
# minimum margin needed between thumbnails
|
||||
|
||||
# for n icons, the n + 1th icon will appear at > n +1.11875
|
||||
# First, calculate the number of images per row
|
||||
@@ -759,32 +753,6 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
|
||||
required_content = current_tab.metadata['content'][chapter_number][1]
|
||||
current_tab.contentView.loadImage(required_content)
|
||||
|
||||
def search_book(self, search_text):
|
||||
if not (self.tabWidget.currentIndex() != 0
|
||||
and not self.tabWidget.currentWidget().are_we_doing_images_only):
|
||||
return
|
||||
|
||||
self.tabWidget.currentWidget().sideDock.setVisible(True)
|
||||
self.tabWidget.currentWidget().sideDockTabWidget.setCurrentIndex(2)
|
||||
|
||||
contentView = self.tabWidget.currentWidget().contentView
|
||||
|
||||
text_cursor = contentView.textCursor()
|
||||
something_found = True
|
||||
if search_text:
|
||||
text_cursor.setPosition(0, QtGui.QTextCursor.MoveAnchor)
|
||||
contentView.setTextCursor(text_cursor)
|
||||
contentView.verticalScrollBar().setValue(contentView.verticalScrollBar().maximum())
|
||||
something_found = contentView.find(search_text)
|
||||
else:
|
||||
text_cursor.clearSelection()
|
||||
contentView.setTextCursor(text_cursor)
|
||||
|
||||
# if not something_found:
|
||||
# self.bookToolBar.searchBar.setStyleSheet("QLineEdit {color: red;}")
|
||||
# else:
|
||||
# self.bookToolBar.searchBar.setStyleSheet(self.lineEditStyleSheet)
|
||||
|
||||
def generate_library_context_menu(self, position):
|
||||
index = self.sender().indexAt(position)
|
||||
if not index.isValid():
|
||||
|
@@ -277,8 +277,7 @@ class ViewProfileModification:
|
||||
self.format_contentView()
|
||||
|
||||
def format_contentView(self):
|
||||
current_tab = self.tabWidget.widget(
|
||||
self.tabWidget.currentIndex())
|
||||
current_tab = self.tabWidget.currentWidget()
|
||||
|
||||
try:
|
||||
current_metadata = current_tab.metadata
|
||||
|
@@ -171,3 +171,65 @@ class BackGroundCacheRefill(QtCore.QThread):
|
||||
self.image_cache.append((next_page, refill_pixmap))
|
||||
except (IndexError, TypeError):
|
||||
self.image_cache.append(None)
|
||||
|
||||
|
||||
class BackGroundTextSearch(QtCore.QThread):
|
||||
def __init__(self):
|
||||
super(BackGroundTextSearch, self).__init__(None)
|
||||
self.search_content = None
|
||||
self.search_text = None
|
||||
self.case_sensitive = False
|
||||
self.match_words = False
|
||||
self.search_results = []
|
||||
|
||||
def set_search_options(
|
||||
self, search_content, search_text,
|
||||
case_sensitive, match_words):
|
||||
self.search_content = search_content
|
||||
self.search_text = search_text
|
||||
self.case_sensitive = case_sensitive
|
||||
self.match_words = match_words
|
||||
|
||||
def run(self):
|
||||
if not self.search_text or len(self.search_text) < 3:
|
||||
return
|
||||
|
||||
self.search_results = {}
|
||||
|
||||
# Create a new QTextDocument of each chapter and iterate
|
||||
# through it looking for hits
|
||||
|
||||
for i in self.search_content:
|
||||
chapter = i[0]
|
||||
chapterDocument = QtGui.QTextDocument()
|
||||
chapterDocument.setHtml(i[1])
|
||||
|
||||
findFlags = QtGui.QTextDocument.FindFlags(0)
|
||||
if self.case_sensitive:
|
||||
findFlags = findFlags | QtGui.QTextDocument.FindCaseSensitively
|
||||
if self.match_words:
|
||||
findFlags = findFlags | QtGui.QTextDocument.FindWholeWords
|
||||
|
||||
findResultCursor = chapterDocument.find(self.search_text, 0, findFlags)
|
||||
while not findResultCursor.isNull():
|
||||
result_position = findResultCursor.position()
|
||||
|
||||
surroundingTextCursor = QtGui.QTextCursor(chapterDocument)
|
||||
surroundingTextCursor.setPosition(
|
||||
result_position, QtGui.QTextCursor.MoveAnchor)
|
||||
surroundingTextCursor.movePosition(
|
||||
QtGui.QTextCursor.WordLeft, QtGui.QTextCursor.MoveAnchor, 2)
|
||||
surroundingTextCursor.movePosition(
|
||||
QtGui.QTextCursor.NextWord, QtGui.QTextCursor.KeepAnchor, 5) # 2n + 1
|
||||
surrounding_text = surroundingTextCursor.selection().toPlainText()
|
||||
surrounding_text = surrounding_text.replace('\n', ' ')
|
||||
|
||||
try:
|
||||
self.search_results[chapter].append(
|
||||
(result_position, surrounding_text))
|
||||
except KeyError:
|
||||
self.search_results[chapter] = [(result_position, surrounding_text)]
|
||||
|
||||
new_position = result_position + len(self.search_text)
|
||||
findResultCursor = chapterDocument.find(
|
||||
self.search_text, new_position, findFlags)
|
||||
|
@@ -25,6 +25,7 @@ from PyQt5 import QtWidgets, QtGui, QtCore
|
||||
|
||||
from lector.models import BookmarkProxyModel
|
||||
from lector.sorter import resize_image
|
||||
from lector.threaded import BackGroundTextSearch
|
||||
from lector.contentwidgets import PliantQGraphicsView, PliantQTextBrowser
|
||||
|
||||
|
||||
@@ -156,6 +157,7 @@ class Tab(QtWidgets.QWidget):
|
||||
# Search view and model
|
||||
self.searchLineEdit = QtWidgets.QLineEdit(self.sideDockTabWidget)
|
||||
self.searchLineEdit.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
self.searchLineEdit.setClearButtonEnabled(True)
|
||||
search_string = self._translate('Tab', 'Search')
|
||||
self.searchLineEdit.setPlaceholderText(search_string)
|
||||
|
||||
@@ -190,13 +192,14 @@ class Tab(QtWidgets.QWidget):
|
||||
self.searchOptionsLayout.addWidget(self.caseSensitiveSearchButton)
|
||||
self.searchOptionsLayout.addWidget(self.matchWholeWordButton)
|
||||
|
||||
self.searchResultsListView = QtWidgets.QListView(self.sideDockTabWidget)
|
||||
self.searchResultsListView.setEditTriggers(QtWidgets.QListView.NoEditTriggers)
|
||||
self.searchResultsListView.doubleClicked.connect(self.go_to_search_result)
|
||||
self.searchResultsTreeView = QtWidgets.QTreeView(self.sideDockTabWidget)
|
||||
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.addLayout(self.searchOptionsLayout)
|
||||
self.searchTabLayout.addWidget(self.searchResultsListView)
|
||||
self.searchTabLayout.addWidget(self.searchResultsTreeView)
|
||||
self.searchTabLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.searchTabWidget = QtWidgets.QWidget(self.sideDockTabWidget)
|
||||
self.searchTabWidget.setLayout(self.searchTabLayout)
|
||||
@@ -227,6 +230,23 @@ class Tab(QtWidgets.QWidget):
|
||||
self.annotationNoteDock.setWindowOpacity(.95)
|
||||
self.sideDock.hide()
|
||||
|
||||
# Create search references
|
||||
if not self.are_we_doing_images_only:
|
||||
self.searchResultsModel = None
|
||||
|
||||
self.searchThread = BackGroundTextSearch()
|
||||
self.searchThread.finished.connect(self.generate_search_result_model)
|
||||
|
||||
self.searchTimer = QtCore.QTimer()
|
||||
self.searchTimer.setSingleShot(True)
|
||||
self.searchTimer.timeout.connect(self.set_search_options)
|
||||
|
||||
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))
|
||||
self.matchWholeWordButton.clicked.connect(lambda: self.searchTimer.start(100))
|
||||
|
||||
# Create tab in the central tab widget
|
||||
title = self.metadata['title']
|
||||
if self.main_window.settings['attenuate_titles'] and len(title) > 30:
|
||||
title = title[:30] + '...'
|
||||
@@ -259,6 +279,7 @@ class Tab(QtWidgets.QWidget):
|
||||
if tab_required == 2:
|
||||
self.sideDock.activateWindow()
|
||||
self.searchLineEdit.setFocus()
|
||||
self.searchLineEdit.selectAll()
|
||||
|
||||
self.sideDockTabWidget.setCurrentIndex(tab_required)
|
||||
|
||||
@@ -278,7 +299,7 @@ class Tab(QtWidgets.QWidget):
|
||||
except IndexError: # The file has been deleted
|
||||
pass
|
||||
|
||||
def set_cursor_position(self, cursor_position=None):
|
||||
def set_cursor_position(self, cursor_position=None, select_chars=0):
|
||||
try:
|
||||
required_position = self.metadata['position']['cursor_position']
|
||||
except KeyError:
|
||||
@@ -296,7 +317,13 @@ class Tab(QtWidgets.QWidget):
|
||||
# textCursor() RETURNS a copy of the textcursor
|
||||
cursor = self.contentView.textCursor()
|
||||
cursor.setPosition(
|
||||
required_position, QtGui.QTextCursor.MoveAnchor)
|
||||
required_position - select_chars,
|
||||
QtGui.QTextCursor.MoveAnchor)
|
||||
if select_chars > 0: # Select search results
|
||||
cursor.movePosition(
|
||||
QtGui.QTextCursor.NextCharacter,
|
||||
QtGui.QTextCursor.KeepAnchor,
|
||||
select_chars)
|
||||
self.contentView.setTextCursor(cursor)
|
||||
self.contentView.ensureCursorVisible()
|
||||
|
||||
@@ -624,19 +651,6 @@ class Tab(QtWidgets.QWidget):
|
||||
self.bookmarkProxyModel.sort(0)
|
||||
self.bookmarkTreeView.setModel(self.bookmarkProxyModel)
|
||||
|
||||
def update_bookmark_proxy_model(self):
|
||||
pass
|
||||
|
||||
# TODO
|
||||
# This isn't being called currently
|
||||
# See if there's any rationale for keeping it / removing it
|
||||
|
||||
# self.bookmarkProxyModel.invalidateFilter()
|
||||
# self.bookmarkProxyModel.setFilterParams(
|
||||
# self.main_window.bookToolBar.searchBar.text())
|
||||
# self.bookmarkProxyModel.setFilterFixedString(
|
||||
# self.main_window.bookToolBar.searchBar.text())
|
||||
|
||||
def generate_bookmark_context_menu(self, position):
|
||||
index = self.bookmarkTreeView.indexAt(position)
|
||||
if not index.isValid():
|
||||
@@ -673,8 +687,56 @@ class Tab(QtWidgets.QWidget):
|
||||
if child_rows == 1:
|
||||
self.bookmarkModel.removeRow(parent_index.row())
|
||||
|
||||
def go_to_search_result(self, event):
|
||||
print(event)
|
||||
def set_search_options(self):
|
||||
search_content = (
|
||||
self.metadata['content'][self.main_window.bookToolBar.tocBox.currentIndex()],)
|
||||
if self.searchBookButton.isChecked():
|
||||
search_content = self.metadata['content']
|
||||
|
||||
self.searchThread.set_search_options(
|
||||
search_content,
|
||||
self.searchLineEdit.text(),
|
||||
self.caseSensitiveSearchButton.isChecked(),
|
||||
self.matchWholeWordButton.isChecked())
|
||||
self.searchThread.start()
|
||||
|
||||
def generate_search_result_model(self):
|
||||
self.searchResultsModel = QtGui.QStandardItemModel()
|
||||
search_results = self.searchThread.search_results
|
||||
for i in search_results:
|
||||
parentItem = QtGui.QStandardItem()
|
||||
parentItem.setText(i)
|
||||
parentItem.setData(True, QtCore.Qt.UserRole)
|
||||
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.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
|
||||
parentItem.appendRow(childItem)
|
||||
self.searchResultsModel.appendRow(parentItem)
|
||||
|
||||
self.searchResultsTreeView.setModel(self.searchResultsModel)
|
||||
self.searchResultsTreeView.expandToDepth(1)
|
||||
|
||||
def navigate_to_search_result(self, index):
|
||||
if not index.isValid():
|
||||
return
|
||||
|
||||
is_parent = self.searchResultsModel.data(index, QtCore.Qt.UserRole)
|
||||
if is_parent:
|
||||
return
|
||||
|
||||
chapter_index = self.searchResultsModel.data(index, QtCore.Qt.UserRole + 1)
|
||||
cursor_position = self.searchResultsModel.data(index, QtCore.Qt.UserRole + 2)
|
||||
|
||||
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()))
|
||||
|
||||
def hide_mouse(self):
|
||||
self.contentView.viewport().setCursor(QtCore.Qt.BlankCursor)
|
||||
|
Reference in New Issue
Block a user