class PliantQGraphicsView(QtWidgets.QGraphicsView): def __init__(self, filepath, main_window, parent=None): super(PliantQGraphicsView, self).__init__(parent) self._translate = QtCore.QCoreApplication.translate self.parent = parent self.main_window = main_window self.image_pixmap = None self.image_cache = [None for _ in range(4)] self.thread = None self.annotation_dict = self.parent.metadata['annotations'] self.filepath = filepath self.filetype = os.path.splitext(self.filepath)[1][1:] if self.filetype == 'cbz': self.book = zipfile.ZipFile(self.filepath) elif self.filetype == 'cbr': self.book = rarfile.RarFile(self.filepath) elif self.filetype == 'pdf': self.book = fitz.open(self.filepath) self.common_functions = PliantWidgetsCommonFunctions( self, self.main_window) self.ignore_wheel_event = False self.ignore_wheel_event_number = 0 self.setMouseTracking(True) self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag) self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.customContextMenuRequested.connect( self.generate_graphicsview_context_menu) def loadImage(self, current_page): 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 not in (0, len(all_pages) - 1))): double_page_mode = True def load_page(current_page): def page_loader(page): pixmap = QtGui.QPixmap() if self.filetype in ('cbz', 'cbr'): page_data = self.book.read(page) pixmap.loadFromData(page_data) elif self.filetype == 'pdf': page_data = self.book.loadPage(page) pixmap = render_pdf_page(page_data) return pixmap firstPixmap = page_loader(current_page) if not double_page_mode: return firstPixmap next_page = all_pages[current_page_index + 1] secondPixmap = page_loader(next_page) # Pixmap height should be the greater of the 2 images pixmap_height = firstPixmap.height() if secondPixmap.height() > pixmap_height: pixmap_height = secondPixmap.height() bigPixmap = QtGui.QPixmap( firstPixmap.width() + secondPixmap.width() + 5, pixmap_height) bigPixmap.fill(QtCore.Qt.transparent) imagePainter = QtGui.QPainter(bigPixmap) manga_mode = self.main_window.settings['manga_mode'] if manga_mode: imagePainter.drawPixmap(0, 0, secondPixmap) imagePainter.drawPixmap(secondPixmap.width() + 4, 0, firstPixmap) else: imagePainter.drawPixmap(0, 0, firstPixmap) imagePainter.drawPixmap(firstPixmap.width() + 4, 0, secondPixmap) imagePainter.end() return bigPixmap def generate_image_cache(current_page): logger.info('(Re)building image cache') current_page_index = all_pages.index(current_page) # Image caching for single and double page views page_indices = (-1, 0, 1, 2) index_modifier = 0 if double_page_mode: index_modifier = 1 for i in page_indices: try: this_page = all_pages[current_page_index + i + index_modifier] this_pixmap = load_page(this_page) self.image_cache[i + 1] = (this_page, this_pixmap) except IndexError: self.image_cache[i + 1] = None def refill_cache(remove_value): # Do NOT put a parent in here or the mother of all # memory leaks will result self.thread = BackGroundCacheRefill(self.image_cache, remove_value, self.filetype, self.book, all_pages) self.thread.finished.connect(overwrite_cache) self.thread.start() def overwrite_cache(): self.image_cache = self.thread.image_cache def check_cache(current_page): for i in self.image_cache: if i: if i[0] == current_page: return_pixmap = i[1] refill_cache(i) return return_pixmap # No return happened so the image isn't in the cache generate_image_cache(current_page) # TODO # Get caching working for double page view if not double_page_mode and self.main_window.settings[ 'caching_enabled']: return_pixmap = None while not return_pixmap: return_pixmap = check_cache(current_page) else: return_pixmap = load_page(current_page) self.image_pixmap = return_pixmap self.resizeEvent() def resizeEvent(self, *args): if not self.image_pixmap: return zoom_mode = self.main_window.comic_profile['zoom_mode'] padding = self.main_window.comic_profile['padding'] if zoom_mode == 'fitWidth': available_width = self.viewport().width() image_pixmap = self.image_pixmap.scaledToWidth( available_width, QtCore.Qt.SmoothTransformation) elif zoom_mode == 'originalSize': image_pixmap = self.image_pixmap new_padding = (self.viewport().width() - image_pixmap.width()) // 2 if new_padding < 0: # The image is larger than the viewport self.main_window.comic_profile['padding'] = 0 else: self.main_window.comic_profile['padding'] = new_padding elif zoom_mode == 'bestFit': available_width = self.viewport().width() available_height = self.viewport().height() image_pixmap = self.image_pixmap.scaled( available_width, available_height, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) self.main_window.comic_profile['padding'] = ( self.viewport().width() - image_pixmap.width()) // 2 elif zoom_mode == 'manualZoom': available_width = self.viewport().width() - 2 * padding image_pixmap = self.image_pixmap.scaledToWidth( available_width, QtCore.Qt.SmoothTransformation) graphicsScene = QtWidgets.QGraphicsScene() graphicsScene.addPixmap(image_pixmap) self.setScene(graphicsScene) self.show() # This prevents a partial page scroll on first load self.verticalScrollBar().setValue(0) def wheelEvent(self, event): self.common_functions.wheelEvent(event) def keyPressEvent(self, event): vertical = self.verticalScrollBar().value() maximum = self.verticalScrollBar().maximum() def scroller(increment, move_forward=True): if move_forward: if vertical == maximum: self.common_functions.change_chapter(1, True) else: next_val = vertical + increment if next_val >= .95 * maximum: next_val = maximum self.verticalScrollBar().setValue(next_val) else: if vertical == 0: self.common_functions.change_chapter(-1, False) else: next_val = vertical - increment if next_val <= .05 * maximum: next_val = 0 self.verticalScrollBar().setValue(next_val) small_increment = maximum // self.main_window.settings[ 'small_increment'] big_increment = maximum // self.main_window.settings['large_increment'] # Scrolling if event.key() == QtCore.Qt.Key_Up: scroller(small_increment, False) if event.key() == QtCore.Qt.Key_Down: scroller(small_increment) if event.key() == QtCore.Qt.Key_Space: scroller(big_increment) # Double page mode and manga mode if event.key() in (QtCore.Qt.Key_D, QtCore.Qt.Key_M): self.main_window.change_page_view(event.key()) # Image fit modes view_modification_keys = (QtCore.Qt.Key_Plus, QtCore.Qt.Key_Minus, QtCore.Qt.Key_Equal, QtCore.Qt.Key_B, QtCore.Qt.Key_W, QtCore.Qt.Key_O) 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, event): if QtWidgets.QApplication.mouseButtons() == QtCore.Qt.NoButton: self.viewport().setCursor(QtCore.Qt.OpenHandCursor) else: self.viewport().setCursor(QtCore.Qt.ClosedHandCursor) self.parent.mouse_hide_timer.start(2000) QtWidgets.QGraphicsView.mouseMoveEvent(self, event) def generate_graphicsview_context_menu(self, position): contextMenu = QtWidgets.QMenu() fsToggleAction = dfToggleAction = 'Caesar si viveret, ad remum dareris' if self.parent.is_fullscreen: fsToggleAction = contextMenu.addAction( self.main_window.QImageFactory.get_image('view-fullscreen'), self._translate('PliantQGraphicsView', 'Exit fullscreen')) elif not self.main_window.settings['show_bars']: distraction_free_prompt = self._translate( 'PliantQGraphicsView', 'Exit Distraction Free mode') dfToggleAction = contextMenu.addAction( self.main_window.QImageFactory.get_image('visibility'), distraction_free_prompt) saveAction = contextMenu.addAction( self.main_window.QImageFactory.get_image('filesaveas'), self._translate('PliantQGraphicsView', 'Save page as...')) view_submenu_string = self._translate('PliantQGraphicsView', 'View') viewSubMenu = contextMenu.addMenu(view_submenu_string) viewSubMenu.setIcon( self.main_window.QImageFactory.get_image('mail-thread-watch')) doublePageAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('page-double'), self._translate('PliantQGraphicsView', 'Double page mode (D)')) doublePageAction.setCheckable(True) doublePageAction.setChecked( self.main_window.bookToolBar.doublePageButton.isChecked()) mangaModeAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('manga-mode'), self._translate('PliantQGraphicsView', 'Manga mode (M)')) mangaModeAction.setCheckable(True) mangaModeAction.setChecked( self.main_window.bookToolBar.mangaModeButton.isChecked()) viewSubMenu.addSeparator() zoominAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('zoom-in'), self._translate('PliantQGraphicsView', 'Zoom in (+)')) zoomoutAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('zoom-out'), self._translate('PliantQGraphicsView', 'Zoom out (-)')) fitWidthAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('zoom-fit-width'), self._translate('PliantQGraphicsView', 'Fit width (W)')) bestFitAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('zoom-fit-best'), self._translate('PliantQGraphicsView', 'Best fit (B)')) originalSizeAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('zoom-original'), self._translate('PliantQGraphicsView', 'Original size (O)')) bookmarksToggleAction = 'Latin quote 2. Electric Boogaloo.' if not self.main_window.settings[ 'show_bars'] or self.parent.is_fullscreen: bookmarksToggleAction = contextMenu.addAction( self.main_window.QImageFactory.get_image('bookmarks'), self._translate('PliantQGraphicsView', 'Bookmarks')) self.common_functions.generate_combo_box_action(contextMenu) action = contextMenu.exec_(self.sender().mapToGlobal(position)) if action == doublePageAction: self.main_window.bookToolBar.doublePageButton.trigger() if action == mangaModeAction: self.main_window.bookToolBar.mangaModeButton.trigger() if action == saveAction: dialog_prompt = self._translate('Main_UI', 'Save page as...') extension_string = self._translate('Main_UI', 'Images') save_file = QtWidgets.QFileDialog.getSaveFileName( self, dialog_prompt, self.main_window.settings['last_open_path'], f'{extension_string} (*.png *.jpg *.bmp)') if save_file: self.image_pixmap.save(save_file[0]) if action == bookmarksToggleAction: self.parent.toggle_side_dock(1) if action == dfToggleAction: self.main_window.toggle_distraction_free() if action == fsToggleAction: self.parent.exit_fullscreen() view_action_dict = { zoominAction: QtCore.Qt.Key_Plus, zoomoutAction: QtCore.Qt.Key_Minus, fitWidthAction: QtCore.Qt.Key_W, bestFitAction: QtCore.Qt.Key_B, originalSizeAction: QtCore.Qt.Key_O } if action in view_action_dict: self.main_window.modify_comic_view(view_action_dict[action]) def closeEvent(self, *args): # In case the program is closed when a contentView is fullscreened self.main_window.closeEvent() def toggle_annotation_mode(self): # The graphics view doesn't currently have annotation functionality # Don't delete this because it's still called pass
class PliantQGraphicsView(QtWidgets.QGraphicsView): def __init__(self, filepath, main_window, parent=None): super(PliantQGraphicsView, self).__init__(parent) self._translate = QtCore.QCoreApplication.translate self.parent = parent self.main_window = main_window self.image_pixmap = None self.image_cache = [None for _ in range(4)] self.thread = None self.annotation_dict = self.parent.metadata['annotations'] self.filepath = filepath self.filetype = os.path.splitext(self.filepath)[1][1:] if self.filetype == 'cbz': self.book = zipfile.ZipFile(self.filepath) elif self.filetype == 'cbr': self.book = rarfile.RarFile(self.filepath) elif self.filetype == 'pdf': self.book = fitz.open(self.filepath) self.common_functions = PliantWidgetsCommonFunctions( self, self.main_window) self.ignore_wheel_event = False self.ignore_wheel_event_number = 0 self.setMouseTracking(True) self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag) self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.customContextMenuRequested.connect( self.generate_graphicsview_context_menu) def loadImage(self, current_page): 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 not in (0, len(all_pages) - 1))): double_page_mode = True def load_page(current_page): def page_loader(page): pixmap = QtGui.QPixmap() if self.filetype in ('cbz', 'cbr'): page_data = self.book.read(page) pixmap.loadFromData(page_data) elif self.filetype == 'pdf': page_data = self.book.loadPage(page) pixmap = render_pdf_page(page_data) return pixmap firstPixmap = page_loader(current_page) if not double_page_mode: return firstPixmap next_page = all_pages[current_page_index + 1] secondPixmap = page_loader(next_page) # Pixmap height should be the greater of the 2 images pixmap_height = firstPixmap.height() if secondPixmap.height() > pixmap_height: pixmap_height = secondPixmap.height() bigPixmap = QtGui.QPixmap( firstPixmap.width() + secondPixmap.width() + 5, pixmap_height) bigPixmap.fill(QtCore.Qt.transparent) imagePainter = QtGui.QPainter(bigPixmap) manga_mode = self.main_window.settings['manga_mode'] if manga_mode: imagePainter.drawPixmap(0, 0, secondPixmap) imagePainter.drawPixmap(secondPixmap.width() + 4, 0, firstPixmap) else: imagePainter.drawPixmap(0, 0, firstPixmap) imagePainter.drawPixmap(firstPixmap.width() + 4, 0, secondPixmap) imagePainter.end() return bigPixmap def generate_image_cache(current_page): logger.info('(Re)building image cache') current_page_index = all_pages.index(current_page) # Image caching for single and double page views page_indices = (-1, 0, 1, 2) index_modifier = 0 if double_page_mode: index_modifier = 1 for i in page_indices: try: this_page = all_pages[current_page_index + i + index_modifier] this_pixmap = load_page(this_page) self.image_cache[i + 1] = (this_page, this_pixmap) except IndexError: self.image_cache[i + 1] = None def refill_cache(remove_value): # Do NOT put a parent in here or the mother of all # memory leaks will result self.thread = BackGroundCacheRefill( self.image_cache, remove_value, self.filetype, self.book, all_pages) self.thread.finished.connect(overwrite_cache) self.thread.start() def overwrite_cache(): self.image_cache = self.thread.image_cache def check_cache(current_page): for i in self.image_cache: if i: if i[0] == current_page: return_pixmap = i[1] refill_cache(i) return return_pixmap # No return happened so the image isn't in the cache generate_image_cache(current_page) # TODO # Get caching working for double page view if not double_page_mode and self.main_window.settings['caching_enabled']: return_pixmap = None while not return_pixmap: return_pixmap = check_cache(current_page) else: return_pixmap = load_page(current_page) if self.main_window.settings['invert_colors']: qImg = return_pixmap.toImage() qImg.invertPixels() if qImg: # Will return None if conversion doesn't work return_pixmap = QtGui.QPixmap().fromImage(qImg) else: logger.error('Color inversion failed: ' + current_page) self.image_pixmap = return_pixmap self.resizeEvent() def resizeEvent(self, *args): if not self.image_pixmap: return zoom_mode = self.main_window.comic_profile['zoom_mode'] padding = self.main_window.comic_profile['padding'] if zoom_mode == 'fitWidth': available_width = self.viewport().width() image_pixmap = self.image_pixmap.scaledToWidth( available_width, QtCore.Qt.SmoothTransformation) elif zoom_mode == 'originalSize': image_pixmap = self.image_pixmap new_padding = (self.viewport().width() - image_pixmap.width()) // 2 if new_padding < 0: # The image is larger than the viewport self.main_window.comic_profile['padding'] = 0 else: self.main_window.comic_profile['padding'] = new_padding elif zoom_mode == 'bestFit': available_width = self.viewport().width() available_height = self.viewport().height() image_pixmap = self.image_pixmap.scaled( available_width, available_height, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) self.main_window.comic_profile['padding'] = ( self.viewport().width() - image_pixmap.width()) // 2 elif zoom_mode == 'manualZoom': available_width = self.viewport().width() - 2 * padding image_pixmap = self.image_pixmap.scaledToWidth( available_width, QtCore.Qt.SmoothTransformation) graphicsScene = QtWidgets.QGraphicsScene() graphicsScene.addPixmap(image_pixmap) self.setScene(graphicsScene) self.show() # This prevents a partial page scroll on first load self.verticalScrollBar().setValue(0) def wheelEvent(self, event): self.common_functions.wheelEvent(event) def keyPressEvent(self, event): vertical = self.verticalScrollBar().value() maximum = self.verticalScrollBar().maximum() def scroller(increment, move_forward=True): if move_forward: if vertical == maximum: self.common_functions.change_chapter(1, True) else: next_val = vertical + increment if next_val >= .95 * maximum: next_val = maximum self.verticalScrollBar().setValue(next_val) else: if vertical == 0: self.common_functions.change_chapter(-1, False) else: next_val = vertical - increment if next_val <= .05 * maximum: next_val = 0 self.verticalScrollBar().setValue(next_val) small_increment = maximum //self.main_window.settings['small_increment'] big_increment = maximum // self.main_window.settings['large_increment'] # Scrolling if event.key() == QtCore.Qt.Key_Up: scroller(small_increment, False) if event.key() == QtCore.Qt.Key_Down: scroller(small_increment) if event.key() == QtCore.Qt.Key_Space: scroller(big_increment) # Double page mode and manga mode if event.key() in (QtCore.Qt.Key_D, QtCore.Qt.Key_M): self.main_window.change_page_view(event.key()) # Image fit modes view_modification_keys = ( QtCore.Qt.Key_Plus, QtCore.Qt.Key_Minus, QtCore.Qt.Key_Equal, QtCore.Qt.Key_B, QtCore.Qt.Key_W, QtCore.Qt.Key_O) 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, event): if QtWidgets.QApplication.mouseButtons() == QtCore.Qt.NoButton: self.viewport().setCursor(QtCore.Qt.OpenHandCursor) else: self.viewport().setCursor(QtCore.Qt.ClosedHandCursor) self.parent.mouse_hide_timer.start(2000) QtWidgets.QGraphicsView.mouseMoveEvent(self, event) def generate_graphicsview_context_menu(self, position): contextMenu = QtWidgets.QMenu() fsToggleAction = dfToggleAction = 'Caesar si viveret, ad remum dareris' if self.parent.is_fullscreen: fsToggleAction = contextMenu.addAction( self.main_window.QImageFactory.get_image('view-fullscreen'), self._translate('PliantQGraphicsView', 'Exit fullscreen')) elif not self.main_window.settings['show_bars']: distraction_free_prompt = self._translate( 'PliantQGraphicsView', 'Exit Distraction Free mode') dfToggleAction = contextMenu.addAction( self.main_window.QImageFactory.get_image('visibility'), distraction_free_prompt) saveAction = contextMenu.addAction( self.main_window.QImageFactory.get_image('filesaveas'), self._translate('PliantQGraphicsView', 'Save page as...')) view_submenu_string = self._translate('PliantQGraphicsView', 'View') viewSubMenu = contextMenu.addMenu(view_submenu_string) viewSubMenu.setIcon( self.main_window.QImageFactory.get_image('mail-thread-watch')) doublePageAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('page-double'), self._translate('PliantQGraphicsView', 'Double page mode (D)')) doublePageAction.setCheckable(True) doublePageAction.setChecked( self.main_window.bookToolBar.doublePageButton.isChecked()) mangaModeAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('manga-mode'), self._translate('PliantQGraphicsView', 'Manga mode (M)')) mangaModeAction.setCheckable(True) mangaModeAction.setChecked( self.main_window.bookToolBar.mangaModeButton.isChecked()) viewSubMenu.addSeparator() zoominAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('zoom-in'), self._translate('PliantQGraphicsView', 'Zoom in (+)')) zoomoutAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('zoom-out'), self._translate('PliantQGraphicsView', 'Zoom out (-)')) fitWidthAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('zoom-fit-width'), self._translate('PliantQGraphicsView', 'Fit width (W)')) bestFitAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('zoom-fit-best'), self._translate('PliantQGraphicsView', 'Best fit (B)')) originalSizeAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('zoom-original'), self._translate('PliantQGraphicsView', 'Original size (O)')) bookmarksToggleAction = 'Latin quote 2. Electric Boogaloo.' if not self.main_window.settings['show_bars'] or self.parent.is_fullscreen: bookmarksToggleAction = contextMenu.addAction( self.main_window.QImageFactory.get_image('bookmarks'), self._translate('PliantQGraphicsView', 'Bookmarks')) self.common_functions.generate_combo_box_action(contextMenu) action = contextMenu.exec_(self.sender().mapToGlobal(position)) if action == doublePageAction: self.main_window.bookToolBar.doublePageButton.trigger() if action == mangaModeAction: self.main_window.bookToolBar.mangaModeButton.trigger() if action == saveAction: dialog_prompt = self._translate('Main_UI', 'Save page as...') extension_string = self._translate('Main_UI', 'Images') save_file = QtWidgets.QFileDialog.getSaveFileName( self, dialog_prompt, self.main_window.settings['last_open_path'], f'{extension_string} (*.png *.jpg *.bmp)') if save_file: self.image_pixmap.save(save_file[0]) if action == bookmarksToggleAction: self.parent.toggle_side_dock(1) if action == dfToggleAction: self.main_window.toggle_distraction_free() if action == fsToggleAction: self.parent.exit_fullscreen() view_action_dict = { zoominAction: QtCore.Qt.Key_Plus, zoomoutAction: QtCore.Qt.Key_Minus, fitWidthAction: QtCore.Qt.Key_W, bestFitAction: QtCore.Qt.Key_B, originalSizeAction: QtCore.Qt.Key_O} if action in view_action_dict: self.main_window.modify_comic_view(view_action_dict[action]) def closeEvent(self, *args): # In case the program is closed when a contentView is fullscreened self.main_window.closeEvent() def toggle_annotation_mode(self): # The graphics view doesn't currently have annotation functionality # Don't delete this because it's still called pass
class PliantQGraphicsView(QtWidgets.QGraphicsView): def __init__(self, filepath, main_window, parent=None): super(PliantQGraphicsView, self).__init__(parent) self._translate = QtCore.QCoreApplication.translate self.parent = parent self.main_window = main_window self.qimage = None # Will be needed to resize pdf self.image_pixmap = None self.image_cache = [None for _ in range(4)] self.thread = None self.filepath = filepath self.filetype = os.path.splitext(self.filepath)[1][1:] if self.filetype == 'cbz': self.book = zipfile.ZipFile(self.filepath) elif self.filetype == 'cbr': self.book = rarfile.RarFile(self.filepath) elif self.filetype == 'pdf': self.book = popplerqt5.Poppler.Document.load(self.filepath) self.book.setRenderHint( popplerqt5.Poppler.Document.Antialiasing and popplerqt5.Poppler.Document.TextAntialiasing) self.common_functions = PliantWidgetsCommonFunctions( self, self.main_window) # TODO # Image panning with mouse self.ignore_wheel_event = False self.ignore_wheel_event_number = 0 self.setMouseTracking(True) self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag) self.viewport().setCursor(QtCore.Qt.ArrowCursor) self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.customContextMenuRequested.connect( self.generate_graphicsview_context_menu) def loadImage(self, current_page): # TODO # For double page view: 1 before, 1 after all_pages = [i[1] for i in self.parent.metadata['content']] def load_page(current_page): image_pixmap = QtGui.QPixmap() if self.filetype in ('cbz', 'cbr'): page_data = self.book.read(current_page) image_pixmap.loadFromData(page_data) elif self.filetype == 'pdf': page_data = self.book.page(current_page) page_qimage = page_data.renderToImage(350, 350) image_pixmap.convertFromImage(page_qimage) return image_pixmap def generate_image_cache(current_page): print('Building image cache') current_page_index = all_pages.index(current_page) for i in (-1, 0, 1, 2): try: this_page = all_pages[current_page_index + i] this_pixmap = load_page(this_page) self.image_cache[i + 1] = (this_page, this_pixmap) except IndexError: self.image_cache[i + 1] = None def refill_cache(remove_value): # Do NOT put a parent in here or the mother of all # memory leaks will result self.thread = BackGroundCacheRefill(self.image_cache, remove_value, self.filetype, self.book, all_pages) self.thread.finished.connect(overwrite_cache) self.thread.start() def overwrite_cache(): self.image_cache = self.thread.image_cache def check_cache(current_page): for i in self.image_cache: if i: if i[0] == current_page: return_pixmap = i[1] refill_cache(i) return return_pixmap # No return happened so the image isn't in the cache generate_image_cache(current_page) if self.main_window.settings['caching_enabled']: return_pixmap = None while not return_pixmap: return_pixmap = check_cache(current_page) else: return_pixmap = load_page(current_page) self.image_pixmap = return_pixmap self.resizeEvent() def resizeEvent(self, *args): if not self.image_pixmap: return zoom_mode = self.main_window.comic_profile['zoom_mode'] padding = self.main_window.comic_profile['padding'] if zoom_mode == 'fitWidth': available_width = self.viewport().width() image_pixmap = self.image_pixmap.scaledToWidth( available_width, QtCore.Qt.SmoothTransformation) elif zoom_mode == 'originalSize': image_pixmap = self.image_pixmap new_padding = (self.viewport().width() - image_pixmap.width()) // 2 if new_padding < 0: # The image is larger than the viewport self.main_window.comic_profile['padding'] = 0 else: self.main_window.comic_profile['padding'] = new_padding elif zoom_mode == 'bestFit': available_width = self.viewport().width() available_height = self.viewport().height() image_pixmap = self.image_pixmap.scaled( available_width, available_height, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) self.main_window.comic_profile['padding'] = ( self.viewport().width() - image_pixmap.width()) // 2 elif zoom_mode == 'manualZoom': available_width = self.viewport().width() - 2 * padding image_pixmap = self.image_pixmap.scaledToWidth( available_width, QtCore.Qt.SmoothTransformation) graphics_scene = QtWidgets.QGraphicsScene() graphics_scene.addPixmap(image_pixmap) self.setScene(graphics_scene) self.show() def wheelEvent(self, event): self.common_functions.wheelEvent(event, True) def keyPressEvent(self, event): vertical = self.verticalScrollBar().value() maximum = self.verticalScrollBar().maximum() def scroller(increment, move_forward=True): if move_forward: if vertical == maximum: self.common_functions.change_chapter(1, True) else: next_val = vertical + increment if next_val >= .95 * maximum: next_val = maximum self.verticalScrollBar().setValue(next_val) else: if vertical == 0: self.common_functions.change_chapter(-1, False) else: next_val = vertical - increment if next_val <= .05 * maximum: next_val = 0 self.verticalScrollBar().setValue(next_val) small_increment = maximum // 4 big_increment = maximum // 2 if event.key() == QtCore.Qt.Key_Up: scroller(small_increment, False) if event.key() == QtCore.Qt.Key_Down: scroller(small_increment) if event.key() == QtCore.Qt.Key_Space: scroller(big_increment) view_modification_keys = (QtCore.Qt.Key_Plus, QtCore.Qt.Key_Minus, QtCore.Qt.Key_Equal, QtCore.Qt.Key_B, QtCore.Qt.Key_W, QtCore.Qt.Key_O) if event.key() in view_modification_keys: self.main_window.modify_comic_view(event.key()) def mouseMoveEvent(self, *args): self.viewport().setCursor(QtCore.Qt.ArrowCursor) self.parent.mouse_hide_timer.start(3000) def generate_graphicsview_context_menu(self, position): contextMenu = QtWidgets.QMenu() saveAction = contextMenu.addAction( self.main_window.QImageFactory.get_image('filesaveas'), self._translate('PliantQGraphicsView', 'Save page as...')) toggleAction = contextMenu.addAction( self.main_window.QImageFactory.get_image('visibility'), self._translate('PliantQGraphicsView', 'Toggle distraction free mode')) viewSubMenu = contextMenu.addMenu('View') viewSubMenu.setIcon( self.main_window.QImageFactory.get_image('mail-thread-watch')) zoominAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('zoom-in'), self._translate('PliantQGraphicsView', 'Zoom in (+)')) zoomoutAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('zoom-out'), self._translate('PliantQGraphicsView', 'Zoom out (-)')) fitWidthAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('zoom-fit-width'), self._translate('PliantQGraphicsView', 'Fit width (W)')) bestFitAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('zoom-fit-best'), self._translate('PliantQGraphicsView', 'Best fit (B)')) originalSizeAction = viewSubMenu.addAction( self.main_window.QImageFactory.get_image('zoom-original'), self._translate('PliantQGraphicsView', 'Original size (O)')) if not self.main_window.settings['show_bars']: self.common_functions.generate_combo_box_action(contextMenu) action = contextMenu.exec_(self.sender().mapToGlobal(position)) if action == saveAction: dialog_prompt = self._translate('Main_UI', 'Save page as...') extension_string = self._translate('Main_UI', 'Images') save_file = QtWidgets.QFileDialog.getSaveFileName( self, dialog_prompt, self.main_window.settings['last_open_path'], f'{extension_string} (*.png *.jpg *.bmp)') if save_file: self.image_pixmap.save(save_file[0]) if action == toggleAction: self.main_window.toggle_distraction_free() view_action_dict = { zoominAction: QtCore.Qt.Key_Plus, zoomoutAction: QtCore.Qt.Key_Minus, fitWidthAction: QtCore.Qt.Key_W, bestFitAction: QtCore.Qt.Key_B, originalSizeAction: QtCore.Qt.Key_O } if action in view_action_dict: self.main_window.modify_comic_view(view_action_dict[action]) def closeEvent(self, *args): # In case the program is closed when a contentView is fullscreened self.main_window.closeEvent()
class PliantQGraphicsView(QtWidgets.QGraphicsView): def __init__(self, filepath, main_window, parent=None): super(PliantQGraphicsView, self).__init__(parent) self.main_window = main_window self.parent = parent self.qimage = None # Will be needed to resize pdf self.image_pixmap = None self.image_cache = [None for _ in range(4)] self.thread = None self.filepath = filepath self.filetype = os.path.splitext(self.filepath)[1][1:] if self.filetype == 'cbz': self.book = zipfile.ZipFile(self.filepath) elif self.filetype == 'cbr': self.book = rarfile.RarFile(self.filepath) elif self.filetype == 'pdf': self.book = popplerqt5.Poppler.Document.load(self.filepath) self.book.setRenderHint( popplerqt5.Poppler.Document.Antialiasing and popplerqt5.Poppler.Document.TextAntialiasing) self.common_functions = PliantWidgetsCommonFunctions( self, self.main_window) # TODO # Image panning with mouse self.ignore_wheel_event = False self.ignore_wheel_event_number = 0 self.setMouseTracking(True) self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag) self.viewport().setCursor(QtCore.Qt.ArrowCursor) def loadImage(self, current_page): # TODO # Threaded caching will still work here # Look at a commit where it's not been deleted # For double page view: 1 before, 1 after all_pages = [i[1] for i in self.parent.metadata['content']] def load_page(current_page): image_pixmap = QtGui.QPixmap() if self.filetype in ('cbz', 'cbr'): page_data = self.book.read(current_page) image_pixmap.loadFromData(page_data) elif self.filetype == 'pdf': page_data = self.book.page(current_page) page_qimage = page_data.renderToImage(350, 350) image_pixmap.convertFromImage(page_qimage) return image_pixmap def generate_image_cache(current_page): print('Building image cache') current_page_index = all_pages.index(current_page) for i in (-1, 0, 1, 2): try: this_page = all_pages[current_page_index + i] this_pixmap = load_page(this_page) self.image_cache[i + 1] = (this_page, this_pixmap) except IndexError: self.image_cache[i + 1] = None def refill_cache(remove_value): # Do NOT put a parent in here or the mother of all # memory leaks will result self.thread = BackGroundCacheRefill(self.image_cache, remove_value, self.filetype, self.book, all_pages) self.thread.finished.connect(overwrite_cache) self.thread.start() def overwrite_cache(): self.image_cache = self.thread.image_cache def check_cache(current_page): for i in self.image_cache: if i: if i[0] == current_page: return_pixmap = i[1] refill_cache(i) return return_pixmap # No return happened so the image isn't in the cache generate_image_cache(current_page) return_pixmap = None while not return_pixmap: return_pixmap = check_cache(current_page) self.image_pixmap = return_pixmap self.resizeEvent() def resizeEvent(self, *args): if not self.image_pixmap: return zoom_mode = self.main_window.comic_profile['zoom_mode'] padding = self.main_window.comic_profile['padding'] if zoom_mode == 'fitWidth': available_width = self.viewport().width() image_pixmap = self.image_pixmap.scaledToWidth( available_width, QtCore.Qt.SmoothTransformation) elif zoom_mode == 'originalSize': image_pixmap = self.image_pixmap new_padding = (self.viewport().width() - image_pixmap.width()) // 2 if new_padding < 0: # The image is larger than the viewport self.main_window.comic_profile['padding'] = 0 else: self.main_window.comic_profile['padding'] = new_padding elif zoom_mode == 'bestFit': available_width = self.viewport().width() available_height = self.viewport().height() image_pixmap = self.image_pixmap.scaled( available_width, available_height, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) self.main_window.comic_profile['padding'] = ( self.viewport().width() - image_pixmap.width()) // 2 elif zoom_mode == 'manualZoom': available_width = self.viewport().width() - 2 * padding image_pixmap = self.image_pixmap.scaledToWidth( available_width, QtCore.Qt.SmoothTransformation) graphics_scene = QtWidgets.QGraphicsScene() graphics_scene.addPixmap(image_pixmap) self.setScene(graphics_scene) self.show() def wheelEvent(self, event): self.common_functions.wheelEvent(event, True) def keyPressEvent(self, event): # This function is sufficiently different to warrant # exclusion from the common functions class if event.key() == 32: # Spacebar press vertical = self.verticalScrollBar().value() maximum = self.verticalScrollBar().maximum() if vertical == maximum: self.common_functions.change_chapter(1, True) else: # Increment by following value scroll_increment = int((maximum - 0) / 2) self.verticalScrollBar().setValue(vertical + scroll_increment) def mouseMoveEvent(self, *args): self.viewport().setCursor(QtCore.Qt.ArrowCursor) self.parent.mouse_hide_timer.start(3000) def closeEvent(self, *args): # In case the program is closed when a contentView is fullscreened self.main_window.closeEvent()