Implement search

This commit is contained in:
BasioMeusPuga
2019-01-09 05:58:47 +05:30
parent 026fff3d7a
commit 930a97a8fa
5 changed files with 148 additions and 56 deletions

1
TODO
View File

@@ -71,6 +71,7 @@ TODO
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 Search document using QTextCursor
Redo context menu order
Filetypes: Filetypes:
✓ pdf support ✓ pdf support
Parse TOC Parse TOC

View File

@@ -398,10 +398,6 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
if not file_paths: if not file_paths:
return return
def finishing_touches():
self.profile_functions.format_contentView()
self.start_culling_timer()
print('Attempting to open: ' + ', '.join(file_paths)) print('Attempting to open: ' + ', '.join(file_paths))
contents = sorter.BookSorter( contents = sorter.BookSorter(
@@ -434,11 +430,9 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
if self.settings['last_open_tab'] == this_path: if self.settings['last_open_tab'] == this_path:
self.tabWidget.setCurrentIndex(i) self.tabWidget.setCurrentIndex(i)
self.settings['last_open_tab'] = None self.settings['last_open_tab'] = None
finishing_touches()
return return
self.tabWidget.setCurrentIndex(self.tabWidget.count() - 1) self.tabWidget.setCurrentIndex(self.tabWidget.count() - 1)
finishing_touches()
def start_culling_timer(self): def start_culling_timer(self):
if self.settings['perform_culling']: if self.settings['perform_culling']:
@@ -457,7 +451,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
# The hackiness of this hack is just... # The hackiness of this hack is just...
default_size = 170 # This is size of the QIcon (160 by default) + 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 # for n icons, the n + 1th icon will appear at > n +1.11875
# First, calculate the number of images per row # 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] required_content = current_tab.metadata['content'][chapter_number][1]
current_tab.contentView.loadImage(required_content) 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): def generate_library_context_menu(self, position):
index = self.sender().indexAt(position) index = self.sender().indexAt(position)
if not index.isValid(): if not index.isValid():

View File

@@ -277,8 +277,7 @@ class ViewProfileModification:
self.format_contentView() self.format_contentView()
def format_contentView(self): def format_contentView(self):
current_tab = self.tabWidget.widget( current_tab = self.tabWidget.currentWidget()
self.tabWidget.currentIndex())
try: try:
current_metadata = current_tab.metadata current_metadata = current_tab.metadata

View File

@@ -171,3 +171,65 @@ class BackGroundCacheRefill(QtCore.QThread):
self.image_cache.append((next_page, refill_pixmap)) self.image_cache.append((next_page, refill_pixmap))
except (IndexError, TypeError): except (IndexError, TypeError):
self.image_cache.append(None) 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)

View File

@@ -25,6 +25,7 @@ from PyQt5 import QtWidgets, QtGui, QtCore
from lector.models import BookmarkProxyModel from lector.models import BookmarkProxyModel
from lector.sorter import resize_image from lector.sorter import resize_image
from lector.threaded import BackGroundTextSearch
from lector.contentwidgets import PliantQGraphicsView, PliantQTextBrowser from lector.contentwidgets import PliantQGraphicsView, PliantQTextBrowser
@@ -156,6 +157,7 @@ class Tab(QtWidgets.QWidget):
# Search view and model # Search view and model
self.searchLineEdit = QtWidgets.QLineEdit(self.sideDockTabWidget) self.searchLineEdit = QtWidgets.QLineEdit(self.sideDockTabWidget)
self.searchLineEdit.setFocusPolicy(QtCore.Qt.StrongFocus) self.searchLineEdit.setFocusPolicy(QtCore.Qt.StrongFocus)
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)
@@ -190,13 +192,14 @@ 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.searchResultsListView = QtWidgets.QListView(self.sideDockTabWidget) self.searchResultsTreeView = QtWidgets.QTreeView(self.sideDockTabWidget)
self.searchResultsListView.setEditTriggers(QtWidgets.QListView.NoEditTriggers) self.searchResultsTreeView.setHeaderHidden(True)
self.searchResultsListView.doubleClicked.connect(self.go_to_search_result) 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.sideDockTabWidget)
self.searchTabLayout.addLayout(self.searchOptionsLayout) self.searchTabLayout.addLayout(self.searchOptionsLayout)
self.searchTabLayout.addWidget(self.searchResultsListView) 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.sideDockTabWidget)
self.searchTabWidget.setLayout(self.searchTabLayout) self.searchTabWidget.setLayout(self.searchTabLayout)
@@ -227,6 +230,23 @@ class Tab(QtWidgets.QWidget):
self.annotationNoteDock.setWindowOpacity(.95) self.annotationNoteDock.setWindowOpacity(.95)
self.sideDock.hide() 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'] title = self.metadata['title']
if self.main_window.settings['attenuate_titles'] and len(title) > 30: if self.main_window.settings['attenuate_titles'] and len(title) > 30:
title = title[:30] + '...' title = title[:30] + '...'
@@ -259,6 +279,7 @@ class Tab(QtWidgets.QWidget):
if tab_required == 2: if tab_required == 2:
self.sideDock.activateWindow() self.sideDock.activateWindow()
self.searchLineEdit.setFocus() self.searchLineEdit.setFocus()
self.searchLineEdit.selectAll()
self.sideDockTabWidget.setCurrentIndex(tab_required) self.sideDockTabWidget.setCurrentIndex(tab_required)
@@ -278,7 +299,7 @@ class Tab(QtWidgets.QWidget):
except IndexError: # The file has been deleted except IndexError: # The file has been deleted
pass pass
def set_cursor_position(self, cursor_position=None): def set_cursor_position(self, cursor_position=None, select_chars=0):
try: try:
required_position = self.metadata['position']['cursor_position'] required_position = self.metadata['position']['cursor_position']
except KeyError: except KeyError:
@@ -296,7 +317,13 @@ class Tab(QtWidgets.QWidget):
# textCursor() RETURNS a copy of the textcursor # textCursor() RETURNS a copy of the textcursor
cursor = self.contentView.textCursor() cursor = self.contentView.textCursor()
cursor.setPosition( 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.setTextCursor(cursor)
self.contentView.ensureCursorVisible() self.contentView.ensureCursorVisible()
@@ -624,19 +651,6 @@ class Tab(QtWidgets.QWidget):
self.bookmarkProxyModel.sort(0) self.bookmarkProxyModel.sort(0)
self.bookmarkTreeView.setModel(self.bookmarkProxyModel) 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): def generate_bookmark_context_menu(self, position):
index = self.bookmarkTreeView.indexAt(position) index = self.bookmarkTreeView.indexAt(position)
if not index.isValid(): if not index.isValid():
@@ -673,8 +687,56 @@ class Tab(QtWidgets.QWidget):
if child_rows == 1: if child_rows == 1:
self.bookmarkModel.removeRow(parent_index.row()) self.bookmarkModel.removeRow(parent_index.row())
def go_to_search_result(self, event): def set_search_options(self):
print(event) 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): def hide_mouse(self):
self.contentView.viewport().setCursor(QtCore.Qt.BlankCursor) self.contentView.viewport().setCursor(QtCore.Qt.BlankCursor)