Refactor sideDock

This commit is contained in:
BasioMeusPuga
2019-01-31 01:51:47 +05:30
parent d1b1d7c59c
commit 268014cc3a
5 changed files with 470 additions and 396 deletions

View File

@@ -462,7 +462,8 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
def add_bookmark(self):
if self.tabWidget.currentIndex() != 0:
self.tabWidget.widget(self.tabWidget.currentIndex()).add_bookmark()
current_tab = self.tabWidget.currentWidget()
current_tab.sideDock.bookmarks.add_bookmark()
def resizeEvent(self, event=None):
if event:

View File

@@ -482,15 +482,15 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser):
self.parent.sideDock.setWindowOpacity(.95)
self.current_annotation = None
self.parent.annotationListView.clearSelection()
self.parent.sideDock.annotations.annotationListView.clearSelection()
else:
self.annotation_mode = True
self.viewport().setCursor(QtCore.Qt.IBeamCursor)
self.parent.sideDock.hide()
selected_index = self.parent.annotationListView.currentIndex()
self.current_annotation = self.parent.annotationModel.data(
selected_index = self.parent.sideDock.annotations.annotationListView.currentIndex()
self.current_annotation = self.parent.sideDock.annotationModel.data(
selected_index, QtCore.Qt.UserRole)
logger.info('Selected annotation: ' + self.current_annotation['name'])
@@ -619,14 +619,14 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser):
action = contextMenu.exec_(self.sender().mapToGlobal(position))
if action == addBookMarkAction:
self.parent.add_bookmark(cursor_at_mouse.position())
self.parent.sideDock.bookmarks.add_bookmark(cursor_at_mouse.position())
if action == defineAction:
self.main_window.definitionDialog.find_definition(selection)
if action == searchAction:
if selection and selection != '':
self.parent.searchLineEdit.setText(selection)
self.parent.sideDock.search.searchLineEdit.setText(selection)
self.parent.toggle_side_dock(2, True)
if action == searchGoogleAction:

View File

