class Tab(QtWidgets.QWidget): def __init__(self, metadata, main_window, parent=None): super(Tab, self).__init__(parent) self._translate = QtCore.QCoreApplication.translate 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'] self.is_fullscreen = False self.masterLayout = QtWidgets.QHBoxLayout(self) self.masterLayout.setContentsMargins(0, 0, 0, 0) self.metadata['last_accessed'] = QtCore.QDateTime().currentDateTime() if self.metadata['position']: if self.metadata['position']['is_read']: self.generate_position(True) current_chapter = self.metadata['position']['current_chapter'] else: 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 self.contentView = PliantQGraphicsView(self.metadata['path'], self.main_window, self) self.contentView.loadImage(chapter_content) else: self.contentView = PliantQTextBrowser(self.main_window, self) 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) 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) self.contentView.setObjectName('contentView') self.contentView.verticalScrollBar().setSingleStep( self.main_window.settings['scroll_speed']) if self.main_window.settings['hide_scrollbars']: self.contentView.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarAlwaysOff) self.contentView.setVerticalScrollBarPolicy( QtCore.Qt.ScrollBarAlwaysOff) else: self.contentView.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarAsNeeded) self.contentView.setVerticalScrollBarPolicy( QtCore.Qt.ScrollBarAsNeeded) # Create a common dock for annotations and bookmarks # And add a vertical layout to it for requisite widgets self.sideDock = PliantDockWidget(self.main_window, False, self.contentView) self.sideDock.setFeatures(QtWidgets.QDockWidget.DockWidgetClosable) self.sideDock.setTitleBarWidget(QtWidgets.QWidget()) self.sideDockTabWidget = QtWidgets.QTabWidget() self.sideDock.setWidget(self.sideDockTabWidget) # Annotation list view and model self.annotationListView = QtWidgets.QListView(self.sideDock) self.annotationListView.setResizeMode(QtWidgets.QListWidget.Adjust) self.annotationListView.setMaximumWidth(350) self.annotationListView.doubleClicked.connect( self.contentView.toggle_annotation_mode) self.annotationListView.setEditTriggers( QtWidgets.QListView.NoEditTriggers) annotations_string = self._translate('Tab', 'Annotations') self.sideDockTabWidget.addTab(self.annotationListView, annotations_string) self.annotationModel = QtGui.QStandardItemModel(self) self.generate_annotation_model() # Bookmark tree view and model self.bookmarkTreeView = QtWidgets.QTreeView(self.sideDock) self.bookmarkTreeView.setHeaderHidden(True) self.bookmarkTreeView.setMaximumWidth(350) self.bookmarkTreeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.bookmarkTreeView.customContextMenuRequested.connect( self.generate_bookmark_context_menu) self.bookmarkTreeView.clicked.connect(self.navigate_to_bookmark) bookmarks_string = self._translate('Tab', 'Bookmarks') self.sideDockTabWidget.addTab(self.bookmarkTreeView, bookmarks_string) self.bookmarkModel = QtGui.QStandardItemModel(self) self.bookmarkProxyModel = BookmarkProxyModel(self) self.generate_bookmark_model() # Create the annotation notes dock self.annotationNoteDock = PliantDockWidget(self.main_window, True, self.contentView) self.annotationNoteDock.setWindowTitle(self._translate('Tab', 'Note')) self.annotationNoteDock.setFeatures( QtWidgets.QDockWidget.DockWidgetClosable) self.annotationNoteDock.hide() self.annotationNoteEdit = QtWidgets.QTextEdit(self.annotationNoteDock) self.annotationNoteEdit.setMaximumSize(QtCore.QSize(250, 250)) self.annotationNoteEdit.setFocusPolicy(QtCore.Qt.StrongFocus) self.annotationNoteDock.setWidget(self.annotationNoteEdit) self.generate_keyboard_shortcuts() self.masterLayout.addWidget(self.contentView) self.masterLayout.addWidget(self.sideDock) self.masterLayout.addWidget(self.annotationNoteDock) # The following has to be after the docks are added to the layout self.sideDock.setFloating(True) self.sideDock.setWindowOpacity(.95) self.annotationNoteDock.setFloating(True) self.annotationNoteDock.setWindowOpacity(.95) title = self.metadata['title'] if self.main_window.settings['attenuate_titles'] and len(title) > 30: title = title[:30] + '...' self.main_window.tabWidget.addTab(self, title) 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) self.mouse_hide_timer.timeout.connect(self.hide_mouse) # Hide the tab bar in case distraction free mode is active if not self.main_window.settings['show_bars']: self.main_window.tabWidget.tabBar().setVisible(False) self.contentView.setFocus() def toggle_side_dock(self, tab_required=1): if (self.sideDock.isVisible() and self.sideDockTabWidget.currentIndex() == tab_required): self.sideDock.hide() elif not self.sideDock.isVisible(): self.sideDock.show() if tab_required == 0: self.sideDockTabWidget.setCurrentIndex(0) else: # Takes care of the action menu as well self.sideDockTabWidget.setCurrentIndex(1) def update_last_accessed_time(self): self.metadata['last_accessed'] = QtCore.QDateTime().currentDateTime() start_index = self.main_window.lib_ref.libraryModel.index(0, 0) matching_item = self.main_window.lib_ref.libraryModel.match( start_index, QtCore.Qt.UserRole + 6, self.metadata['hash'], 1, QtCore.Qt.MatchExactly) try: self.main_window.lib_ref.libraryModel.setData( matching_item[0], self.metadata['last_accessed'], QtCore.Qt.UserRole + 12) except IndexError: # The file has been deleted pass def set_cursor_position(self, cursor_position=None): try: required_position = self.metadata['position']['cursor_position'] except KeyError: print('Database: Cursor position error. Recommend retry.') return 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 if is_read: current_chapter = total_chapters # Generate block count @ time of first read # Blocks are indexed from 0 up blocks_per_chapter = [] total_blocks = 0 if not self.are_we_doing_images_only: for i in self.metadata['content']: chapter_html = i[1] textDocument = QtGui.QTextDocument(None) textDocument.setHtml(chapter_html) block_count = textDocument.blockCount() blocks_per_chapter.append(block_count) total_blocks += block_count self.metadata['position'] = { 'current_chapter': current_chapter, 'total_chapters': total_chapters, 'blocks_per_chapter': blocks_per_chapter, 'total_blocks': total_blocks, 'is_read': is_read, 'current_block': 0, 'cursor_position': 0 } def generate_keyboard_shortcuts(self): self.ksNextChapter = QtWidgets.QShortcut(QtGui.QKeySequence('Right'), self.contentView) self.ksNextChapter.setObjectName('nextChapter') self.ksNextChapter.activated.connect(self.sneaky_change) self.ksPrevChapter = QtWidgets.QShortcut(QtGui.QKeySequence('Left'), self.contentView) self.ksPrevChapter.setObjectName('prevChapter') self.ksPrevChapter.activated.connect(self.sneaky_change) self.ksGoFullscreen = QtWidgets.QShortcut(QtGui.QKeySequence('F11'), self.contentView) self.ksGoFullscreen.activated.connect(self.go_fullscreen) self.ksExitFullscreen = QtWidgets.QShortcut( QtGui.QKeySequence('Escape'), self.contentView) self.ksExitFullscreen.setContext(QtCore.Qt.ApplicationShortcut) self.ksExitFullscreen.activated.connect(self.exit_fullscreen) self.ksToggleBookMarks = QtWidgets.QShortcut( QtGui.QKeySequence('Ctrl+B'), self.contentView) self.ksToggleBookMarks.activated.connect(self.toggle_side_dock) def go_fullscreen(self): # To allow toggles to function # properly after the fullscreening self.sideDock.hide() self.annotationNoteDock.hide() if self.contentView.windowState() == QtCore.Qt.WindowFullScreen: self.exit_fullscreen() return if not self.are_we_doing_images_only: self.contentView.record_position() self.contentView.setWindowFlags(QtCore.Qt.Window) self.contentView.setWindowState(QtCore.Qt.WindowFullScreen) self.contentView.show() self.main_window.hide() if not self.are_we_doing_images_only: self.hiddenButton.animateClick(100) self.is_fullscreen = True def exit_fullscreen(self): # Intercept escape presses for i in (self.annotationNoteDock, self.sideDock): if i.isVisible(): i.setVisible(False) return # Prevents cursor position change on escape presses if self.main_window.isVisible(): return if not self.are_we_doing_images_only: self.contentView.record_position() self.main_window.show() self.contentView.setWindowFlags(QtCore.Qt.Widget) self.contentView.setWindowState(QtCore.Qt.WindowNoState) self.contentView.show() self.is_fullscreen = False if not self.are_we_doing_images_only: self.hiddenButton.animateClick(100) # Hide the view modification buttons in case they're visible self.main_window.bookToolBar.customize_view_off() # Exit distraction free mode too 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] if self.are_we_doing_images_only: self.contentView.loadImage(required_content) else: 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): if self.are_we_doing_images_only: # Tab color does not need to be set separately in case # no padding is set for the viewport of a QGraphicsView # and image resizing in done in the pixmap my_qbrush = QtGui.QBrush(QtCore.Qt.SolidPattern) my_qbrush.setColor(background) self.contentView.setBackgroundBrush(my_qbrush) self.contentView.resizeEvent() else: self.contentView.setStyleSheet( "QTextEdit {{font-family: {0}; font-size: {1}px; color: {2}; background-color: {3}}}" .format(font, font_size, foreground.name(), background.name())) # Line spacing # Set line spacing per a block format # This is proportional line spacing so assume a divisor of 100 block_format = QtGui.QTextBlockFormat() block_format.setLineHeight( line_spacing, QtGui.QTextBlockFormat.ProportionalHeight) block_format.setTextIndent(50) # Give options for alignment alignment_dict = { 'left': QtCore.Qt.AlignLeft, 'right': QtCore.Qt.AlignRight, 'center': QtCore.Qt.AlignCenter, 'justify': QtCore.Qt.AlignJustify } current_index = self.main_window.bookToolBar.tocBox.currentIndex() if current_index == 0: block_format.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignHCenter) else: block_format.setAlignment(alignment_dict[text_alignment]) # Also for padding # Using setViewPortMargins for this disables scrolling in the margins block_format.setLeftMargin(padding) block_format.setRightMargin(padding) this_cursor = self.contentView.textCursor() this_cursor.movePosition(QtGui.QTextCursor.Start, 0, 1) # Iterate over the entire document block by block # The document ends when the cursor position can no longer be incremented while True: old_position = this_cursor.position() this_cursor.mergeBlockFormat(block_format) this_cursor.movePosition(QtGui.QTextCursor.NextBlock, 0, 1) new_position = this_cursor.position() if old_position == new_position: break def generate_annotation_model(self): saved_annotations = self.main_window.settings['annotations'] if not saved_annotations: return def add_to_model(annotation): item = QtGui.QStandardItem() 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 add_bookmark(self): 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) self.metadata['bookmarks'][identifier] = { 'chapter': chapter, 'cursor_position': cursor_position, 'description': description } self.sideDock.setVisible(True) self.add_bookmark_to_model(description, chapter, cursor_position, identifier, True) def add_bookmark_to_model(self, description, chapter, 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) bookmark = QtGui.QStandardItem() bookmark.setData(False, QtCore.Qt.UserRole + 10) # Is Parent bookmark.setData(chapter, QtCore.Qt.UserRole) # Chapter name 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 for i in range(self.bookmarkModel.rowCount()): parentIndex = self.bookmarkModel.index(i, 0) parent_chapter = parentIndex.data(QtCore.Qt.UserRole) if parent_chapter == chapter: 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 bookmarkParent.setData(chapter_name, QtCore.Qt.DisplayRole) bookmarkParent.setData(chapter, QtCore.Qt.UserRole) # Chapter Number 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.main_window.bookToolBar.tocBox.setCurrentIndex( chapter_number - 1) 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) if not self.are_we_doing_images_only: self.set_cursor_position(cursor_position) def generate_bookmark_model(self): 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) 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 update_bookmark_proxy_model(self): # TODO # This isn't being called currently # See if there's any rationale for keeping it / removing it self.bookmarkProxyModel.invalidateFilter() self.bookmarkProxyModel.setFilterParams( self.main_window.bookToolBar.searchBar.text()) self.bookmarkProxyModel.setFilterFixedString( self.main_window.bookToolBar.searchBar.text()) 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 hide_mouse(self): self.contentView.viewport().setCursor(QtCore.Qt.BlankCursor) def sneaky_change(self): direction = -1 if self.sender().objectName() == 'nextChapter': direction = 1 self.contentView.common_functions.change_chapter(direction, True) def sneaky_exit(self): self.contentView.hide() self.main_window.closeEvent()
class Tab(QtWidgets.QWidget): def __init__(self, metadata, parent=None): super(Tab, self).__init__(parent) self._translate = QtCore.QCoreApplication.translate self.parent = parent self.metadata = metadata # Save progress data into this dictionary self.are_we_doing_images_only = self.metadata['images_only'] self.main_window = self.window() self.masterLayout = QtWidgets.QHBoxLayout(self) self.horzLayout = QtWidgets.QSplitter(self) self.horzLayout.setOrientation(QtCore.Qt.Horizontal) self.masterLayout.addWidget(self.horzLayout) self.metadata['last_accessed'] = QtCore.QDateTime().currentDateTime() if self.metadata['position']: if self.metadata['position']['is_read']: self.generate_position(True) current_chapter = self.metadata['position']['current_chapter'] else: self.generate_position() current_chapter = 1 chapter_content = self.metadata['content'][current_chapter - 1][1] # 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 self.contentView = PliantQGraphicsView(self.metadata['path'], self.main_window, self) self.contentView.loadImage(chapter_content) else: self.contentView = PliantQTextBrowser(self.main_window, self) 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) tempHiddenButton = QtWidgets.QToolButton(self) tempHiddenButton.setVisible(False) tempHiddenButton.clicked.connect(self.set_scroll_value) tempHiddenButton.animateClick(100) # The following are common to both the text browser and # the graphics view self.contentView.setFrameShape(QtWidgets.QFrame.NoFrame) self.contentView.setObjectName('contentView') self.contentView.verticalScrollBar().setSingleStep(7) self.contentView.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarAsNeeded) # See bookmark availability if not self.metadata['bookmarks']: self.metadata['bookmarks'] = {} # Create the dock widget for context specific display self.dockWidget = PliantDockWidget(self) self.dockWidget.setWindowTitle(self._translate('Tab', 'Bookmarks')) self.dockWidget.setFeatures(QtWidgets.QDockWidget.DockWidgetClosable) self.dockWidget.setFloating(False) self.dockWidget.hide() self.dockListView = QtWidgets.QListView(self.dockWidget) self.dockListView.setResizeMode(QtWidgets.QListWidget.Adjust) self.dockListView.setMaximumWidth(350) self.dockListView.setItemDelegate(BookmarkDelegate(self.dockListView)) self.dockListView.setUniformItemSizes(True) self.dockListView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.dockListView.customContextMenuRequested.connect( self.generate_bookmark_context_menu) self.dockListView.clicked.connect(self.navigate_to_bookmark) self.dockWidget.setWidget(self.dockListView) self.bookmark_model = QtGui.QStandardItemModel(self) self.proxy_model = BookmarkProxyModel(self) self.generate_bookmark_model() self.generate_keyboard_shortcuts() self.horzLayout.addWidget(self.contentView) self.horzLayout.addWidget(self.dockWidget) title = self.metadata['title'] self.parent.addTab(self, title) # Hide mouse cursor timer self.mouse_hide_timer = QtCore.QTimer() self.mouse_hide_timer.setSingleShot(True) self.mouse_hide_timer.timeout.connect(self.hide_mouse) self.contentView.setFocus() def update_last_accessed_time(self): self.metadata['last_accessed'] = QtCore.QDateTime().currentDateTime() start_index = self.main_window.lib_ref.view_model.index(0, 0) matching_item = self.main_window.lib_ref.view_model.match( start_index, QtCore.Qt.UserRole + 6, self.metadata['hash'], 1, QtCore.Qt.MatchExactly) try: self.main_window.lib_ref.view_model.setData( matching_item[0], self.metadata['last_accessed'], QtCore.Qt.UserRole + 12) except IndexError: # The file has been deleted pass def set_scroll_value(self, switch_widgets=True, search_data=None): # TODO # Bookmark navigation does not work in case 2 entries in the same # chapter are clicked successively # It plain refuses to work other times if self.sender().objectName() == 'tabWidget': return if switch_widgets: previous_widget = self.main_window.tabWidget.currentWidget() self.main_window.tabWidget.setCurrentWidget(self) scroll_value = self.metadata['position']['scroll_value'] if search_data: scroll_value = search_data[0] # Scroll a little ahead # This avoids confusion with potentially duplicate phrases # And the found result is at the top of the window scroll_position = scroll_value * self.contentView.verticalScrollBar( ).maximum() self.contentView.verticalScrollBar().setValue(scroll_position * 1.02) try: search_text = self.metadata['position']['last_visible_text'] if search_data: search_text = search_data[1] if search_text: find_backward = False find_forward = self.contentView.find(search_text) if not find_forward: find_backward = self.contentView.find( search_text, QtGui.QTextDocument.FindBackward) if find_backward: current_scroll_position = self.contentView.verticalScrollBar( ).value() new_scroll_position = current_scroll_position * .98 self.contentView.verticalScrollBar().setValue( new_scroll_position) text_cursor = self.contentView.textCursor() text_cursor.clearSelection() self.contentView.setTextCursor(text_cursor) except KeyError: pass if switch_widgets: self.main_window.tabWidget.setCurrentWidget(previous_widget) 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 blocks_per_chapter = [] total_blocks = 0 if not self.are_we_doing_images_only: for i in self.metadata['content']: chapter_html = i[1] textDocument = QtGui.QTextDocument(None) textDocument.setHtml(chapter_html) block_count = textDocument.blockCount() blocks_per_chapter.append(block_count) total_blocks += block_count self.metadata['position'] = { 'current_chapter': current_chapter, 'total_chapters': total_chapters, 'blocks_per_chapter': blocks_per_chapter, 'total_blocks': total_blocks, 'scroll_value': scroll_value, 'last_visible_text': None, 'is_read': is_read } def generate_keyboard_shortcuts(self): self.next_chapter = QtWidgets.QShortcut(QtGui.QKeySequence('Right'), self.contentView) self.next_chapter.setObjectName('nextChapter') self.next_chapter.activated.connect(self.sneaky_change) self.prev_chapter = QtWidgets.QShortcut(QtGui.QKeySequence('Left'), self.contentView) self.prev_chapter.setObjectName('prevChapter') self.prev_chapter.activated.connect(self.sneaky_change) self.go_fs = QtWidgets.QShortcut(QtGui.QKeySequence('F11'), self.contentView) self.go_fs.activated.connect(self.go_fullscreen) self.exit_fs = QtWidgets.QShortcut(QtGui.QKeySequence('Escape'), self.contentView) self.exit_fs.setContext(QtCore.Qt.ApplicationShortcut) self.exit_fs.activated.connect(self.exit_fullscreen) def go_fullscreen(self): if self.contentView.windowState() == QtCore.Qt.WindowFullScreen: self.exit_fullscreen() return self.contentView.setWindowFlags(QtCore.Qt.Window) self.contentView.setWindowState(QtCore.Qt.WindowFullScreen) self.contentView.show() self.main_window.hide() def exit_fullscreen(self): self.main_window.show() self.contentView.setWindowFlags(QtCore.Qt.Widget) self.contentView.setWindowState(QtCore.Qt.WindowNoState) self.contentView.show() # Hide the view modification buttons in case they're visible self.main_window.bookToolBar.customize_view_off() def change_chapter_tocBox(self): chapter_number = self.main_window.bookToolBar.tocBox.currentIndex() required_content = self.metadata['content'][chapter_number][1] if self.are_we_doing_images_only: self.contentView.loadImage(required_content) else: self.contentView.clear() self.contentView.setHtml(required_content) 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 # no padding is set for the viewport of a QGraphicsView # and image resizing in done in the pixmap my_qbrush = QtGui.QBrush(QtCore.Qt.SolidPattern) my_qbrush.setColor(background) self.contentView.setBackgroundBrush(my_qbrush) self.contentView.resizeEvent() else: self.contentView.setStyleSheet( "QTextEdit {{font-family: {0}; font-size: {1}px; color: {2}; background-color: {3}}}" .format(font, font_size, foreground.name(), background.name())) # Line spacing # Set line spacing per a block format # This is proportional line spacing so assume a divisor of 100 block_format = QtGui.QTextBlockFormat() block_format.setLineHeight( line_spacing, QtGui.QTextBlockFormat.ProportionalHeight) block_format.setTextIndent(50) # Give options for alignment alignment_dict = { 'left': QtCore.Qt.AlignLeft, 'right': QtCore.Qt.AlignRight, 'center': QtCore.Qt.AlignCenter, 'justify': QtCore.Qt.AlignJustify } current_index = self.main_window.bookToolBar.tocBox.currentIndex() if current_index == 0: block_format.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignHCenter) else: block_format.setAlignment(alignment_dict[text_alignment]) # Also for padding # Using setViewPortMargins for this disables scrolling in the margins block_format.setLeftMargin(padding) block_format.setRightMargin(padding) this_cursor = self.contentView.textCursor() this_cursor.movePosition(QtGui.QTextCursor.Start, 0, 1) # Iterate over the entire document block by block # The document ends when the cursor position can no longer be incremented while True: old_position = this_cursor.position() this_cursor.mergeBlockFormat(block_format) this_cursor.movePosition(QtGui.QTextCursor.NextBlock, 0, 1) new_position = this_cursor.position() if old_position == new_position: break def toggle_bookmarks(self): if self.dockWidget.isVisible(): self.dockWidget.hide() else: self.dockWidget.show() def add_bookmark(self): # TODO # Start dockListView.edit(index) when something new is added identifier = uuid.uuid4().hex[:10] description = self._translate('Tab', 'New bookmark') if self.are_we_doing_images_only: chapter = self.metadata['position']['current_chapter'] search_data = (0, None) else: chapter, scroll_position, visible_text = self.contentView.record_scroll_position( True) search_data = (scroll_position, visible_text) self.metadata['bookmarks'][identifier] = { 'chapter': chapter, 'search_data': search_data, 'description': description } self.add_bookmark_to_model(description, chapter, search_data, identifier) self.dockWidget.setVisible(True) def add_bookmark_to_model(self, description, chapter, search_data, 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(identifier, QtCore.Qt.UserRole + 2) self.bookmark_model.appendRow(bookmark) self.update_bookmark_proxy_model() def navigate_to_bookmark(self, index): if not index.isValid(): return chapter = self.proxy_model.data(index, QtCore.Qt.UserRole) search_data = 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_scroll_value(False, search_data) 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]) self.generate_bookmark_proxy_model() def generate_bookmark_proxy_model(self): self.proxy_model.setSourceModel(self.bookmark_model) self.proxy_model.setSortCaseSensitivity(False) self.proxy_model.setSortRole(QtCore.Qt.UserRole) self.dockListView.setModel(self.proxy_model) def update_bookmark_proxy_model(self): self.proxy_model.invalidateFilter() self.proxy_model.setFilterParams( self.main_window.bookToolBar.searchBar.text()) self.proxy_model.setFilterFixedString( self.main_window.bookToolBar.searchBar.text()) def generate_bookmark_context_menu(self, position): index = self.dockListView.indexAt(position) if not index.isValid(): return bookmark_menu = QtWidgets.QMenu() editAction = bookmark_menu.addAction( self.main_window.QImageFactory.get_image('edit-rename'), self._translate('Tab', 'Edit')) deleteAction = bookmark_menu.addAction( self.main_window.QImageFactory.get_image('trash-empty'), self._translate('Tab', 'Delete')) action = bookmark_menu.exec_(self.dockListView.mapToGlobal(position)) if action == editAction: self.dockListView.edit(index) if action == deleteAction: row = index.row() delete_uuid = self.bookmark_model.item(row).data( QtCore.Qt.UserRole + 2) self.metadata['bookmarks'].pop(delete_uuid) self.bookmark_model.removeRow(index.row()) def hide_mouse(self): self.contentView.viewport().setCursor(QtCore.Qt.BlankCursor) def sneaky_change(self): direction = -1 if self.sender().objectName() == 'nextChapter': direction = 1 self.contentView.common_functions.change_chapter(direction, True) def sneaky_exit(self): self.contentView.hide() self.main_window.closeEvent()