diff --git a/TODO b/TODO index b27dfb3..491f43c 100644 --- a/TODO +++ b/TODO @@ -58,6 +58,8 @@ TODO ✓ Comic view keyboard shortcuts ✓ 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 Adjust key navigation according to viewport dimensions Search document using QTextCursor Filetypes: @@ -74,6 +76,8 @@ 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 Secondary: Annotations @@ -86,7 +90,6 @@ TODO Pagination Use embedded fonts + CSS Scrolling: Smooth / By Line - Spacebar should not cut off lines at the top Shift to logging instead of print statements txt, doc, chm, djvu, fb2 support Include icons for filetype emblems diff --git a/lector/__main__.py b/lector/__main__.py index dbe5325..f03d6c5 100755 --- a/lector/__main__.py +++ b/lector/__main__.py @@ -155,6 +155,8 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): self.libraryToolBar.deleteButton.triggered.connect(self.delete_books) self.libraryToolBar.coverViewButton.triggered.connect(self.switch_library_view) self.libraryToolBar.tableViewButton.triggered.connect(self.switch_library_view) + self.libraryToolBar.reloadLibraryButton.triggered.connect( + self.settingsDialog.start_library_scan) self.libraryToolBar.colorButton.triggered.connect(self.get_color) self.libraryToolBar.settingsButton.triggered.connect(self.show_settings) self.libraryToolBar.searchBar.textChanged.connect(self.lib_ref.update_proxymodels) @@ -218,16 +220,11 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): self.available_parsers = '*.' + ' *.'.join(sorter.available_parsers) print('Available parsers: ' + self.available_parsers) - # The library refresh button on the Library tab - self.reloadLibrary.setFlat(True) - self.reloadLibrary.setIcon(self.QImageFactory.get_image('reload')) - self.reloadLibrary.setObjectName('reloadLibrary') - self.reloadLibrary.setToolTip(self._translate('Main_UI', 'Scan library')) - self.reloadLibrary.clicked.connect(self.settingsDialog.start_library_scan) - + # The Library tab gets no button self.tabWidget.tabBar().setTabButton( - 0, QtWidgets.QTabBar.RightSide, self.reloadLibrary) + 0, QtWidgets.QTabBar.RightSide, None) self.tabWidget.tabCloseRequested.connect(self.tab_close) + self.tabWidget.setTabBarAutoHide(True) # Init display models self.lib_ref.generate_model('build') @@ -556,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_scroll_value(True) + current_tab.set_cursor_position() self.bookToolBar.tocBox.blockSignals(False) self.profile_functions.format_contentView() diff --git a/lector/database.py b/lector/database.py index 4e49aa7..b49fb62 100644 --- a/lector/database.py +++ b/lector/database.py @@ -42,7 +42,8 @@ class DatabaseInit: 'LastAccessed': 'BLOB', 'Bookmarks': 'BLOB', 'CoverImage': 'BLOB', - 'Addition': 'TEXT'} + 'Addition': 'TEXT', + 'Annotations': 'BLOB'} self.directories_table_columns = { 'id': 'INTEGER PRIMARY KEY', @@ -82,7 +83,7 @@ class DatabaseInit: for i in self.books_table_columns.items(): if i[0] not in database_columns: commit_required = True - print(f'Database: Adding column {i[0]}') + print(f'Database: Adding column "{i[0]}"') sql_command = f"ALTER TABLE books ADD COLUMN {i[0]} {i[1]}" self.database.execute(sql_command) diff --git a/lector/sorter.py b/lector/sorter.py index aeed23c..ee4b75c 100644 --- a/lector/sorter.py +++ b/lector/sorter.py @@ -118,7 +118,7 @@ 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'), + ('Title', 'Author', 'Year', 'ISBN', 'Tags', 'Position', 'Bookmarks', 'CoverImage'), 'books', {'Hash': file_hash}, 'EQUALS')[0] @@ -231,11 +231,13 @@ class BookSorter: tags = book_data[4] position = book_data[5] bookmarks = book_data[6] + cover = book_data[7] 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]['title'] = title this_book[file_md5]['author'] = author diff --git a/lector/toolbars.py b/lector/toolbars.py index 37b66ae..17a8bfc 100644 --- a/lector/toolbars.py +++ b/lector/toolbars.py @@ -376,6 +376,11 @@ class LibraryToolBar(QtWidgets.QToolBar): self) self.tableViewButton.setCheckable(True) + self.reloadLibraryButton = QtWidgets.QAction( + image_factory.get_image('reload'), + self._translate('LibraryToolBar', 'Scan Library'), + self) + self.libraryFilterButton = QtWidgets.QToolButton(self) self.libraryFilterButton.setIcon(image_factory.get_image('view-readermode')) self.libraryFilterButton.setText( @@ -396,6 +401,7 @@ class LibraryToolBar(QtWidgets.QToolBar): self.addAction(self.coverViewButton) self.addAction(self.tableViewButton) self.addSeparator() + self.addAction(self.reloadLibraryButton) self.addWidget(self.libraryFilterButton) self.addSeparator() self.addAction(self.colorButton) diff --git a/lector/widgets.py b/lector/widgets.py index 02042be..ba63d90 100644 --- a/lector/widgets.py +++ b/lector/widgets.py @@ -105,7 +105,7 @@ class Tab(QtWidgets.QWidget): self.hiddenButton = QtWidgets.QToolButton(self) self.hiddenButton.setVisible(False) - self.hiddenButton.clicked.connect(self.set_scroll_value) + self.hiddenButton.clicked.connect(self.set_cursor_position) self.hiddenButton.animateClick(50) # The following are common to both the text browser and @@ -135,7 +135,8 @@ class Tab(QtWidgets.QWidget): self.dockListView = QtWidgets.QListView(self.dockWidget) self.dockListView.setResizeMode(QtWidgets.QListWidget.Adjust) self.dockListView.setMaximumWidth(350) - self.dockListView.setItemDelegate(BookmarkDelegate(self.main_window, self.dockListView)) + self.dockListView.setItemDelegate( + BookmarkDelegate(self.main_window, self.dockListView)) self.dockListView.setUniformItemSizes(True) self.dockListView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.dockListView.customContextMenuRequested.connect( @@ -157,6 +158,14 @@ class Tab(QtWidgets.QWidget): title = self.metadata['title'] self.main_window.tabWidget.addTab(self, title) + # TODO + # Show cover image as tooltip text + this_tab_index = self.main_window.tabWidget.indexOf(self) + cover_icon = QtGui.QPixmap() + cover_icon.loadFromData(self.metadata['cover']) + self.main_window.tabWidget.setTabIcon( + this_tab_index, QtGui.QIcon(cover_icon)) + # Hide mouse cursor timer self.mouse_hide_timer = QtCore.QTimer() self.mouse_hide_timer.setSingleShot(True) @@ -180,38 +189,38 @@ class Tab(QtWidgets.QWidget): except IndexError: # The file has been deleted pass - def set_scroll_value(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 is needed or how it works - # but scroll positioning does NOT work without the return - # Enabling it somehow makes document formatting not work either + 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) try: - search_text = self.metadata['position']['last_visible_text'] + required_position = self.metadata['position']['cursor_position'] if search_data: - search_text = search_data[1] + required_position = search_data[1] - # textCursor() RETURNS a copy of the textcursor - cursor = self.contentView.textCursor() - cursor.movePosition(QtGui.QTextCursor.Start, QtGui.QTextCursor.KeepAnchor) - self.contentView.setTextCursor(cursor) + # 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 search results are always at the top - # of the window + # 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()) - # find_forward is a new cursor object that must replace - # the existing text cursor - find_forward = self.contentView.document().find(search_text) - find_forward.clearSelection() - self.contentView.setTextCursor(find_forward) + # 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: @@ -254,8 +263,8 @@ class Tab(QtWidgets.QWidget): 'blocks_per_chapter': blocks_per_chapter, 'total_blocks': total_blocks, 'scroll_value': scroll_value, - 'last_visible_text': None, - 'is_read': is_read} + 'is_read': is_read, + 'cursor_position': 0} def generate_keyboard_shortcuts(self): self.ksNextChapter = QtWidgets.QShortcut( @@ -323,6 +332,8 @@ class Tab(QtWidgets.QWidget): if not self.main_window.settings['show_bars']: self.main_window.toggle_distraction_free() + self.contentView.setFocus() + def change_chapter_tocBox(self): chapter_number = self.main_window.bookToolBar.tocBox.currentIndex() required_content = self.metadata['content'][chapter_number][1] @@ -440,7 +451,7 @@ class Tab(QtWidgets.QWidget): self.main_window.bookToolBar.tocBox.setCurrentIndex(chapter - 1) if not self.are_we_doing_images_only: - self.set_scroll_value(False, search_data) + self.set_cursor_position(False, search_data) def generate_bookmark_model(self): # TODO @@ -811,7 +822,8 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser): self.setMouseTracking(True) self.viewport().setCursor(QtCore.Qt.IBeamCursor) - self.verticalScrollBar().sliderMoved.connect(self.record_scroll_position) + self.verticalScrollBar().sliderMoved.connect( + self.record_scroll_position) self.ignore_wheel_event = False self.ignore_wheel_event_number = 0 @@ -828,19 +840,12 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser): self.set_top_line_cleanly() def set_top_line_cleanly(self): - # TODO - # This can't find the next line sometimes despite having - # a valid search text to look up - # It could have something to do with textCursor position - - self.record_scroll_position() - - search_text = self.parent.metadata['position']['last_visible_text'] - new_cursor = self.document().find(search_text) - if not new_cursor.isNull(): - new_cursor.clearSelection() - self.setTextCursor(new_cursor) - self.ensureCursorVisible() + # Find the cursor position of the top line and move to it + find_cursor = self.cursorForPosition(QtCore.QPoint(0, 0)) + find_cursor.movePosition( + find_cursor.position(), QtGui.QTextCursor.KeepAnchor) + self.setTextCursor(find_cursor) + self.ensureCursorVisible() def record_scroll_position(self, return_as_bookmark=False): self.parent.metadata['position']['is_read'] = False @@ -853,20 +858,14 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser): self.parent.metadata['position']['scroll_value'] = (vertical / maximum) cursor = self.cursorForPosition(QtCore.QPoint(0, 0)) - bottom_right = QtCore.QPoint(self.viewport().width() - 1, self.viewport().height()) - bottom_right_cursor = self.cursorForPosition(bottom_right).position() - cursor.setPosition(bottom_right_cursor, QtGui.QTextCursor.KeepAnchor) - visible_text = cursor.selectedText() - - if len(visible_text) > 50: - visible_text = visible_text[:51] + cursor_position = cursor.position() if return_as_bookmark: return (self.parent.metadata['position']['current_chapter'], self.parent.metadata['position']['scroll_value'], - visible_text) + cursor_position) else: - self.parent.metadata['position']['last_visible_text'] = visible_text + self.parent.metadata['position']['cursor_position'] = cursor_position def generate_textbrowser_context_menu(self, position): selected_word = self.textCursor().selection()