Ejemplo n.º 1
0
 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()
Ejemplo n.º 2
0
 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()
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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()
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
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()