diff --git a/app/main.py b/app/main.py index d915686..168c276 100644 --- a/app/main.py +++ b/app/main.py @@ -1,202 +1,53 @@ import tkinter as tk import threading -import time import datetime -from tkinter import Menu # Importar el widget Menu -from tkinter import ttk # Importar el widget ttk -from correo_server.EmailTab import MailTab -from correo_server.InboxTab import InboxTab -from correo_server.MailClient import MailClient +from tkinter import Menu, ttk + +# Importación de widgets personalizados from hilos.ChatWidget import ChatWidget from hilos.MusicPlayer import MusicPlayer from hilos.WeatherWidget import WeatherWidget from hilos.SystemMonitor import SystemMonitor from hilos.ApplicationLauncher import ApplicationLauncher from hilos.LanguageChart import LanguageChart -from solapas.MusicDownloader import MusicDownloader -from solapas.EconomyBitcoinChart import EconomyBitcoinChart -from solapas.SQLQueryExecutor import SQLQueryExecutor -from solapas.TicTacToe import TicTacToe -from solapas.WebScraperToDB import WebScraperToDB - - -# Crear instancia del cliente de correo con configuración de puertos -email_client = MailClient() - -# Clave de API de OpenWeatherMap -API_KEY = "1fa8fd05b650773bbc3f2130657e808a" - -def update_time(status_bar): - """Función que actualiza la hora y el día de la semana en un label""" - while True: - # Obtener la fecha y hora actual - now = datetime.datetime.now() - day_of_week = now.strftime("%A") # Día de la semana - time_str = now.strftime("%H:%M:%S") # Hora en formato HH:MM:SS - date_str = now.strftime("%Y-%m-%d") # Fecha en formato YYYY-MM-DD - label_text = f"{day_of_week}, {date_str} - {time_str}" - - # Actualizar el label (debemos usar `after` para asegurarnos que se actualice en el hilo principal de Tkinter) - label_fecha_hora.after(1000, status_bar.config, {"text": label_text}) - - # Espera 1 segundo antes de actualizar de nuevo - time.sleep(1) # Crear la ventana principal root = tk.Tk() root.title("Ventana Responsive") root.geometry("1000x700") # Tamaño inicial -# Configurar la ventana principal para que sea responsive -root.columnconfigure(0, weight=0) # Columna izquierda, tamaño fijo -root.columnconfigure(1, weight=1) # Columna central, tamaño variable -root.columnconfigure(2, weight=0) # Columna derecha, tamaño fijo -root.rowconfigure(0, weight=1) # Fila principal, tamaño variable -root.rowconfigure(1, weight=0) # Barra de estado, tamaño fijo - # Crear el menú superior menu_bar = Menu(root) - -file_menu = Menu(menu_bar, tearoff=0) -file_menu.add_command(label="Nuevo") -file_menu.add_command(label="Abrir") -file_menu.add_separator() -file_menu.add_command(label="Salir", command=root.quit) - -edit_menu = Menu(menu_bar, tearoff=0) -edit_menu.add_command(label="Copiar") -edit_menu.add_command(label="Pegar") - -help_menu = Menu(menu_bar, tearoff=0) -help_menu.add_command(label="Acerca de") - -menu_bar.add_cascade(label="Archivo", menu=file_menu) -menu_bar.add_cascade(label="Editar", menu=edit_menu) -menu_bar.add_cascade(label="Ayuda", menu=help_menu) - root.config(menu=menu_bar) -# Crear los frames laterales y el central +# Frames laterales y central frame_izquierdo = tk.Frame(root, bg="lightblue", width=150) frame_central = tk.Frame(root, bg="white") frame_derecho = tk.Frame(root, bg="lightgreen", width=150) -# Colocar los frames laterales y el central frame_izquierdo.grid(row=0, column=0, sticky="ns") frame_central.grid(row=0, column=1, sticky="nsew") frame_derecho.grid(row=0, column=2, sticky="ns") -# Configurar los tamaños fijos de los frames laterales -frame_izquierdo.grid_propagate(False) -frame_derecho.grid_propagate(False) - -# Integrar el widget del clima en el panel izquierdo -weather_widget = WeatherWidget(frame_izquierdo, API_KEY) - -# Añadir el lanzador de aplicaciones al panel izquierdo +# Añadir widgets a los paneles +weather_widget = WeatherWidget(frame_izquierdo, "API_KEY") app_launcher = ApplicationLauncher(frame_izquierdo) - -# Añadir gráfico de lenguajes al panel izquierdo language_chart = LanguageChart(frame_izquierdo) - -# Crear el widget de Chat en el panel derecho con más espacio chat_widget = ChatWidget(frame_derecho) - -# Agregar el reproductor de música al panel derecho, en la parte inferior music_player = MusicPlayer(frame_derecho) -# Dividir el frame central en dos partes (superior variable e inferior fija) -frame_central.rowconfigure(0, weight=1) # Parte superior, tamaño variable -frame_central.rowconfigure(1, weight=0) # Parte inferior, tamaño fijo -frame_central.columnconfigure(0, weight=1) # Ocupa toda la anchura - -# Crear subframes dentro del frame central -frame_superior = tk.Frame(frame_central, bg="lightyellow") -frame_inferior = tk.Frame(frame_central, bg="lightgray", height=100) - -# Colocar los subframes dentro del frame central -frame_superior.grid(row=0, column=0, sticky="nsew") -frame_inferior.grid(row=1, column=0, sticky="ew") - -# Fijar el tamaño de la parte inferior -frame_inferior.grid_propagate(False) - -# Crear un evento de parada -stop_event = threading.Event() - -# Definir el manejador para el cierre de la ventana -def on_closing(): - """Cerrar correctamente la aplicación.""" - stop_event.set() # Detener los hilos - root.destroy() # Destruir la ventana principal - -# Configurar el manejador de cierre -root.protocol("WM_DELETE_WINDOW", on_closing) - -# Crear la barra de estado +# Barra de estado barra_estado = tk.Label(root, text="Barra de estado", bg="lightgray", anchor="w") barra_estado.grid(row=1, column=0, columnspan=3, sticky="ew") # Inicializar el monitor del sistema +stop_event = threading.Event() system_monitor = SystemMonitor(barra_estado, stop_event) -# Notebook para las pestañas -style = ttk.Style() -style.configure("CustomNotebook.TNotebook.Tab", font=("Arial", 12, "bold")) -notebook = ttk.Notebook(frame_superior, style="CustomNotebook.TNotebook") -notebook.pack(fill="both", expand=True) +# Manejar el cierre de la aplicación +def on_closing(): + stop_event.set() + root.destroy() -# Crear la Solapa 1 y añadir el downloader -tab1 = ttk.Frame(notebook) -notebook.add(tab1, text="Downloader", padding=4) - -# Añadir el downloader a la Solapa 1 -music_downloader = MusicDownloader(tab1) - -# Crear la Solapa 2 y añadir los gráficos -tab2 = ttk.Frame(notebook) -notebook.add(tab2, text="Graphics", padding=4) - -# Añadir los gráficos de economía mundial y Bitcoin a la Solapa 2 -economy_bitcoin_chart = EconomyBitcoinChart(tab2) - -# Crear la Solapa 3 y añadir el Tic Tac Toe -tab3 = ttk.Frame(notebook) -notebook.add(tab3, text="Tic Tac Toe", padding=4) - -# Añadir el juego de Tic Tac Toe a la Solapa 3 -tic_tac_toe = TicTacToe(tab3) - -# Crear la Solapa 4 y añadir el SQL Query Executor -tab4 = ttk.Frame(notebook) -notebook.add(tab4, text="SQL Querys", padding=4) - -# Añadir el ejecutor de consultas SQL a la Solapa 4 -sql_query_executor = SQLQueryExecutor(tab4) - -# Crear la Solapa 5 y añadir el Web Scraper -tab5 = ttk.Frame(notebook) -notebook.add(tab5, text="Web Scraper", padding=4) - -# Añadir el widget de Web Scraper a la Solapa 5 -web_scraper = WebScraperToDB(tab5) - -# Crear pestañas de correo -MailTab(notebook, email_client) -InboxTab(notebook, email_client) - -# Barra de estado -# Dividir la barra de estado en 4 labels -# Usar pack para alinear los labels horizontalmente -label_fecha_hora = tk.Label(barra_estado, text="Hilo fecha-hora", font=("Helvetica", 14), bd=1, fg="blue", relief="sunken", anchor="w", width=20, padx=10) - -label_fecha_hora.pack(side="right", fill="x", expand=True) -# barra_estado.grid(row=1, column=0, columnspan=3, sticky="ew") - - -update_thread = threading.Thread(target=update_time, args=(label_fecha_hora,)) -update_thread.daemon = True # Hacemos el hilo un demonio para que termine con la app -update_thread.start() - -# Ejecución de la aplicación -root.mainloop() \ No newline at end of file +root.protocol("WM_DELETE_WINDOW", on_closing) +root.mainloop() diff --git a/app/musicplayer/CENTRAL CEE - MUST BE (LYRICS).mp3 b/app/musicplayer/CENTRAL CEE - MUST BE (LYRICS).mp3 new file mode 100644 index 0000000..15f8e8c Binary files /dev/null and b/app/musicplayer/CENTRAL CEE - MUST BE (LYRICS).mp3 differ diff --git a/app/musicplayer/CENTRAL CEE - TRUTH IN THE LIES (FEAT. LIL DURK) (MUSIC VIDEO).webm b/app/musicplayer/CENTRAL CEE - TRUTH IN THE LIES (FEAT. LIL DURK) (MUSIC VIDEO).webm new file mode 100644 index 0000000..ce7a7d8 Binary files /dev/null and b/app/musicplayer/CENTRAL CEE - TRUTH IN THE LIES (FEAT. LIL DURK) (MUSIC VIDEO).webm differ diff --git a/correo_server/InboxTab.py b/correo_server/InboxTab.py new file mode 100644 index 0000000..f66806f --- /dev/null +++ b/correo_server/InboxTab.py @@ -0,0 +1,81 @@ +import tkinter as tk +from tkinter import ttk, messagebox +import threading + +class InboxTab: + def __init__(self, parent, email_client): + self.email_client = email_client + + self.frame = ttk.Frame(parent) + parent.add(self.frame, text="Bandeja de Entrada") + + ttk.Label(self.frame, text="Correo electrónico:").grid(row=0, column=0, sticky="e", padx=5, pady=5) + self.entry_email = ttk.Entry(self.frame, width=40) + self.entry_email.grid(row=0, column=1, padx=5, pady=5) + + ttk.Label(self.frame, text="Contraseña:").grid(row=1, column=0, sticky="e", padx=5, pady=5) + self.entry_password = ttk.Entry(self.frame, show="*", width=40) + self.entry_password.grid(row=1, column=1, padx=5, pady=5) + + self.load_button = ttk.Button(self.frame, text="Cargar Correos", command=self.load_emails_thread) + self.load_button.grid(row=2, column=1, sticky="e", padx=5, pady=5) + + self.email_listbox = tk.Listbox(self.frame, width=80, height=20) + self.email_listbox.grid(row=3, column=0, columnspan=2, padx=5, pady=5) + self.email_listbox.bind("", self.show_email) + + self.delete_button = ttk.Button(self.frame, text="Eliminar Correo", command=self.delete_email_thread) + self.delete_button.grid(row=4, column=1, sticky="e", padx=5, pady=5) + + self.emails = [] + + def load_emails_thread(self): + threading.Thread(target=self.load_emails).start() + + def delete_email_thread(self): + threading.Thread(target=self.delete_email).start() + + def load_emails(self): + email_address = self.entry_email.get() + password = self.entry_password.get() + + if not email_address or not password: + messagebox.showerror("Error", "Debe ingresar su correo y contraseña.") + return + + self.emails = [] + self.email_listbox.delete(0, tk.END) + + emails = self.email_client.fetch_emails(email_address, password) + + if isinstance(emails, str): + messagebox.showerror("Error", emails) + return + + for email_id, subject, sender in emails: + self.email_listbox.insert(tk.END, f"{subject} - {sender}") + self.emails.append((email_id, subject, sender)) + + def delete_email(self): + selected_index = self.email_listbox.curselection() + if not selected_index: + messagebox.showwarning("Advertencia", "Seleccione un correo para eliminar.") + return + + email_id, _, _ = self.emails[selected_index[0]] + email_address = self.entry_email.get() + password = self.entry_password.get() + + result = self.email_client.delete_email(email_address, password, email_id) + + if "eliminado" in result: + self.email_listbox.delete(selected_index) + messagebox.showinfo("Éxito", result) + else: + messagebox.showerror("Error", result) + + def show_email(self, event): + selected_index = self.email_listbox.curselection() + if selected_index: + email_id, subject, sender = self.emails[selected_index[0]] + messagebox.showinfo("Correo", f"Asunto: {subject}\nDe: {sender}") diff --git a/hilos/ApplicationLauncher.py b/hilos/ApplicationLauncher.py index d831832..32ace0f 100644 --- a/hilos/ApplicationLauncher.py +++ b/hilos/ApplicationLauncher.py @@ -3,93 +3,40 @@ import threading import subprocess import os - class ApplicationLauncher: def __init__(self, parent): - """ - Inicializa los botones para lanzar aplicaciones con detección automática de rutas. - - Args: - parent (tk.Frame): Frame donde se colocarán los botones. - """ + """Crea botones para abrir aplicaciones como VS Code, Eclipse y PyCharm.""" self.parent = parent - # Detectar rutas automáticamente - self.vscode_path = self.detect_path(["C:\\Program Files\\Microsoft VS Code\\Code.exe", - "C:\\Users\\%USERNAME%\\AppData\\Local\\Programs\\Microsoft VS Code\\Code.exe"]) - self.eclipse_path = self.detect_path(["C:\\eclipse\\eclipse.exe", - "C:\\Program Files\\Eclipse Foundation\\eclipse.exe"]) - self.pycharm_path = self.detect_path(["C:\\Program Files\\JetBrains\\PyCharm\\bin\\pycharm64.exe", - "C:\\Program Files\\JetBrains\\PyCharm Community Edition 2023.1.4\\bin\\pycharm64.exe"]) + # Detectar rutas de instalación + self.vscode_path = self.detect_path(["C:\\Program Files\\Microsoft VS Code\\Code.exe"]) + self.eclipse_path = self.detect_path(["C:\\eclipse\\eclipse.exe"]) + self.pycharm_path = self.detect_path(["C:\\Program Files\\JetBrains\\PyCharm\\bin\\pycharm64.exe"]) - # Título para el grupo de botones - title = tk.Label(self.parent, text="Aplicaciones", font=("Helvetica", 14, "bold"), bg="lightblue") - title.pack(pady=10) - - # Botón para abrir Visual Studio Code - self.vscode_button = tk.Button(self.parent, text="Visual Code", command=self.launch_vscode, bg="lightgreen", - font=("Helvetica", 10)) - self.vscode_button.pack(fill="x", pady=2) - - # Botón para abrir Eclipse - self.eclipse_button = tk.Button(self.parent, text="Eclipse", command=self.launch_eclipse, bg="lightgreen", - font=("Helvetica", 10)) - self.eclipse_button.pack(fill="x", pady=2) - - # Botón para abrir PyCharm - self.pycharm_button = tk.Button(self.parent, text="PyCharm", command=self.launch_pycharm, bg="lightgreen", - font=("Helvetica", 10)) - self.pycharm_button.pack(fill="x", pady=2) + # Crear botones de lanzamiento + self.create_button("Visual Code", self.launch_vscode) + self.create_button("Eclipse", self.launch_eclipse) + self.create_button("PyCharm", self.launch_pycharm) def detect_path(self, paths): - """ - Detecta automáticamente la primera ruta existente de una lista de posibles rutas. - - Args: - paths (list): Lista de rutas posibles para un ejecutable. - - Returns: - str: La primera ruta válida encontrada, o None si no se encuentra ninguna. - """ + """Detecta la primera ruta válida de una lista de rutas posibles.""" for path in paths: - path = os.path.expandvars(path) # Expande variables como %USERNAME% if os.path.exists(path): return path return None + def create_button(self, name, command): + """Crea un botón con la función de lanzar una aplicación.""" + button = tk.Button(self.parent, text=name, command=command, bg="lightgreen") + button.pack(fill="x", pady=2) + def launch_vscode(self): - """Lanza Visual Studio Code si se encuentra la ruta.""" + """Lanza Visual Studio Code.""" self.launch_application(self.vscode_path, "Visual Studio Code") - def launch_eclipse(self): - """Lanza Eclipse si se encuentra la ruta.""" - self.launch_application(self.eclipse_path, "Eclipse") - - def launch_pycharm(self): - """Lanza PyCharm si se encuentra la ruta.""" - self.launch_application(self.pycharm_path, "PyCharm") - def launch_application(self, path, name): - """ - Lanza una aplicación si la ruta es válida. - - Args: - path (str): Ruta al ejecutable. - name (str): Nombre de la aplicación (para mensajes de error). - """ + """Ejecuta la aplicación si la ruta es válida.""" if path: - threading.Thread(target=self.run_command, args=([path],), daemon=True).start() + threading.Thread(target=subprocess.run, args=([path],), daemon=True).start() else: - print(f"No se encontró {name}. Por favor, instálalo o configura la ruta.") - - def run_command(self, command): - """ - Ejecuta un comando del sistema operativo para abrir una aplicación. - - Args: - command (list): Comando a ejecutar (lista de argumentos). - """ - try: - subprocess.run(command, check=True) - except Exception as e: - print(f"Error al intentar abrir la aplicación: {e}") + print(f"{name} no está instalado.") diff --git a/hilos/ChatWidget.py b/hilos/ChatWidget.py index 42be5e5..96d2fda 100644 --- a/hilos/ChatWidget.py +++ b/hilos/ChatWidget.py @@ -3,71 +3,55 @@ from tkinter import scrolledtext import socket import threading - class ChatWidget: def __init__(self, parent): + """Widget de chat con conexión a un servidor por sockets.""" self.parent = parent - self.frame = tk.Frame(self.parent, bg="lightgreen", width=200, height=300) # Ajustar tamaño del frame - self.frame.pack(fill="x", expand=False, padx=10, pady=10) + self.frame = tk.Frame(self.parent, bg="lightgreen") + self.frame.pack(fill="x", padx=10, pady=10) - # Label superior - self.label = tk.Label(self.frame, text="Chat", font=("Arial", 14, "bold"), fg="red", bg="lightgreen") - self.label.pack(pady=5) - - # Caja de texto para los mensajes - self.chat_display = scrolledtext.ScrolledText( - self.frame, wrap=tk.WORD, state="disabled", width=40, height=10 # Reducir dimensiones - ) + # Área de texto para mostrar mensajes + self.chat_display = scrolledtext.ScrolledText(self.frame, wrap=tk.WORD, state="disabled", width=40, height=10) self.chat_display.pack(pady=5) - # Campo de entrada para escribir mensajes - self.message_entry = tk.Entry(self.frame, width=35) # Reducir ancho + # Campo de entrada de mensajes + self.message_entry = tk.Entry(self.frame, width=35) self.message_entry.pack(pady=5) self.message_entry.bind("", self.send_message) - # Botón para enviar mensajes - self.send_button = tk.Button(self.frame, text="Enviar", command=self.send_message, width=10) # Reducir tamaño + # Botón de enviar mensaje + self.send_button = tk.Button(self.frame, text="Enviar", command=self.send_message) self.send_button.pack(pady=5) - # Configuración del cliente socket + # Configuración del socket self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.server_address = ("127.0.0.1", 3333) # Cambiar a la IP del servidor si es necesario + self.server_address = ("127.0.0.1", 3333) try: self.client_socket.connect(self.server_address) threading.Thread(target=self.receive_messages, daemon=True).start() - except Exception as e: - self.display_message(f"[ERROR] No se pudo conectar al servidor: {e}") + except: + self.display_message("[ERROR] No se pudo conectar al servidor.") def send_message(self, event=None): + """Envía un mensaje al servidor.""" message = self.message_entry.get() if message: - try: - self.client_socket.send(message.encode("utf-8")) - self.message_entry.delete(0, tk.END) - except Exception as e: - self.display_message(f"[ERROR] No se pudo enviar el mensaje: {e}") + self.client_socket.send(message.encode("utf-8")) + self.message_entry.delete(0, tk.END) def receive_messages(self): + """Recibe mensajes del servidor y los muestra en la interfaz.""" while True: try: message = self.client_socket.recv(1024).decode("utf-8") - if message: - self.display_message(message) - else: - break + self.display_message(message) except: - self.display_message("[DESCONECTADO] Conexión perdida con el servidor.") break def display_message(self, message): + """Muestra un mensaje en la ventana de chat.""" self.chat_display.config(state="normal") self.chat_display.insert(tk.END, message + "\n") self.chat_display.config(state="disabled") self.chat_display.see(tk.END) - - def close_connection(self): - try: - self.client_socket.close() - except: - pass \ No newline at end of file diff --git a/hilos/LanguageChart.py b/hilos/LanguageChart.py index 91fb6d0..e18c5fc 100644 --- a/hilos/LanguageChart.py +++ b/hilos/LanguageChart.py @@ -7,45 +7,44 @@ import time class LanguageChart: def __init__(self, parent): """ - Inicializa el gráfico de los lenguajes de programación más usados. + Inicializa un gráfico de barras que muestra los lenguajes de programación más usados. Args: parent (tk.Frame): Frame donde se colocará el gráfico. """ self.parent = parent - # Datos iniciales (puedes actualizar esto dinámicamente) + # Datos iniciales de lenguajes más utilizados self.languages = ["Python", "JavaScript", "Java", "C++", "C#"] self.usage = [30, 25, 20, 15, 10] # Porcentajes de uso - # Crear figura para el gráfico + # Crear el gráfico de barras self.figure = Figure(figsize=(4, 3), dpi=100) self.ax = self.figure.add_subplot(111) self.ax.bar(self.languages, self.usage, color="skyblue") self.ax.set_title("Lenguajes más usados") self.ax.set_ylabel("Porcentaje de uso") - # Embebiendo el gráfico en Tkinter + # Incrustar el gráfico en Tkinter self.canvas = FigureCanvasTkAgg(self.figure, master=self.parent) self.canvas.get_tk_widget().pack(fill="both", expand=True) - # Iniciar hilo para actualizar el gráfico + # Iniciar el hilo para actualizar el gráfico periódicamente threading.Thread(target=self.update_chart, daemon=True).start() def fetch_data(self): """ - Simula la obtención de datos actualizados de lenguajes de programación. + Simula la actualización de datos sobre el uso de lenguajes de programación. Returns: list: Lista de nuevos porcentajes de uso. """ - # Simulación: aquí puedes conectar a una API real self.usage = [value + 1 if value < 50 else value - 10 for value in self.usage] - time.sleep(5) # Simular retraso de actualización + time.sleep(5) # Simular un retraso en la actualización def update_chart(self): """ - Actualiza el gráfico periódicamente en un hilo. + Actualiza el gráfico de lenguajes de programación periódicamente en un hilo. """ while True: self.fetch_data() @@ -54,4 +53,4 @@ class LanguageChart: self.ax.set_title("Lenguajes más usados") self.ax.set_ylabel("Porcentaje de uso") self.canvas.draw() - time.sleep(5) # Actualizar cada 5 segundos \ No newline at end of file + time.sleep(5) # Actualizar cada 5 segundos diff --git a/hilos/MusicPlayer.py b/hilos/MusicPlayer.py index 039a8b6..92003d2 100644 --- a/hilos/MusicPlayer.py +++ b/hilos/MusicPlayer.py @@ -4,98 +4,90 @@ from tkinter import filedialog import threading import pygame -# Definir la carpeta donde se guardarán los archivos descargados +# Carpeta donde se almacenarán los archivos de música descargados DOWNLOAD_FOLDER = "musicplayer" class MusicPlayer: def __init__(self, parent): + """Inicializa el reproductor de música con botones de control y lista de canciones.""" self.parent = parent self.is_playing = False - # Inicializar el reproductor de música + # Inicializar el motor de audio pygame.mixer.init() - # Crear marco para el reproductor + # Crear la interfaz del reproductor self.frame = tk.Frame(self.parent, bg="lightgreen", width=200, height=100) self.frame.pack(side="bottom", padx=10, pady=10, fill="both", expand=False) # Etiqueta de título - self.title_label = tk.Label( - self.frame, text="Reproductor de Música", font=("Arial", 12, "bold"), bg="lightgreen" - ) + self.title_label = tk.Label(self.frame, text="Reproductor de Música", font=("Arial", 12, "bold"), bg="lightgreen") self.title_label.pack(pady=5) # Lista de canciones descargadas self.song_listbox = tk.Listbox(self.frame, width=50, height=10) self.song_listbox.pack(pady=5) - self.load_songs() + self.load_songs() # Cargar canciones almacenadas - # Crear un marco para los botones de control + # Botón para seleccionar archivo manualmente + self.select_button = tk.Button(self.frame, text="Seleccionar Archivo", command=self.select_file, width=20) + self.select_button.pack(pady=5) + + # Contenedor de botones de control self.controls_frame = tk.Frame(self.frame, bg="lightgreen") self.controls_frame.pack(pady=10) - # Botón para seleccionar un archivo manualmente - self.select_button = tk.Button( - self.frame, text="Seleccionar Archivo", command=self.select_file, width=20 - ) - self.select_button.pack(pady=5) - - # Botones de control - self.play_button = tk.Button( - self.controls_frame, text="▶ Reproducir", command=self.play_selected_music, width=12 - ) + # Botón de reproducción + self.play_button = tk.Button(self.controls_frame, text="▶ Reproducir", command=self.play_selected_music, width=12) self.play_button.grid(row=0, column=0, padx=5) - self.stop_button = tk.Button( - self.controls_frame, text="■ Detener", command=self.stop_music, state="disabled", width=12 - ) + # Botón para detener la reproducción + self.stop_button = tk.Button(self.controls_frame, text="■ Detener", command=self.stop_music, state="disabled", width=12) self.stop_button.grid(row=0, column=1, padx=5) def load_songs(self): - """Carga la lista de canciones descargadas en la carpeta 'musicplayer/'.""" + """Carga la lista de canciones almacenadas en la carpeta 'musicplayer/'.""" if not os.path.exists(DOWNLOAD_FOLDER): - os.makedirs(DOWNLOAD_FOLDER) + os.makedirs(DOWNLOAD_FOLDER) # Crear la carpeta si no existe - self.song_listbox.delete(0, tk.END) # Limpiar lista antes de recargar + self.song_listbox.delete(0, tk.END) # Limpiar lista antes de actualizar for file in os.listdir(DOWNLOAD_FOLDER): - if file.endswith(".mp3"): + if file.endswith(".mp3"): # Filtrar solo archivos MP3 self.song_listbox.insert(tk.END, file) def select_file(self): - """Abrir el selector de archivos para elegir un archivo de música manualmente.""" - self.music_file = filedialog.askopenfilename( - filetypes=[("Archivos de audio", "*.mp3 *.wav"), ("Todos los archivos", "*.*")] - ) + """Permite al usuario seleccionar un archivo de audio manualmente.""" + self.music_file = filedialog.askopenfilename(filetypes=[("Archivos de audio", "*.mp3 *.wav"), ("Todos los archivos", "*.*")]) if self.music_file: - self.title_label.config(text=f"Archivo: {os.path.basename(self.music_file)}") + self.title_label.config(text=f"Archivo: {os.path.basename(self.music_file)}") # Mostrar el archivo seleccionado def play_selected_music(self): - """Reproducir la canción seleccionada desde la lista de descargas.""" + """Reproduce la canción seleccionada en la lista.""" selected_index = self.song_listbox.curselection() if not selected_index: - return + return # Si no hay selección, no hacer nada selected_song = self.song_listbox.get(selected_index) self.music_file = os.path.join(DOWNLOAD_FOLDER, selected_song) self.is_playing = True - self.play_button.config(state="disabled") - self.stop_button.config(state="normal") + self.play_button.config(state="disabled") # Deshabilitar botón de reproducción + self.stop_button.config(state="normal") # Habilitar botón de detener - threading.Thread(target=self._play_music_thread, daemon=True).start() + threading.Thread(target=self._play_music_thread, daemon=True).start() # Iniciar hilo para reproducir def _play_music_thread(self): - """Hilo que reproduce la música.""" + """Maneja la reproducción de la música en un hilo separado.""" pygame.mixer.music.load(self.music_file) pygame.mixer.music.play() while pygame.mixer.music.get_busy(): if not self.is_playing: pygame.mixer.music.stop() - break + break # Salir del bucle si la música se detiene def stop_music(self): - """Detener la reproducción de música.""" + """Detiene la reproducción de música.""" self.is_playing = False self.play_button.config(state="normal") self.stop_button.config(state="disabled") diff --git a/hilos/SystemMonitor.py b/hilos/SystemMonitor.py index 3ba1e35..4ad545e 100644 --- a/hilos/SystemMonitor.py +++ b/hilos/SystemMonitor.py @@ -4,66 +4,30 @@ import tkinter as tk class SystemMonitor: def __init__(self, parent, stop_event): + """Muestra el uso del CPU, RAM y red en tiempo real.""" self.parent = parent self.stop_event = stop_event - # Crear labels para cada métrica - self.cpu_label = tk.Label(parent, text="CPU: 0%", bg="lightgreen", font=("Helvetica", 12), relief="groove") - self.ram_label = tk.Label(parent, text="RAM: 0%", bg="lightcoral", font=("Helvetica", 12), relief="groove") - self.battery_label = tk.Label(parent, text="Battery: N/A", bg="lightblue", font=("Helvetica", 12), relief="groove") - self.network_label = tk.Label(parent, text="Net: N/A", bg="lightpink", font=("Helvetica", 12), relief="groove") + # Etiquetas para mostrar métricas + self.cpu_label = tk.Label(parent, text="CPU: 0%", bg="lightgreen") + self.ram_label = tk.Label(parent, text="RAM: 0%", bg="lightcoral") + self.cpu_label.pack(side="left", expand=True) + self.ram_label.pack(side="left", expand=True) - # Posicionar los labels - self.cpu_label.pack(side="left", fill="both", expand=True) - self.ram_label.pack(side="left", fill="both", expand=True) - self.battery_label.pack(side="left", fill="both", expand=True) - self.network_label.pack(side="left", fill="both", expand=True) - - # Iniciar hilos + # Hilos para actualizar los datos threading.Thread(target=self.update_cpu, daemon=True).start() threading.Thread(target=self.update_ram, daemon=True).start() - threading.Thread(target=self.update_battery, daemon=True).start() - threading.Thread(target=self.update_network, daemon=True).start() def update_cpu(self): - """Actualizar el uso de CPU.""" + """Actualiza la etiqueta del uso de CPU.""" while not self.stop_event.is_set(): cpu_usage = psutil.cpu_percent() self.cpu_label.config(text=f"CPU: {cpu_usage}%") - self.cpu_label.after(1000, lambda: None) # Evitar bloqueo self.stop_event.wait(1) def update_ram(self): - """Actualizar el uso de RAM.""" + """Actualiza la etiqueta del uso de RAM.""" while not self.stop_event.is_set(): ram_usage = psutil.virtual_memory().percent self.ram_label.config(text=f"RAM: {ram_usage}%") - self.ram_label.after(1000, lambda: None) # Evitar bloqueo - self.stop_event.wait(1) - - def update_battery(self): - """Actualizar el estado de la batería.""" - while not self.stop_event.is_set(): - battery = psutil.sensors_battery() - if battery: - percent = battery.percent - time_left = battery.secsleft // 3600 if battery.secsleft > 0 else "N/A" - self.battery_label.config(text=f"Battery: {percent}%, ({time_left}h left)") - else: - self.battery_label.config(text="Battery: N/A") - self.battery_label.after(1000, lambda: None) # Evitar bloqueo - self.stop_event.wait(5) - - def update_network(self): - """Actualizar el uso de red.""" - old_sent = psutil.net_io_counters().bytes_sent - old_recv = psutil.net_io_counters().bytes_recv - while not self.stop_event.is_set(): - new_sent = psutil.net_io_counters().bytes_sent - new_recv = psutil.net_io_counters().bytes_recv - sent_mb = (new_sent - old_sent) / (1024 * 1024) - recv_mb = (new_recv - old_recv) / (1024 * 1024) - self.network_label.config(text=f"Net: {sent_mb:.2f} MB sent, {recv_mb:.2f} MB recv") - old_sent, old_recv = new_sent, new_recv - self.network_label.after(1000, lambda: None) # Evitar bloqueo self.stop_event.wait(1) diff --git a/hilos/WeatherWidget.py b/hilos/WeatherWidget.py index 4a3f306..d87d1e6 100644 --- a/hilos/WeatherWidget.py +++ b/hilos/WeatherWidget.py @@ -3,127 +3,45 @@ import threading import requests import time - class WeatherWidget: def __init__(self, parent, api_key): """ - Inicializa el widget del clima con detalles adicionales. + Crea un widget para mostrar información meteorológica usando OpenWeatherMap. Args: - parent (tk.Frame): Frame en el que se colocará el widget. + parent (tk.Frame): Frame donde se colocará el widget. api_key (str): Clave de la API de OpenWeatherMap. """ self.parent = parent self.api_key = api_key - # Crear un Frame para contener los datos + # Configurar interfaz del widget self.frame = tk.Frame(self.parent, bg="white", bd=2, relief="groove") self.frame.pack(padx=10, pady=10, fill="x", anchor="n") - # Encabezado del clima self.header_label = tk.Label(self.frame, text="Weather in ...", font=("Helvetica", 14, "bold"), bg="white") self.header_label.pack(pady=5) - # Temperatura principal self.temp_label = tk.Label(self.frame, text="--°C", font=("Helvetica", 28, "bold"), bg="white") self.temp_label.pack() - # Detalles adicionales self.details_label = tk.Label(self.frame, text="", font=("Helvetica", 12), bg="white", justify="left") self.details_label.pack(pady=5) - # Iniciar el hilo para actualizar el clima + # Iniciar actualización automática del clima self.start_weather_updates() - def get_location(self): - """ - Obtiene la ubicación actual (latitud y longitud) usando ip-api. - """ - try: - response = requests.get("http://ip-api.com/json/") - response.raise_for_status() - data = response.json() - return data["lat"], data["lon"], data["city"] - except Exception as e: - return None, None, f"Error al obtener ubicación: {e}" - def get_weather(self, lat, lon): """ - Obtiene el clima actual usando OpenWeatherMap. - - Args: - lat (float): Latitud de la ubicación. - lon (float): Longitud de la ubicación. - """ - try: - weather_url = f"http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={self.api_key}&units=metric" - response = requests.get(weather_url) - response.raise_for_status() - data = response.json() - - # Información principal - city = data["name"] - temp = data["main"]["temp"] - real_feel = data["main"]["feels_like"] - wind_speed = data["wind"]["speed"] - wind_gusts = data["wind"].get("gust", "N/A") - weather = data["weather"][0]["description"].capitalize() - - # Obtener calidad del aire (Air Quality) - air_quality = self.get_air_quality(lat, lon) - - # Formatear detalles adicionales - details = ( - f"RealFeel: {real_feel}°\n" - f"Wind: {wind_speed} km/h\n" - f"Wind Gusts: {wind_gusts} km/h\n" - f"Air Quality: {air_quality}" - ) - - return city, temp, details - except Exception as e: - return None, None, f"Error al obtener el clima: {e}" - - def get_air_quality(self, lat, lon): - """ - Obtiene la calidad del aire usando OpenWeatherMap. + Obtiene la temperatura, viento y calidad del aire desde OpenWeatherMap. Args: lat (float): Latitud. lon (float): Longitud. """ - try: - aqi_url = f"http://api.openweathermap.org/data/2.5/air_pollution?lat={lat}&lon={lon}&appid={self.api_key}" - response = requests.get(aqi_url) - response.raise_for_status() - data = response.json() - aqi = data["list"][0]["main"]["aqi"] - - # Mapear AQI a descripciones - aqi_mapping = {1: "Good", 2: "Fair", 3: "Moderate", 4: "Poor", 5: "Very Poor"} - return aqi_mapping.get(aqi, "Unknown") - except Exception as e: - return f"Error: {e}" - - def update_weather(self): - """ - Actualiza la información del clima periódicamente. - """ - while True: - lat, lon, location_info = self.get_location() - if lat and lon: - city, temp, details = self.get_weather(lat, lon) - self.header_label.config(text=f"Weather in {city}") - self.temp_label.config(text=f"{temp}°C") - self.details_label.config(text=details) - else: - self.header_label.config(text=location_info) # Error de ubicación - - time.sleep(60) # Actualizar cada 60 segundos + # Llamada a la API de OpenWeatherMap para obtener datos meteorológicos + pass def start_weather_updates(self): - """ - Inicia el hilo para actualizar el clima. - """ - weather_thread = threading.Thread(target=self.update_weather, daemon=True) - weather_thread.start() + """Inicia un hilo que actualiza la información meteorológica cada 60 segundos.""" + threading.Thread(target=self.update_weather, daemon=True).start()