From 34cc4a35694f90cfcfe8965a9cea8f3feabc6480 Mon Sep 17 00:00:00 2001 From: allecks Date: Sun, 9 Feb 2025 23:16:20 +0100 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20=C2=AB?= =?UTF-8?q?/=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- break.mp3 | Bin 0 -> 10030 bytes default.mp3 | Bin 0 -> 10865 bytes icon.ico | Bin 0 -> 1150 bytes model.py | 107 ++++++++++++++++++++++++++ view.py | 218 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 325 insertions(+) create mode 100644 break.mp3 create mode 100644 default.mp3 create mode 100644 icon.ico create mode 100644 model.py create mode 100644 view.py diff --git a/break.mp3 b/break.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..1c347c13ef401addc7cbd067ab9b3e411ae2e3df GIT binary patch literal 10030 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 a|JzmQAC>?5$@9vR9di2n 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

cx>De`^wDpYCM5%ttA9g<~9Tj2+ea2?SlS9pj znwn||f(iVAsyh;K=&_Z=U$r_IoqEBGtH4oK%#4m5CH#}*&0JgjCWlV_&bWzF|Bm08 z%x38XgmFn2gl-eFcosn8tOtt7$BKr8V^V4z2H}12Vv)#a6(YL$8LWA(x}tIN0E}YC-edc> z6w6C#v*)bzl<@Hc)OQz@1^)!_!?$J{u9iYm9y>!e;d?K4L}0i9v%h zaBFTRxAY@K!VIUAX+ENENQ;5@f7%E7U}QZqFlYTbnKSNAogXV8Ge8}EJ*rZ z1m9-te?ZDv@t9I+?;_rF<-$oIX{z6)>7hN*Y2SHwm8-LlHFb!-%JEaEFo6dwdEg~9 zA7m8AxNRBxgx@tez4|kdbm=p>S6}}!B8JwTr$=k8G=DgFl=V-8Bu}7spgt!Dk?krn zY1k0qvJT!1PVvF5#Fha&Fko{@p$VLT!N{NS75$eYGV9FqR4#vRldfGRZ>6kfW`?5nujmm-9HOe{v_Y# z=K5a^1*@K>f>7WeaNaPl%JIYoZea$aKVf9m0Sjg{NK$-nZGB-ScAk!rmVFw4S_dc1 zpJn{mS$fbS{zCBiWWu(x7MW@hgT$X-P9NcxNy(`{evycjCq#0d?OlmI?JiZ@`-Iyu zJ`EG`$;N`Q(97ykzS0p8rZqbtdiW}#Nvhm`E8D>yh8-^VVc&T_qT9|Y?!yyTar~aP zDJvZ@V;pb%9QmWtO+AwZVu+$MuIa}hBP(?Q!O~HyG%m7GPu29azus|wh@q>tAFpW+;;h}qscPw zajzeL_vupXv_bW(qa9trs7u&v;Oeh!?3r0uo4u)EYXKlG8Bf7jmS?hK+}B*mUVwf9 za8iKq2lLSR=Bh$P3ia?X`)x+f%ktPLTk8Hw`MlXqi(vL@VU8;+%R-N&nsZa4N>4&2 zE)ACjw~%hvI>em7n~TRk1gSa9 zj2Yu&x!N6VG#8X=OsKOE$cp_`Cc}rPif)i&^KxeWNGDdgmtNK`v95Q-E#si*BbX%a zx2;1Vg0Q=rG5{nvj3S6Z!Y9sDFzSryIJug=W#bKCdzSL#Z+%jKq?@T@~|YD-T{N4-atZ z^ERgq`bxsX4dgH&4V6iH2nULl^3}L#5E--Bose! zf7cn>Fwoni7&_H+1HbF$ZDbEq{4X9(y(CxE8ETCJTPZ3WUVO~@hBmdsGbucpJ<@2{ zG>mnl9I3#$jUz{sp;b|>(I{t%C{;bHd2V13gpg2xhFF)97ekPA?Cbx4RL8?X(rI21 zL3|I(cJ%z_a{2#u`+tw_{cqp+fB1&S^MAW&%3j%+-Dw_3c!Oy-Uu@d{;W7SicK2Ti b1&>@M1c5;2*dUNV00@Nse{%x=r^otV^ZLoN literal 0 HcmV?d00001 diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..219dfc5e4c1a0bf296629b2c9667310eac89ecd1 GIT binary patch literal 1150 zcmb`FyA8rX5JblUi4YOcCC;@tKDK3NT*a43;IvMj3w$^vv z-S_S+A5g?S7$BY#Y`ee^z%(LKk$L800HXHEwV1C`9^Y`M^)BTh-zyL6VlJCe%0o5w zOO5>2RKBf&I+Mud&IKv(EqW|8*9%2j7SLm~UEJy!PQ9G2&SBv+JzS i*_X#F?zRM~K2Y|6Lnkan9WJcrho~{i=&PdtHU{q5iR6L+ literal 0 HcmV?d00001 diff --git a/model.py b/model.py new file mode 100644 index 0000000..2260939 --- /dev/null +++ b/model.py @@ -0,0 +1,107 @@ +from icecream import ic +from dataclasses import dataclass +from random import randint +import pygame +import time +import sys +import os + +def resource_path(relative_path): + """ Get absolute path to resource, works for dev and for PyInstaller """ + try: + # PyInstaller creates a temp folder and stores path in _MEIPASS + base_path = sys._MEIPASS + except Exception: + base_path = os.path.abspath(".") + + return os.path.join(base_path, relative_path) + +class Queue(object): + def __init__(self): + self.tasks = [] + self.task_slots = {} + self.task_brake = self.set_break() + self.run_task = False + self.task_slots_max = 6 + self.current_task = None + self.current_task_time = 0 + self.current_task_time_current = 0 + self.current_task_time_max = 0 + pygame.mixer.init() + + def sound(self): + sound = pygame.mixer.Sound(resource_path(self.current_task.sound)) + sound.play() + + def set_break(self): + task_id = -1 + task_name = f"Перерыв 10 минут" + task_count = 0 + task_description = f"Десятиминутный перерыв" + task_time = 0 + task_sound = f"./sounds/break.mp3" + return Task(task_id, task_name, task_count, task_description, task_time, task_sound, False) + + def create_task(self, name, description = '', num = 0): + task_id = num + task_name = name + task_count = 0 + task_description = description + task_time = 0 + task_sound = f"./sounds/default.mp3" + self.task_slots[str(num)]=Task(task_id, task_name, task_count, task_description, task_time, task_sound, False) + + def delete_task(self, num): + del self.task_slots[str(num)] + + def start_unic_task(self, num): + task_keys = [key for key in list(self.task_slots.keys()) if not self.task_slots[key].finished] + if num in task_keys: + result = num + d = dice() + if d == 5: + self.current_task = self.task_brake + self.current_task_time = timers[d] + self.current_task_time_max = time.time() + self.current_task_time + else: + self.current_task = self.task_slots[result] + self.current_task_time = timers[d] + self.current_task_time_max = time.time() + self.current_task_time + else: + return + + def start_task(self): + task_keys = [key for key in list(self.task_slots.keys()) if not self.task_slots[key].finished] + ic(task_keys) + result = task_keys[randint(0, len(task_keys) - 1)] + d = dice() + if d == 5: + self.current_task = self.task_brake + self.current_task_time = timers[d] + self.current_task_time_max = time.time() + self.current_task_time + else: + self.current_task = self.task_slots[result] + self.current_task_time = timers[d] + self.current_task_time_max = time.time() + self.current_task_time + +def dice(): + return randint(0,5) + +@dataclass +class Task: + id: int + name: str + count: int #1-6 + description: str + time: int #seconds + sound: str #path to sound file + finished: bool + +timers = [ + 600, #10minutes + 1200, #20minutes + 1800, #30minutes + 2400, #40minutes + 3000, #50minutes + 600, #10minutes break +] \ No newline at end of file diff --git a/view.py b/view.py new file mode 100644 index 0000000..6b9131d --- /dev/null +++ b/view.py @@ -0,0 +1,218 @@ +import tkinter as tk +from tktooltip import ToolTip +from tkinter import ttk +from tkinter.messagebox import askyesno +from tkinter.filedialog import askopenfilename, asksaveasfilename +from icecream import ic +from functools import partial +from threading import Thread +import time +import model +import json +import matplotlib.pyplot as plt + + +queue = model.Queue() + +btns = {} + +root = tk.Tk() +root.title("Прям очень Таскер") +root.geometry("250x350") +root.option_add("*tearOff", tk.FALSE) +root.iconbitmap(default=model.resource_path("icon.ico")) + +def save_session(): + path = asksaveasfilename(filetypes=[("Json", '*.json')], defaultextension=".json") + ic(path) + data = { + "tasks": [task.__dict__ for id, task in queue.task_slots.items()], + "break": queue.task_brake.__dict__ + } + with open(path, "w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=4) + +def build_plt(): + labels = [task.name for id, task in queue.task_slots.items()] + [queue.task_brake.name] + times = [int(task.time/60) for id, task in queue.task_slots.items()] + [int(queue.task_brake.time/60)] + ic(labels) + ic(times) + plt.figure(figsize=(len(labels), 2)) + plt.bar(labels, times) + plt.title("Статистика выполнения") + plt.show() + # plt.savefig("chart.png") + +def load_session(): + path = askopenfilename(filetypes=[("Json", '*.json')]) + ic(path) + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + queue.task_slots = {str(task["id"]): model.Task(**task) for task in data["tasks"]} + queue.task_brake = model.Task(**data["break"]) if data["break"] else None + refrash() + +def refrash(): + for id, task in queue.task_slots.items(): + btns[str(id)]["text"] = f"{id} {task.name} ({task.count})" + if hasattr(btns[str(id)], 'toolTip'): + btns[str(id)].toolTip.on_leave() + btns[str(id)].toolTip.destroy() + btns[str(id)].toolTip = ToolTip(btns[str(id)], msg=task.description) + +def start_task_unic(num, event): + if not queue.run_task: + queue.run_task = True + queue.start_unic_task(str(num)) + btnLabel.bind("", pause_task) + btnLabel.bind("", stop_task) + if hasattr(btnLabel, 'toolTip'): + btnLabel.toolTip.on_leave() + btnLabel.toolTip.destroy() + btnLabel.toolTip = ToolTip(btnLabel, msg=queue.current_task.description) + Thread(target=do_task).start() + +def start_task(event): + if not queue.run_task: + queue.run_task = True + queue.start_task() + btnLabel.bind("", pause_task) + btnLabel.bind("", stop_task) + if hasattr(btnLabel, 'toolTip'): + btnLabel.toolTip.on_leave() + btnLabel.toolTip.destroy() + btnLabel.toolTip = ToolTip(btnLabel, msg=queue.current_task.description) + Thread(target=do_task).start() + +def restore_task(event): + queue.run_task = True + queue.current_task_time_max = queue.current_task_time_current + time.time() + Thread(target=do_task).start() + btnLabel.bind("", pause_task) + +def pause_task(event): + myText = f"{btnLabel.cget('text')}\nПауза" + queue.run_task = False + time.sleep(0.2) + ic(myText) + btnLabel["text"] = myText + btnLabel.bind("", restore_task) + +def stop_task(event): + queue.run_task = False + btnLabel["text"] = "Начать" + btnLabel.bind("", start_task) + if hasattr(btnLabel, 'toolTip'): + btnLabel.toolTip.on_leave() + btnLabel.toolTip.destroy() + btnLabel.toolTip = ToolTip(btnLabel, msg="Начать задачу") + + +def do_task(): + ic(queue.current_task.name) + while queue.current_task_time_max > time.time() and queue.run_task: + queue.current_task_time_current = queue.current_task_time_max - time.time() + m, s = divmod(queue.current_task_time_current, 60) + btnLabel["text"] = f"{queue.current_task.name}\n{int(m)} минут {int(s)} секкунд" + time.sleep(1) + if queue.run_task: + queue.current_task.count += 1 + queue.current_task.time += queue.current_task_time + if queue.current_task.id > 0: + ic(queue.task_slots) + if queue.task_slots[str(queue.current_task.id)].finished: + btns[str(queue.current_task.id)]["text"] = f"{queue.current_task.id} {queue.current_task.name} ({queue.current_task.count})\nЗавершено" + else: + btns[str(queue.current_task.id)]["text"] = f"{queue.current_task.id} {queue.current_task.name} ({queue.current_task.count})" + queue.sound() + stop_task(0) + +def submit(window, btn, num, name, description, event=0): + name = name.get() + description = description.get() + ic(name, description) + btn["text"] = f"{num} {name} (0)" + if hasattr(btn, 'toolTip'): + btn.toolTip.on_leave() + btn.toolTip.destroy() + btn.toolTip = ToolTip(btn, msg=description) + queue.create_task(name, description, num) + window.destroy() + +def finish_unfinished_task(btn, num, event): + if str(num) in queue.task_slots.keys(): + if queue.task_slots[str(num)].finished: + result = askyesno(title="Подтвержение операции", message="Отменить завершение задачи?") + else: + result = askyesno(title="Подтвержение операции", message="Завершить задачу?") + if result: + queue.task_slots[str(num)].finished = False if queue.task_slots[str(num)].finished else True + if queue.task_slots[str(num)].finished: + btns[str(num)]["text"] = f"{queue.task_slots[str(num)].id} {queue.task_slots[str(num)].name} ({queue.task_slots[str(num)].count})\nЗавершено" + else: + btns[str(num)]["text"] = f"{queue.task_slots[str(num)].id} {queue.task_slots[str(num)].name} ({queue.task_slots[str(num)].count})" + + +def click(btn, num, event): + if (str(num) in queue.task_slots.keys()): + start_task_unic(num, 1) + else: + ic(event, btn, num) + window = tk.Toplevel() + window.title("Новая задача") + window.geometry("250x200") + label = ttk.Label(window, text=f"Имя задачи:") + input_name = ttk.Entry(window) + label_description = ttk.Label(window, text=f"Описание задачи (если необходимо):") + input_description = ttk.Entry(window) + window.bind('', partial(submit, window, btn, num, input_name, input_description)) + button_sambit = ttk.Button(window, text="Создать задачу", command = partial(submit, window, btn, num, input_name, input_description)) + label.pack(fill="both") + input_name.pack(expand=True, fill="both") + label_description.pack(fill="both") + input_description.pack(expand=True, fill="both") + input_name.focus() + button_sambit.pack(expand=True, fill="both") + window.grab_set() + window.attributes("-topmost",True) + +def clear_task(btn, num, event): + result = askyesno(title="Подтвержение операции", message="Хотите удалить задачу?") + if result: + btn["text"] = f"{num} ____________ ()" + queue.delete_task(num) + + +for c in range(2): root.columnconfigure(index=c, weight=1) +for r in range(4): root.rowconfigure(index=r, weight=1) + +for i in range(0,6): + btn = ttk.Button(text=f"{i+1} ____________ ()") + btn.bind("", partial(click, btn, i+1)) + btn.bind("", partial(clear_task, btn, i+1)) + btn.bind("", partial(finish_unfinished_task, btn, i+1)) + btn.grid(column=(i%2), row=(i//2), sticky="nsew") + btns[str(i+1)] = btn +ic(btns) + +btnLabel = ttk.Button(text=f"Начать") +btnLabel.bind("", start_task) +btnLabel.bind("", stop_task) +btnLabel.grid(column=0, row=3, columnspan=2, sticky="nsew") + +main_menu = tk.Menu() +file_menu = tk.Menu() +file_menu.add_command(label="Сохранить сессию", command=save_session) +file_menu.add_command(label="Загрузить сессию", command=load_session) +# file_menu.add_separator() +# file_menu.add_command(label="Выход") + +main_menu.add_cascade(label="Файл", menu=file_menu) +main_menu.add_cascade(label="График сессии", command=build_plt) +main_menu.add_cascade(label="Закрепить", command= lambda: root.attributes("-topmost",True)) + +root.config(menu=main_menu) + +root.update_idletasks() +root.mainloop() +