From aff69d95c137af83aee7b3e4297150446866a85b Mon Sep 17 00:00:00 2001 From: BasioMeusPuga Date: Sat, 31 Mar 2018 03:03:49 +0530 Subject: [PATCH] Make progress work with block count Break database thoroughly Fix pdf year bug --- TODO | 12 ++- lector/__main__.py | 36 +------ lector/database.py | 5 +- lector/delegates.py | 22 ++-- lector/models.py | 22 ++-- lector/parsers/pdf.py | 4 +- lector/resources/pie_chart.py | 9 +- lector/widgets.py | 182 ++++++++++++++++++++-------------- 8 files changed, 151 insertions(+), 141 deletions(-) diff --git a/TODO b/TODO index 491f43c..d5836a8 100644 --- a/TODO +++ b/TODO @@ -2,7 +2,7 @@ TODO General: ✓ Internationalization ✓ Application icon - ✓ .desktop file + ✓ .desktop file Options: ✓ Automatic library management ✓ Recursive file addition @@ -59,7 +59,10 @@ TODO ✓ Comic view context menu ✓ Make the bookmark dock float over the reading area ✓ 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 + Annotations + Text + Image Adjust key navigation according to viewport dimensions Search document using QTextCursor Filetypes: @@ -76,14 +79,13 @@ TODO ✓ Define every widget in code Bugs: Deselecting all directories in the settings dialog also filters out manually added books - Only one tab has its scroll position set when opening multiple books @ startup - It's the one that has focus when application starts and ends + PDF year Secondary: - Annotations Graphical themes Change focus rectangle dimensions Tab reordering + Universal Ctrl + Tab Allow tabs to detach and form their own windows Goodreads API: Ratings, Read, Recommendations Get ISBN using python-isbnlib diff --git a/lector/__main__.py b/lector/__main__.py index 4e399b5..35fcea7 100755 --- a/lector/__main__.py +++ b/lector/__main__.py @@ -553,7 +553,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): self.bookToolBar.tocBox.setCurrentIndex( current_position['current_chapter'] - 1) if not current_metadata['images_only']: - current_tab.set_cursor_position() + current_tab.hiddenButton.animateClick(25) self.bookToolBar.tocBox.blockSignals(False) self.profile_functions.format_contentView() @@ -582,45 +582,15 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): def set_toc_position(self, event=None): current_tab = self.tabWidget.widget(self.tabWidget.currentIndex()) - # We're updating the underlying model to have real-time - # updates on the read status - - # Set a baseline model index in case the item gets deleted - # E.g It's open in a tab and deleted from the library - model_index = None - start_index = self.lib_ref.view_model.index(0, 0) - # Find index of the model item that corresponds to the tab - matching_item = self.lib_ref.view_model.match( - start_index, - QtCore.Qt.UserRole + 6, - current_tab.metadata['hash'], - 1, QtCore.Qt.MatchExactly) - if matching_item: - model_row = matching_item[0].row() - model_index = self.lib_ref.view_model.index(model_row, 0) - current_tab.metadata[ 'position']['current_chapter'] = event + 1 current_tab.metadata[ 'position']['is_read'] = False - # TODO - # This doesn't update correctly - # try: - # position_perc = ( - # current_tab.metadata[ - # 'current_chapter'] * 100 / current_tab.metadata['total_chapters']) - # except KeyError: - # position_perc = None - - if model_index: - self.lib_ref.view_model.setData( - model_index, current_tab.metadata, QtCore.Qt.UserRole + 3) - # self.lib_ref.view_model.setData( - # model_index, position_perc, QtCore.Qt.UserRole + 7) - # Go on to change the value of the Table of Contents box current_tab.change_chapter_tocBox() + current_tab.contentView.record_position() + self.profile_functions.format_contentView() def set_fullscreen(self): diff --git a/lector/database.py b/lector/database.py index b49fb62..0ecd06a 100644 --- a/lector/database.py +++ b/lector/database.py @@ -53,7 +53,7 @@ class DatabaseInit: 'CheckState': 'INTEGER'} if os.path.exists(self.database_path): - self.check_database() + self.check_columns() else: self.create_database() @@ -72,7 +72,7 @@ class DatabaseInit: self.database.commit() self.database.close() - def check_database(self): + def check_columns(self): self.database = sqlite3.connect(self.database_path) database_return = self.database.execute("PRAGMA table_info(books)").fetchall() @@ -89,7 +89,6 @@ class DatabaseInit: if commit_required: self.database.commit() - self.database.close() class DatabaseFunctions: diff --git a/lector/delegates.py b/lector/delegates.py index 1fedada..7738470 100644 --- a/lector/delegates.py +++ b/lector/delegates.py @@ -33,6 +33,7 @@ class LibraryDelegate(QtWidgets.QStyledItemDelegate): # painter.fillRect(option.rect, QtGui.QColor().fromRgb(255, 0, 0, 20)) option = option.__class__(option) + title = index.data(QtCore.Qt.UserRole) file_exists = index.data(QtCore.Qt.UserRole + 5) metadata = index.data(QtCore.Qt.UserRole + 3) @@ -65,21 +66,28 @@ class LibraryDelegate(QtWidgets.QStyledItemDelegate): QtWidgets.QStyledItemDelegate.paint(self, painter, option, index) if position: if is_read: - current_chapter = total_chapters = 100 + progress = total = -1 else: try: - current_chapter = position['current_chapter'] - total_chapters = position['total_chapters'] + progress = position['current_block'] + total = position['total_blocks'] + if progress == total == 0: + raise KeyError except KeyError: - return + # For comics and older database entries + # It looks ugly but leave it like this + try: + progress = position['current_chapter'] + total = position['total_chapters'] + except KeyError: + return read_icon = pie_chart.pixmapper( - current_chapter, total_chapters, self.temp_dir, 36) + progress, total, self.temp_dir, 36) x_draw = option.rect.bottomRight().x() - 30 y_draw = option.rect.bottomRight().y() - 35 - if current_chapter != 1: - painter.drawPixmap(x_draw, y_draw, read_icon) + painter.drawPixmap(x_draw, y_draw, read_icon) class BookmarkDelegate(QtWidgets.QStyledItemDelegate): diff --git a/lector/models.py b/lector/models.py index 6d99d5e..aa2d2cd 100644 --- a/lector/models.py +++ b/lector/models.py @@ -122,6 +122,8 @@ class TableProxyModel(QtCore.QSortFilterProxyModel): file_exists = item.data(QtCore.Qt.UserRole + 5) metadata = item.data(QtCore.Qt.UserRole + 3) + progress_perc = item.data(QtCore.Qt.UserRole + 7) + position = metadata['position'] if position: is_read = position['is_read'] @@ -132,27 +134,29 @@ class TableProxyModel(QtCore.QSortFilterProxyModel): if position: if is_read: - current_chapter = total_chapters = 100 + progress = total = -2 else: try: - current_chapter = position['current_chapter'] - total_chapters = position['total_chapters'] + progress = position['current_block'] + total = position['total_blocks'] - # TODO - # See if there's any rationale for this - if current_chapter == 1: + if progress == total == 0: raise KeyError except KeyError: - return + try: + progress = position['current_chapter'] + total = position['total_chapters'] + except KeyError: + return return_pixmap = pie_chart.pixmapper( - current_chapter, total_chapters, self.temp_dir, + progress, total, self.temp_dir, QtCore.Qt.SizeHintRole + 10) return return_pixmap elif role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole: - if index.column() in (0, 5): # Cover and Status + if index.column() in (0, 5): # Cover and Status return QtCore.QVariant() if index.column() == 4: diff --git a/lector/parsers/pdf.py b/lector/parsers/pdf.py index b5aeb60..228f873 100644 --- a/lector/parsers/pdf.py +++ b/lector/parsers/pdf.py @@ -63,8 +63,8 @@ class ParsePDF: def get_year(self): try: year = self.metadata.find('MetadataDate').text - return year.replace('\n', '') - except AttributeError: + return int(year.replace('\n', '')[:4]) + except (AttributeError, ValueError): return 9999 def get_cover_image(self): diff --git a/lector/resources/pie_chart.py b/lector/resources/pie_chart.py index 6ff99a8..ec93c10 100644 --- a/lector/resources/pie_chart.py +++ b/lector/resources/pie_chart.py @@ -94,25 +94,24 @@ def generate_pie(progress_percent, temp_dir=None): return lSvg -def pixmapper(current_chapter, total_chapters, temp_dir, size): +def pixmapper(progress, total, temp_dir, size): # A current_chapter of -1 implies the files does not exist # A chapter number == Total chapters implies the file is unread return_pixmap = None - if current_chapter == -1: + if progress == -1: return_pixmap = QtGui.QIcon(':/images/error.svg').pixmap(size) return return_pixmap - if current_chapter == total_chapters: + if progress >= .95 * total: # Consider book read @ 95% progress return_pixmap = QtGui.QIcon(':/images/checkmark.svg').pixmap(size) else: # TODO # See if saving the svg to disk can be avoided - # Shift to lines to track progress # Maybe make the alignment a little more uniform across emblems - progress_percent = int(current_chapter * 100 / total_chapters) + progress_percent = int(progress * 100 / total) generate_pie(progress_percent, temp_dir) svg_path = os.path.join(temp_dir, 'lector_progress.svg') return_pixmap = QtGui.QIcon(svg_path).pixmap(size - 4) ## The -4 looks more proportional diff --git a/lector/widgets.py b/lector/widgets.py index 7775a5b..a7002ae 100644 --- a/lector/widgets.py +++ b/lector/widgets.py @@ -20,7 +20,6 @@ # Reading modes # Double page, Continuous etc # Especially for comics -# Remove variables that have anything to do with scroll position import os @@ -191,57 +190,34 @@ class Tab(QtWidgets.QWidget): except IndexError: # The file has been deleted pass - def set_cursor_position(self, switch_widgets=True, search_data=None): - # if self.sender().objectName() == 'tabWidget' and self.first_run: - # return - # self.first_run = False - # ^^^ I have NO IDEA why this might be needed or how it works - - if switch_widgets: - previous_widget = self.main_window.tabWidget.currentWidget() - self.main_window.tabWidget.setCurrentWidget(self) - + def set_cursor_position(self, cursor_position=None): try: required_position = self.metadata['position']['cursor_position'] - if search_data: - required_position = search_data[1] - - # TODO - # Remove this at the time when a library rebuild is - # finally required - # Bookmarks will have to be regenerated in the meantime - if isinstance(required_position, str): - return - - # This is needed so that the line we want is - # always at the top of the window - self.contentView.verticalScrollBar().setValue( - self.contentView.verticalScrollBar().maximum()) - - # textCursor() RETURNS a copy of the textcursor - cursor = self.contentView.textCursor() - cursor.setPosition( - required_position, QtGui.QTextCursor.MoveAnchor) - self.contentView.setTextCursor(cursor) - self.contentView.ensureCursorVisible() - except KeyError: - pass + print(f'Database: Cursor position error. Recommend retry.') + return - if switch_widgets: - self.main_window.tabWidget.setCurrentWidget(previous_widget) + if cursor_position: + required_position = cursor_position + + # This is needed so that the line we want is + # always at the top of the window + self.contentView.verticalScrollBar().setValue( + self.contentView.verticalScrollBar().maximum()) + + # textCursor() RETURNS a copy of the textcursor + cursor = self.contentView.textCursor() + cursor.setPosition( + required_position, QtGui.QTextCursor.MoveAnchor) + self.contentView.setTextCursor(cursor) + self.contentView.ensureCursorVisible() def generate_position(self, is_read=False): total_chapters = len(self.metadata['content']) current_chapter = 1 - scroll_value = 0 if is_read: current_chapter = total_chapters - scroll_value = 1 - - # TODO - # Use this to generate position # Generate block count @ time of first read # Blocks are indexed from 0 up @@ -264,8 +240,8 @@ class Tab(QtWidgets.QWidget): 'total_chapters': total_chapters, 'blocks_per_chapter': blocks_per_chapter, 'total_blocks': total_blocks, - 'scroll_value': scroll_value, 'is_read': is_read, + 'current_block': 0, 'cursor_position': 0} def generate_keyboard_shortcuts(self): @@ -298,7 +274,7 @@ class Tab(QtWidgets.QWidget): return if not self.are_we_doing_images_only: - self.contentView.record_scroll_position() + self.contentView.record_position() self.contentView.setWindowFlags(QtCore.Qt.Window) self.contentView.setWindowState(QtCore.Qt.WindowFullScreen) @@ -316,7 +292,7 @@ class Tab(QtWidgets.QWidget): return if not self.are_we_doing_images_only: - self.contentView.record_scroll_position() + self.contentView.record_position() self.main_window.show() self.contentView.setWindowFlags(QtCore.Qt.Widget) @@ -419,26 +395,25 @@ class Tab(QtWidgets.QWidget): if self.are_we_doing_images_only: chapter = self.metadata['position']['current_chapter'] - search_data = (0, None) + cursor_position = 0 else: - chapter, scroll_position, visible_text = self.contentView.record_scroll_position(True) - search_data = (scroll_position, visible_text) + chapter, cursor_position = self.contentView.record_position(True) self.metadata['bookmarks'][identifier] = { 'chapter': chapter, - 'search_data': search_data, + 'cursor_position': cursor_position, 'description': description} self.add_bookmark_to_model( - description, chapter, search_data, identifier) + description, chapter, cursor_position, identifier) self.dockWidget.setVisible(True) - def add_bookmark_to_model(self, description, chapter, search_data, identifier): + def add_bookmark_to_model(self, description, chapter, cursor_position, identifier): bookmark = QtGui.QStandardItem() bookmark.setData(description, QtCore.Qt.DisplayRole) bookmark.setData(chapter, QtCore.Qt.UserRole) - bookmark.setData(search_data, QtCore.Qt.UserRole + 1) + bookmark.setData(cursor_position, QtCore.Qt.UserRole + 1) bookmark.setData(identifier, QtCore.Qt.UserRole + 2) self.bookmark_model.appendRow(bookmark) @@ -449,22 +424,30 @@ class Tab(QtWidgets.QWidget): return chapter = self.proxy_model.data(index, QtCore.Qt.UserRole) - search_data = self.proxy_model.data(index, QtCore.Qt.UserRole + 1) + cursor_position = self.proxy_model.data(index, QtCore.Qt.UserRole + 1) self.main_window.bookToolBar.tocBox.setCurrentIndex(chapter - 1) if not self.are_we_doing_images_only: - self.set_cursor_position(False, search_data) + self.set_cursor_position(cursor_position) def generate_bookmark_model(self): # TODO # Sorting is not working correctly - for i in self.metadata['bookmarks'].items(): - self.add_bookmark_to_model( - i[1]['description'], - i[1]['chapter'], - i[1]['search_data'], - i[0]) + try: + for i in self.metadata['bookmarks'].items(): + self.add_bookmark_to_model( + i[1]['description'], + i[1]['chapter'], + i[1]['cursor_position'], + i[0]) + except KeyError: + title = self.metadata['title'] + + # TODO + # Delete the bookmarks entry for this file + print(f'Database: Bookmark error for {title}. Recommend delete entry.') + return self.generate_bookmark_proxy_model() @@ -579,7 +562,7 @@ class PliantQGraphicsView(QtWidgets.QGraphicsView): image_pixmap.loadFromData(page_data) elif self.filetype == 'pdf': page_data = self.book.page(current_page) - page_qimage = page_data.renderToImage(350, 350) + page_qimage = page_data.renderToImage(400, 400) # TODO Maybe this needs a setting? image_pixmap.convertFromImage(page_qimage) return image_pixmap @@ -672,7 +655,7 @@ class PliantQGraphicsView(QtWidgets.QGraphicsView): self.show() def wheelEvent(self, event): - self.common_functions.wheelEvent(event, True) + self.common_functions.wheelEvent(event) def keyPressEvent(self, event): vertical = self.verticalScrollBar().value() @@ -712,6 +695,10 @@ class PliantQGraphicsView(QtWidgets.QGraphicsView): if event.key() in view_modification_keys: self.main_window.modify_comic_view(event.key()) + def record_position(self): + self.parent.metadata['position']['is_read'] = False + self.common_functions.update_model() + def mouseMoveEvent(self, *args): self.viewport().setCursor(QtCore.Qt.ArrowCursor) self.parent.mouse_hide_timer.start(3000) @@ -825,13 +812,13 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser): self.setMouseTracking(True) self.viewport().setCursor(QtCore.Qt.IBeamCursor) self.verticalScrollBar().sliderMoved.connect( - self.record_scroll_position) + self.record_position) self.ignore_wheel_event = False self.ignore_wheel_event_number = 0 def wheelEvent(self, event): - self.record_scroll_position() - self.common_functions.wheelEvent(event, False) + self.record_position() + self.common_functions.wheelEvent(event) def keyPressEvent(self, event): QtWidgets.QTextEdit.keyPressEvent(self, event) @@ -840,6 +827,7 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser): self.common_functions.change_chapter(1, True) else: self.set_top_line_cleanly() + self.record_position() def set_top_line_cleanly(self): # Find the cursor position of the top line and move to it @@ -849,22 +837,27 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser): self.setTextCursor(find_cursor) self.ensureCursorVisible() - def record_scroll_position(self, return_as_bookmark=False): + def record_position(self, return_as_bookmark=False): self.parent.metadata['position']['is_read'] = False - vertical = self.verticalScrollBar().value() - maximum = self.verticalScrollBar().maximum() - - self.parent.metadata['position']['scroll_value'] = 1 - if maximum != 0: - self.parent.metadata['position']['scroll_value'] = (vertical / maximum) - cursor = self.cursorForPosition(QtCore.QPoint(0, 0)) cursor_position = cursor.position() + # Current block for progress measurement + current_block = cursor.block().blockNumber() + current_chapter = self.parent.metadata['position']['current_chapter'] + + blocks_per_chapter = self.parent.metadata['position']['blocks_per_chapter'] + block_sum = sum(blocks_per_chapter[:(current_chapter - 1)]) + block_sum += current_block + + # This 'current_block' refers to the number of + # blocks in the book upto this one + self.parent.metadata['position']['current_block'] = block_sum + self.common_functions.update_model() + if return_as_bookmark: return (self.parent.metadata['position']['current_chapter'], - self.parent.metadata['position']['scroll_value'], cursor_position) else: self.parent.metadata['position']['cursor_position'] = cursor_position @@ -936,14 +929,15 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser): QtWidgets.QTextBrowser.mouseMoveEvent(self, event) -class PliantWidgetsCommonFunctions(): +class PliantWidgetsCommonFunctions: def __init__(self, parent_widget, main_window): self.pw = parent_widget self.main_window = main_window + self.are_we_doing_images_only = self.pw.parent.are_we_doing_images_only - def wheelEvent(self, event, are_we_doing_images_only): + def wheelEvent(self, event): ignore_events = 20 - if are_we_doing_images_only: + if self.are_we_doing_images_only: ignore_events = 10 if self.pw.ignore_wheel_event: @@ -953,7 +947,7 @@ class PliantWidgetsCommonFunctions(): self.pw.ignore_wheel_event_number = 0 return - if are_we_doing_images_only: + if self.are_we_doing_images_only: QtWidgets.QGraphicsView.wheelEvent(self.pw, event) else: QtWidgets.QTextBrowser.wheelEvent(self.pw, event) @@ -1002,6 +996,40 @@ class PliantWidgetsCommonFunctions(): if not was_button_pressed: self.pw.ignore_wheel_event = True + if not self.are_we_doing_images_only: + self.pw.record_position() + + def update_model(self): + # We're updating the underlying model to have real-time + # updates on the read status + + # Set a baseline model index in case the item gets deleted + # E.g It's open in a tab and deleted from the library + model_index = None + start_index = self.main_window.lib_ref.view_model.index(0, 0) + + # Find index of the model item that corresponds to the tab + model_index = self.main_window.lib_ref.view_model.match( + start_index, + QtCore.Qt.UserRole + 6, + self.pw.parent.metadata['hash'], + 1, QtCore.Qt.MatchExactly) + + if self.are_we_doing_images_only: + position_percentage = (self.pw.parent.metadata['position']['current_chapter'] / + self.pw.parent.metadata['position']['total_chapters']) + else: + position_percentage = (self.pw.parent.metadata['position']['current_block'] / + self.pw.parent.metadata['position']['total_blocks']) + + # Update book metadata and position percentage + if model_index: + self.main_window.lib_ref.view_model.setData( + model_index[0], self.pw.parent.metadata, QtCore.Qt.UserRole + 3) + + self.main_window.lib_ref.view_model.setData( + model_index[0], position_percentage, QtCore.Qt.UserRole + 7) + def generate_combo_box_action(self, contextMenu): contextMenu.addSeparator()