From f4c44756f63620228c297cb18f7e2b8a9ab4a9b4 Mon Sep 17 00:00:00 2001 From: Clement Desmidt Date: Wed, 24 May 2023 14:14:07 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20classes=20t?= =?UTF-8?q?o=20avoid=20big=20ones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #20 --- image.py | 159 ------------------------------------ main.py | 2 +- src/element/image.py | 50 ++++++++++++ src/element/toolbar.py | 107 ++++++++++++++++++++++++ util.py => src/util.py | 0 app.py => src/window/app.py | 6 +- src/window/image.py | 50 ++++++++++++ 7 files changed, 211 insertions(+), 163 deletions(-) delete mode 100644 image.py create mode 100644 src/element/image.py create mode 100644 src/element/toolbar.py rename util.py => src/util.py (100%) rename app.py => src/window/app.py (96%) create mode 100644 src/window/image.py diff --git a/image.py b/image.py deleted file mode 100644 index 6f3b716..0000000 --- a/image.py +++ /dev/null @@ -1,159 +0,0 @@ -import os -import random -from tkinter import * -from pygame import mixer -from PIL import ImageTk, Image, ImageOps -from util import Util - - -class ImageWindow: - def __init__(self, app): - self.app = app - self.window = Toplevel(app.root) - self.window.title("Image") - self.window.geometry("1280x1024") - self.option = { - "bw": False, - "mirror": False, - "always_on_top": False, - } - mixer.init() - self.countdown_sound = mixer.Sound('assets/sounds/countdown.mp3') - self.images = [] - self.current_image = None - self.image_label = None - toolbar = Frame(self.window, bd=1, relief=RAISED) - toolbar.pack(side=BOTTOM, fill=X) - self.timer = 0 - self.timer_label = None - self.timer_check = None - self.timer_check = None - - next_button = Button(toolbar, relief=FLAT, compound=LEFT, text="next ➡️", command=self.next_image) - next_button.pack(side=LEFT, padx=0, pady=0) - - self.bw_button = Button(toolbar, relief=FLAT, compound=LEFT, text="black&white", - command=self.toggle_black_white) - self.bw_button.pack(side=LEFT, padx=0, pady=0) - - self.mirror_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.toggle_mirror, text="mirror") - self.mirror_button.pack(side=LEFT, padx=0, pady=0) - - self.open_folder_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.open_folder - , text="open folder") - self.open_folder_button.pack(side=LEFT, padx=0, pady=0) - - self.always_on_top_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.toggle_always_on_top - , text="always on top") - self.always_on_top_button.pack(side=LEFT, padx=0, pady=0) - self.always_on_top_check = None - - self.timer_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.toggle_timer, text="timer") - self.timer_button.pack(side=LEFT, padx=0, pady=0) - - self.image_label = Label(self.window) - self.image_label.pack(side=TOP, fill=BOTH, expand=1) - - self.timer_label = Label(toolbar, text=self.timer) - self.timer_label.pack(side=RIGHT, ipadx=20) - - def update_timer(self, current): - current -= 1 - if current == 10: - self.play_countdown() - if current > 0: - self.timer_label.configure(text=Util.format_seconds(current)) - self.timer_check = self.window.after(1000, self.update_timer, current) - else: - self.window.after_cancel(self.timer_check) - if len(self.images) == 0: - self.window.destroy() - self.window.update() - return - self.next_image() - - def next_image(self): - if self.timer_check is not None: - self.window.after_cancel(self.timer_check) - self.display_new_image() - self.display_new_timer() - - def toggle_black_white(self): - self.option["bw"] = not self.option["bw"] - self.bw_button.config(bg="blue" if self.option["bw"] else "grey85") - - def toggle_mirror(self): - self.option["mirror"] = not self.option["mirror"] - self.mirror_button.config(bg="blue" if self.option["mirror"] else "grey85") - - def toggle_timer(self): - try: - self.timer_label.pack_info() - self.timer_label.pack_forget() - except TclError: - self.timer_label.pack(side=RIGHT, ipadx=20) - - def open_folder(self): - os.startfile(self.app.selected_folder) - - def toggle_always_on_top(self): - self.option["always_on_top"] = not self.option["always_on_top"] - if self.option["always_on_top"]: - self.always_on_top_button.config(bg="blue") - self.stay_on_top() - else: - self.always_on_top_button.config(bg="grey85") - self.window.after_cancel(self.always_on_top_check) - - def stay_on_top(self): - # self.window.lift() - self.window.attributes('-topmost', True) - self.always_on_top_check = self.window.after(2000, self.stay_on_top) - - def lets_draw(self, images, timer): - random.shuffle(images) - self.images = images - self.timer = timer - self.next_image() - - def display_new_timer(self): - self.timer_label.configure(text=self.timer) - self.timer_check = self.window.after(1000, self.update_timer, self.timer) - - def display_new_image(self): - image_path = self.images[0] - self.images.pop(0) - self.current_image = Image.open(image_path) - - self.resize_image() - - if self.option["bw"]: - self.current_image = self.current_image.convert("L") - - if self.option["mirror"]: - self.current_image = ImageOps.mirror(self.current_image) - - image_to_display = ImageTk.PhotoImage(self.current_image) - - self.image_label.configure(image=image_to_display) - self.image_label.bind('', lambda event: self.resize_image()) - self.image_label.image = image_to_display - - def resize_image(self): - ma = self.window.winfo_toplevel() - w, h = ma.winfo_width(), ma.winfo_height() - - # if self.current_image.size[0] > w or self.current_image.size[1] > h: - if w < 21 or h < 21: # too small - w = 1280 - h = 1024 - self.current_image.thumbnail((w - 20, h - 20), Image.ANTIALIAS) - - print("resized: win %s >= img %s", (w, h), self.current_image.size) - - image_to_display = ImageTk.PhotoImage(self.current_image) - self.image_label.configure(image=image_to_display) - self.image_label.image = image_to_display - - def play_countdown(self): - self.countdown_sound.play() diff --git a/main.py b/main.py index efac809..337d3e8 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,5 @@ from tkinter import * -from app import App +from src.window.app import App def main(): diff --git a/src/element/image.py b/src/element/image.py new file mode 100644 index 0000000..528f941 --- /dev/null +++ b/src/element/image.py @@ -0,0 +1,50 @@ +import copy +from tkinter import * +from PIL import ImageTk, Image, ImageOps + + +class ImagePlaceholder: + def __init__(self, image_window, images): + self.current_image = None + self.current_original_image = None + self.image_window = image_window + self.images = images.copy() + + self.image_label = Label(self.image_window.window) + self.image_label.bind('', lambda event: self.resize_image(True)) + self.image_label.pack(side=TOP, fill=BOTH, expand=1) + + def display_new_image(self): + image_path = self.images[0] + self.images.pop(0) + self.current_original_image = Image.open(image_path) + self.current_image = copy.deepcopy(self.current_original_image) + self.apply_options() + + def resize_image(self, reload=False): + ma = self.image_window.window.winfo_toplevel() + w, h = ma.winfo_width(), ma.winfo_height() + + if w < 21 or h < 21: + w = 1280 + h = 1024 + self.current_image.thumbnail((w - 20, h - 20), Image.ANTIALIAS) + + if reload: + self.load_widget() + + def apply_options(self): + self.current_image = copy.deepcopy(self.current_original_image) + + if self.image_window.option["bw"]: + self.current_image = self.current_image.convert("L") + + if self.image_window.option["mirror"]: + self.current_image = ImageOps.mirror(self.current_image) + + self.resize_image(True) + + def load_widget(self): + image_to_display = ImageTk.PhotoImage(self.current_image) + self.image_label.configure(image=image_to_display) + self.image_label.image = image_to_display diff --git a/src/element/toolbar.py b/src/element/toolbar.py new file mode 100644 index 0000000..d3d8ce8 --- /dev/null +++ b/src/element/toolbar.py @@ -0,0 +1,107 @@ +import copy +import os +import subprocess +import sys +from tkinter import * +from src.util import Util + + +class Toolbar: + def __init__(self, image_window, timer): + self.current_image = None + self.current_original_image = None + self.image_window = image_window + + self.timer = timer + self.timer_label = None + self.timer_check = None + + toolbar = Frame(self.image_window.window, bd=1, relief=RAISED) + toolbar.pack(side=BOTTOM, fill=X) + + next_button = Button(toolbar, relief=FLAT, compound=LEFT, text="next ➡️", command=self.image_window.next_image) + next_button.pack(side=LEFT, padx=0, pady=0) + + self.bw_button = Button(toolbar, relief=FLAT, compound=LEFT, text="black&white", + command=self.toggle_black_white) + self.bw_button.pack(side=LEFT, padx=0, pady=0) + + self.mirror_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.toggle_mirror, + text="mirror") + self.mirror_button.pack(side=LEFT, padx=0, pady=0) + + self.open_folder_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.open_folder + , text="open folder") + self.open_folder_button.pack(side=LEFT, padx=0, pady=0) + + self.always_on_top_button = Button(toolbar, relief=FLAT, compound=LEFT, + command=self.toggle_always_on_top + , text="always on top") + self.always_on_top_button.pack(side=LEFT, padx=0, pady=0) + self.always_on_top_check = None + + self.timer_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.toggle_timer, + text="timer") + self.timer_button.pack(side=LEFT, padx=0, pady=0) + + self.timer_label = Label(toolbar, text=self.timer) + self.timer_label.pack(side=RIGHT, ipadx=20) + + def display_new_timer(self): + self.timer_label.configure(text=Util.format_seconds(self.timer)) + self.timer_check = self.image_window.window.after(1000, self.update_timer, self.timer) + + def update_timer(self, current): + current -= 1 + if current == 10: + self.image_window.play_countdown() + if current > 0: + self.timer_label.configure(text=Util.format_seconds(current)) + self.timer_check = self.image_window.window.after(1000, self.update_timer, current) + else: + self.image_window.window.after_cancel(self.timer_check) + if len(self.image_window.images) == 0: + self.image_window.window.destroy() + self.image_window.window.update() + return + self.image_window.next_image() + + def toggle_black_white(self): + self.image_window.option["bw"] = not self.image_window.option["bw"] + self.bw_button.config(bg="blue" if self.image_window.option["bw"] else "grey85") + + self.image_window.image.apply_options() + + def toggle_mirror(self): + self.image_window.option["mirror"] = not self.image_window.option["mirror"] + self.mirror_button.config(bg="blue" if self.image_window.option["mirror"] else "grey85") + + self.image_window.image.apply_options() + + def toggle_timer(self): + try: + self.timer_label.pack_info() + self.timer_label.pack_forget() + except TclError: + self.timer_label.pack(side=RIGHT, ipadx=20) + + def open_folder(self): + if sys.platform == "win32": + os.startfile(self.image_window.app.selected_folder) + else: + opener = "open" if sys.platform == "darwin" else "xdg-open" + subprocess.call([opener, self.image_window.app.selected_folder]) + + def toggle_always_on_top(self): + self.image_window.option["always_on_top"] = not self.image_window.option["always_on_top"] + if self.image_window.option["always_on_top"]: + self.always_on_top_button.config(bg="blue") + self.stay_on_top() + else: + self.always_on_top_button.config(bg="grey85") + self.image_window.window.after_cancel(self.always_on_top_check) + + def stay_on_top(self): + # self.window.lift() + self.image_window.window.attributes('-topmost', True) + self.always_on_top_check = self.image_window.window.after(2000, self.stay_on_top) diff --git a/util.py b/src/util.py similarity index 100% rename from util.py rename to src/util.py diff --git a/app.py b/src/window/app.py similarity index 96% rename from app.py rename to src/window/app.py index 0aa46d8..358cfcd 100644 --- a/app.py +++ b/src/window/app.py @@ -1,9 +1,9 @@ from tkinter import * from tkinter import filedialog from pathlib import Path -from util import Util +from src.util import Util from functools import partial -from image import ImageWindow +from src.window.image import ImageWindow class App: @@ -36,7 +36,7 @@ class App: t = i new_button = Button(self.button_frame, text=Util.format_seconds(t), command=partial(self.set_timer_in_seconds, t)) - # new_button.pack(side="left") + self.buttons.append(new_button) self.button_frame.columnconfigure(i, weight=1) new_button.grid(row=0, column=i, sticky=W + E) diff --git a/src/window/image.py b/src/window/image.py new file mode 100644 index 0000000..9d16c13 --- /dev/null +++ b/src/window/image.py @@ -0,0 +1,50 @@ +import random +from tkinter import * +from pygame import mixer +from src.element.image import ImagePlaceholder +from src.element.toolbar import Toolbar + + +class ImageWindow: + def __init__(self, app): + self.current_original_image = None + self.app = app + + self.window = Toplevel(app.root) + self.window.title("Image") + self.window.geometry("1280x1024") + self.option = { + "bw": False, + "mirror": False, + "always_on_top": False, + } + + mixer.init() + self.countdown_sound = mixer.Sound('assets/sounds/countdown.mp3') + + self.images = [] + self.current_image = None + self.image_label = None + toolbar = Frame(self.window, bd=1, relief=RAISED) + toolbar.pack(side=BOTTOM, fill=X) + self.timer = 0 + + self.toolbar = Toolbar(self, self.timer) + self.image = ImagePlaceholder(self, self.images) + + def next_image(self): + if self.toolbar.timer_check is not None: + self.window.after_cancel(self.toolbar.timer_check) + self.image.display_new_image() + self.toolbar.display_new_timer() + + def lets_draw(self, images, timer): + random.shuffle(images) + # self.images = images + self.image.images = images + self.toolbar.timer = timer + # self.timer = timer + self.next_image() + + def play_countdown(self): + self.countdown_sound.play() -- 2.30.2 From 508e384858c1af13feee941f8bbec08f118ce06c Mon Sep 17 00:00:00 2001 From: Clement Desmidt Date: Wed, 24 May 2023 14:28:56 +0200 Subject: [PATCH 2/2] :sparkles: Add fullscreen Fix #23 --- src/element/toolbar.py | 29 +++++++++++++++++------------ src/window/image.py | 1 + 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/element/toolbar.py b/src/element/toolbar.py index d3d8ce8..eadf2c8 100644 --- a/src/element/toolbar.py +++ b/src/element/toolbar.py @@ -11,10 +11,9 @@ class Toolbar: self.current_image = None self.current_original_image = None self.image_window = image_window - self.timer = timer - self.timer_label = None self.timer_check = None + self.always_on_top_check = None toolbar = Frame(self.image_window.window, bd=1, relief=RAISED) toolbar.pack(side=BOTTOM, fill=X) @@ -30,19 +29,19 @@ class Toolbar: text="mirror") self.mirror_button.pack(side=LEFT, padx=0, pady=0) - self.open_folder_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.open_folder - , text="open folder") - self.open_folder_button.pack(side=LEFT, padx=0, pady=0) + open_folder_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.open_folder, text="open folder") + open_folder_button.pack(side=LEFT, padx=0, pady=0) - self.always_on_top_button = Button(toolbar, relief=FLAT, compound=LEFT, - command=self.toggle_always_on_top - , text="always on top") + self.always_on_top_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.toggle_always_on_top, + text="always on top") self.always_on_top_button.pack(side=LEFT, padx=0, pady=0) - self.always_on_top_check = None - self.timer_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.toggle_timer, - text="timer") - self.timer_button.pack(side=LEFT, padx=0, pady=0) + self.fullscreen_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.toggle_fullscreen, + text="fullscreen") + self.fullscreen_button.pack(side=LEFT, padx=0, pady=0) + + timer_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.toggle_timer, text="timer") + timer_button.pack(side=LEFT, padx=0, pady=0) self.timer_label = Label(toolbar, text=self.timer) self.timer_label.pack(side=RIGHT, ipadx=20) @@ -105,3 +104,9 @@ class Toolbar: # self.window.lift() self.image_window.window.attributes('-topmost', True) self.always_on_top_check = self.image_window.window.after(2000, self.stay_on_top) + + def toggle_fullscreen(self): + self.image_window.option["fullscreen"] = not self.image_window.option["fullscreen"] + self.fullscreen_button.config(bg="blue" if self.image_window.option["fullscreen"] else "grey85") + + self.image_window.window.attributes("-fullscreen", self.image_window.option["fullscreen"]) diff --git a/src/window/image.py b/src/window/image.py index 9d16c13..56472a5 100644 --- a/src/window/image.py +++ b/src/window/image.py @@ -17,6 +17,7 @@ class ImageWindow: "bw": False, "mirror": False, "always_on_top": False, + "fullscreen": False, } mixer.init() -- 2.30.2