Compare commits

..

No commits in common. "9d1bbdaa85ffb73b94df0dba5493c3f171b212ff" and "973c5bdfc9b00e58b8ec442ac223c673a91715b6" have entirely different histories.

44 changed files with 0 additions and 605 deletions

8
.idea/.gitignore vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.13 (pythonProject)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

View File

@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.11.2 WSL (Debian): (/home/santi/.virtualenvs/Final/bin/python)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (pythonProject)" project-jdk-type="Python SDK" />
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Final.iml" filepath="$PROJECT_DIR$/.idea/Final.iml" />
</modules>
</component>
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/app" vcs="Git" />
</component>
</project>

View File

@ -1,83 +0,0 @@
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()

View File

Binary file not shown.

View File

@ -1,89 +0,0 @@
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}

View File

@ -1,2 +0,0 @@
1|Santi|Hola como estas?
2|Santi|Andres no me copies el TO DO

View File

@ -1,4 +0,0 @@
[Chat]
server = http://localhost:2020
name = Santi

View File

@ -1,130 +0,0 @@
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()

View File

@ -1,13 +0,0 @@
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

View File

@ -1,111 +0,0 @@
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('<Configure>', lambda e: self.history_canvas.configure(scrollregion=self.history_canvas.bbox("all")))
self.history_container = tk.Frame(self.history_canvas)
self.history_container.bind("<Configure>", 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("<Return>", 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

View File

@ -1,16 +0,0 @@
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)

View File

@ -1,50 +0,0 @@
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)

View File

@ -1,4 +0,0 @@
from .ClockLabel import ClockLabel
from .UsageLabels import CPULabel, RAMLabel
__all__ = ['ClockLabel', 'CPULabel', 'RAMLabel']

View File

@ -1,22 +0,0 @@
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

View File

@ -1,26 +0,0 @@
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")

View File

@ -1,4 +0,0 @@
from .ThreadedLabel import ThreadedLabel
from .ThreadedTab import ThreadedTab
__all__ = ['ThreadedLabel', 'ThreadedTab']