From 5515fd3660d4157f9791ed2f45e4fa844b34f048 Mon Sep 17 00:00:00 2001 From: Oleskii Pyskun Date: Tue, 2 Jul 2024 07:58:08 +0300 Subject: [PATCH] Implementation of preview book, by analogy of pillow.Image.show --- cbz/comic.py | 10 +++++- cbz/ui.py | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 cbz/ui.py diff --git a/cbz/comic.py b/cbz/comic.py index d81a95c..f0010ee 100644 --- a/cbz/comic.py +++ b/cbz/comic.py @@ -4,6 +4,7 @@ import json import zipfile from io import BytesIO from pathlib import Path + from typing import Union import xmltodict @@ -12,6 +13,7 @@ from cbz import utils from cbz.constants import xml_name from cbz.models import ComicModel from cbz.page import PageInfo +from cbz.ui import show_in_tk class ComicInfo(ComicModel): @@ -32,6 +34,13 @@ class ComicInfo(ComicModel): def from_pages(cls, pages: [PageInfo], **kwargs) -> ComicInfo: return cls(pages, **kwargs) + def show(self): + """ + display cbz for preview, after call open ui with info and pages and wait for close preview windows + :return: + """ + show_in_tk(self.title, self.__pages, utils.dumps(self._get())) + @classmethod def from_cbz(cls, path: Union[Path, str]) -> ComicInfo: """ @@ -104,7 +113,6 @@ class ComicInfo(ComicModel): xml_name, xmltodict.unparse({'ComicInfo': self.dumps()}, pretty=True).encode('utf-8') ) - for i, page in enumerate(self.__pages): zip_file.writestr(f'page-{i + 1:03d}{page.suffix}', page.content) result = zip_buffer.getvalue() diff --git a/cbz/ui.py b/cbz/ui.py new file mode 100644 index 0000000..1a48463 --- /dev/null +++ b/cbz/ui.py @@ -0,0 +1,95 @@ +from io import BytesIO +from tkinter import (Tk, Button, BOTTOM, TOP, PhotoImage, Scrollbar, X, Y, Canvas, HORIZONTAL, VERTICAL, YES, BOTH, + SUNKEN, RIGHT, LEFT, DISABLED, NORMAL, Label, Frame, ALL) +from PIL import ImageTk, Image + + +class ScrolledCanvas(Frame): + image_cursor: int = -2 + info_text: str = "" + pages = list() + next_btn = None + prev_btn = None + counter = None + image: PhotoImage + + def __init__(self, parent=None): + Frame.__init__(self, parent) + self.master.title("Spectrogram Viewer") + self.pack(expand=YES, fill=BOTH) + self.canvas = Canvas(self, relief=SUNKEN) + self.canvas.config(width=400, height=200) + self.canvas.config(highlightthickness=0) + + s_bar_v = Scrollbar(self, orient=VERTICAL) + s_bar_h = Scrollbar(self, orient=HORIZONTAL) + + s_bar_v.config(command=self.canvas.yview) + s_bar_h.config(command=self.canvas.xview) + + self.canvas.config(yscrollcommand=s_bar_v.set) + self.canvas.config(xscrollcommand=s_bar_h.set) + + s_bar_v.pack(side=RIGHT, fill=Y) + s_bar_h.pack(side=BOTTOM, fill=X) + + self.canvas.pack(side=LEFT, expand=YES, fill=BOTH) + + def set_text(self) -> None: + self.canvas.delete('image') + self.canvas.create_text(0, 0, anchor="nw", text=self.info_text, tags="image") + self.canvas.config(scrollregion=self.canvas.bbox(ALL)) + + def set_image(self, content: bytes) -> None: + self.canvas.delete('image') + with BytesIO(content) as f: + with Image.open(f) as image: + self.image = ImageTk.PhotoImage(image) + self.canvas.create_image(0, 0, anchor="nw", image=self.image, tags='image') + self.canvas.config(scrollregion=self.canvas.bbox(ALL)) + + def show_image(self, n: int) -> None: + self.image_cursor += n + if self.image_cursor == -1: + self.set_text() + self.prev_btn.config(state=DISABLED) + self.next_btn.config(state=NORMAL if self.pages else DISABLED) + elif self.image_cursor >= 0: + self.set_image(self.pages[self.image_cursor].content) + if self.image_cursor == len(self.pages) - 1: + self.next_btn.config(state=DISABLED) + self.prev_btn.config(state=NORMAL) + else: + self.prev_btn.config(state=NORMAL) + self.next_btn.config(state=NORMAL) + self.counter.config(text=f"{self.image_cursor + 1}/{len(self.pages)}") + self.canvas.yview_moveto(0) + self.canvas.xview_moveto(0) + + +def show_in_tk(title, pages, info): + root = Tk() + root.geometry("700x700") + frame = ScrolledCanvas(root) + frame.info_text = "\n".join([f"{key}: {value}" for key, value in info.items()]) + frame.pages = pages + frame.pack(side=TOP) + button_frame = Frame(root) + button_next = Button(button_frame, text="Next", command=lambda: frame.show_image(1)) + button_exit = Button(button_frame, text="Exit", command=root.quit) + counter = Label(button_frame) + button_prev = Button(button_frame, text="Previous", command=lambda: frame.show_image(-1)) + counter.grid(row=0, column=1) + button_exit.grid(row=0, column=2) + button_next.grid(row=0, column=3) + button_prev.grid(row=0, column=0) + frame.next_btn = button_next + frame.prev_btn = button_prev + frame.counter = counter + button_frame.pack(side=BOTTOM) + root.bind('', lambda x: frame.show_image(-1) if button_prev["state"] == NORMAL else True) + root.bind('', lambda x: frame.show_image(1) if button_next["state"] == NORMAL else True) + root.bind('', lambda x: root.quit()) + frame.show_image(1) + root.title(title) + root.mainloop()