diff --git a/app b/app deleted file mode 160000 index 752997b..0000000 --- a/app +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 752997b2e6303a61982840307e55c910cccd6536 diff --git a/app/ConfigMgr.py b/app/ConfigMgr.py new file mode 100644 index 0000000..9d2a8ce --- /dev/null +++ b/app/ConfigMgr.py @@ -0,0 +1,83 @@ +import tkinter as tk +from tkinter import ttk +import os +import configparser + +class ConfigMgr: + + def __init__(self, top_level, config_changed_listener=None): + self.top_level = top_level + self.config_window = None + self.config = configparser.ConfigParser() + self.__load_config() + self.config_changed_listener=config_changed_listener + self.config.read('config.ini') + + def __load_config(self): + if os.path.exists('config.ini'): + self.config.read('config.ini') + else: + print("Config file not found, creating default config file") + with open('config.ini', 'w') as f: + self.__write_default_config(f) + self.config.read('config.ini') + + def __write_default_config(self, file): + chat_config = ("[Chat]\n" + "server=http://localhost:2020\n" + "name=User\n") + file.write(chat_config) + + def display_config_window(self): + if (self.config_window is None + or not tk.Toplevel.winfo_exists(self.config_window)): + self.config_window = self.__build_config_window() + else: + self.config_window.lift() + + def __build_config_window(self): + config_window = tk.Toplevel(self.top_level) + config_window.title("Config") + config_window.geometry("400x300") + + notebook = ttk.Notebook(config_window) + notebook.pack(expand=True, fill="both") + + chat_tab = ttk.Frame(notebook) + notebook.add(chat_tab, text="Chat Config") + + chat_server_label = tk.Label(chat_tab, text="Chat Server URL") + chat_server_label.pack() + self.chat_server_variable = tk.StringVar() + try: + self.chat_server_variable.set(self.config["Chat"]["server"]) + except KeyError: + self.chat_server_variable.set("") + chat_server_input = tk.Entry(chat_tab, textvariable=self.chat_server_variable) + chat_server_input.pack() + + chat_name_label = tk.Label(chat_tab, text="Name in the Chat") + chat_name_label.pack() + self.chat_name_variable = tk.StringVar() + try: + self.chat_name_variable.set(self.config["Chat"]["name"]) + except KeyError: + self.chat_name_variable.set("") + chat_name_input = tk.Entry(chat_tab, textvariable=self.chat_name_variable) + chat_name_input.pack() + + self.save_button = tk.Button(config_window, text="Save", command=self.save_config) + self.save_button.pack(pady=10) + + return config_window + + def save_config(self): + self.config["Chat"] = {"server": self.chat_server_variable.get(), + "name": self.chat_name_variable.get()} + with open('config.ini', 'w') as configfile: + self.config.write(configfile) + + self.config_changed_listener() + + # Close window + self.config_window.destroy() \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/__pycache__/ConfigMgr.cpython-312.pyc b/app/__pycache__/ConfigMgr.cpython-312.pyc new file mode 100644 index 0000000..409d567 Binary files /dev/null and b/app/__pycache__/ConfigMgr.cpython-312.pyc differ diff --git a/app/__pycache__/ConfigMgr.cpython-313.pyc b/app/__pycache__/ConfigMgr.cpython-313.pyc new file mode 100644 index 0000000..3082899 Binary files /dev/null and b/app/__pycache__/ConfigMgr.cpython-313.pyc differ diff --git a/app/__pycache__/__init__.cpython-312.pyc b/app/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..1a35c83 Binary files /dev/null and b/app/__pycache__/__init__.cpython-312.pyc differ diff --git a/app/__pycache__/__init__.cpython-313.pyc b/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..977055b Binary files /dev/null and b/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/app/__pycache__/main.cpython-312.pyc b/app/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000..3ef0306 Binary files /dev/null and b/app/__pycache__/main.cpython-312.pyc differ diff --git a/app/chat_server/Server.py b/app/chat_server/Server.py new file mode 100644 index 0000000..d4dfb45 --- /dev/null +++ b/app/chat_server/Server.py @@ -0,0 +1,89 @@ +import threading +import time +import flask +from werkzeug.serving import make_server + + +class Server: + """ + This server does NOT use Flask's built-in development server. + Instead, it uses Werkzeug's make_server to create a WSGI server. + + This server is thread-safe and can be stopped by setting the stop_event. + + This server works as a chat server by polling, not by using websockets (to keep it simple). + """ + + def __init__(self, host: str, port: int, stop_event: threading.Event): + self.host = host + self.port = port + self.stop_event = stop_event + + # Flask definition + self.flask = flask.Flask(__name__) + self.flask.add_url_rule('/status', methods=['POST'], view_func=self.status) + self.flask.add_url_rule('/send_message', methods=['POST'], view_func=self.send_message) + self.flask.add_url_rule('/get_messages', methods=['POST'], view_func=self.get_messages) + self.server = make_server(self.host, self.port, self.flask) + self.watcher_thread = threading.Thread(target=self.__watcher) + self.server_thread = threading.Thread(target=self.server.serve_forever) + self.server_thread.start() + self.watcher_thread.start() + + # Message initialization + self.message_id = 0 + self.messages = self.__init_messages() + + def __watcher(self): + while not self.stop_event.is_set(): + time.sleep(1) + + self.shutdown() + + def shutdown(self): + self.__persist_messages() + self.server.shutdown() + + def __init_messages(self): + messages = [] + try: + with open('chat_server/messages.txt', 'r') as f: + for line in f: + parts = line.strip().split('|') + messages.append({ + 'id': int(parts[0]), + 'sender': parts[1], + 'content': parts[2] + }) + self.message_id = messages[-1]['id'] if messages else 0 + except FileNotFoundError: + with open('chat_server/messages.txt', 'w+') as f: + pass + return messages + + def __persist_messages(self): + with open('chat_server/messages.txt', 'w') as f: + for message in self.messages: + f.write(f"{message['id']}|{message['sender']}|{message['content']}\n") + + def status(self): + return 'OK' + + def send_message(self): + sender = flask.request.json['sender'] + content = flask.request.json['content'] + self.message_id += 1 + message = { + 'id': self.message_id, + 'sender': sender, + 'content': content + } + self.messages.append(message) + return {'id': self.message_id} + + def get_messages(self): + try: last_id = flask.request.json.get('last_id') + # last_id is a mandatory parameter + except AttributeError: return flask.Response('Last ID not specified', status=400) + new_messages = [msg for msg in self.messages if msg['id'] > last_id] + return {'messages': new_messages} diff --git a/app/chat_server/__init__.py b/app/chat_server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/chat_server/__pycache__/Server.cpython-312.pyc b/app/chat_server/__pycache__/Server.cpython-312.pyc new file mode 100644 index 0000000..b6f4dc0 Binary files /dev/null and b/app/chat_server/__pycache__/Server.cpython-312.pyc differ diff --git a/app/chat_server/__pycache__/Server.cpython-313.pyc b/app/chat_server/__pycache__/Server.cpython-313.pyc new file mode 100644 index 0000000..1c98264 Binary files /dev/null and b/app/chat_server/__pycache__/Server.cpython-313.pyc differ diff --git a/app/chat_server/__pycache__/__init__.cpython-312.pyc b/app/chat_server/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..5951f6e Binary files /dev/null and b/app/chat_server/__pycache__/__init__.cpython-312.pyc differ diff --git a/app/chat_server/__pycache__/__init__.cpython-313.pyc b/app/chat_server/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..76fdae9 Binary files /dev/null and b/app/chat_server/__pycache__/__init__.cpython-313.pyc differ diff --git a/app/chat_server/messages.txt b/app/chat_server/messages.txt new file mode 100644 index 0000000..b463e24 --- /dev/null +++ b/app/chat_server/messages.txt @@ -0,0 +1,2 @@ +1|Santi|Hola como estas? +2|Santi|Andres no me copies el TO DO diff --git a/app/config.ini b/app/config.ini new file mode 100644 index 0000000..f0b8909 --- /dev/null +++ b/app/config.ini @@ -0,0 +1,4 @@ +[Chat] +server = http://localhost:2020 +name = Santi + diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..bc80d0f --- /dev/null +++ b/app/main.py @@ -0,0 +1,130 @@ +import tkinter as tk +from tkinter import Menu +from tkinter import ttk +import threading + +from app.chat_server.Server import Server +from app.widgets import ClockLabel +from app.ConfigMgr import ConfigMgr +from app.widgets.ChatTab import ChatTab +from app.widgets.UsageLabels import CPULabel, RAMLabel, BatteryLabel, NetworkLabel + +# Evento para detener threads de manera segura +stop_event = threading.Event() + +def on_closing(): + """Gestiona el cierre de la aplicación de manera segura.""" + # Detiene todos los threads relacionados con stop_event + stop_event.set() + + # Cierra la ventana principal + root.quit() + root.destroy() + +def on_config_changed(): + """Actualiza la configuración del servidor y nombre de usuario en el chat.""" + chat_frame.change_server_url(config_manager.config["Chat"]["server"]) + chat_frame.change_sender_name(config_manager.config["Chat"]["name"]) + +# Crea la ventana principal +root = tk.Tk() +root.title("Responsive Window") # Título de la ventana +root.geometry("1150x700") # Tamaño inicial de la ventana + +# Inicializa el gestor de configuración +config_manager = ConfigMgr(root, config_changed_listener=on_config_changed) + +# Inicializa el servidor de chat +server = Server(host="localhost", port=2020, stop_event=stop_event) + +# Configura la ventana principal para que sea responsive +root.columnconfigure(0, weight=0) # Columna izquierda con tamaño fijo +root.columnconfigure(1, weight=1) # Columna central ajustable +root.columnconfigure(2, weight=0) # Columna derecha con tamaño fijo +root.rowconfigure(0, weight=1) # Fila principal ajustable +root.rowconfigure(1, weight=0) # Barra de estado con tamaño fijo + +# Crea el menú superior +menu_bar = Menu(root) + +# Menú Archivo +file_menu = Menu(menu_bar, tearoff=0) +file_menu.add_command(label="New") # Comando Nuevo +file_menu.add_command(label="Open") # Comando Abrir +file_menu.add_separator() # Separador visual +file_menu.add_command(label="Config", command=config_manager.display_config_window) # Configuración +file_menu.add_command(label="Exit", command=on_closing) # Salir + +# Menú Edición +edit_menu = Menu(menu_bar, tearoff=0) +edit_menu.add_command(label="Copy") # Comando Copiar +edit_menu.add_command(label="Paste") # Comando Pegar + +# Añade los menús al menú principal +menu_bar.add_cascade(label="File", menu=file_menu) +menu_bar.add_cascade(label="Edit", menu=edit_menu) + +# Asigna el menú a la ventana principal +root.config(menu=menu_bar) + +# Crea los marcos laterales y central +frame_left = tk.Frame(root, bg="lightblue", width=200) # Marco izquierdo +frame_center = tk.Frame(root, bg="white") # Marco central +chat_frame = ChatTab(root, chat_server_url=config_manager.config["Chat"]["server"], + sender_name=config_manager.config["Chat"]["name"], + stop_event=stop_event, width=200, bg="lightgreen") # Marco derecho para el chat + +# Coloca los marcos en la cuadrícula +frame_left.grid(row=0, column=0, sticky="ns") # Marco izquierdo +frame_center.grid(row=0, column=1, sticky="nsew") # Marco central +chat_frame.grid(row=0, column=2, sticky="ns") # Marco derecho + +# Configura tamaños fijos para los marcos laterales +frame_left.grid_propagate(False) +chat_frame.grid_propagate(False) + +# Divide el marco central en dos partes (superior ajustable e inferior fijo) +frame_center.rowconfigure(0, weight=1) # Parte superior ajustable +frame_center.rowconfigure(1, weight=0) # Parte inferior fija +frame_center.columnconfigure(0, weight=1) # Ancho ajustable + +# Crea sub-marcos dentro del marco central +frame_top = tk.Frame(frame_center, bg="lightyellow") # Parte superior +frame_bottom = tk.Frame(frame_center, bg="lightgray", height=100) # Parte inferior + +# Coloca los sub-marcos en el marco central +frame_top.grid(row=0, column=0, sticky="nsew") # Parte superior +frame_bottom.grid(row=1, column=0, sticky="ew") # Parte inferior + +# Fija el tamaño de la parte inferior +frame_bottom.grid_propagate(False) + +# Crea la barra de estado +status_bar = tk.Label(root, text="Status bar", bg="lightgray", anchor="w") # Barra de estado +status_bar.grid(row=1, column=0, columnspan=3, sticky="ew") + +# Configura un cuaderno (notebook) para widgets +style = ttk.Style() +style.configure("CustomNotebook.TNotebook.Tab", font=("Arial", 12, "bold")) +notebook = ttk.Notebook(frame_top, style="CustomNotebook.TNotebook") +notebook.pack(fill="both", expand=True) + +# Añade etiquetas de uso del sistema +label_cpu = CPULabel(status_bar, bg="lightgreen", font=("Helvetica", 10), relief="groove", anchor="center", width=10, stop_event=stop_event) # Uso de CPU +label_ram = RAMLabel(status_bar, bg="lightcoral", font=("Helvetica", 10), relief="groove", anchor="center", width=10, stop_event=stop_event) # Uso de RAM +label_battery = BatteryLabel(status_bar, bg="lightblue", font=("Helvetica", 10), relief="groove", anchor="center", width=20, stop_event=stop_event) # Batería +label_net = NetworkLabel(status_bar, text="Network", bg="lightpink", font=("Helvetica", 10), relief="groove", anchor="center", width=20, stop_event=stop_event) # Red +label_time = ClockLabel(status_bar, font=("Helvetica", 12), bd=1, fg="darkblue", relief="sunken", anchor="center", width=20, stop_event=stop_event) # Reloj + +# Coloca las etiquetas en la barra de estado +label_cpu.pack(side="left", fill="both", expand=True) +label_ram.pack(side="left", fill="both", expand=True) +label_battery.pack(side="left", fill="both", expand=True) +label_net.pack(side="left", fill="both", expand=True) +label_time.pack(side="right", fill="both", expand=True) + +# Configura la acción para el cierre de la ventana +root.protocol("WM_DELETE_WINDOW", on_closing) + +# Inicia la aplicación +root.mainloop() \ No newline at end of file diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 0000000..3856835 --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,13 @@ +blinker==1.9.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +click==8.1.7 +Flask==3.1.0 +idna==3.10 +itsdangerous==2.2.0 +Jinja2==3.1.4 +MarkupSafe==3.0.2 +psutil==6.1.0 +requests==2.32.3 +urllib3==2.2.3 +Werkzeug==3.1.3 diff --git a/app/widgets/ChatTab.py b/app/widgets/ChatTab.py new file mode 100644 index 0000000..925854d --- /dev/null +++ b/app/widgets/ChatTab.py @@ -0,0 +1,111 @@ +import time +import tkinter as tk +from tkinter import ttk +from tkinter.ttk import Notebook +from tkinter import Tk + +import requests + +from app.widgets.abc import ThreadedTab + + +class ChatTab(ThreadedTab): + + def __init__(self, root: Notebook | Tk, chat_server_url: str, sender_name: str, **kwargs): + self.chat_server_url = chat_server_url + self.sender_name = sender_name + self.conn = False + self.last_msg = 0 + super().__init__(root, **kwargs) + + def task(self): + try: + self.get_messages() + if not self.conn: + self.connected() + self.conn = True + except requests.ConnectionError: + self.disconnected() + time.sleep(1) + + def build(self): + # Create the main frame for the chat interface + self.chat_frame = tk.Frame(self) + self.chat_frame.pack(fill="both", expand=True) + + # Create the status label + self.status_label = tk.Label(self.chat_frame, text="", font=("Helvetica", 16)) + self.status_label.pack(fill="x") + + # Create the history frame with a scrollbar + self.history_frame = tk.Frame(self.chat_frame) + self.history_frame.pack(fill="both", expand=True) + + self.history_canvas = tk.Canvas(self.history_frame) + self.history_canvas.pack(side="left", fill="both", expand=True) + + self.scrollbar = ttk.Scrollbar(self.history_frame, orient="vertical", command=self.history_canvas.yview) + self.scrollbar.pack(side="right", fill="y") + + self.history_canvas.configure(yscrollcommand=self.scrollbar.set) + self.history_canvas.bind('', lambda e: self.history_canvas.configure(scrollregion=self.history_canvas.bbox("all"))) + + self.history_container = tk.Frame(self.history_canvas) + self.history_container.bind("", self.on_frame_configure) + self.history_canvas.create_window((0, 0), window=self.history_container, anchor="nw") + + # Create the input frame at the bottom + self.input_frame = tk.Frame(self.chat_frame) + self.input_frame.pack(fill="x") + + self.message_entry = tk.Entry(self.input_frame) + self.message_entry.pack(side="left", fill="x", expand=True, padx=5, pady=5) + self.message_entry.bind("", lambda event: self.send_message()) # Bind Enter key to send_message + + self.send_button = tk.Button(self.input_frame, text="Send", command=self.send_message) + self.send_button.pack(side="right", padx=5, pady=5) + + def on_frame_configure(self, event): + self.history_canvas.configure(scrollregion=self.history_canvas.bbox("all")) + + def send_message(self): + message = self.message_entry.get() + if message and self.conn: + response = requests.post(f"{self.chat_server_url}/send_message", json={"content": message, "sender": self.sender_name}) + self.last_msg = response.json().get("id") + self.display_message(self.sender_name, message) + self.message_entry.delete(0, tk.END) + + def display_message(self, sender, message): + message_frame = tk.Frame(self.history_container, bg="lightgray", pady=5) + message_frame.pack(fill="x", padx=5, pady=5) + + message_label = tk.Label(message_frame, text=f"{sender}: {message}", anchor="w", justify="left", wraplength=300) + message_label.pack(fill="x") + + self.history_canvas.update_idletasks() + self.history_canvas.yview_moveto(1) + + def get_messages(self): + response = requests.post(f"{self.chat_server_url}/get_messages", json={"last_id": self.last_msg}) + messages = response.json().get("messages") + for message in messages: + self.display_message(message["sender"], message["content"]) + + self.last_msg = messages[-1]["id"] if messages else self.last_msg + + def connected(self): + self.conn = True + self.status_label.config(text="Connected to chat", fg="green") + self.send_button.config(state="normal") + + def disconnected(self): + self.conn = False + self.status_label.config(text="Disconnected from Chat", fg="red") + self.send_button.config(state="disabled") + + def change_sender_name(self, new_name: str): + self.sender_name = new_name + + def change_server_url(self, new_url: str): + self.chat_server_url = new_url \ No newline at end of file diff --git a/app/widgets/ClockLabel.py b/app/widgets/ClockLabel.py new file mode 100644 index 0000000..c4f784c --- /dev/null +++ b/app/widgets/ClockLabel.py @@ -0,0 +1,16 @@ +import time +from datetime import datetime + +from .abc import ThreadedLabel + +class ClockLabel(ThreadedLabel): + + def task(self): + now = datetime.now() + day_of_week = now.strftime("%A") + time_str = now.strftime("%H:%M:%S") + date_str = now.strftime("%Y-%m-%d") + label_text = f"{day_of_week}, {date_str} - {time_str}" + try: self.config(text=label_text) + except RuntimeError: pass + time.sleep(0.5) \ No newline at end of file diff --git a/app/widgets/UsageLabels.py b/app/widgets/UsageLabels.py new file mode 100644 index 0000000..8e7e709 --- /dev/null +++ b/app/widgets/UsageLabels.py @@ -0,0 +1,50 @@ +import time +import psutil + +from .abc import ThreadedLabel + +class CPULabel(ThreadedLabel): + + def task(self, *args): + cpu_percent = psutil.cpu_percent() + try: self.config(text=f'CPU: {cpu_percent}%') + except RuntimeError: pass + time.sleep(1) + +class RAMLabel(ThreadedLabel): + + def task(self, *args): + memory = psutil.virtual_memory() + try: self.config(text=f'RAM: {memory.percent}%') + except RuntimeError: pass + time.sleep(1) + +class BatteryLabel(ThreadedLabel): + + def task(self, *args): + battery = psutil.sensors_battery() + if battery is None: + self.config(text='Battery: N/A') + return + battery_percent = battery.percent + is_charging = battery.power_plugged + time_left = battery.secsleft + text = f'Battery: {battery_percent:.0f}%' + if is_charging: + text += ', Plugged in' + else: + text += f', ({time_left // 3600}h {time_left % 3600 // 60}m left)' + + try: self.config(text=text) + except RuntimeError: pass # Catch update on closed widget + + time.sleep(1) + +class NetworkLabel(ThreadedLabel): + + def task(self, *args): + network = psutil.net_io_counters() + try: self.config(text=f'Net: {network.bytes_sent / 1024 / 1024:.2f} MB snt,' + f' {network.bytes_recv / 1024 / 1024:.2f} MB rcv') + except RuntimeError: pass # Catch update on closed widget + time.sleep(1) \ No newline at end of file diff --git a/app/widgets/__init__.py b/app/widgets/__init__.py new file mode 100644 index 0000000..3f69feb --- /dev/null +++ b/app/widgets/__init__.py @@ -0,0 +1,4 @@ +from .ClockLabel import ClockLabel +from .UsageLabels import CPULabel, RAMLabel + +__all__ = ['ClockLabel', 'CPULabel', 'RAMLabel'] \ No newline at end of file diff --git a/app/widgets/__pycache__/ChatTab.cpython-312.pyc b/app/widgets/__pycache__/ChatTab.cpython-312.pyc new file mode 100644 index 0000000..a2bf680 Binary files /dev/null and b/app/widgets/__pycache__/ChatTab.cpython-312.pyc differ diff --git a/app/widgets/__pycache__/ChatTab.cpython-313.pyc b/app/widgets/__pycache__/ChatTab.cpython-313.pyc new file mode 100644 index 0000000..5f34974 Binary files /dev/null and b/app/widgets/__pycache__/ChatTab.cpython-313.pyc differ diff --git a/app/widgets/__pycache__/ClockLabel.cpython-312.pyc b/app/widgets/__pycache__/ClockLabel.cpython-312.pyc new file mode 100644 index 0000000..2471248 Binary files /dev/null and b/app/widgets/__pycache__/ClockLabel.cpython-312.pyc differ diff --git a/app/widgets/__pycache__/ClockLabel.cpython-313.pyc b/app/widgets/__pycache__/ClockLabel.cpython-313.pyc new file mode 100644 index 0000000..82abdbb Binary files /dev/null and b/app/widgets/__pycache__/ClockLabel.cpython-313.pyc differ diff --git a/app/widgets/__pycache__/UsageLabels.cpython-312.pyc b/app/widgets/__pycache__/UsageLabels.cpython-312.pyc new file mode 100644 index 0000000..1281f7e Binary files /dev/null and b/app/widgets/__pycache__/UsageLabels.cpython-312.pyc differ diff --git a/app/widgets/__pycache__/UsageLabels.cpython-313.pyc b/app/widgets/__pycache__/UsageLabels.cpython-313.pyc new file mode 100644 index 0000000..974e62f Binary files /dev/null and b/app/widgets/__pycache__/UsageLabels.cpython-313.pyc differ diff --git a/app/widgets/__pycache__/__init__.cpython-312.pyc b/app/widgets/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..d1bb0ab Binary files /dev/null and b/app/widgets/__pycache__/__init__.cpython-312.pyc differ diff --git a/app/widgets/__pycache__/__init__.cpython-313.pyc b/app/widgets/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..2b53f3e Binary files /dev/null and b/app/widgets/__pycache__/__init__.cpython-313.pyc differ diff --git a/app/widgets/abc/ThreadedLabel.py b/app/widgets/abc/ThreadedLabel.py new file mode 100644 index 0000000..4e37887 --- /dev/null +++ b/app/widgets/abc/ThreadedLabel.py @@ -0,0 +1,22 @@ +import threading +from abc import ABC, abstractmethod +from threading import Thread +from tkinter import Label + + +class ThreadedLabel(ABC, Label): + + def __init__(self, root: Label, stop_event: threading.Event, **kwargs): + super().__init__(root, **kwargs) + self.stop_event = stop_event + self.declared_thread: Thread = threading.Thread(target=self.__loop) + + self.declared_thread.start() + + def __loop(self): + while not self.stop_event.is_set(): + self.task() + + @abstractmethod + def task(self, *args): + pass \ No newline at end of file diff --git a/app/widgets/abc/ThreadedTab.py b/app/widgets/abc/ThreadedTab.py new file mode 100644 index 0000000..0d90e5e --- /dev/null +++ b/app/widgets/abc/ThreadedTab.py @@ -0,0 +1,26 @@ +import threading +from abc import ABC, abstractmethod +from tkinter import Tk +from tkinter.ttk import Notebook +from tkinter import Frame + +class ThreadedTab(ABC, Frame): + + def __init__(self, root: Notebook | Tk, stop_event: threading.Event, **kwargs): + super().__init__(root, **kwargs) + self.stop_event = stop_event + self._thread = threading.Thread(target=self.__loop) + self.build() + self._thread.start() + + def __loop(self): + while not self.stop_event.is_set(): + self.task() + + @abstractmethod + def build(self): + pass + + @abstractmethod + def task(self, *args): + raise NotImplementedError("Method not implemented") \ No newline at end of file diff --git a/app/widgets/abc/__init__.py b/app/widgets/abc/__init__.py new file mode 100644 index 0000000..075749b --- /dev/null +++ b/app/widgets/abc/__init__.py @@ -0,0 +1,4 @@ +from .ThreadedLabel import ThreadedLabel +from .ThreadedTab import ThreadedTab + +__all__ = ['ThreadedLabel', 'ThreadedTab'] \ No newline at end of file diff --git a/app/widgets/abc/__pycache__/ThreadedLabel.cpython-312.pyc b/app/widgets/abc/__pycache__/ThreadedLabel.cpython-312.pyc new file mode 100644 index 0000000..9b99c2e Binary files /dev/null and b/app/widgets/abc/__pycache__/ThreadedLabel.cpython-312.pyc differ diff --git a/app/widgets/abc/__pycache__/ThreadedLabel.cpython-313.pyc b/app/widgets/abc/__pycache__/ThreadedLabel.cpython-313.pyc new file mode 100644 index 0000000..41aebf3 Binary files /dev/null and b/app/widgets/abc/__pycache__/ThreadedLabel.cpython-313.pyc differ diff --git a/app/widgets/abc/__pycache__/ThreadedTab.cpython-312.pyc b/app/widgets/abc/__pycache__/ThreadedTab.cpython-312.pyc new file mode 100644 index 0000000..ae9a589 Binary files /dev/null and b/app/widgets/abc/__pycache__/ThreadedTab.cpython-312.pyc differ diff --git a/app/widgets/abc/__pycache__/ThreadedTab.cpython-313.pyc b/app/widgets/abc/__pycache__/ThreadedTab.cpython-313.pyc new file mode 100644 index 0000000..beb64f2 Binary files /dev/null and b/app/widgets/abc/__pycache__/ThreadedTab.cpython-313.pyc differ diff --git a/app/widgets/abc/__pycache__/__init__.cpython-312.pyc b/app/widgets/abc/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..66e19c4 Binary files /dev/null and b/app/widgets/abc/__pycache__/__init__.cpython-312.pyc differ diff --git a/app/widgets/abc/__pycache__/__init__.cpython-313.pyc b/app/widgets/abc/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..9bfc320 Binary files /dev/null and b/app/widgets/abc/__pycache__/__init__.cpython-313.pyc differ