@@ -14,9 +14,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import uuid
from PyQt5 import QtWidgets, QtGui, QtCore
from lector.models import BookmarkProxyModel
from lector.threaded import BackGroundTextSearch
class PliantDockWidget(QtWidgets.QDockWidget):
@@ -26,6 +29,27 @@ class PliantDockWidget(QtWidgets.QDockWidget):
self.notes_only = notes_only
self.contentView = contentView
self.current_annotation = None
self.parent = parent
# Models
# The following models belong to the sideDock
# bookmarkModel, bookmarkProxyModel
# annotationModel
# searchResultsModel
self.bookmarkModel = None
self.bookmarkProxyModel = None
self.annotationModel = None
self.searchResultsModel = None
# References
# All widgets belong to these
self.bookmarks = None
self.annotations = None
self.search = None
# Widgets
# Except this one
self.sideDockTabWidget = None
def showEvent(self, event=None):
viewport_topRight = self.contentView.mapToGlobal(
@@ -60,96 +84,434 @@ class PliantDockWidget(QtWidgets.QDockWidget):
def set_annotation(self, annotation):
self.current_annotation = annotation
def populate(self):
self.setFeatures(QtWidgets.QDockWidget.DockWidgetClosable)
self.setTitleBarWidget(QtWidgets.QWidget(self)) # Removes titlebar
self.sideDockTabWidget = QtWidgets.QTabWidget(self)
self.setWidget(self.sideDockTabWidget)
# This order is important
self.bookmarkModel = QtGui.QStandardItemModel(self)
self.bookmarkProxyModel = BookmarkProxyModel(self)
self.bookmarks = Bookmarks(self)
self.bookmarks.generate_bookmark_model()
if not self.parent.are_we_doing_images_only:
self.annotationModel = QtGui.QStandardItemModel(self)
self.annotations = Annotations(self)
self.annotations.generate_annotation_model()
self.searchResultsModel = QtGui.QStandardItemModel(self)
self.search = Search(self)
def closeEvent(self, event):
self.hide()
# Ignoring this event prevents application closure when everything is fullscreened
# Ignoring this event prevents application closure
# when everything is fullscreened
event.ignore()
# TODO
# Maybe subclass PliantDockWidget for this
def populate_sideDock(tabWidget):
tabWidget.sideDock.setFeatures(QtWidgets.QDockWidget.DockWidgetClosable)
tabWidget.sideDock.setTitleBarWidget(QtWidgets.QWidget())
tabWidget.sideDockTabWidget = QtWidgets.QTabWidget()
tabWidget.sideDock.setWidget(tabWidget.sideDockTabWidget)
# For the following classes, the parent is the sideDock
# The parentTab is the parent... tab. So self.parent.parent
class Bookmarks:
def __init__(self, parent):
self.parent = parent
self.parentTab = self.parent.parent
self.bookmarkTreeView = QtWidgets.QTreeView(self.parent)
self._translate = QtCore.QCoreApplication.translate
self.bookmarks_string = self._translate('SideDock', 'Bookmarks')
# Bookmark tree view and model
tabWidget.bookmarkTreeView = QtWidgets.QTreeView(tabWidget)
tabWidget.bookmarkTreeView.setHeaderHidden(True)
tabWidget.bookmarkTreeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
tabWidget.bookmarkTreeView.customContextMenuRequested.connect(
tabWidget.generate_bookmark_context_menu)
tabWidget.bookmarkTreeView.clicked.connect(tabWidget.navigate_to_bookmark)
bookmarks_string = tabWidget._translate('Tab', 'Bookmarks')
tabWidget.sideDockTabWidget.addTab(tabWidget.bookmarkTreeView, bookmarks_string)
self.create_widgets()
tabWidget.bookmarkModel = QtGui.QStandardItemModel(tabWidget)
tabWidget.bookmarkProxyModel = BookmarkProxyModel(tabWidget)
tabWidget.generate_bookmark_model()
def create_widgets(self):
self.bookmarkTreeView.setHeaderHidden(True)
self.bookmarkTreeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.bookmarkTreeView.customContextMenuRequested.connect(
self.generate_bookmark_context_menu)
self.bookmarkTreeView.clicked.connect(self.navigate_to_bookmark)
# Annotation list view and model
# Leave this without a parent or it shows up in the image viewer
tabWidget.annotationListView = QtWidgets.QListView()
tabWidget.annotationListView.setEditTriggers(QtWidgets.QListView.NoEditTriggers)
tabWidget.annotationListView.doubleClicked.connect(tabWidget.contentView.toggle_annotation_mode)
annotations_string = tabWidget._translate('Tab', 'Annotations')
# Add widget to side dock
self.parent.sideDockTabWidget.addTab(
self.bookmarkTreeView, self.bookmarks_string)
tabWidget.annotationModel = QtGui.QStandardItemModel(tabWidget)
tabWidget.generate_annotation_model()
def add_bookmark(self, position=None):
identifier = uuid.uuid4().hex[:10]
description = self._translate('SideDock', 'New bookmark')
# Search view and model
tabWidget.searchLineEdit = QtWidgets.QLineEdit()
tabWidget.searchLineEdit.setFocusPolicy(QtCore.Qt.StrongFocus)
tabWidget.searchLineEdit.setClearButtonEnabled(True)
search_string = tabWidget._translate('Tab', 'Search')
tabWidget.searchLineEdit.setPlaceholderText(search_string)
if self.parentTab.are_we_doing_images_only:
chapter = self.parentTab.metadata['position']['current_chapter']
cursor_position = 0
else:
chapter, cursor_position = self.parent.contentView.record_position(True)
if position: # Should be the case when called from the context menu
cursor_position = position
search_book_string = tabWidget._translate('Tab', 'Search entire book')
tabWidget.searchBookButton = QtWidgets.QToolButton()
tabWidget.searchBookButton.setIcon(
tabWidget.main_window.QImageFactory.get_image('view-readermode'))
tabWidget.searchBookButton.setToolTip(search_book_string)
tabWidget.searchBookButton.setCheckable(True)
tabWidget.searchBookButton.setAutoRaise(True)
self.parentTab.metadata['bookmarks'][identifier] = {
'chapter': chapter,
'cursor_position': cursor_position,
'description': description}
case_sensitive_string = tabWidget._translate('Tab', 'Match case')
tabWidget.caseSensitiveSearchButton = QtWidgets.QToolButton()
tabWidget.caseSensitiveSearchButton.setIcon(
tabWidget.main_window.QImageFactory.get_image('search-case'))
tabWidget.caseSensitiveSearchButton.setToolTip(case_sensitive_string)
tabWidget.caseSensitiveSearchButton.setCheckable(True)
tabWidget.caseSensitiveSearchButton.setAutoRaise(True)
self.parent.setVisible(True)
self.parent.sideDockTabWidget.setCurrentIndex(0)
self.add_bookmark_to_model(
description, chapter, cursor_position, identifier, True)
match_word_string = tabWidget._translate('Tab', 'Match word')
tabWidget.matchWholeWordButton = QtWidgets.QToolButton()
tabWidget.matchWholeWordButton.setIcon(
tabWidget.main_window.QImageFactory.get_image('search-word'))
tabWidget.matchWholeWordButton.setToolTip(match_word_string)
tabWidget.matchWholeWordButton.setCheckable(True)
tabWidget.matchWholeWordButton.setAutoRaise(True)
def add_bookmark_to_model(
self, description, chapter_number, cursor_position,
identifier, new_bookmark=False):
tabWidget.searchOptionsLayout = QtWidgets.QHBoxLayout()
tabWidget.searchOptionsLayout.setContentsMargins(0, 3, 0, 0)
tabWidget.searchOptionsLayout.addWidget(tabWidget.searchLineEdit)
tabWidget.searchOptionsLayout.addWidget(tabWidget.searchBookButton)
tabWidget.searchOptionsLayout.addWidget(tabWidget.caseSensitiveSearchButton)
tabWidget.searchOptionsLayout.addWidget(tabWidget.matchWholeWordButton)
def edit_new_bookmark(parent_item):
new_child = parent_item.child(parent_item.rowCount() - 1, 0)
source_index = self.parent.bookmarkModel.indexFromItem(new_child)
edit_index = self.bookmarkTreeView.model().mapFromSource(source_index)
self.parent.activateWindow()
self.bookmarkTreeView.setFocus()
self.bookmarkTreeView.setCurrentIndex(edit_index)
self.bookmarkTreeView.edit(edit_index)
# Leave this without a parent or it shows up in the image viewer
tabWidget.searchResultsTreeView = QtWidgets.QTreeView()
tabWidget.searchResultsTreeView.setHeaderHidden(True)
tabWidget.searchResultsTreeView.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers)
tabWidget.searchResultsTreeView.clicked.connect(tabWidget.navigate_to_search_result)
def get_chapter_name(chapter_number):
for i in reversed(self.parentTab.metadata['toc']):
if i[2] <= chapter_number:
return i[1]
return 'Unknown'
tabWidget.searchTabLayout = QtWidgets.QVBoxLayout()
tabWidget.searchTabLayout.addLayout(tabWidget.searchOptionsLayout)
tabWidget.searchTabLayout.addWidget(tabWidget.searchResultsTreeView)
tabWidget.searchTabLayout.setContentsMargins(0, 0, 0, 0)
tabWidget.searchTabWidget = QtWidgets.QWidget()
tabWidget.searchTabWidget.setLayout(tabWidget.searchTabLayout)
bookmark = QtGui.QStandardItem()
bookmark.setData(False, QtCore.Qt.UserRole + 10) # Is Parent
bookmark.setData(chapter_number, QtCore.Qt.UserRole) # Chapter number
bookmark.setData(cursor_position, QtCore.Qt.UserRole + 1) # Cursor Position
bookmark.setData(identifier, QtCore.Qt.UserRole + 2) # Identifier
bookmark.setData(description, QtCore.Qt.DisplayRole) # Description
bookmark_chapter_name = get_chapter_name(chapter_number)
if not tabWidget.are_we_doing_images_only:
tabWidget.sideDockTabWidget.addTab(tabWidget.searchTabWidget, search_string)
tabWidget.sideDockTabWidget.addTab(tabWidget.annotationListView, annotations_string)
for i in range(self.parent.bookmarkModel.rowCount()):
parentIndex = self.parent.bookmarkModel.index(i, 0)
parent_chapter_number = parentIndex.data(QtCore.Qt.UserRole)
parent_chapter_name = parentIndex.data(QtCore.Qt.DisplayRole)
# This prevents duplication of the bookmark in the new
# navigation model
if ((parent_chapter_number <= chapter_number) and
(parent_chapter_name == bookmark_chapter_name)):
bookmarkParent = self.parent.bookmarkModel.itemFromIndex(parentIndex)
bookmarkParent.appendRow(bookmark)
if new_bookmark:
edit_new_bookmark(bookmarkParent)
return
# In case no parent item exists
bookmarkParent = QtGui.QStandardItem()
bookmarkParent.setData(True, QtCore.Qt.UserRole + 10) # Is Parent
bookmarkParent.setFlags(bookmarkParent.flags() & ~QtCore.Qt.ItemIsEditable) # Is Editable
bookmarkParent.setData(get_chapter_name(chapter_number), QtCore.Qt.DisplayRole)
bookmarkParent.setData(chapter_number, QtCore.Qt.UserRole)
bookmarkParent.appendRow(bookmark)
self.parent.bookmarkModel.appendRow(bookmarkParent)
if new_bookmark:
edit_new_bookmark(bookmarkParent)
def navigate_to_bookmark(self, index):
if not index.isValid():
return
is_parent = self.parent.bookmarkProxyModel.data(
index, QtCore.Qt.UserRole + 10)
if is_parent:
chapter_number = self.parent.bookmarkProxyModel.data(
index, QtCore.Qt.UserRole)
self.parentTab.set_content(chapter_number, True)
return
chapter = self.parent.bookmarkProxyModel.data(
index, QtCore.Qt.UserRole)
cursor_position = self.parent.bookmarkProxyModel.data(
index, QtCore.Qt.UserRole + 1)
self.parentTab.set_content(chapter, True)
if not self.parentTab.are_we_doing_images_only:
self.parentTab.set_cursor_position(cursor_position)
def generate_bookmark_model(self):
for i in self.parentTab.metadata['bookmarks'].items():
description = i[1]['description']
chapter = i[1]['chapter']
cursor_position = i[1]['cursor_position']
identifier = i[0]
self.add_bookmark_to_model(
description, chapter, cursor_position, identifier)
self.generate_bookmark_proxy_model()
def generate_bookmark_proxy_model(self):
self.parent.bookmarkProxyModel.setSourceModel(self.parent.bookmarkModel)
self.parent.bookmarkProxyModel.setSortCaseSensitivity(False)
self.parent.bookmarkProxyModel.setSortRole(QtCore.Qt.UserRole)
self.parent.bookmarkProxyModel.sort(0)
self.bookmarkTreeView.setModel(self.parent.bookmarkProxyModel)
def generate_bookmark_context_menu(self, position):
index = self.bookmarkTreeView.indexAt(position)
if not index.isValid():
return
is_parent = self.parent.bookmarkProxyModel.data(
index, QtCore.Qt.UserRole + 10)
if is_parent:
return
bookmarkMenu = QtWidgets.QMenu()
editAction = bookmarkMenu.addAction(
self.parentTab.main_window.QImageFactory.get_image('edit-rename'),
self._translate('Tab', 'Edit'))
deleteAction = bookmarkMenu.addAction(
self.parentTab.main_window.QImageFactory.get_image('trash-empty'),
self._translate('Tab', 'Delete'))
action = bookmarkMenu.exec_(
self.bookmarkTreeView.mapToGlobal(position))
if action == editAction:
self.bookmarkTreeView.edit(index)
if action == deleteAction:
child_index = self.parent.bookmarkProxyModel.mapToSource(index)
parent_index = child_index.parent()
child_rows = self.parent.bookmarkModel.itemFromIndex(parent_index).rowCount()
delete_uuid = self.parent.bookmarkModel.data(
child_index, QtCore.Qt.UserRole + 2)
self.parentTab.metadata['bookmarks'].pop(delete_uuid)
self.parent.bookmarkModel.removeRow(child_index.row(), child_index.parent())
if child_rows == 1:
self.parent.bookmarkModel.removeRow(parent_index.row())
class Annotations:
def __init__(self, parent):
self.parent = parent
self.parentTab = self.parent.parent
self.annotationListView = QtWidgets.QListView(self.parent)
self._translate = QtCore.QCoreApplication.translate
self.annotations_string = self._translate('SideDock', 'Annotations')
self.create_widgets()
def create_widgets(self):
self.annotationListView.setEditTriggers(QtWidgets.QListView.NoEditTriggers)
self.annotationListView.doubleClicked.connect(
self.parent.contentView.toggle_annotation_mode)
# Add widget to side dock
self.parent.sideDockTabWidget.addTab(
self.annotationListView, self.annotations_string)
def generate_annotation_model(self):
# TODO
# Annotation previews will require creation of a
# QStyledItemDelegate
saved_annotations = self.parent.main_window.settings['annotations']
if not saved_annotations:
return
# Create annotation model
for i in saved_annotations:
item = QtGui.QStandardItem()
item.setText(i['name'])
item.setData(i, QtCore.Qt.UserRole)
self.parent.annotationModel.appendRow(item)
self.annotationListView.setModel(self.parent.annotationModel)
class Search:
def __init__(self, parent):
self.parent = parent
self.parentTab = self.parent.parent
self.searchThread = BackGroundTextSearch()
self.searchOptionsLayout = QtWidgets.QHBoxLayout()
self.searchTabLayout = QtWidgets.QVBoxLayout()
self.searchTimer = QtCore.QTimer(self.parent)
self.searchLineEdit = QtWidgets.QLineEdit(self.parent)
self.searchBookButton = QtWidgets.QToolButton(self.parent)
self.caseSensitiveSearchButton = QtWidgets.QToolButton(self.parent)
self.matchWholeWordButton = QtWidgets.QToolButton(self.parent)
self.searchResultsTreeView = QtWidgets.QTreeView(self.parent)
self.create_widgets()
def create_widgets(self):
self.searchThread.finished.connect(self.generate_search_result_model)
self.searchTimer.setSingleShot(True)
self.searchTimer.timeout.connect(self.set_search_options)
self.searchLineEdit.textChanged.connect(
lambda: self.searchLineEdit.setStyleSheet(
QtWidgets.QLineEdit.styleSheet(self.parent)))
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))
self.searchLineEdit.setFocusPolicy(QtCore.Qt.StrongFocus)
self.searchLineEdit.setClearButtonEnabled(True)
self._translate = QtCore.QCoreApplication.translate
self.search_string = self._translate('SideDock', 'Search')
self.searchLineEdit.setPlaceholderText(self.search_string)
search_book_string = self._translate('SideDock', 'Search entire book')
self.searchBookButton.setIcon(
self.parent.main_window.QImageFactory.get_image('view-readermode'))
self.searchBookButton.setToolTip(search_book_string)
self.searchBookButton.setCheckable(True)
self.searchBookButton.setAutoRaise(True)
self.searchBookButton.setIconSize(QtCore.QSize(20, 20))
case_sensitive_string = self._translate('SideDock', 'Match case')
self.caseSensitiveSearchButton.setIcon(
self.parent.main_window.QImageFactory.get_image('search-case'))
self.caseSensitiveSearchButton.setToolTip(case_sensitive_string)
self.caseSensitiveSearchButton.setCheckable(True)
self.caseSensitiveSearchButton.setAutoRaise(True)
self.caseSensitiveSearchButton.setIconSize(QtCore.QSize(20, 20))
match_word_string = self._translate('SideDock', 'Match word')
self.matchWholeWordButton.setIcon(
self.parent.main_window.QImageFactory.get_image('search-word'))
self.matchWholeWordButton.setToolTip(match_word_string)
self.matchWholeWordButton.setCheckable(True)
self.matchWholeWordButton.setAutoRaise(True)
self.matchWholeWordButton.setIconSize(QtCore.QSize(20, 20))
self.searchOptionsLayout.setContentsMargins(0, 3, 0, 0)
self.searchOptionsLayout.addWidget(self.searchLineEdit)
self.searchOptionsLayout.addWidget(self.searchBookButton)
self.searchOptionsLayout.addWidget(self.caseSensitiveSearchButton)
self.searchOptionsLayout.addWidget(self.matchWholeWordButton)
self.searchResultsTreeView.setHeaderHidden(True)
self.searchResultsTreeView.setEditTriggers(
QtWidgets.QTreeView.NoEditTriggers)
self.searchResultsTreeView.clicked.connect(
self.navigate_to_search_result)
self.searchTabLayout.addLayout(self.searchOptionsLayout)
self.searchTabLayout.addWidget(self.searchResultsTreeView)
self.searchTabLayout.setContentsMargins(0, 0, 0, 0)
self.searchTabWidget = QtWidgets.QWidget(self.parent)
self.searchTabWidget.setLayout(self.searchTabLayout)
# Add widget to side dock
self.parent.sideDockTabWidget.addTab(
self.searchTabWidget, self.search_string)
def set_search_options(self):
def generate_title_content_pair(required_chapters):
title_content_list = []
for i in self.parentTab.metadata['toc']:
if i[2] in required_chapters:
title_content_list.append(
(i[1], self.parentTab.metadata['content'][i[2] - 1], i[2]))
return title_content_list
# Select either the current chapter or all chapters
# Function name is descriptive
chapter_numbers = (self.parentTab.metadata['position']['current_chapter'],)
if self.searchBookButton.isChecked():
chapter_numbers = [i + 1 for i in range(len(self.parentTab.metadata['content']))]
search_content = generate_title_content_pair(chapter_numbers)
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.parent.searchResultsModel.clear()
search_results = self.searchThread.search_results
for i in search_results:
parentItem = QtGui.QStandardItem()
parentItem.setData(True, QtCore.Qt.UserRole) # Is parent?
parentItem.setData(i, QtCore.Qt.UserRole + 3) # Display text for label
for j in search_results[i]:
childItem = QtGui.QStandardItem(parentItem)
childItem.setData(False, QtCore.Qt.UserRole) # Is parent?
childItem.setData(j[3], 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.parent.searchResultsModel.appendRow(parentItem)
self.searchResultsTreeView.setModel(self.parent.searchResultsModel)
self.searchResultsTreeView.expandToDepth(1)
# Reset stylesheet in case something is found
if search_results:
self.searchLineEdit.setStyleSheet(
QtWidgets.QLineEdit.styleSheet(self.parent))
# Or set to Red in case nothing is found
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.parent.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.parent.searchResultsModel.rowCount()):
parentItem = self.parent.searchResultsModel.item(parent_iter)
parentIndex = self.parent.searchResultsModel.index(parent_iter, 0)
generate_label(parentIndex)
for child_iter in range(parentItem.rowCount()):
childIndex = self.parent.searchResultsModel.index(child_iter, 0, parentIndex)
generate_label(childIndex)
def navigate_to_search_result(self, index):
if not index.isValid():
return
is_parent = self.parent.searchResultsModel.data(index, QtCore.Qt.UserRole)
if is_parent:
return
chapter_number = self.parent.searchResultsModel.data(index, QtCore.Qt.UserRole + 1)
cursor_position = self.parent.searchResultsModel.data(index, QtCore.Qt.UserRole + 2)
search_term = self.parent.searchResultsModel.data(index, QtCore.Qt.UserRole + 4)
self.parentTab.set_content(chapter_number, True)
if not self.parentTab.are_we_doing_images_only:
self.parentTab.set_cursor_position(
cursor_position, len(search_term))
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)

