893 lines
39 KiB
Python
893 lines
39 KiB
Python
import tkinter as tk
|
|
from tkinter import Menu, ttk, messagebox, filedialog
|
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
|
import os
|
|
import threading
|
|
from PIL import Image, ImageTk
|
|
|
|
import utility_thread as ut
|
|
import monitor_logic as ml
|
|
import alarm_logic as al
|
|
import backup_logic as bl
|
|
import notepad_logic as nl
|
|
import network_monitor as nm
|
|
import camel_game_logic as cgl
|
|
import audio_player_logic as apl
|
|
import external_launcher as el
|
|
import scraping_logic as sl
|
|
import email_logic as eml
|
|
import chat_logic as cl
|
|
|
|
# --- CLASE ALARMLISTFRAME ---
|
|
class AlarmListFrame(tk.Frame):
|
|
def __init__(self, master, root_app, **kwargs):
|
|
super().__init__(master, **kwargs)
|
|
self.root_app = root_app
|
|
self.columnconfigure(0, weight=1)
|
|
self.rowconfigure(0, weight=1)
|
|
|
|
self.tree = ttk.Treeview(self, columns=('Time', 'Message', 'Status'), show='headings')
|
|
self.tree.heading('Time', text='Hora')
|
|
self.tree.heading('Message', text='Mensaje')
|
|
self.tree.heading('Status', text='Estado')
|
|
self.tree.column('Time', width=80, anchor=tk.CENTER)
|
|
self.tree.column('Status', width=80, anchor=tk.CENTER)
|
|
|
|
self.tree.grid(row=0, column=0, sticky='nsew', padx=5, pady=5)
|
|
|
|
vsb = ttk.Scrollbar(self, orient="vertical", command=self.tree.yview)
|
|
vsb.grid(row=0, column=1, sticky='ns')
|
|
self.tree.configure(yscrollcommand=vsb.set)
|
|
|
|
self.tree.bind('<<TreeviewSelect>>', self.on_select)
|
|
|
|
self.update_list()
|
|
|
|
def update_list(self, alarms=None):
|
|
if alarms is None:
|
|
alarms = al.load_alarms()
|
|
|
|
for item in self.tree.get_children():
|
|
self.tree.delete(item)
|
|
|
|
for alarm in alarms:
|
|
time_str = f"{alarm['hour']:02d}:{alarm['minute']:02d}"
|
|
status_str = "ACTIVA" if alarm['active'] else "INACTIVA"
|
|
tag = 'active' if alarm['active'] else 'inactive'
|
|
|
|
self.tree.insert('', tk.END, iid=str(alarm['id']), text='',
|
|
values=(time_str, alarm['message'], status_str),
|
|
tags=(tag,))
|
|
|
|
self.tree.tag_configure('active', background='#D4EDDA', foreground='#155724')
|
|
self.tree.tag_configure('inactive', background='#F8D7DA', foreground='#721C24')
|
|
|
|
def on_select(self, event):
|
|
selected_items = self.tree.selection()
|
|
if not selected_items:
|
|
self.root_app.tree_deselect_and_disable_buttons()
|
|
return
|
|
|
|
alarm_id = int(selected_items[0])
|
|
alarms = al.load_alarms()
|
|
|
|
selected_alarm = next((a for a in alarms if a['id'] == alarm_id), None)
|
|
|
|
if selected_alarm:
|
|
self.root_app.set_selected_alarm(alarm_id, selected_alarm['active'])
|
|
|
|
|
|
# --- CLASE MAINAPPLICATION ---
|
|
class MainApplication(tk.Tk):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.title("Ventana Responsive Modular")
|
|
self.geometry("1000x700")
|
|
|
|
self.root = self
|
|
self.monitor_elements = ml.initialize_monitor_figures()
|
|
self.selected_alarm_id = None
|
|
self.selected_alarm_is_active = False
|
|
self.camel_threads = []
|
|
self.winner_name = None
|
|
|
|
self.scraping_search_term_var = None
|
|
self.scraping_results_text = None
|
|
self.DEBUG_HTML_FILE = "amazon_debugging_output.html"
|
|
|
|
self.chat_display = None
|
|
self.chat_net = cl.ChatNetwork(self.receive_chat_message)
|
|
self.chat_net.start_server()
|
|
|
|
self.configure_layout()
|
|
self.create_widgets()
|
|
|
|
ut.start_time_thread(self.label_fecha_hora)
|
|
self.start_monitor()
|
|
self.start_alarm_checker()
|
|
nm.start_network_monitoring_thread(self)
|
|
|
|
self.music_player = apl.MusicPlayer(self)
|
|
self.protocol("WM_DELETE_WINDOW", self.on_closing)
|
|
|
|
|
|
def on_closing(self):
|
|
"""Detiene la música y destruye la ventana al cerrar la app."""
|
|
self.music_player.stop_music()
|
|
self.destroy()
|
|
if hasattr(self, 'chat_net'):
|
|
self.chat_net.stop()
|
|
|
|
def configure_layout(self):
|
|
self.columnconfigure(0, weight=0)
|
|
self.columnconfigure(1, weight=1)
|
|
self.columnconfigure(2, weight=0)
|
|
self.rowconfigure(0, weight=1)
|
|
self.rowconfigure(1, weight=0)
|
|
|
|
def create_widgets(self):
|
|
self.create_menu()
|
|
self.create_frames()
|
|
self.create_left_panel_controls()
|
|
self.create_central_subframes()
|
|
self.create_status_bar()
|
|
self.create_notebook()
|
|
|
|
self.create_monitor_tab()
|
|
self.create_alarm_tab()
|
|
self.create_notepad_tab()
|
|
self.create_camel_game_tab()
|
|
self.create_audio_player_tab()
|
|
self.create_external_launcher_tab()
|
|
self.create_scraping_tab()
|
|
self.create_email_tab()
|
|
|
|
self.notebook.bind('<<NotebookTabChanged>>', self.on_tab_change)
|
|
self.update_activity_status("Inicio de la aplicación")
|
|
self.create_chat_tab()
|
|
|
|
|
|
def create_menu(self):
|
|
menu_bar = Menu(self)
|
|
file_menu = Menu(menu_bar, tearoff=0)
|
|
file_menu.add_command(label="Salir", command=self.on_closing)
|
|
menu_bar.add_cascade(label="Archivo", menu=file_menu)
|
|
self.config(menu=menu_bar)
|
|
|
|
def create_frames(self):
|
|
self.frame_izquierdo = tk.Frame(self, bg="lightblue", width=200)
|
|
self.frame_central = tk.Frame(self, bg="white")
|
|
self.frame_derecho = tk.Frame(self, bg="lightgreen", width=200)
|
|
|
|
self.frame_izquierdo.grid(row=0, column=0, sticky="ns")
|
|
self.frame_central.grid(row=0, column=1, sticky="nsew")
|
|
self.frame_derecho.grid(row=0, column=2, sticky="ns")
|
|
|
|
self.frame_izquierdo.grid_propagate(False)
|
|
self.frame_derecho.grid_propagate(False)
|
|
|
|
def create_left_panel_controls(self):
|
|
self.frame_izquierdo.columnconfigure(0, weight=1)
|
|
|
|
tk.Label(self.frame_izquierdo, text="Procesos batch", font=("Arial", 12, "bold"), bg="lightblue").grid(row=0, column=0, padx=10, pady=(10, 5), sticky="w")
|
|
|
|
btn_backup = tk.Button(
|
|
self.frame_izquierdo,
|
|
text="Copias de seguridad",
|
|
command=self.backup_app_state,
|
|
bg="#D4EDDA",
|
|
fg="#155724",
|
|
font=("Arial", 10, "bold"),
|
|
relief=tk.RAISED,
|
|
bd=3
|
|
)
|
|
btn_backup.grid(row=1, column=0, padx=10, pady=5, sticky="ew")
|
|
|
|
def create_central_subframes(self):
|
|
self.frame_central.rowconfigure(0, weight=1)
|
|
self.frame_central.rowconfigure(1, weight=0)
|
|
self.frame_central.columnconfigure(0, weight=1)
|
|
|
|
self.frame_superior = tk.Frame(self.frame_central, bg="lightyellow")
|
|
self.frame_inferior = tk.Frame(self.frame_central, bg="lightgray", height=100)
|
|
|
|
self.frame_superior.grid(row=0, column=0, sticky="nsew")
|
|
self.frame_inferior.grid(row=1, column=0, sticky="ew")
|
|
self.frame_inferior.grid_propagate(False)
|
|
|
|
def create_status_bar(self):
|
|
self.barra_estado = tk.Label(self, bg="lightgray", anchor="w")
|
|
self.barra_estado.grid(row=1, column=0, columnspan=3, sticky="ew")
|
|
|
|
self.label_status_1 = tk.Label(self.barra_estado, text="Listo", bg="green", anchor="w", fg='white')
|
|
self.label_status_1.pack(side="left", fill="x", expand=True)
|
|
|
|
self.label_net_status = tk.Label(self.barra_estado, text="Red: 0.00/0.00 KB", bg="blue", anchor="w", fg='white')
|
|
self.label_net_status.pack(side="left", fill="x", expand=True)
|
|
|
|
self.label_audio_status = tk.Label(self.barra_estado, text="Música: Ninguno", bg="cyan", anchor="w")
|
|
self.label_audio_status.pack(side="left", fill="x", expand=True)
|
|
|
|
self.label_fecha_hora = tk.Label(self.barra_estado, text="Hilo fecha-hora", font=("Helvetica", 14), bd=1, fg="blue", relief="sunken", anchor="w", width=20, padx=10)
|
|
self.label_fecha_hora.pack(side="right", fill="x", expand=True)
|
|
|
|
def create_notebook(self):
|
|
style = ttk.Style()
|
|
style.configure("CustomNotebook.TNotebook.Tab", font=("Arial", 12, "bold"))
|
|
self.notebook = ttk.Notebook(self.frame_superior, style="CustomNotebook.TNotebook")
|
|
self.notebook.pack(fill="both", expand=True)
|
|
|
|
|
|
|
|
def create_monitor_tab(self):
|
|
tab_monitor = ttk.Frame(self.notebook)
|
|
self.notebook.add(tab_monitor, text="Monitor de Sistema 📈", padding=4)
|
|
|
|
tab_monitor.columnconfigure(0, weight=1); tab_monitor.columnconfigure(1, weight=1)
|
|
tab_monitor.rowconfigure(0, weight=1); tab_monitor.rowconfigure(1, weight=0)
|
|
|
|
self.canvas_usage = FigureCanvasTkAgg(self.monitor_elements['fig_usage'], master=tab_monitor)
|
|
self.canvas_widget_usage = self.canvas_usage.get_tk_widget()
|
|
self.canvas_widget_usage.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
|
|
|
|
self.canvas_net = FigureCanvasTkAgg(self.monitor_elements['fig_net'], master=tab_monitor)
|
|
self.canvas_widget_net = self.canvas_net.get_tk_widget()
|
|
self.canvas_widget_net.grid(row=0, column=1, sticky="nsew", padx=5, pady=5)
|
|
|
|
frame_metrics = ttk.Frame(tab_monitor, padding=10, relief="groove")
|
|
frame_metrics.grid(row=1, column=0, columnspan=2, sticky="ew", padx=5, pady=5)
|
|
frame_metrics.columnconfigure(0, weight=1); frame_metrics.columnconfigure(1, weight=1)
|
|
|
|
self.label_load = tk.Label(frame_metrics, text="Carga: -", anchor="w", font=("Arial", 10, "bold"))
|
|
self.label_load.grid(row=0, column=0, sticky="ew", pady=2)
|
|
|
|
self.label_threads = tk.Label(frame_metrics, text="Hilos: -", anchor="w", font=("Arial", 10, "bold"))
|
|
self.label_threads.grid(row=0, column=1, sticky="ew", pady=2)
|
|
|
|
def create_alarm_tab(self):
|
|
tab_alarm = ttk.Frame(self.notebook)
|
|
self.notebook.add(tab_alarm, text="Gestor de Alarmas ⏰", padding=10)
|
|
|
|
tab_alarm.columnconfigure(0, weight=1)
|
|
tab_alarm.columnconfigure(1, weight=0)
|
|
tab_alarm.rowconfigure(0, weight=1)
|
|
|
|
frame_lista = ttk.Frame(tab_alarm)
|
|
frame_lista.grid(row=0, column=0, sticky='nsew', padx=10, pady=10)
|
|
frame_lista.columnconfigure(0, weight=1)
|
|
frame_lista.rowconfigure(0, weight=1)
|
|
frame_lista.rowconfigure(1, weight=0)
|
|
|
|
self.alarm_list_widget = AlarmListFrame(frame_lista, self, relief='sunken', borderwidth=2)
|
|
self.alarm_list_widget.grid(row=0, column=0, sticky='nsew')
|
|
|
|
self.alarm_notification_label = tk.Label(frame_lista, text="Sistema de Alarmas Activo", fg='green', font=("Arial", 14, "bold"))
|
|
self.alarm_notification_label.grid(row=1, column=0, sticky='ew', pady=5)
|
|
|
|
frame_control = ttk.Frame(tab_alarm, padding=15, relief='groove')
|
|
frame_control.grid(row=0, column=1, sticky='ns', padx=10, pady=10)
|
|
|
|
ttk.Label(frame_control, text="Añadir Nueva Alarma", font=("Arial", 12, "bold")).grid(row=0, column=0, columnspan=2, pady=(0, 10))
|
|
|
|
ttk.Label(frame_control, text="Hora (HH):").grid(row=1, column=0, sticky='w', pady=5)
|
|
self.alarm_hour_var = tk.StringVar(value="08")
|
|
ttk.Entry(frame_control, textvariable=self.alarm_hour_var, width=5).grid(row=1, column=1, sticky='e', pady=5)
|
|
|
|
ttk.Label(frame_control, text="Minuto (MM):").grid(row=2, column=0, sticky='w', pady=5)
|
|
self.alarm_minute_var = tk.StringVar(value="00")
|
|
ttk.Entry(frame_control, textvariable=self.alarm_minute_var, width=5).grid(row=2, column=1, sticky='e', pady=5)
|
|
|
|
ttk.Label(frame_control, text="Mensaje:").grid(row=3, column=0, sticky='w', pady=5)
|
|
self.alarm_message_var = tk.StringVar(value="Recordatorio")
|
|
ttk.Entry(frame_control, textvariable=self.alarm_message_var, width=15).grid(row=3, column=1, sticky='e', pady=5)
|
|
|
|
ttk.Button(frame_control, text="Programar Alarma", command=self.add_alarm_callback).grid(row=4, column=0, columnspan=2, pady=(10, 20), sticky='ew')
|
|
|
|
ttk.Label(frame_control, text="Control de Alarma Seleccionada", font=("Arial", 12, "bold")).grid(row=5, column=0, columnspan=2, pady=(10, 10))
|
|
|
|
self.btn_toggle = ttk.Button(frame_control, text="Activar / Desactivar", command=self.toggle_alarm_callback, state=tk.DISABLED)
|
|
self.btn_toggle.grid(row=6, column=0, columnspan=2, pady=5, sticky='ew')
|
|
|
|
self.btn_delete = ttk.Button(frame_control, text="Eliminar Alarma", command=self.delete_alarm_callback, state=tk.DISABLED)
|
|
self.btn_delete.grid(row=7, column=0, columnspan=2, pady=5, sticky='ew')
|
|
|
|
def create_notepad_tab(self):
|
|
tab_notepad = ttk.Frame(self.notebook)
|
|
self.notebook.add(tab_notepad, text="Notas 📝", padding=10)
|
|
|
|
tab_notepad.columnconfigure(0, weight=1)
|
|
tab_notepad.rowconfigure(0, weight=1)
|
|
tab_notepad.rowconfigure(1, weight=0)
|
|
|
|
frame_editor = ttk.Frame(tab_notepad)
|
|
frame_editor.grid(row=0, column=0, sticky='nsew', padx=5, pady=5)
|
|
frame_editor.columnconfigure(0, weight=1)
|
|
frame_editor.rowconfigure(0, weight=1)
|
|
|
|
self.text_editor = tk.Text(
|
|
frame_editor,
|
|
wrap=tk.WORD,
|
|
font=("Consolas", 10),
|
|
padx=10,
|
|
pady=10
|
|
)
|
|
self.text_editor.grid(row=0, column=0, sticky='nsew')
|
|
|
|
vsb = ttk.Scrollbar(frame_editor, orient="vertical", command=self.text_editor.yview)
|
|
vsb.grid(row=0, column=1, sticky='ns')
|
|
self.text_editor.configure(yscrollcommand=vsb.set)
|
|
|
|
self.load_notes_callback(startup=True)
|
|
|
|
frame_buttons = ttk.Frame(tab_notepad)
|
|
frame_buttons.grid(row=1, column=0, sticky='ew', padx=5, pady=(0, 5))
|
|
frame_buttons.columnconfigure(0, weight=1)
|
|
frame_buttons.columnconfigure(1, weight=1)
|
|
|
|
ttk.Button(frame_buttons, text="Guardar Notas 💾", command=self.save_notes_callback).grid(row=0, column=0, sticky='ew', padx=5, pady=5)
|
|
|
|
ttk.Button(frame_buttons, text="Recargar (Deshacer) 🔄", command=self.load_notes_callback).grid(row=0, column=1, sticky='ew', padx=5, pady=5)
|
|
|
|
def create_camel_game_tab(self):
|
|
tab_game = ttk.Frame(self.notebook)
|
|
self.notebook.add(tab_game, text="Carrera de Camellos 🏁", padding=10)
|
|
|
|
tab_game.columnconfigure(0, weight=1)
|
|
tab_game.rowconfigure(0, weight=0)
|
|
tab_game.rowconfigure(1, weight=1)
|
|
|
|
frame_control = ttk.Frame(tab_game, padding=5)
|
|
frame_control.grid(row=0, column=0, sticky='ew')
|
|
|
|
ttk.Style().configure("Accent.TButton", font=("Arial", 12, "bold"), foreground="blue")
|
|
ttk.Button(frame_control, text="¡INICIAR CARRERA!", command=self.start_game_callback, style="Accent.TButton").pack(pady=10)
|
|
|
|
self.frame_game = ttk.Frame(tab_game, padding=10, relief='groove', borderwidth=2)
|
|
self.frame_game.grid(row=1, column=0, sticky='nsew', padx=5, pady=5)
|
|
self.frame_game.columnconfigure(0, weight=1)
|
|
|
|
tk.Label(self.frame_game, text="Pulsa 'INICIAR CARRERA' para empezar...", fg='gray', font=("Arial", 14)).pack(pady=20)
|
|
|
|
def create_audio_player_tab(self):
|
|
tab_audio = ttk.Frame(self.notebook)
|
|
self.notebook.add(tab_audio, text="Música 🎧", padding=20)
|
|
|
|
ttk.Label(tab_audio, text="Reproductor de Música de Fondo (Pygame)", font=("Arial", 16, "bold")).pack(pady=10)
|
|
|
|
self.audio_filepath_var = tk.StringVar(value="Ningún archivo cargado.")
|
|
|
|
ttk.Label(tab_audio, textvariable=self.audio_filepath_var, wraplength=500).pack(pady=(10, 5))
|
|
|
|
ttk.Button(tab_audio, text="Seleccionar Archivo MP3/OGG", command=self.select_audio_file).pack(pady=10)
|
|
|
|
frame_controls = ttk.Frame(tab_audio)
|
|
frame_controls.pack(pady=20)
|
|
|
|
ttk.Button(frame_controls, text="⏸️ Pausa", command=self.pause_audio).pack(side=tk.LEFT, padx=10)
|
|
ttk.Button(frame_controls, text="▶️ Reanudar", command=self.unpause_audio).pack(side=tk.LEFT, padx=10)
|
|
ttk.Button(frame_controls, text="⏹️ Detener", command=self.stop_audio).pack(side=tk.LEFT, padx=10)
|
|
|
|
def create_external_launcher_tab(self):
|
|
tab_external = ttk.Frame(self.notebook)
|
|
self.notebook.add(tab_external, text="Lanzador 🌐", padding=20)
|
|
|
|
tab_external.columnconfigure(0, weight=1)
|
|
tab_external.rowconfigure(0, weight=0)
|
|
|
|
ttk.Label(tab_external, text="Lanzar Aplicaciones Externas", font=("Arial", 16, "bold")).pack(pady=10)
|
|
|
|
frame_browser = ttk.LabelFrame(tab_external, text="Abrir Navegador", padding=15)
|
|
frame_browser.pack(fill='x', pady=10, padx=50)
|
|
|
|
ttk.Label(frame_browser, text="URL (ej: google.com):").pack(pady=5)
|
|
self.url_var = tk.StringVar(value="www.google.com")
|
|
ttk.Entry(frame_browser, textvariable=self.url_var, width=50).pack(pady=5)
|
|
|
|
ttk.Button(frame_browser, text="Abrir URL en Navegador Predeterminado", command=self.launch_browser_callback).pack(pady=10)
|
|
|
|
frame_custom = ttk.LabelFrame(tab_external, text="Abrir Aplicación (Ej: Bloc de Notas)", padding=15)
|
|
frame_custom.pack(fill='x', pady=20, padx=50)
|
|
|
|
ttk.Label(frame_custom, text="Comando (ej: notepad.exe):").pack(pady=5)
|
|
self.app_command_var = tk.StringVar(value="notepad.exe")
|
|
ttk.Entry(frame_custom, textvariable=self.app_command_var, width=50).pack(pady=5)
|
|
|
|
ttk.Button(frame_custom, text="Lanzar Aplicación Externa", command=self.launch_custom_app_callback).pack(pady=10)
|
|
|
|
# --- MÉTODO PARA LA SOLAPA WEB SCRAPING ---
|
|
def create_scraping_tab(self):
|
|
tab_scraping = ttk.Frame(self.notebook)
|
|
self.notebook.add(tab_scraping, text="Web Scraper 🔎", padding=15)
|
|
|
|
tab_scraping.columnconfigure(0, weight=1)
|
|
tab_scraping.rowconfigure(1, weight=1)
|
|
|
|
frame_controls = ttk.Frame(tab_scraping, padding=10)
|
|
frame_controls.grid(row=0, column=0, sticky='ew', pady=(0, 10))
|
|
frame_controls.columnconfigure(1, weight=1)
|
|
|
|
ttk.Label(frame_controls, text="Término de Búsqueda (Ej: Portátil, Auriculares):").grid(row=0, column=0, padx=5, sticky='w')
|
|
|
|
self.scraping_search_term_var = tk.StringVar(value="auriculares bluetooth")
|
|
ttk.Entry(frame_controls, textvariable=self.scraping_search_term_var, width=40).grid(row=0, column=1, padx=5, sticky='ew')
|
|
|
|
ttk.Button(frame_controls, text="START SCRAPING", command=self.start_scraping_callback).grid(row=0, column=2, padx=10, sticky='e')
|
|
|
|
frame_results = ttk.Frame(tab_scraping, relief=tk.SUNKEN, borderwidth=2)
|
|
frame_results.grid(row=1, column=0, sticky='nsew', padx=5, pady=5)
|
|
frame_results.columnconfigure(0, weight=1)
|
|
frame_results.rowconfigure(0, weight=1)
|
|
|
|
self.scraping_results_text = tk.Text(
|
|
frame_results,
|
|
wrap=tk.WORD,
|
|
font=("Consolas", 10),
|
|
padx=10,
|
|
pady=10
|
|
)
|
|
self.scraping_results_text.grid(row=0, column=0, sticky='nsew')
|
|
|
|
vsb = ttk.Scrollbar(frame_results, orient="vertical", command=self.scraping_results_text.yview)
|
|
vsb.grid(row=0, column=1, sticky='ns')
|
|
self.scraping_results_text.configure(yscrollcommand=vsb.set)
|
|
|
|
# --- MÉTODOS DE CALLBACKS DE LÓGICA ---
|
|
|
|
# ------------------------------------------------------------------
|
|
# GESTIÓN DEL SCRAPING DE AMAZON (Llamada al hilo)
|
|
# ------------------------------------------------------------------
|
|
def start_scraping_callback(self):
|
|
"""Inicia el proceso de scraping en un hilo separado (llama a Playwright)."""
|
|
search_term = self.scraping_search_term_var.get()
|
|
if not search_term:
|
|
messagebox.showwarning("Advertencia", "Por favor, introduce un término de búsqueda.")
|
|
return
|
|
|
|
self.update_activity_status(f"Iniciando scraping (Playwright) para: {search_term}...")
|
|
self.scraping_results_text.delete("1.0", tk.END)
|
|
self.scraping_results_text.insert("1.0", f"Buscando productos en Amazon para '{search_term}' usando Playwright. Esto puede tardar unos segundos...\n")
|
|
|
|
sl.start_playwright_scraper(search_term, self)
|
|
|
|
def _display_scraping_results(self, results, search_term):
|
|
"""
|
|
Recibe los resultados del hilo de Playwright (llamado vía self.after)
|
|
y los muestra en la interfaz.
|
|
"""
|
|
self.scraping_results_text.delete("1.0", tk.END)
|
|
|
|
if results and "error" in results[0]:
|
|
output = f"--- ERROR en Scraping para {search_term} ---\n\n{results[0]['error']}\n\nSi obtienes un Timeout (30s), Amazon te ha bloqueado."
|
|
self.scraping_results_text.insert("1.0", output)
|
|
self.update_activity_status(f"Scraping fallido para {search_term}.")
|
|
return
|
|
|
|
output = f"--- PRODUCTOS de Amazon para '{search_term}' ({len(results)} encontrados) ---\n\n"
|
|
|
|
for i, product in enumerate(results):
|
|
output += f"[{i+1}] Producto: {product['nombre']}\n"
|
|
output += f" Precio: {product['precio']}\n"
|
|
output += f" Vendedor/Marca: {product['vendedor']}\n"
|
|
output += f" URL Imagen: {product['imagen_url']}\n"
|
|
output += "-" * 50 + "\n"
|
|
|
|
output += f"\nDatos guardados en {sl.SCRAPING_FILE}."
|
|
|
|
self.scraping_results_text.insert("1.0", output)
|
|
self.update_activity_status(f"Scraping completado para '{search_term}'. Resultados guardados.")
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
# --- GESTIÓN DE AUDIO ---
|
|
|
|
def select_audio_file(self):
|
|
filepath = filedialog.askopenfilename(
|
|
defaultextension=".mp3",
|
|
filetypes=[("Archivos de Audio", "*.mp3 *.ogg"), ("Todos los archivos", "*.*")]
|
|
)
|
|
if filepath:
|
|
self.audio_filepath_var.set(filepath)
|
|
self.update_activity_status(f"Archivo seleccionado: {os.path.basename(filepath)}")
|
|
self.music_player.load_and_play(filepath)
|
|
|
|
def pause_audio(self):
|
|
self.music_player.pause_music()
|
|
self.update_activity_status("Música pausada.")
|
|
|
|
def unpause_audio(self):
|
|
self.music_player.unpause_music()
|
|
self.update_activity_status("Música reanudada.")
|
|
|
|
def stop_audio(self):
|
|
self.music_player.stop_music()
|
|
self.audio_filepath_var.set("Ningún archivo cargado.")
|
|
self.update_activity_status("Música detenida.")
|
|
|
|
def update_audio_status(self, message):
|
|
self.label_audio_status.config(text=f"{message}")
|
|
|
|
# --- GESTIÓN DE LANZADOR EXTERNO ---
|
|
|
|
def launch_browser_callback(self):
|
|
url = self.url_var.get()
|
|
if el.launch_browser(url):
|
|
self.update_activity_status(f"Lanzando URL: {url}")
|
|
else:
|
|
self.update_activity_status("Error al lanzar el navegador.")
|
|
|
|
def launch_custom_app_callback(self):
|
|
command = self.app_command_var.get().split()
|
|
|
|
if not command:
|
|
messagebox.showwarning("Advertencia", "El campo de comando no puede estar vacío.")
|
|
return
|
|
|
|
app_name = command[0]
|
|
if el.launch_custom_app(command, app_name):
|
|
self.update_activity_status(f"Lanzando aplicación: {app_name}")
|
|
else:
|
|
self.update_activity_status(f"Fallo al lanzar la aplicación: {app_name}")
|
|
|
|
|
|
# --- GESTIÓN DE ESTADO Y UTILIDADES ---
|
|
|
|
def on_tab_change(self, event):
|
|
selected_tab = self.notebook.tab(self.notebook.select(), "text")
|
|
self.update_activity_status(f"{selected_tab}")
|
|
|
|
def update_activity_status(self, message):
|
|
self.label_status_1.config(text=f"{message}")
|
|
|
|
def update_net_status(self, kb_sent, kb_recv):
|
|
text = f"Red: ↑{kb_sent:.2f} / ↓{kb_recv:.2f} KB"
|
|
self.label_net_status.config(text=text)
|
|
|
|
def start_game_callback(self):
|
|
for thread in self.camel_threads:
|
|
thread.is_running = False
|
|
|
|
self.update_activity_status("Preparando la carrera...")
|
|
cgl.start_camel_game(self, track_length=50)
|
|
|
|
def clear_camel_game_area(self):
|
|
for widget in self.frame_game.winfo_children():
|
|
widget.destroy()
|
|
|
|
def show_winner(self, winner_name):
|
|
messagebox.showinfo("¡Carrera Terminada!", f"¡HA GANADO {winner_name.upper()}!")
|
|
self.update_activity_status(f"¡{winner_name} ha ganado la carrera!")
|
|
|
|
def backup_app_state(self):
|
|
current_alarms = al.load_alarms()
|
|
bl.create_backup(current_alarms)
|
|
self.update_activity_status("Copia de seguridad de Alarmas creada.")
|
|
|
|
def save_notes_callback(self):
|
|
content = self.text_editor.get("1.0", tk.END)
|
|
if nl.save_notes(content):
|
|
self.text_editor.edit_modified(False)
|
|
self.update_activity_status("Notas guardadas correctamente.")
|
|
|
|
def load_notes_callback(self, startup=False):
|
|
|
|
if not startup and self.text_editor.edit_modified():
|
|
response = messagebox.askyesno(
|
|
"Confirmar Recarga",
|
|
"El editor tiene cambios sin guardar. ¿Estás seguro de que quieres recargar el archivo (y perder los cambios)?"
|
|
)
|
|
if not response:
|
|
return
|
|
|
|
loaded_content = nl.load_notes()
|
|
|
|
self.text_editor.delete("1.0", tk.END)
|
|
self.text_editor.insert("1.0", loaded_content)
|
|
self.text_editor.edit_modified(False)
|
|
if not startup:
|
|
self.update_activity_status("Notas recargadas desde el archivo.")
|
|
|
|
def set_selected_alarm(self, alarm_id, is_active):
|
|
self.selected_alarm_id = alarm_id
|
|
self.selected_alarm_is_active = is_active
|
|
|
|
self.btn_toggle.config(state=tk.NORMAL)
|
|
self.btn_delete.config(state=tk.NORMAL)
|
|
|
|
toggle_text = "Desactivar" if is_active else "Activar"
|
|
self.btn_toggle.config(text=toggle_text)
|
|
self.update_activity_status(f"Alarma {alarm_id} seleccionada (Estado: {'ACTIVA' if is_active else 'INACTIVA'}).")
|
|
|
|
|
|
def add_alarm_callback(self):
|
|
try:
|
|
hour = self.alarm_hour_var.get()
|
|
minute = self.alarm_minute_var.get()
|
|
message = self.alarm_message_var.get()
|
|
|
|
if not (0 <= int(hour) <= 23 and 0 <= int(minute) <= 59):
|
|
messagebox.showerror("Error", "La hora debe estar entre 00 y 23, y el minuto entre 00 y 59.")
|
|
return
|
|
|
|
alarms = al.add_alarm(hour, minute, message)
|
|
self.alarm_list_widget.update_list(alarms)
|
|
self.alarm_notification_label.config(text="Alarma programada con éxito.", fg='blue')
|
|
self.update_activity_status(f"Alarma programada: {hour}:{minute}.")
|
|
|
|
|
|
except ValueError:
|
|
messagebox.showerror("Error", "Por favor, introduce números válidos para hora y minuto.")
|
|
|
|
def toggle_alarm_callback(self):
|
|
if self.selected_alarm_id is not None:
|
|
new_state = not self.selected_alarm_is_active
|
|
alarms = al.toggle_alarm(self.selected_alarm_id, new_state)
|
|
self.alarm_list_widget.update_list(alarms)
|
|
self.alarm_notification_label.config(text=f"Alarma {self.selected_alarm_id} {'ACTIVADA' if new_state else 'DESACTIVADA'}.", fg='green' if new_state else 'orange')
|
|
self.tree_deselect_and_disable_buttons()
|
|
self.update_activity_status(f"Alarma {self.selected_alarm_id} {'ACTIVADA' if new_state else 'DESACTIVADA'}.")
|
|
|
|
|
|
def delete_alarm_callback(self):
|
|
if self.selected_alarm_id is not None:
|
|
alarms = al.delete_alarm(self.selected_alarm_id)
|
|
self.alarm_list_widget.update_list(alarms)
|
|
self.alarm_notification_label.config(text=f"Alarma {self.selected_alarm_id} eliminada.", fg='red')
|
|
self.tree_deselect_and_disable_buttons()
|
|
self.update_activity_status(f"Alarma {self.selected_alarm_id} eliminada.")
|
|
|
|
|
|
def tree_deselect_and_disable_buttons(self):
|
|
if self.alarm_list_widget.tree.selection():
|
|
self.alarm_list_widget.tree.selection_remove(self.alarm_list_widget.tree.selection())
|
|
self.selected_alarm_id = None
|
|
self.btn_toggle.config(state=tk.DISABLED)
|
|
self.btn_delete.config(state=tk.DISABLED)
|
|
|
|
def show_alarm_popup(self, alarm_data):
|
|
|
|
self.alarm_notification_label.config(text=f"¡ALARMA SONANDO!", fg='red')
|
|
|
|
dialog = al.CustomAlarmDialog(self, alarm_data)
|
|
result = dialog.result
|
|
|
|
if result == 'posponer':
|
|
alarms = al.postpone_alarm(alarm_data['id'])
|
|
self.alarm_notification_label.config(text=f"Alarma pospuesta 1 minuto.", fg='blue')
|
|
self.update_activity_status(f"Alarma {alarm_data['id']} pospuesta.")
|
|
|
|
|
|
elif result == 'detener':
|
|
alarms = al.toggle_alarm(alarm_data['id'], False)
|
|
self.alarm_notification_label.config(text=f"Alarma detenida.", fg='green')
|
|
self.update_activity_status(f"Alarma {alarm_data['id']} detenida.")
|
|
|
|
|
|
else:
|
|
alarms = al.toggle_alarm(alarm_data['id'], False)
|
|
self.alarm_notification_label.config(text=f"Alarma detenida (cierre inesperado).", fg='green')
|
|
self.update_activity_status(f"Alarma {alarm_data['id']} detenida (cierre inesperado).")
|
|
|
|
|
|
self.alarm_list_widget.update_list()
|
|
self.tree_deselect_and_disable_buttons()
|
|
|
|
|
|
def start_monitor(self):
|
|
self.after(
|
|
100,
|
|
ml.update_monitor,
|
|
self.root,
|
|
self.monitor_elements['lines'],
|
|
self.monitor_elements['ax_net'],
|
|
self.canvas_usage,
|
|
self.canvas_net,
|
|
self.label_load,
|
|
self.label_threads
|
|
)
|
|
|
|
def start_alarm_checker(self):
|
|
self.after(100, al.check_alarms, self.root, self.alarm_list_widget, self.show_alarm_popup)
|
|
|
|
|
|
|
|
# --- GESTIÓN DE EMAIL ---
|
|
|
|
def create_email_tab(self):
|
|
"""Inicializa la pestaña de correo."""
|
|
self.tab_email = ttk.Frame(self.notebook)
|
|
self.notebook.add(self.tab_email, text="Correo ✉️")
|
|
self.current_user = ""
|
|
self.current_pass = ""
|
|
self.show_email_login_screen()
|
|
|
|
def logout_email(self):
|
|
"""Limpia la sesión y vuelve al login."""
|
|
self.current_user = ""
|
|
self.current_pass = ""
|
|
self.show_email_login_screen()
|
|
self.update_activity_status("Sesión de correo cerrada.")
|
|
|
|
def show_email_inbox(self, email_list):
|
|
"""Bandeja de entrada responsive."""
|
|
for widget in self.tab_email.winfo_children(): widget.destroy()
|
|
|
|
navbar = tk.Frame(self.tab_email, bg="white", height=50)
|
|
navbar.pack(fill="x")
|
|
ttk.Button(navbar, text="📝 Redactar", command=self.open_compose_window).pack(side="left", padx=10)
|
|
ttk.Button(navbar, text="🔄 Actualizar", command=self.refresh_inbox).pack(side="left")
|
|
ttk.Button(navbar, text="🚪 Cerrar Sesión", command=self.logout_email).pack(side="right", padx=10)
|
|
|
|
container = tk.Frame(self.tab_email, bg="#f0f2f5")
|
|
container.pack(fill="both", expand=True)
|
|
|
|
canvas = tk.Canvas(container, bg="#f0f2f5", highlightthickness=0)
|
|
sb = ttk.Scrollbar(container, command=canvas.yview)
|
|
self.mail_frame = tk.Frame(canvas, bg="#f0f2f5")
|
|
|
|
self.mail_frame.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
|
|
c_win = canvas.create_window((0,0), window=self.mail_frame, anchor="nw")
|
|
canvas.bind("<Configure>", lambda e: canvas.itemconfig(c_win, width=e.width))
|
|
|
|
canvas.configure(yscrollcommand=sb.set)
|
|
canvas.pack(side="left", fill="both", expand=True)
|
|
sb.pack(side="right", fill="y")
|
|
|
|
for m in email_list: self.create_mail_card(m)
|
|
|
|
def show_email_login_screen(self):
|
|
for widget in self.tab_email.winfo_children(): widget.destroy()
|
|
|
|
container = tk.Frame(self.tab_email, bg="#f5f7fa")
|
|
container.place(relx=0, rely=0, relwidth=1, relheight=1)
|
|
|
|
card = tk.Frame(container, bg="white", padx=40, pady=40, highlightbackground="#d1d9e6", highlightthickness=1)
|
|
card.place(relx=0.5, rely=0.5, anchor="center")
|
|
|
|
tk.Label(card, text="Acceso Correo", font=("Arial", 16, "bold"), bg="white").pack(pady=(0, 20))
|
|
|
|
tk.Label(card, text="Usuario", bg="white").pack(anchor="w")
|
|
self.ent_user = tk.Entry(card, width=30, highlightthickness=1)
|
|
self.ent_user.insert(0, "alumno@10.10.0.101")
|
|
self.ent_user.pack(pady=5)
|
|
|
|
tk.Label(card, text="Contraseña", bg="white").pack(anchor="w")
|
|
self.ent_pass = tk.Entry(card, show="*", width=30, highlightthickness=1)
|
|
self.ent_pass.pack(pady=5)
|
|
|
|
tk.Button(card, text="ENTRAR", bg="#3498db", fg="white", font=("Arial", 10, "bold"),
|
|
command=self.do_email_login, padx=20, pady=10).pack(pady=20, fill="x")
|
|
|
|
def do_email_login(self):
|
|
"""Realiza la conexión inicial."""
|
|
u, p = self.ent_user.get(), self.ent_pass.get()
|
|
success, data = eml.fetch_emails_logic(u, p)
|
|
if success:
|
|
self.current_user, self.current_pass = u, p
|
|
self.show_email_inbox(data)
|
|
else:
|
|
messagebox.showerror("Error", "Credenciales incorrectas o error de red")
|
|
|
|
def refresh_inbox(self):
|
|
"""Actualiza la bandeja."""
|
|
success, data = eml.fetch_emails_logic(self.current_user, self.current_pass)
|
|
if success: self.show_email_inbox(data)
|
|
|
|
def create_mail_card(self, mail):
|
|
"""Tarjetas expandibles de correo."""
|
|
card = tk.Frame(self.mail_frame, bg="white", highlightbackground="#ddd", highlightthickness=1)
|
|
card.pack(fill="x", pady=5, padx=15)
|
|
|
|
header = tk.Label(card, text=f"De: {mail['sender']}\nAsunto: {mail['subject']}",
|
|
font=("Arial", 10, "bold"), bg="white", justify="left", anchor="w", pady=10, padx=10)
|
|
header.pack(fill="x")
|
|
|
|
body_box = tk.Frame(card, bg="#f9f9f9")
|
|
self.img_refs = []
|
|
|
|
def expand(event):
|
|
if body_box.winfo_ismapped(): body_box.pack_forget()
|
|
else:
|
|
success, data = eml.fetch_email_full_data(self.current_user, self.current_pass, mail['id'])
|
|
if success:
|
|
for w in body_box.winfo_children(): w.destroy()
|
|
txt = tk.Text(body_box, height=8, bg="#f9f9f9", bd=0, padx=10, pady=10)
|
|
txt.insert("1.0", data['body'])
|
|
txt.config(state="disabled")
|
|
txt.pack(fill="x")
|
|
|
|
for img in data['images']:
|
|
try:
|
|
photo = ImageTk.PhotoImage(Image.open(img).convert("RGB").resize((300, 300)))
|
|
self.img_refs.append(photo)
|
|
tk.Label(body_box, image=photo, bg="#f9f9f9").pack(pady=5)
|
|
except: pass
|
|
|
|
for f_path in data['files']:
|
|
tk.Button(body_box, text=f"📄 Abrir: {os.path.basename(f_path)}",
|
|
fg="blue", command=lambda p=f_path: os.system(f'xdg-open "{p}"'),
|
|
bg="#f9f9f9", bd=0).pack(anchor="w", padx=20, pady=5)
|
|
|
|
body_box.pack(fill="x")
|
|
|
|
header.bind("<Button-1>", expand)
|
|
|
|
def open_compose_window(self):
|
|
comp = tk.Toplevel(self); comp.title("Redactar"); comp.geometry("400x500")
|
|
tk.Label(comp, text="Para:").pack(padx=10, anchor="w")
|
|
e_to = tk.Entry(comp); e_to.pack(fill="x", padx=10)
|
|
tk.Label(comp, text="Asunto:").pack(padx=10, anchor="w")
|
|
e_sub = tk.Entry(comp); e_sub.pack(fill="x", padx=10)
|
|
|
|
self.attachment_path = None
|
|
lbl_file = tk.Label(comp, text="Sin archivo")
|
|
def attach():
|
|
self.attachment_path = filedialog.askopenfilename()
|
|
if self.attachment_path: lbl_file.config(text=os.path.basename(self.attachment_path), fg="blue")
|
|
|
|
tk.Button(comp, text="📎 Adjuntar", command=attach).pack(pady=5)
|
|
lbl_file.pack()
|
|
txt = tk.Text(comp, height=10); txt.pack(fill="both", expand=True, padx=10, pady=10)
|
|
|
|
def send():
|
|
s, m = eml.send_email_logic(self.current_user, e_to.get(), e_sub.get(), txt.get("1.0", "end"), self.current_pass, self.attachment_path)
|
|
if s: messagebox.showinfo("OK", "Enviado"); comp.destroy()
|
|
else: messagebox.showerror("Error", m)
|
|
|
|
tk.Button(comp, text="ENVIAR 🚀", bg="#2ecc71", fg="white", command=send).pack(pady=10)
|
|
|
|
|
|
|
|
# --- GESTIÓN DEL CHAT ---
|
|
|
|
def create_chat_tab(self):
|
|
import os
|
|
import tkinter as tk
|
|
from tkinter import ttk
|
|
|
|
tab_chat = ttk.Frame(self.notebook)
|
|
self.notebook.add(tab_chat, text="Chat Grupal 💬", padding=15)
|
|
|
|
tab_chat.columnconfigure(0, weight=1)
|
|
tab_chat.rowconfigure(2, weight=1)
|
|
|
|
mi_pid = os.getpid()
|
|
|
|
lbl_pid = tk.Label(tab_chat, text=f"📍 TU NÚMERO DE PROCESO (PID) ES: {mi_pid}",
|
|
font=("Arial", 14, "bold"), fg="#2c3e50", bg="#ecf0f1", pady=10, relief="groove")
|
|
lbl_pid.grid(row=0, column=0, sticky="ew", pady=(0, 10))
|
|
|
|
tk.Label(tab_chat, text="Sala general (todos los procesos leen y escriben aquí):",
|
|
font=("Arial", 10, "bold"), anchor="w").grid(row=1, column=0, sticky="ew")
|
|
|
|
self.chat_display = tk.Text(tab_chat, state='disabled', height=15, bg="#fdfdfd", font=("Consolas", 11))
|
|
self.chat_display.grid(row=2, column=0, sticky="nsew", pady=5)
|
|
|
|
frame_input = ttk.Frame(tab_chat)
|
|
frame_input.grid(row=3, column=0, sticky="ew", pady=(5, 0))
|
|
|
|
self.chat_entry = ttk.Entry(frame_input, font=("Arial", 11))
|
|
self.chat_entry.pack(side="left", fill="x", expand=True, padx=(0, 5))
|
|
self.chat_entry.bind("<Return>", lambda e: self.send_chat_message())
|
|
|
|
ttk.Button(frame_input, text="Enviar 🚀", command=self.send_chat_message).pack(side="right")
|
|
|
|
def send_chat_message(self):
|
|
msg = self.chat_entry.get()
|
|
if msg.strip():
|
|
if self.chat_net.send_message(msg):
|
|
self.chat_entry.delete(0, tk.END)
|
|
|
|
def receive_chat_message(self, message):
|
|
import tkinter as tk
|
|
def _actualizar_ui():
|
|
if self.chat_display and self.chat_display.winfo_exists():
|
|
self.chat_display.config(state='normal')
|
|
self.chat_display.insert(tk.END, message + "\n")
|
|
self.chat_display.see(tk.END)
|
|
self.chat_display.config(state='disabled')
|
|
|
|
self.after(0, _actualizar_ui)
|
|
|
|
if __name__ == "__main__":
|
|
app = MainApplication()
|
|
app.mainloop() |