From 739b84e9f4f77781cc0a796e3afe8a1b4d1ac128 Mon Sep 17 00:00:00 2001 From: BasioMeusPuga Date: Sat, 26 Jan 2019 19:03:30 +0530 Subject: [PATCH] Overhaul TOC generation and navigation --- TODO | 2 + lector/__main__.py | 78 +++++------ lector/contentwidgets.py | 82 ++++++----- lector/parsers/comicbooks.py | 8 +- lector/parsers/epub.py | 12 +- lector/parsers/fb2.py | 12 +- lector/parsers/pdf.py | 23 +--- lector/sorter.py | 20 ++- lector/threaded.py | 16 ++- lector/toolbars.py | 27 ++-- lector/widgets.py | 255 +++++++++++++++++++++++++++-------- 11 files changed, 335 insertions(+), 200 deletions(-) diff --git a/TODO b/TODO index 770fc0a..65f3e72 100644 --- a/TODO +++ b/TODO @@ -94,6 +94,8 @@ TODO Colors aren't loaded properly for annotation previews Last line in QTextBrowser should never be cut off Something is wrong with image alignment + Scrolling the toc Combobox does nothing + fb2 images need a newline preceding them Secondary: Graphical themes diff --git a/lector/__main__.py b/lector/__main__.py index 4badc8f..16c1c59 100755 --- a/lector/__main__.py +++ b/lector/__main__.py @@ -637,14 +637,15 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): self.current_tab = self.tabWidget.currentIndex() - # Hide bookmark and annotation widgets + # Hide all side docks whenever a tab is switched for i in range(1, self.tabWidget.count()): self.tabWidget.widget(i).sideDock.setVisible(False) + # If library if self.tabWidget.currentIndex() == 0: - self.resizeEvent() self.start_culling_timer() + if self.settings['show_bars']: self.bookToolBar.hide() self.libraryToolBar.show() @@ -660,45 +661,34 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): self.statusBar.setVisible(True) else: - if self.settings['show_bars']: self.bookToolBar.show() self.libraryToolBar.hide() current_tab = self.tabWidget.currentWidget() - current_metadata = current_tab.metadata + self.bookToolBar.tocBox.setModel(current_tab.tocModel) + self.bookToolBar.tocTreeView.expandAll() + current_tab.set_tocBox_index(None, None) + + # Needed to set the contentView widget background + # on first run. Subsequent runs might be redundant, + # but it doesn't seem to visibly affect performance + self.profile_functions.format_contentView() + self.statusBar.setVisible(False) if self.bookToolBar.fontButton.isChecked(): self.bookToolBar.customize_view_on() - - # Disable irrelevant buttons in image view mode - if current_tab.are_we_doing_images_only: - self.bookToolBar.searchButton.setVisible(False) - self.bookToolBar.annotationButton.setVisible(False) - self.bookToolBar.bookSeparator2.setVisible(False) - self.bookToolBar.bookSeparator3.setVisible(False) else: - self.bookToolBar.searchButton.setVisible(True) - self.bookToolBar.annotationButton.setVisible(True) - self.bookToolBar.bookSeparator2.setVisible(True) - self.bookToolBar.bookSeparator3.setVisible(True) - - current_position = current_metadata['position'] - current_toc = [i[0] for i in current_metadata['content']] - - self.bookToolBar.tocBox.blockSignals(True) - self.bookToolBar.tocBox.clear() - self.bookToolBar.tocBox.addItems(current_toc) - if current_position: - self.bookToolBar.tocBox.setCurrentIndex( - current_position['current_chapter'] - 1) - if not current_metadata['images_only']: - current_tab.hiddenButton.animateClick(25) - self.bookToolBar.tocBox.blockSignals(False) - - self.profile_functions.format_contentView() - - self.statusBar.setVisible(False) + if current_tab.are_we_doing_images_only: + self.bookToolBar.searchButton.setVisible(False) + self.bookToolBar.annotationButton.setVisible(False) + self.bookToolBar.bookSeparator2.setVisible(False) + self.bookToolBar.bookSeparator3.setVisible(False) + else: + self.bookToolBar.searchButton.setVisible(True) + self.bookToolBar.annotationButton.setVisible(True) + self.bookToolBar.bookSeparator2.setVisible(True) + self.bookToolBar.bookSeparator3.setVisible(True) def tab_close(self, tab_index=None): if not tab_index: @@ -726,18 +716,15 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): self.tabWidget.setMovable(True) def set_toc_position(self, event=None): + currentIndex = self.bookToolBar.tocTreeView.currentIndex() + required_position = currentIndex.data(QtCore.Qt.UserRole) + if not required_position: + return # Initial startup might return a None + + # The set_content method is universal + # It's going to do position tracking current_tab = self.tabWidget.currentWidget() - - current_tab.metadata[ - 'position']['current_chapter'] = event + 1 - current_tab.metadata[ - 'position']['is_read'] = False - - # 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() + current_tab.set_content(required_position) def set_fullscreen(self): self.tabWidget.currentWidget().go_fullscreen() @@ -808,12 +795,11 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): # current state of each of the toolbar buttons self.settings['double_page_mode'] = self.bookToolBar.doublePageButton.isChecked() self.settings['manga_mode'] = self.bookToolBar.mangaModeButton.isChecked() - chapter_number = self.bookToolBar.tocBox.currentIndex() # Switch page to whatever index is selected in the tocBox current_tab = self.tabWidget.currentWidget() - required_content = current_tab.metadata['content'][chapter_number][1] - current_tab.contentView.loadImage(required_content) + chapter_number = current_tab.metadata['position']['current_chapter'] + current_tab.set_content(chapter_number, False) def generate_library_context_menu(self, position): index = self.sender().indexAt(position) diff --git a/lector/contentwidgets.py b/lector/contentwidgets.py index 9fe7b6b..0d6bebd 100644 --- a/lector/contentwidgets.py +++ b/lector/contentwidgets.py @@ -73,12 +73,12 @@ class PliantQGraphicsView(QtWidgets.QGraphicsView): self.generate_graphicsview_context_menu) def loadImage(self, current_page): - all_pages = [i[1] for i in self.parent.metadata['content']] + all_pages = self.parent.metadata['content'] current_page_index = all_pages.index(current_page) double_page_mode = False if (self.main_window.settings['double_page_mode'] - and (current_page_index != 0 and current_page_index != len(all_pages) - 1)): + and (current_page_index not in (0, len(all_pages) - 1))): double_page_mode = True def load_page(current_page): @@ -492,7 +492,7 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser): selected_index = self.parent.annotationListView.currentIndex() self.current_annotation = self.parent.annotationModel.data( selected_index, QtCore.Qt.UserRole) - logger.info('Selected annotation: ' + + self.current_annotation['name']) + logger.info('Selected annotation: ' + self.current_annotation['name']) def mouseReleaseEvent(self, event): # This takes care of annotation placement @@ -716,37 +716,34 @@ class PliantWidgetsCommonFunctions: self.change_chapter(-1) def change_chapter(self, direction, was_button_pressed=None): - current_toc_index = self.main_window.bookToolBar.tocBox.currentIndex() - max_toc_index = self.main_window.bookToolBar.tocBox.count() - 1 + current_tab = self.pw.parent + current_position = current_tab.metadata['position']['current_chapter'] - if (current_toc_index < max_toc_index and direction == 1) or ( - current_toc_index > 0 and direction == -1): + # Special cases for double page view + # Page limits are taken care of by the set_content method + def get_modifier(): + if (not self.main_window.settings['double_page_mode'] + or not self.are_we_doing_images_only): + return 0 - # Special cases for double page view - def get_modifier(): - if (not self.main_window.settings['double_page_mode'] - or not self.are_we_doing_images_only): - return 0 + if (current_position == 0 or current_position % 2 == 0): + return 0 - if (current_toc_index == 0 - or current_toc_index % 2 == 0 - or current_toc_index == max_toc_index): - return 0 - if current_toc_index % 2 == 1: - return direction + if current_position % 2 == 1: + return direction - self.main_window.bookToolBar.tocBox.setCurrentIndex( - current_toc_index + direction + get_modifier()) + current_tab.set_content( + current_position + direction + get_modifier(), True) - # Set page position depending on if the chapter number is increasing or decreasing - if direction == 1 or was_button_pressed: - self.pw.verticalScrollBar().setValue(0) - else: - self.pw.verticalScrollBar().setValue( - self.pw.verticalScrollBar().maximum()) + # Set page position depending on if the chapter number is increasing or decreasing + if direction == 1 or was_button_pressed: + self.pw.verticalScrollBar().setValue(0) + else: + self.pw.verticalScrollBar().setValue( + self.pw.verticalScrollBar().maximum()) - if not was_button_pressed: - self.pw.ignore_wheel_event = True + if not was_button_pressed: + self.pw.ignore_wheel_event = True def load_annotations(self, chapter): try: @@ -849,14 +846,27 @@ class PliantWidgetsCommonFunctions: def generate_combo_box_action(self, contextMenu): contextMenu.addSeparator() - tocCombobox = QtWidgets.QComboBox() - toc_data = [i[0] for i in self.pw.parent.metadata['content']] - tocCombobox.addItems(toc_data) - tocCombobox.setCurrentIndex( - self.pw.main_window.bookToolBar.tocBox.currentIndex()) - tocCombobox.currentIndexChanged.connect( - self.pw.main_window.bookToolBar.tocBox.setCurrentIndex) + def set_toc_position(tocTree): + currentIndex = tocTree.currentIndex() + required_position = currentIndex.data(QtCore.Qt.UserRole) + self.pw.parent.set_content(required_position, True) + + # Create the Combobox / Treeview combination + tocComboBox = QtWidgets.QComboBox() + tocTree = QtWidgets.QTreeView() + tocComboBox.setView(tocTree) + tocComboBox.setModel(self.pw.parent.tocModel) + tocTree.setRootIsDecorated(False) + tocTree.setItemsExpandable(False) + tocTree.expandAll() + + # Set the position of the QComboBox + self.pw.parent.set_tocBox_index(None, tocComboBox) + + # Make clicking do something + tocComboBox.currentIndexChanged.connect( + lambda: set_toc_position(tocTree)) comboboxAction = QtWidgets.QWidgetAction(self.pw) - comboboxAction.setDefaultWidget(tocCombobox) + comboboxAction.setDefaultWidget(tocComboBox) contextMenu.addAction(comboboxAction) diff --git a/lector/parsers/comicbooks.py b/lector/parsers/comicbooks.py index 6fb05df..0c0595b 100644 --- a/lector/parsers/comicbooks.py +++ b/lector/parsers/comicbooks.py @@ -83,11 +83,11 @@ class ParseCOMIC: return None def get_contents(self): - file_settings = {'images_only': True} - contents = [(f'Page {count + 1}', i) for count, i in enumerate(self.image_list)] - - return contents, file_settings + image_number = len(self.image_list) + toc = [(1, f'Page {i + 1}', i + 1) for i in range(image_number)] + # Return toc, content, images_only + return toc, self.image_list, True def is_image(filename): valid_image_extensions = ['.png', '.jpg', '.bmp'] diff --git a/lector/parsers/epub.py b/lector/parsers/epub.py index ec87a9c..c9eb96d 100644 --- a/lector/parsers/epub.py +++ b/lector/parsers/epub.py @@ -63,6 +63,12 @@ class ParseEPUB: self.book_ref.parse_toc() self.book_ref.parse_chapters(temp_dir=self.extract_path) - file_settings = { - 'images_only': False} - return self.book['book_list'], file_settings + + toc = [] + content = [] + for count, i in enumerate(self.book['book_list']): + toc.append((1, i[0], count + 1)) + content.append(i[1]) + + # Return toc, content, images_only + return toc, content, False diff --git a/lector/parsers/fb2.py b/lector/parsers/fb2.py index 32c54e1..d35067a 100644 --- a/lector/parsers/fb2.py +++ b/lector/parsers/fb2.py @@ -60,6 +60,12 @@ class ParseFB2: def get_contents(self): os.makedirs(self.extract_path, exist_ok=True) # Manual creation is required here self.book_ref.parse_chapters(temp_dir=self.extract_path) - file_settings = { - 'images_only': False} - return self.book['book_list'], file_settings + + toc = [] + content = [] + for count, i in enumerate(self.book['book_list']): + toc.append((1, i[0], count + 1)) + content.append(i[1]) + + # Return toc, content, images_only + return toc, content, False diff --git a/lector/parsers/pdf.py b/lector/parsers/pdf.py index 0265fbb..93b7aba 100644 --- a/lector/parsers/pdf.py +++ b/lector/parsers/pdf.py @@ -73,24 +73,13 @@ class ParsePDF: return tags # Fine if it returns None def get_contents(self): - # Contents are to be returned as: - # Level, Title, Page Number - # Increasing the level number means the - # title is one level up in the tree + content = list(range(self.book.pageCount)) + toc = self.book.getToC() + if not toc: + toc = [(1, f'Page {i + 1}', i + 1) for i in range(self.book.pageCount)] - # TODO - # Better parsing of TOC - # file_settings = {'images_only': True} - # contents = self.book.getToC() - # if not contents: - # contents = [ - # (1, f'Page {i + 1}', i) for i in range(self.book.pageCount)] - - # return contents, file_settings - - file_settings = {'images_only': True} - contents = [(f'Page {i + 1}', i) for i in range(self.book.pageCount)] - return contents, file_settings + # Return toc, content, images_only + return toc, content, True def render_pdf_page(page_data, for_cover=False): diff --git a/lector/sorter.py b/lector/sorter.py index 5fcf49b..cce970c 100644 --- a/lector/sorter.py +++ b/lector/sorter.py @@ -217,6 +217,8 @@ class BookSorter: return if book_ref.book: + # TODO + # For the love of God clean this up. It's junk. this_book = {} this_book[file_md5] = { @@ -246,18 +248,13 @@ class BookSorter: this_book[file_md5]['addition_mode'] = self.addition_mode if self.work_mode == 'reading': - all_content = book_ref.get_contents() + # All books must return the following list + # Indices are as described below + book_breakdown = book_ref.get_contents() - # get_contents() returns a tuple. Index 1 is a collection of - # special settings that depend on the kind of data being parsed. - # Currently, this includes: - # Only images included images_only BOOL Book contains only images - - content = all_content[0] - images_only = all_content[1]['images_only'] - - if not content: - content = [('Invalid', 'Something went horribly wrong')] + toc = book_breakdown[0] + content = book_breakdown[1] + images_only = book_breakdown[2] book_data = self.database_entry_for_book(file_md5) title = book_data[0] @@ -272,6 +269,7 @@ class BookSorter: this_book[file_md5]['position'] = position this_book[file_md5]['bookmarks'] = bookmarks + this_book[file_md5]['toc'] = toc this_book[file_md5]['content'] = content this_book[file_md5]['images_only'] = images_only this_book[file_md5]['cover'] = cover diff --git a/lector/threaded.py b/lector/threaded.py index 07fa21d..7a7559d 100644 --- a/lector/threaded.py +++ b/lector/threaded.py @@ -222,15 +222,16 @@ class BackGroundTextSearch(QtCore.QThread): # through it looking for hits for i in self.search_content: - chapter = i[0] + chapter_title = i[0] chapterDocument = QtGui.QTextDocument() chapterDocument.setHtml(i[1]) + chapter_number = i[2] findFlags = QtGui.QTextDocument.FindFlags(0) - if self.case_sensitive: - findFlags = findFlags | QtGui.QTextDocument.FindCaseSensitively if self.match_words: findFlags = findFlags | QtGui.QTextDocument.FindWholeWords + if self.case_sensitive: + findFlags = findFlags | QtGui.QTextDocument.FindCaseSensitively findResultCursor = chapterDocument.find(self.search_text, 0, findFlags) while not findResultCursor.isNull(): @@ -252,12 +253,13 @@ class BackGroundTextSearch(QtCore.QThread): surrounding_text = replace_pattern.sub( f'{self.search_text}', surrounding_text) + result_tuple = ( + result_position, surrounding_text, self.search_text, chapter_number) + try: - self.search_results[chapter].append( - (result_position, surrounding_text, self.search_text)) + self.search_results[chapter_title].append(result_tuple) except KeyError: - self.search_results[chapter] = [ - (result_position, surrounding_text, self.search_text)] + self.search_results[chapter_title] = [result_tuple] new_position = result_position + len(self.search_text) findResultCursor = chapterDocument.find( diff --git a/lector/toolbars.py b/lector/toolbars.py index 54e6107..b70d088 100644 --- a/lector/toolbars.py +++ b/lector/toolbars.py @@ -289,11 +289,15 @@ class BookToolBar(QtWidgets.QToolBar): for i in self.comicActions: i.setVisible(False) - # Sorter + # Table of contents Combo Box + # Has to have a QTreeview associated with it self.tocBox = FixedComboBox(self) - self.tocBox.setObjectName('sortingBox') self.tocBox.setToolTip( self._translate('BookToolBar', 'Table of Contents')) + self.tocTreeView = QtWidgets.QTreeView(self.tocBox) + self.tocBox.setView(self.tocTreeView) + self.tocTreeView.setItemsExpandable(False) + self.tocTreeView.setRootIsDecorated(False) # All of these will be put after the spacer # This means that the buttons in the left side of @@ -326,17 +330,16 @@ class BookToolBar(QtWidgets.QToolBar): self.customize_view_off() def customize_view_on(self): - if self.parent().tabWidget.widget( - self.parent().tabWidget.currentIndex()).metadata['images_only']: - - # The following might seem redundant, - # but it's necessary for tab switching - + images_only = self.parent().tabWidget.currentWidget().are_we_doing_images_only + # The following might seem redundant, + # but it's necessary for tab switching + if images_only: for i in self.comicActions: i.setVisible(True) for i in self.fontActions: i.setVisible(False) + else: for i in self.fontActions: i.setVisible(True) @@ -368,7 +371,6 @@ class LibraryToolBar(QtWidgets.QToolBar): self.setIconSize(QtCore.QSize(22, 22)) self.setFloatable(False) self.setContextMenuPolicy(QtCore.Qt.PreventContextMenu) - self.setObjectName("LibraryToolBar") image_factory = self.window().QImageFactory @@ -443,7 +445,6 @@ class LibraryToolBar(QtWidgets.QToolBar): self._translate('LibraryToolBar', 'Search for Title, Author, Tags...')) self.searchBar.setSizePolicy(sizePolicy) self.searchBar.setContentsMargins(0, 0, 10, 0) - self.searchBar.setObjectName('searchBar') # Sorter title_string = self._translate('LibraryToolBar', 'Title') @@ -458,8 +459,6 @@ class LibraryToolBar(QtWidgets.QToolBar): self.sortingBox = FixedComboBox(self) self.sortingBox.addItems(sorting_choices) - self.sortingBox.setObjectName('sortingBox') - self.sortingBox.setSizePolicy(sizePolicy) self.sortingBox.setMinimumContentsLength(10) self.sortingBox.setToolTip(self._translate('LibraryToolBar', 'Sort by')) @@ -479,7 +478,7 @@ class FixedComboBox(QtWidgets.QComboBox): def __init__(self, parent=None): super(FixedComboBox, self).__init__(parent) screen_width = QtWidgets.QDesktopWidget().screenGeometry().width() - self.adjusted_size = screen_width // 4.8 + self.adjusted_size = screen_width // 4.5 def sizeHint(self): # This and the one below should adjust to screen size @@ -490,7 +489,7 @@ class FixedLineEdit(QtWidgets.QLineEdit): def __init__(self, parent=None): super(FixedLineEdit, self).__init__(parent) screen_width = QtWidgets.QDesktopWidget().screenGeometry().width() - self.adjusted_size = screen_width // 4.8 + self.adjusted_size = screen_width // 4.5 def sizeHint(self): return QtCore.QSize(self.adjusted_size, 22) diff --git a/lector/widgets.py b/lector/widgets.py index 9b2796f..d5d9b9c 100644 --- a/lector/widgets.py +++ b/lector/widgets.py @@ -39,7 +39,6 @@ class Tab(QtWidgets.QWidget): self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - self.first_run = True self.main_window = main_window self.metadata = metadata # Save progress data into this dictionary self.are_we_doing_images_only = self.metadata['images_only'] @@ -51,7 +50,20 @@ class Tab(QtWidgets.QWidget): self.metadata['last_accessed'] = QtCore.QDateTime().currentDateTime() + # Create relevant containers + if not self.metadata['annotations']: + self.metadata['annotations'] = {} + if not self.metadata['bookmarks']: + self.metadata['bookmarks'] = {} + + # Generate toc Model + self.tocModel = QtGui.QStandardItemModel() + self.tocModel.setHorizontalHeaderLabels(('Table of Contents',)) + self.generate_toc_model() + + # Get the current position of the book if self.metadata['position']: + # A book might have been marked read without being opened if self.metadata['position']['is_read']: self.generate_position(True) current_chapter = self.metadata['position']['current_chapter'] @@ -59,53 +71,49 @@ class Tab(QtWidgets.QWidget): self.generate_position() current_chapter = 1 - 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, # we want a QGraphicsView widget doing all the heavy lifting # instead of a QTextBrowser - if self.are_we_doing_images_only: # Boolean + + if self.are_we_doing_images_only: self.contentView = PliantQGraphicsView( self.metadata['path'], self.main_window, self) - self.contentView.loadImage(chapter_content) - else: - self.contentView = PliantQTextBrowser(self.main_window, self) + else: + self.contentView = PliantQTextBrowser( + self.main_window, self) + self.contentView.setReadOnly(True) + + # TODO + # Change this when HTML navigation works + self.contentView.setOpenLinks(False) + + # TODO + # Rename the .css files to something else here and keep + # a record of them .Currently, I'm just removing them + # for the sake of simplicity relative_path_root = os.path.join( self.main_window.temp_dir.path(), self.metadata['hash']) relative_paths = [] for i in os.walk(relative_path_root): - - # TODO - # Rename the .css files to something else here and keep - # a record of them - # Currently, I'm just removing them for the sake of simplicity for j in i[2]: file_extension = os.path.splitext(j)[1] if file_extension == '.css': file_path = os.path.join(i[0], j) os.remove(file_path) - relative_paths.append(os.path.join(relative_path_root, i[0])) self.contentView.setSearchPaths(relative_paths) - self.contentView.setOpenLinks(False) # TODO Change this when HTML navigation works - self.contentView.setHtml(chapter_content) - self.contentView.setReadOnly(True) - self.hiddenButton = QtWidgets.QToolButton(self) self.hiddenButton.setVisible(False) self.hiddenButton.clicked.connect(self.set_cursor_position) + + # All content must be set through this function + self.set_content(current_chapter, True) + if not self.are_we_doing_images_only: + # Setting this later breaks cursor positioning for search results self.hiddenButton.animateClick(50) # Load annotations for current content @@ -414,6 +422,45 @@ class Tab(QtWidgets.QWidget): QtGui.QKeySequence('Ctrl+F'), self.contentView) ksToggleSearch.activated.connect(lambda: self.toggle_side_dock(2)) + def generate_toc_model(self): + # The toc list is: + # 0: Level + # 1: Title + # 2: Chapter content / page number + # pprint it out to get a better idea of structure + + toc = self.metadata['toc'] + parent_list = [] + for i in toc: + item = QtGui.QStandardItem() + item.setText(i[1]) + item.setData(i[2], QtCore.Qt.UserRole) + item.setData(i[1], QtCore.Qt.UserRole + 1) + + current_level = i[0] + if current_level == 1: + self.tocModel.appendRow(item) + parent_list.clear() + parent_list.append(item) + else: + parent_list[current_level - 2].appendRow(item) + try: + next_level = toc[toc.index(i) + 1][0] + if next_level > current_level: + parent_list.append(item) + + if next_level < current_level: + level_difference = current_level - next_level + parent_list = parent_list[:-level_difference] + except IndexError: + pass + + # This is needed to be able to have the toc Combobox + # jump to the correct position in the book when it is + # first opened + self.main_window.bookToolBar.tocBox.setModel(self.tocModel) + self.main_window.bookToolBar.tocTreeView.expandAll() + def go_fullscreen(self): # To allow toggles to function # properly after the fullscreening @@ -434,7 +481,7 @@ class Tab(QtWidgets.QWidget): self.main_window.hide() if not self.are_we_doing_images_only: - self.hiddenButton.animateClick(100) + self.hiddenButton.animateClick(50) self.mouse_hide_timer.start(2000) self.is_fullscreen = True @@ -472,9 +519,27 @@ class Tab(QtWidgets.QWidget): self.mouse_hide_timer.start(2000) self.contentView.setFocus() - def change_chapter_tocBox(self): - chapter_number = self.main_window.bookToolBar.tocBox.currentIndex() - required_content = self.metadata['content'][chapter_number][1] + def set_content(self, required_position, tocBox_readjust=False): + # All content changes must come through here + # This function will decide how to relate + # entries in the toc to the actual content + + # Do not allow cycling below page 1 + # Required position goes to -1 in double page view + if required_position <= 0: + return + + # Set the required page to the corresponding index + # For images this is simply a page number + # For text based books, this is the entire text of the chapter + try: + required_content = self.metadata['content'][required_position - 1] + except IndexError: + return # Do not allow cycling beyond last page + + # Update the metadata dictionary to save position + self.metadata['position']['current_chapter'] = required_position + self.metadata['position']['is_read'] = False if self.are_we_doing_images_only: self.contentView.loadImage(required_content) @@ -482,11 +547,68 @@ class Tab(QtWidgets.QWidget): self.contentView.clear() self.contentView.setHtml(required_content) - self.contentView.common_functions.load_annotations(chapter_number + 1) + # Set the contentview to look the way God intended + self.main_window.profile_functions.format_contentView() + self.contentView.common_functions.load_annotations(required_position) - def format_view(self, font, font_size, foreground, - background, padding, line_spacing, - text_alignment): + # Change the index of the tocBox. This is manual and each function + # that calls set_position must specify if it needs this adjustment + if tocBox_readjust: + self.set_tocBox_index(required_position, None) + + def set_tocBox_index(self, current_position=None, tocBox=None): + # Get current position from the metadata dictionary + # in case it isn't specified + if not current_position: + current_position = self.metadata['position']['current_chapter'] + + # Just look at the variable names. They're practically sentences. + positions_available_in_toc = [i[2] for i in self.metadata['toc']] + try: + position_reference_index = positions_available_in_toc.index( + current_position) + position_reference = positions_available_in_toc[ + position_reference_index] + + except ValueError: # No specific corresponding value was found + # Going for nearest preceding neighbor + for count, i in enumerate(positions_available_in_toc): + try: + if (positions_available_in_toc[count] < + current_position < + positions_available_in_toc[count + 1]): + position_reference = i + break + except IndexError: # Set to the last chapter + position_reference = positions_available_in_toc[-1] + + # Match the position reference to the corresponding + # index in the QTreeView / QCombobox + try: + matchingIndex = self.tocModel.match( + self.tocModel.index(0, 0), + QtCore.Qt.UserRole, + position_reference, + 2, QtCore.Qt.MatchRecursive)[0] + except IndexError: + return + + if not tocBox: + tocBox = self.main_window.bookToolBar.tocBox + + # The following sets the QCombobox index according + # to the index found above. + tocBox.blockSignals(True) + currentRootModelIndex = tocBox.rootModelIndex() + tocBox.setRootModelIndex(matchingIndex.parent()) + tocBox.setCurrentIndex(matchingIndex.row()) + tocBox.setRootModelIndex(currentRootModelIndex) + tocBox.blockSignals(False) + + def format_view( + self, font, font_size, foreground, + background, padding, line_spacing, + text_alignment): if self.are_we_doing_images_only: # Tab color does not need to be set separately in case @@ -543,14 +665,15 @@ class Tab(QtWidgets.QWidget): 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 - # TODO - # Annotation previews will require creation of a - # QStyledItemDelegate for i in saved_annotations: item = QtGui.QStandardItem() item.setText(i['name']) @@ -581,7 +704,7 @@ class Tab(QtWidgets.QWidget): description, chapter, cursor_position, identifier, True) def add_bookmark_to_model( - self, description, chapter, cursor_position, + self, description, chapter_number, cursor_position, identifier, new_bookmark=False): def edit_new_bookmark(parent_item): @@ -596,7 +719,7 @@ class Tab(QtWidgets.QWidget): bookmark = QtGui.QStandardItem() bookmark.setData(False, QtCore.Qt.UserRole + 10) # Is Parent - bookmark.setData(chapter, QtCore.Qt.UserRole) # Chapter name + 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 @@ -604,21 +727,20 @@ class Tab(QtWidgets.QWidget): for i in range(self.bookmarkModel.rowCount()): parentIndex = self.bookmarkModel.index(i, 0) parent_chapter = parentIndex.data(QtCore.Qt.UserRole) - if parent_chapter == chapter: + if parent_chapter == chapter_number: 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 - chapter_name = self.metadata['content'][chapter - 1][0] # Chapter Name + chapter_name = [i[1] for i in self.metadata['toc'] if i[2] == chapter_number][0] bookmarkParent.setData(chapter_name, QtCore.Qt.DisplayRole) - bookmarkParent.setData(chapter, QtCore.Qt.UserRole) # Chapter Number + bookmarkParent.setData(chapter_number, QtCore.Qt.UserRole) bookmarkParent.appendRow(bookmark) self.bookmarkModel.appendRow(bookmarkParent) @@ -632,13 +754,13 @@ class Tab(QtWidgets.QWidget): is_parent = self.bookmarkProxyModel.data(index, QtCore.Qt.UserRole + 10) if is_parent: chapter_number = self.bookmarkProxyModel.data(index, QtCore.Qt.UserRole) - self.main_window.bookToolBar.tocBox.setCurrentIndex(chapter_number - 1) + 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.main_window.bookToolBar.tocBox.setCurrentIndex(chapter - 1) + self.set_content(chapter, True) if not self.are_we_doing_images_only: self.set_cursor_position(cursor_position) @@ -646,13 +768,14 @@ class Tab(QtWidgets.QWidget): self.bookmarkModel = QtGui.QStandardItemModel(self) if self.main_window.settings['toc_with_bookmarks']: - for chapter_number, i in enumerate(self.metadata['content']): - chapterItem = QtGui.QStandardItem() - chapterItem.setData(i[0], QtCore.Qt.DisplayRole) # Display name - chapterItem.setData(chapter_number + 1, QtCore.Qt.UserRole) # Chapter Number - chapterItem.setData(True, QtCore.Qt.UserRole + 10) # Is Parent - chapterItem.setFlags(chapterItem.flags() & ~QtCore.Qt.ItemIsEditable) # Is Editable - self.bookmarkModel.appendRow(chapterItem) + pass + # for chapter_number, i in enumerate(self.metadata['content']): + # chapterItem = QtGui.QStandardItem() + # chapterItem.setData(i[0], QtCore.Qt.DisplayRole) # Display name + # chapterItem.setData(chapter_number + 1, QtCore.Qt.UserRole) # Chapter Number + # chapterItem.setData(True, QtCore.Qt.UserRole + 10) # Is Parent + # chapterItem.setFlags(chapterItem.flags() & ~QtCore.Qt.ItemIsEditable) # Is Editable + # self.bookmarkModel.appendRow(chapterItem) for i in self.metadata['bookmarks'].items(): description = i[1]['description'] @@ -708,10 +831,20 @@ class Tab(QtWidgets.QWidget): self.bookmarkModel.removeRow(parent_index.row()) def set_search_options(self): - search_content = ( - self.metadata['content'][self.main_window.bookToolBar.tocBox.currentIndex()],) + 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(): - search_content = self.metadata['content'] + 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, @@ -727,13 +860,11 @@ class Tab(QtWidgets.QWidget): parentItem = QtGui.QStandardItem() parentItem.setData(True, QtCore.Qt.UserRole) # Is parent? parentItem.setData(i, QtCore.Qt.UserRole + 3) # Display text for label - chapter_index = self.main_window.bookToolBar.tocBox.findText( - i, QtCore.Qt.MatchExactly) for j in search_results[i]: childItem = QtGui.QStandardItem(parentItem) childItem.setData(False, QtCore.Qt.UserRole) # Is parent? - childItem.setData(chapter_index, QtCore.Qt.UserRole + 1) # Chapter index + 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 @@ -743,6 +874,12 @@ class Tab(QtWidgets.QWidget): 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;}") @@ -773,11 +910,11 @@ class Tab(QtWidgets.QWidget): if is_parent: return - chapter_index = self.searchResultsModel.data(index, QtCore.Qt.UserRole + 1) + 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.main_window.bookToolBar.tocBox.setCurrentIndex(chapter_index) + self.set_content(chapter_number, True) if not self.are_we_doing_images_only: self.set_cursor_position( cursor_position, len(search_term))