View File

@@ -27,23 +27,19 @@ class BookmarkProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, parent=None):
super(BookmarkProxyModel, self).__init__(parent)
self.parent = parent
self.parentTab = self.parent.parent
self.filter_text = None
def setFilterParams(self, filter_text):
self.filter_text = filter_text
def filterAcceptsRow(self, row, parent):
# TODO
# Connect this to the search bar
return True
def setData(self, index, value, role):
if role == QtCore.Qt.EditRole:
source_index = self.mapToSource(index)
identifier = self.sourceModel().data(source_index, QtCore.Qt.UserRole + 2)
self.sourceModel().setData(source_index, value, QtCore.Qt.DisplayRole)
self.parent.metadata['bookmarks'][identifier]['description'] = value
self.parentTab.metadata['bookmarks'][identifier]['description'] = value
return True

View File

@@ -19,14 +19,12 @@
# Double page, Continuous etc
import os
import uuid
import logging
from PyQt5 import QtWidgets, QtGui, QtCore
from lector.sorter import resize_image
from lector.threaded import BackGroundTextSearch
from lector.dockwidgets import PliantDockWidget, populate_sideDock
from lector.dockwidgets import PliantDockWidget
from lector.contentwidgets import PliantQGraphicsView, PliantQTextBrowser
logger = logging.getLogger(__name__)
@@ -137,13 +135,14 @@ class Tab(QtWidgets.QWidget):
self.contentView.setVerticalScrollBarPolicy(
QtCore.Qt.ScrollBarAsNeeded)
# Create a common dock for annotations and bookmarks
# It is populated by the following method
self.sideDock = PliantDockWidget(self.main_window, False, self.contentView)
populate_sideDock(self)
# Create a common dock for bookmarks, annotations, and search
self.sideDock = PliantDockWidget(
self.main_window, False, self.contentView, self)
self.sideDock.populate()
# Create the annotation notes dock
self.annotationNoteDock = PliantDockWidget(self.main_window, True, self.contentView)
self.annotationNoteDock = PliantDockWidget(
self.main_window, True, self.contentView, self)
self.annotationNoteDock.setWindowTitle(self._translate('Tab', 'Note'))
self.annotationNoteDock.setFeatures(QtWidgets.QDockWidget.DockWidgetClosable)
self.annotationNoteDock.hide()
@@ -166,29 +165,6 @@ 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.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))
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:
@@ -214,17 +190,17 @@ class Tab(QtWidgets.QWidget):
def toggle_side_dock(self, tab_required, override_hide=False):
if (self.sideDock.isVisible()
and self.sideDockTabWidget.currentIndex() == tab_required
and self.sideDock.sideDockTabWidget.currentIndex() == tab_required
and not override_hide):
self.sideDock.hide()
elif not self.sideDock.isVisible():
self.sideDock.show()
if tab_required == 2:
self.sideDock.activateWindow()
self.searchLineEdit.setFocus()
self.searchLineEdit.selectAll()
self.sideDock.search.searchLineEdit.setFocus()
self.sideDock.search.searchLineEdit.selectAll()
self.sideDockTabWidget.setCurrentIndex(tab_required)
self.sideDock.sideDockTabWidget.setCurrentIndex(tab_required)
def update_last_accessed_time(self):
self.metadata['last_accessed'] = QtCore.QDateTime().currentDateTime()
@@ -238,7 +214,8 @@ class Tab(QtWidgets.QWidget):
try:
self.main_window.lib_ref.libraryModel.setData(
matching_item[0], self.metadata['last_accessed'], QtCore.Qt.UserRole + 12)
matching_item[0],
self.metadata['last_accessed'], QtCore.Qt.UserRole + 12)
except IndexError: # The file has been deleted
pass
@@ -329,17 +306,20 @@ class Tab(QtWidgets.QWidget):
ksToggleBookmarks = QtWidgets.QShortcut(
QtGui.QKeySequence('Ctrl+B'), self.contentView)
ksToggleBookmarks.activated.connect(lambda: self.toggle_side_dock(0))
ksToggleBookmarks.activated.connect(
lambda: self.toggle_side_dock(0))
# Shortcuts not required for comic view functionality
if not self.are_we_doing_images_only:
ksToggleAnnotations = QtWidgets.QShortcut(
QtGui.QKeySequence('Ctrl+N'), self.contentView)
ksToggleAnnotations.activated.connect(lambda: self.toggle_side_dock(1))
ksToggleAnnotations.activated.connect(
lambda: self.toggle_side_dock(1))
ksToggleSearch = QtWidgets.QShortcut(
QtGui.QKeySequence('Ctrl+F'), self.contentView)
ksToggleSearch.activated.connect(lambda: self.toggle_side_dock(2))
ksToggleSearch.activated.connect(
lambda: self.toggle_side_dock(2))
def generate_toc_model(self):
# The toc list is:
@@ -548,7 +528,8 @@ class Tab(QtWidgets.QWidget):
current_index = self.main_window.bookToolBar.tocBox.currentIndex()
if current_index == 0:
block_format.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignHCenter)
block_format.setAlignment(
QtCore.Qt.AlignVCenter | QtCore.Qt.AlignHCenter)
else:
block_format.setAlignment(alignment_dict[text_alignment])
@@ -570,259 +551,6 @@ class Tab(QtWidgets.QWidget):
if old_position == new_position:
break
def generate_annotation_model(self):
# TODO
# Annotation previews will require creation of a
# QStyledItemDelegate
saved_annotations = self.main_window.settings['annotations']
if not saved_annotations:
return
# Create annotation model
for i in saved_annotations:
item = QtGui.QStandardItem()
item.setText(i['name'])
item.setData(i, QtCore.Qt.UserRole)
self.annotationModel.appendRow(item)
self.annotationListView.setModel(self.annotationModel)
def add_bookmark(self, position=None):
identifier = uuid.uuid4().hex[:10]
description = self._translate('Tab', 'New bookmark')
if self.are_we_doing_images_only:
chapter = self.metadata['position']['current_chapter']
cursor_position = 0
else:
chapter, cursor_position = self.contentView.record_position(True)
if position: # Should be the case when called from the context menu
cursor_position = position
self.metadata['bookmarks'][identifier] = {
'chapter': chapter,
'cursor_position': cursor_position,
'description': description}
self.sideDock.setVisible(True)
self.sideDockTabWidget.setCurrentIndex(0)
self.add_bookmark_to_model(
description, chapter, cursor_position, identifier, True)
def add_bookmark_to_model(
self, description, chapter_number, cursor_position,
identifier, new_bookmark=False):
def edit_new_bookmark(parent_item):
new_child = parent_item.child(parent_item.rowCount() - 1, 0)
source_index = self.bookmarkModel.indexFromItem(new_child)
edit_index = self.bookmarkTreeView.model().mapFromSource(source_index)
self.sideDock.activateWindow()
self.bookmarkTreeView.setFocus()
self.bookmarkTreeView.setCurrentIndex(edit_index)
self.bookmarkTreeView.edit(edit_index)
def get_chapter_name(chapter_number):
for i in reversed(self.metadata['toc']):
if i[2] <= chapter_number:
return i[1]
return 'Unknown'
bookmark = QtGui.QStandardItem()
bookmark.setData(False, QtCore.Qt.UserRole + 10) # Is Parent
bookmark.setData(chapter_number, QtCore.Qt.UserRole) # Chapter number
bookmark.setData(cursor_position, QtCore.Qt.UserRole + 1) # Cursor Position
bookmark.setData(identifier, QtCore.Qt.UserRole + 2) # Identifier
bookmark.setData(description, QtCore.Qt.DisplayRole) # Description
bookmark_chapter_name = get_chapter_name(chapter_number)
for i in range(self.bookmarkModel.rowCount()):
parentIndex = self.bookmarkModel.index(i, 0)
parent_chapter_number = parentIndex.data(QtCore.Qt.UserRole)
parent_chapter_name = parentIndex.data(QtCore.Qt.DisplayRole)
# This prevents duplication of the bookmark in the new
# navigation model
if ((parent_chapter_number <= chapter_number) and
(parent_chapter_name == bookmark_chapter_name)):
bookmarkParent = self.bookmarkModel.itemFromIndex(parentIndex)
bookmarkParent.appendRow(bookmark)
if new_bookmark:
edit_new_bookmark(bookmarkParent)
return
# In case no parent item exists
bookmarkParent = QtGui.QStandardItem()
bookmarkParent.setData(True, QtCore.Qt.UserRole + 10) # Is Parent
bookmarkParent.setFlags(bookmarkParent.flags() & ~QtCore.Qt.ItemIsEditable) # Is Editable
bookmarkParent.setData(get_chapter_name(chapter_number), QtCore.Qt.DisplayRole)
bookmarkParent.setData(chapter_number, QtCore.Qt.UserRole)
bookmarkParent.appendRow(bookmark)
self.bookmarkModel.appendRow(bookmarkParent)
if new_bookmark:
edit_new_bookmark(bookmarkParent)
def navigate_to_bookmark(self, index):
if not index.isValid():
return
is_parent = self.bookmarkProxyModel.data(index, QtCore.Qt.UserRole + 10)
if is_parent:
chapter_number = self.bookmarkProxyModel.data(index, QtCore.Qt.UserRole)
self.set_content(chapter_number, True)
return
chapter = self.bookmarkProxyModel.data(index, QtCore.Qt.UserRole)
cursor_position = self.bookmarkProxyModel.data(index, QtCore.Qt.UserRole + 1)
self.set_content(chapter, True)
if not self.are_we_doing_images_only:
self.set_cursor_position(cursor_position)
def generate_bookmark_model(self):
for i in self.metadata['bookmarks'].items():
description = i[1]['description']
chapter = i[1]['chapter']
cursor_position = i[1]['cursor_position']
identifier = i[0]
self.add_bookmark_to_model(
description, chapter, cursor_position, identifier)
self.generate_bookmark_proxy_model()
def generate_bookmark_proxy_model(self):
self.bookmarkProxyModel.setSourceModel(self.bookmarkModel)
self.bookmarkProxyModel.setSortCaseSensitivity(False)
self.bookmarkProxyModel.setSortRole(QtCore.Qt.UserRole)
self.bookmarkProxyModel.sort(0)
self.bookmarkTreeView.setModel(self.bookmarkProxyModel)
def generate_bookmark_context_menu(self, position):
index = self.bookmarkTreeView.indexAt(position)
if not index.isValid():
return
is_parent = self.bookmarkProxyModel.data(index, QtCore.Qt.UserRole + 10)
if is_parent:
return
bookmarkMenu = QtWidgets.QMenu()
editAction = bookmarkMenu.addAction(
self.main_window.QImageFactory.get_image('edit-rename'),
self._translate('Tab', 'Edit'))
deleteAction = bookmarkMenu.addAction(
self.main_window.QImageFactory.get_image('trash-empty'),
self._translate('Tab', 'Delete'))
action = bookmarkMenu.exec_(
self.bookmarkTreeView.mapToGlobal(position))
if action == editAction:
self.bookmarkTreeView.edit(index)
if action == deleteAction:
child_index = self.bookmarkProxyModel.mapToSource(index)
parent_index = child_index.parent()
child_rows = self.bookmarkModel.itemFromIndex(parent_index).rowCount()
delete_uuid = self.bookmarkModel.data(
child_index, QtCore.Qt.UserRole + 2)
self.metadata['bookmarks'].pop(delete_uuid)
self.bookmarkModel.removeRow(child_index.row(), child_index.parent())
if child_rows == 1:
self.bookmarkModel.removeRow(parent_index.row())
def set_search_options(self):
def generate_title_content_pair(required_chapters):
title_content_list = []
for i in self.metadata['toc']:
if i[2] in required_chapters:
title_content_list.append(
(i[1], self.metadata['content'][i[2] - 1], i[2]))
return title_content_list
# Select either the current chapter or all chapters
# Function name is descriptive
chapter_numbers = (self.metadata['position']['current_chapter'],)
if self.searchBookButton.isChecked():
chapter_numbers = [i + 1 for i in range(len(self.metadata['content']))]
search_content = generate_title_content_pair(chapter_numbers)
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.setData(True, QtCore.Qt.UserRole) # Is parent?
parentItem.setData(i, QtCore.Qt.UserRole + 3) # Display text for label
for j in search_results[i]:
childItem = QtGui.QStandardItem(parentItem)
childItem.setData(False, QtCore.Qt.UserRole) # Is parent?
childItem.setData(j[3], 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)
# Reset stylesheet in case something is found
if search_results:
self.searchLineEdit.setStyleSheet(
QtWidgets.QLineEdit.styleSheet(self))
# Or set to Red in case nothing is found
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
is_parent = self.searchResultsModel.data(index, QtCore.Qt.UserRole)
if is_parent:
return
chapter_number = 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.set_content(chapter_number, True)
if not self.are_we_doing_images_only:
self.set_cursor_position(
cursor_position, len(search_term))
def hide_mouse(self):
self.contentView.viewport().setCursor(QtCore.Qt.BlankCursor)
@@ -839,20 +567,6 @@ 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 PliantQGraphicsScene(QtWidgets.QGraphicsScene):
def __init__(self, parent=None):
super(PliantQGraphicsScene, self).__init__(parent)
@@ -933,7 +647,8 @@ class DragDropTableView(QtWidgets.QTableView):
self.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
self.setFrameShape(QtWidgets.QFrame.Box)
self.setFrameShadow(QtWidgets.QFrame.Plain)
self.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContentsOnFirstShow)
self.setSizeAdjustPolicy(
QtWidgets.QAbstractScrollArea.AdjustToContentsOnFirstShow)
self.setEditTriggers(
QtWidgets.QAbstractItemView.DoubleClicked |
QtWidgets.QAbstractItemView.EditKeyPressed |