PyQt5でコミックビューワーっぽいアプリを作ってみる

2022/02/11 categories:Python| tags:Python|PyMuPDF|PyQt5|PDF|

Windowsアプリでいい感じのコミックビューワーが見つからなかったので、PyQtで作れないか試してみました。

処理内容

以下のような処理を実装してみました。

実行結果

簡単な画像表示の切り替えだけですが、十分使用に耐えうる処理かなと思いました。もう少し機能を追加して、C++で書いてバイナリにすれば普通に使えるアプリになるかなと思いました。

Pythonコード

# -*- coding: utf-8 -*-
import re
import sys
from pathlib import Path
from PyQt5 import QtWidgets, QtCore, QtGui

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.books = []
        self.book_number = -1
        self.page_number = -1
        self.books_widget = BooksWidget()
        self.image_view = ImageView()

        self.resize(1200, 800)
        self.setCentralWidget( QtWidgets.QStackedWidget() )
        self.centralWidget().layout().addWidget(self.books_widget)
        self.centralWidget().layout().addWidget(self.image_view)

        self.books_widget.clicked.connect(self.set_image)
        self.books_widget.add_button.clicked.connect(self.folder_open)
        self.image_view.back.connect( lambda : self.centralWidget().setCurrentIndex(0) )
        self.image_view.page_change_signal.connect(self.page_changed)

    def folder_open(self):
        for path in Path('comic').iterdir():
            if path.is_file():
                continue
            images = sorted([p for p in path.glob('*') if re.search('/*\.(jpg|jpeg|png)', str(p))])
            pixmap = QtGui.QPixmap( str(images[0]) ).scaledToHeight(300)
            thumbnail = Thumbnail(pixmap, path.name)
            self.books_widget.add_thumbnail(thumbnail)
            self.books.append(images)

    def set_image(self, index:int):
        self.book_number = index
        self.page_number = 0
        pixmap = QtGui.QPixmap( str(self.books[index][self.page_number]) )
        self.image_view.set_pixmap(pixmap)
        self.centralWidget().setCurrentIndex(1)

    def page_changed(self, value):
        self.page_number += value
        book = self.books[self.book_number]
        if self.page_number < 0 or len(book) < self.page_number + 1:
            self.page_number -= value
            return
        pixmap = QtGui.QPixmap( str(book[self.page_number]) )
        self.image_view.set_pixmap(pixmap)

class BooksWidget(QtWidgets.QWidget):
    clicked = QtCore.pyqtSignal(int)
    def __init__(self):
        super(BooksWidget, self).__init__()
        self.book_count = 0

        self.add_button = QtWidgets.QToolButton()
        self.add_button.setText('Add folder')
        self.add_button.setMaximumSize(100, 40), self.add_button.setMinimumSize(100, 40)

        menu = QtWidgets.QWidget()
        menu.setLayout( QtWidgets.QVBoxLayout() )
        menu.layout().addWidget(self.add_button)

        self.grid = QtWidgets.QGridLayout()
        scroll_area = QtWidgets.QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_area.setWidget( QtWidgets.QWidget() )
        scroll_area.widget().setLayout(self.grid)

        self.setLayout( QtWidgets.QHBoxLayout() )
        self.layout().addWidget(menu)
        self.layout().addWidget(scroll_area)
        
    def add_thumbnail(self, thumbnail):
        thumbnail.clicked.connect(self.image_clicked)
        r = int( self.book_count / 3 )
        c = int( self.book_count % 3 )
        self.grid.addWidget( thumbnail, r, c, 1, 1 )
        self.book_count += 1

    def image_clicked(self, widget:QtWidgets.QWidget):
        self.clicked.emit( self.grid.indexOf(widget) )

class Thumbnail(QtWidgets.QGraphicsView):
    class View(QtWidgets.QGraphicsView):
        clicked = QtCore.pyqtSignal()
        def __init__(self):
            super().__init__()
            self.setScene( QtWidgets.QGraphicsScene() )

        def mousePressEvent(self, event: QtGui.QMouseEvent):
            self.clicked.emit()
            
        def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
            self.fitInView(self.scene().itemsBoundingRect(), QtCore.Qt.KeepAspectRatio)
            return super().resizeEvent(event)
    
    clicked = QtCore.pyqtSignal(QtWidgets.QGraphicsView)
    def __init__(self, pixmap, title):
        super(Thumbnail, self).__init__()
        self.view = self.View()
        self.view.scene().addPixmap(pixmap)
        self.title = QtWidgets.QLabel(title)
        self.setMinimumSize(300, 300)
        self.setMaximumSize(300, 300)
        self.setLayout( QtWidgets.QVBoxLayout() )
        self.layout().addWidget(self.view)
        self.layout().addWidget(self.title)
        self.view.clicked.connect( lambda : self.clicked.emit(self) )

class ImageView(QtWidgets.QWidget):
    class GraphicsView(QtWidgets.QGraphicsView):
        page_change_signal = QtCore.pyqtSignal(int)
        def __init__(self):
            super().__init__()
            self.setScene( QtWidgets.QGraphicsScene() )
            

        def mouseReleaseEvent(self, event: QtGui.QMouseEvent):
            w = self.rect().width() * 0.2
            if event.pos().x() < w:
                self.page_change_signal.emit(-1)
            elif self.rect().width() - w < event.pos().x():
                self.page_change_signal.emit(1)
            
        def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
            self.fitInView(self.scene().itemsBoundingRect(), QtCore.Qt.KeepAspectRatio)
            return super().resizeEvent(event)

    back = QtCore.pyqtSignal()
    page_change_signal = QtCore.pyqtSignal(int)

    def __init__(self):
        super(ImageView, self).__init__()

        self.back_button = QtWidgets.QToolButton()
        self.back_button.setText('Back')
        self.back_button.clicked.connect(self.back)
        self.back_button.setMaximumSize(100, 40), self.back_button.setMinimumSize(100, 40)

        self.menu = QtWidgets.QWidget()
        self.menu.setLayout( QtWidgets.QVBoxLayout() )
        self.menu.layout().addWidget(self.back_button)

        self.view = self.GraphicsView()
        self.view.page_change_signal.connect( lambda x : self.page_change_signal.emit(x) )

        self.setLayout( QtWidgets.QHBoxLayout() )
        self.layout().addWidget(self.menu)
        self.layout().addWidget(self.view)

    def set_pixmap(self, pixmap):
        self.view.scene().clear()
        self.view.scene().addPixmap(pixmap)
        self.view.scene().setSceneRect( QtCore.QRectF(pixmap.rect()) )
        self.view.fitInView(self.view.scene().itemsBoundingRect(), QtCore.Qt.KeepAspectRatio)
        self.view.setRenderHints(
            QtGui.QPainter.Antialiasing | 
            QtGui.QPainter.SmoothPixmapTransform | 
            QtGui.QPainter.HighQualityAntialiasing 
        )

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    widget = MainWindow()
    widget.show()
    app.exec()

Share post

Related Posts

Comments

comments powered by Disqus