Annotation saving, loading, and deletion
This commit is contained in:
@@ -234,6 +234,7 @@ class AnnotationsUI(QtWidgets.QDialog, annotationswindow.Ui_Dialog):
|
||||
|
||||
self.current_annotation = {
|
||||
'name': annotation_name,
|
||||
'applicable_to': 'text',
|
||||
'type': 'text_markup',
|
||||
'components': annotation_components}
|
||||
|
||||
|
@@ -43,6 +43,8 @@ class PliantQGraphicsView(QtWidgets.QGraphicsView):
|
||||
|
||||
self.thread = None
|
||||
|
||||
self.annotation_dict = self.parent.metadata['annotations']
|
||||
|
||||
self.filepath = filepath
|
||||
self.filetype = os.path.splitext(self.filepath)[1][1:]
|
||||
|
||||
@@ -318,6 +320,9 @@ class PliantQGraphicsView(QtWidgets.QGraphicsView):
|
||||
# In case the program is closed when a contentView is fullscreened
|
||||
self.main_window.closeEvent()
|
||||
|
||||
def toggle_annotation_mode(self):
|
||||
pass
|
||||
|
||||
|
||||
class PliantQTextBrowser(QtWidgets.QTextBrowser):
|
||||
def __init__(self, main_window, parent=None):
|
||||
@@ -330,6 +335,7 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser):
|
||||
self.annotation_mode = False
|
||||
self.annotator = AnnotationPlacement()
|
||||
self.current_annotation = None
|
||||
self.annotation_dict = self.parent.metadata['annotations']
|
||||
|
||||
self.common_functions = PliantWidgetsCommonFunctions(
|
||||
self, self.main_window)
|
||||
@@ -396,6 +402,15 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser):
|
||||
self.parent.metadata['position']['cursor_position'] = cursor_position
|
||||
|
||||
def toggle_annotation_mode(self):
|
||||
if self.annotation_mode:
|
||||
self.annotation_mode = False
|
||||
self.viewport().setCursor(QtCore.Qt.ArrowCursor)
|
||||
self.parent.annotationDock.setWindowOpacity(.95)
|
||||
|
||||
self.current_annotation = None
|
||||
self.parent.annotationListView.clearSelection()
|
||||
|
||||
else:
|
||||
self.annotation_mode = True
|
||||
self.viewport().setCursor(QtCore.Qt.IBeamCursor)
|
||||
self.parent.annotationDock.setWindowOpacity(.40)
|
||||
@@ -407,35 +422,62 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser):
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
# This takes care of annotation placement
|
||||
# and addition to the list that holds all current annotations
|
||||
if not self.current_annotation:
|
||||
QtWidgets.QTextBrowser.mouseReleaseEvent(self, event)
|
||||
return
|
||||
|
||||
self.annotator.set_current_annotation(
|
||||
'text_markup', self.current_annotation['components'])
|
||||
|
||||
current_chapter = self.parent.metadata['position']['current_chapter']
|
||||
cursor = self.textCursor()
|
||||
cursor_start = cursor.selectionStart()
|
||||
cursor_end = cursor.selectionEnd()
|
||||
annotation_type = 'text_markup'
|
||||
applicable_to = 'text'
|
||||
annotation_components = self.current_annotation['components']
|
||||
|
||||
self.annotator.set_current_annotation(
|
||||
annotation_type, annotation_components)
|
||||
|
||||
new_cursor = self.annotator.format_text(
|
||||
cursor, cursor.selectionStart(), cursor.selectionEnd())
|
||||
cursor, cursor_start, cursor_end)
|
||||
self.setTextCursor(new_cursor)
|
||||
|
||||
self.annotation_mode = False
|
||||
self.viewport().setCursor(QtCore.Qt.ArrowCursor)
|
||||
self.current_annotation = None
|
||||
self.parent.annotationListView.clearSelection()
|
||||
self.parent.annotationDock.setWindowOpacity(.95)
|
||||
# TODO
|
||||
# Maybe use annotation name for a consolidated annotation list
|
||||
|
||||
this_annotation = {
|
||||
'name': self.current_annotation['name'],
|
||||
'applicable_to': applicable_to,
|
||||
'type': annotation_type,
|
||||
'cursor': (cursor_start, cursor_end),
|
||||
'components': annotation_components,
|
||||
'note': None}
|
||||
|
||||
try:
|
||||
self.annotation_dict[current_chapter].append(this_annotation)
|
||||
except KeyError:
|
||||
self.annotation_dict[current_chapter] = []
|
||||
self.annotation_dict[current_chapter].append(this_annotation)
|
||||
|
||||
self.toggle_annotation_mode()
|
||||
|
||||
def generate_textbrowser_context_menu(self, position):
|
||||
selection = self.textCursor().selection()
|
||||
selection = selection.toPlainText()
|
||||
|
||||
current_chapter = self.parent.metadata['position']['current_chapter']
|
||||
cursor_at_mouse = self.cursorForPosition(position)
|
||||
annotation_is_present = self.common_functions.check_annotation_position(
|
||||
'text', current_chapter, cursor_at_mouse.position())
|
||||
|
||||
contextMenu = QtWidgets.QMenu()
|
||||
|
||||
# The following cannot be None because a click
|
||||
# outside the menu means that the action variable is None.
|
||||
defineAction = fsToggleAction = dfToggleAction = 'Caesar si viveret, ad remum dareris'
|
||||
searchAction = searchGoogleAction = 'TODO Insert Latin Joke'
|
||||
searchWikipediaAction = searchYoutubeAction = 'Does anyone know something funny in Latin?'
|
||||
searchAction = searchGoogleAction = bookmarksToggleAction = 'TODO Insert Latin Joke'
|
||||
deleteAnnotationAction = editAnnotationNoteAction = 'Latin quote 2. Electric Boogaloo.'
|
||||
|
||||
if selection and selection != '':
|
||||
first_selected_word = selection.split()[0]
|
||||
@@ -462,6 +504,17 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser):
|
||||
QtGui.QIcon(':/images/Youtube.png'),
|
||||
'Youtube')
|
||||
|
||||
if annotation_is_present:
|
||||
annotationsubMenu = contextMenu.addMenu('Annotation')
|
||||
annotationsubMenu.setIcon(self.main_window.QImageFactory.get_image('annotate'))
|
||||
|
||||
editAnnotationNoteAction = annotationsubMenu.addAction(
|
||||
self.main_window.QImageFactory.get_image('edit-rename'),
|
||||
self._translate('PliantQTextBrowser', 'Edit note'))
|
||||
deleteAnnotationAction = annotationsubMenu.addAction(
|
||||
self.main_window.QImageFactory.get_image('remove'),
|
||||
self._translate('PliantQTextBrowser', 'Delete annotation'))
|
||||
|
||||
if self.parent.is_fullscreen:
|
||||
fsToggleAction = contextMenu.addAction(
|
||||
self.main_window.QImageFactory.get_image('view-fullscreen'),
|
||||
@@ -478,7 +531,6 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser):
|
||||
self.main_window.QImageFactory.get_image('visibility'),
|
||||
distraction_free_prompt)
|
||||
|
||||
bookmarksToggleAction = 'Latin quote 2. Electric Boogaloo.'
|
||||
if not self.main_window.settings['show_bars'] or self.parent.is_fullscreen:
|
||||
bookmarksToggleAction = contextMenu.addAction(
|
||||
self.main_window.QImageFactory.get_image('bookmarks'),
|
||||
@@ -490,6 +542,7 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser):
|
||||
|
||||
if action == defineAction:
|
||||
self.main_window.definitionDialog.find_definition(selection)
|
||||
|
||||
if action == searchAction:
|
||||
self.main_window.bookToolBar.searchBar.setText(selection)
|
||||
self.main_window.bookToolBar.searchBar.setFocus()
|
||||
@@ -502,8 +555,14 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser):
|
||||
if action == searchYoutubeAction:
|
||||
webbrowser.open_new_tab(
|
||||
f'https://www.youtube.com/results?search_query={selection}')
|
||||
|
||||
if action == deleteAnnotationAction:
|
||||
self.common_functions.delete_annotation(
|
||||
'text', current_chapter, cursor_at_mouse.position())
|
||||
|
||||
if action == bookmarksToggleAction:
|
||||
self.parent.toggle_bookmarks()
|
||||
|
||||
if action == fsToggleAction:
|
||||
self.parent.exit_fullscreen()
|
||||
if action == dfToggleAction:
|
||||
@@ -588,8 +647,76 @@ class PliantWidgetsCommonFunctions:
|
||||
if not was_button_pressed:
|
||||
self.pw.ignore_wheel_event = True
|
||||
|
||||
def load_annotations(self, chapter):
|
||||
try:
|
||||
chapter_annotations = self.pw.annotation_dict[chapter]
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
for i in chapter_annotations:
|
||||
applicable_to = i['applicable_to']
|
||||
annotation_type = i['type']
|
||||
annotation_components = i['components']
|
||||
|
||||
if not self.are_we_doing_images_only and applicable_to == 'text':
|
||||
cursor = self.pw.textCursor()
|
||||
cursor_start = i['cursor'][0]
|
||||
cursor_end = i['cursor'][1]
|
||||
|
||||
self.pw.annotator.set_current_annotation(
|
||||
annotation_type, annotation_components)
|
||||
|
||||
new_cursor = self.pw.annotator.format_text(
|
||||
cursor, cursor_start, cursor_end)
|
||||
self.pw.setTextCursor(new_cursor)
|
||||
|
||||
def check_annotation_position(self, annotation_type, chapter, cursor_position):
|
||||
try:
|
||||
chapter_annotations = self.pw.annotation_dict[chapter]
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
for i in chapter_annotations:
|
||||
if annotation_type == 'text':
|
||||
cursor_start = i['cursor'][0]
|
||||
cursor_end = i['cursor'][1]
|
||||
|
||||
if cursor_start <= cursor_position <= cursor_end:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def delete_annotation(self, annotation_type, chapter, cursor_position):
|
||||
try:
|
||||
chapter_annotations = self.pw.annotation_dict[chapter]
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
for i in chapter_annotations:
|
||||
if annotation_type == 'text':
|
||||
cursor_start = i['cursor'][0]
|
||||
cursor_end = i['cursor'][1]
|
||||
|
||||
if cursor_start <= cursor_position <= cursor_end:
|
||||
self.pw.annotation_dict[chapter].remove(i)
|
||||
|
||||
current_scroll_position = self.pw.verticalScrollBar().value()
|
||||
self.clear_annotations()
|
||||
self.load_annotations(chapter)
|
||||
self.pw.verticalScrollBar().setValue(current_scroll_position)
|
||||
|
||||
def clear_annotations(self):
|
||||
if not self.are_we_doing_images_only:
|
||||
self.pw.record_position()
|
||||
cursor = self.pw.textCursor()
|
||||
cursor.setPosition(0)
|
||||
cursor.movePosition(QtGui.QTextCursor.End, QtGui.QTextCursor.KeepAnchor)
|
||||
|
||||
previewCharFormat = QtGui.QTextCharFormat()
|
||||
previewCharFormat.setFontStyleStrategy(
|
||||
QtGui.QFont.PreferAntialias)
|
||||
cursor.setCharFormat(previewCharFormat)
|
||||
cursor.clearSelection()
|
||||
self.pw.setTextCursor(cursor)
|
||||
|
||||
def update_model(self):
|
||||
# We're updating the underlying model to have real-time
|
||||
|
@@ -219,7 +219,7 @@ class DatabaseFunctions:
|
||||
|
||||
def modify_metadata(self, metadata_dict, book_hash):
|
||||
def generate_binary(column, data):
|
||||
if column in ('Position', 'LastAccessed', 'Bookmarks'):
|
||||
if column in ('Position', 'LastAccessed', 'Bookmarks', 'Annotations'):
|
||||
return sqlite3.Binary(pickle.dumps(data))
|
||||
elif column == 'CoverImage':
|
||||
return sqlite3.Binary(data)
|
||||
|
@@ -126,7 +126,8 @@ class BookSorter:
|
||||
def database_entry_for_book(self, file_hash):
|
||||
database_return = database.DatabaseFunctions(
|
||||
self.database_path).fetch_data(
|
||||
('Title', 'Author', 'Year', 'ISBN', 'Tags', 'Position', 'Bookmarks', 'CoverImage'),
|
||||
('Title', 'Author', 'Year', 'ISBN', 'Tags',
|
||||
'Position', 'Bookmarks', 'CoverImage', 'Annotations'),
|
||||
'books',
|
||||
{'Hash': file_hash},
|
||||
'EQUALS')[0]
|
||||
@@ -134,7 +135,7 @@ class BookSorter:
|
||||
book_data = []
|
||||
|
||||
for count, i in enumerate(database_return):
|
||||
if count in (5, 6):
|
||||
if count in (5, 6, 8): # Position, Bookmarks, and Annotations are pickled
|
||||
if i:
|
||||
book_data.append(pickle.loads(i))
|
||||
else:
|
||||
@@ -233,12 +234,14 @@ class BookSorter:
|
||||
position = book_data[5]
|
||||
bookmarks = book_data[6]
|
||||
cover = book_data[7]
|
||||
annotations = book_data[8]
|
||||
|
||||
this_book[file_md5]['position'] = position
|
||||
this_book[file_md5]['bookmarks'] = bookmarks
|
||||
this_book[file_md5]['content'] = content
|
||||
this_book[file_md5]['images_only'] = images_only
|
||||
this_book[file_md5]['cover'] = cover
|
||||
this_book[file_md5]['annotations'] = annotations
|
||||
|
||||
this_book[file_md5]['title'] = title
|
||||
this_book[file_md5]['author'] = author
|
||||
|
@@ -36,7 +36,8 @@ class BackGroundTabUpdate(QtCore.QThread):
|
||||
database_dict = {
|
||||
'Position': i['position'],
|
||||
'LastAccessed': i['last_accessed'],
|
||||
'Bookmarks': i['bookmarks']}
|
||||
'Bookmarks': i['bookmarks'],
|
||||
'Annotations': i['annotations']}
|
||||
|
||||
database.DatabaseFunctions(self.database_path).modify_metadata(
|
||||
database_dict, book_hash)
|
||||
|
@@ -58,6 +58,14 @@ class Tab(QtWidgets.QWidget):
|
||||
|
||||
chapter_content = self.metadata['content'][current_chapter - 1][1]
|
||||
|
||||
# Create relevant containers
|
||||
if not self.metadata['annotations']:
|
||||
self.metadata['annotations'] = {}
|
||||
|
||||
# See bookmark availability
|
||||
if not self.metadata['bookmarks']:
|
||||
self.metadata['bookmarks'] = {}
|
||||
|
||||
# The content display widget is, by default a QTextBrowser.
|
||||
# In case the incoming data is only images
|
||||
# such as in the case of comic book files,
|
||||
@@ -97,6 +105,9 @@ class Tab(QtWidgets.QWidget):
|
||||
self.hiddenButton.clicked.connect(self.set_cursor_position)
|
||||
self.hiddenButton.animateClick(50)
|
||||
|
||||
# Load annotations for current content
|
||||
self.contentView.common_functions.load_annotations(current_chapter)
|
||||
|
||||
# The following are common to both the text browser and
|
||||
# the graphics view
|
||||
self.contentView.setFrameShape(QtWidgets.QFrame.NoFrame)
|
||||
@@ -120,19 +131,13 @@ class Tab(QtWidgets.QWidget):
|
||||
self.annotationListView = QtWidgets.QListView(self.annotationDock)
|
||||
self.annotationListView.setResizeMode(QtWidgets.QListWidget.Adjust)
|
||||
self.annotationListView.setMaximumWidth(350)
|
||||
if not self.are_we_doing_images_only:
|
||||
self.annotationListView.doubleClicked.connect(
|
||||
self.contentView.toggle_annotation_mode)
|
||||
self.annotationListView.doubleClicked.connect(self.contentView.toggle_annotation_mode)
|
||||
self.annotationListView.setEditTriggers(QtWidgets.QListView.NoEditTriggers)
|
||||
self.annotationDock.setWidget(self.annotationListView)
|
||||
|
||||
self.annotationModel = QtGui.QStandardItemModel(self)
|
||||
self.generate_annotation_model()
|
||||
|
||||
# See bookmark availability
|
||||
if not self.metadata['bookmarks']:
|
||||
self.metadata['bookmarks'] = {}
|
||||
|
||||
# Create the dock widget for context specific display
|
||||
self.bookmarkDock = PliantDockWidget(self.main_window, 'bookmarks', self.contentView)
|
||||
self.bookmarkDock.setWindowTitle(self._translate('Tab', 'Bookmarks'))
|
||||
@@ -335,6 +340,8 @@ class Tab(QtWidgets.QWidget):
|
||||
self.contentView.clear()
|
||||
self.contentView.setHtml(required_content)
|
||||
|
||||
self.contentView.common_functions.load_annotations(chapter_number + 1)
|
||||
|
||||
def format_view(self, font, font_size, foreground,
|
||||
background, padding, line_spacing,
|
||||
text_alignment):
|
||||
@@ -405,12 +412,19 @@ class Tab(QtWidgets.QWidget):
|
||||
if not saved_annotations:
|
||||
return
|
||||
|
||||
for i in saved_annotations:
|
||||
def add_to_model(annotation):
|
||||
item = QtGui.QStandardItem()
|
||||
item.setText(i['name'])
|
||||
item.setData(i, QtCore.Qt.UserRole)
|
||||
item.setText(annotation['name'])
|
||||
item.setData(annotation, QtCore.Qt.UserRole)
|
||||
self.annotationModel.appendRow(item)
|
||||
|
||||
# Prevent annotation mixup
|
||||
for i in saved_annotations:
|
||||
if self.are_we_doing_images_only and i['applicable_to'] == 'images':
|
||||
add_to_model(i)
|
||||
elif not self.are_we_doing_images_only and i['applicable_to'] == 'text':
|
||||
add_to_model(i)
|
||||
|
||||
self.annotationListView.setModel(self.annotationModel)
|
||||
|
||||
def toggle_bookmarks(self):
|
||||
@@ -556,13 +570,13 @@ class PliantDockWidget(QtWidgets.QDockWidget):
|
||||
desktop_size = QtWidgets.QDesktopWidget().screenGeometry()
|
||||
|
||||
if self.intended_for == 'bookmarks':
|
||||
dock_x = viewport_topRight.x() - dock_width + 1
|
||||
dock_width = desktop_size.width() // 5.5
|
||||
dock_x = viewport_topRight.x() - dock_width + 1
|
||||
self.main_window.bookToolBar.bookmarkButton.setChecked(True)
|
||||
|
||||
elif self.intended_for == 'annotations':
|
||||
dock_x = viewport_topLeft.x()
|
||||
dock_width = desktop_size.width() // 10
|
||||
dock_x = viewport_topLeft.x()
|
||||
self.main_window.bookToolBar.annotationButton.setChecked(True)
|
||||
|
||||
dock_y = viewport_topRight.y() + (viewport_height * .10)
|
||||
|
Reference in New Issue
Block a user