683 lines
28 KiB
Python
683 lines
28 KiB
Python
# ui_layout.py
|
||
import tkinter as tk
|
||
from tkinter import Menu, ttk, messagebox
|
||
from tkinter.scrolledtext import ScrolledText
|
||
import threading
|
||
import matplotlib.pyplot as plt
|
||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||
import datetime
|
||
import os
|
||
|
||
# Importar funciones y variables
|
||
import system_utils
|
||
import monitor_manager
|
||
import config
|
||
|
||
|
||
def crear_ui_completa(root):
|
||
"""Configura el layout principal, crea todos los widgets e inicia hilos."""
|
||
|
||
# Aplicar un tema más moderno para ttk
|
||
style = ttk.Style()
|
||
style.theme_use('clam')
|
||
|
||
# --- FUNCIONES AUXILIARES DE UI (Para llamadas a eventos y botones) ---
|
||
|
||
def abrir_editor_alarma(event_or_none, treeview_alarmas):
|
||
"""
|
||
Verifica la selección en el Treeview y abre la ventana flotante con los datos cargados.
|
||
Puede ser llamada por un evento (doble clic) o por un botón.
|
||
"""
|
||
# Obtenemos el ítem seleccionado (focus() funciona para botón y doble clic si hay foco)
|
||
selected_item = treeview_alarmas.focus()
|
||
|
||
# Si no hay selección, salimos.
|
||
if not selected_item:
|
||
messagebox.showwarning("Advertencia", "Selecciona una alarma para modificar.")
|
||
return
|
||
|
||
# Obtenemos el ID de la alarma (primer valor de la fila)
|
||
alarma_id = int(treeview_alarmas.item(selected_item, 'values')[0])
|
||
data = config.alarmas_programadas.get(alarma_id)
|
||
|
||
if not data:
|
||
messagebox.showerror("Error", "No se encontraron los datos de la alarma seleccionada.")
|
||
return
|
||
|
||
# Llamamos a la función flotante en modo modificación
|
||
mostrar_selector_alarma_flotante(treeview_alarmas, alarma_id, data)
|
||
|
||
|
||
def on_closing():
|
||
config.monitor_running = False
|
||
system_utils.detener_sonido_alarma()
|
||
system_utils.detener_mp3() # Detener música al cerrar
|
||
root.destroy()
|
||
|
||
root.protocol("WM_DELETE_WINDOW", on_closing)
|
||
|
||
# Configuración de Layout
|
||
root.columnconfigure(0, weight=0)
|
||
root.columnconfigure(1, weight=1)
|
||
root.columnconfigure(2, weight=0)
|
||
root.rowconfigure(0, weight=1)
|
||
root.rowconfigure(1, weight=0)
|
||
|
||
# --- Creación de Frames Principales ---
|
||
frame_izquierdo = tk.Frame(root, bg="#f0f0f0", width=200)
|
||
frame_central = tk.Frame(root, bg="white")
|
||
frame_derecho = tk.Frame(root, bg="#f0f0f0", width=10)
|
||
|
||
frame_izquierdo.grid(row=0, column=0, sticky="nsew")
|
||
frame_central.grid(row=0, column=1, sticky="nsew")
|
||
frame_derecho.grid(row=0, column=2, sticky="nsew")
|
||
|
||
frame_izquierdo.grid_propagate(False)
|
||
frame_derecho.grid_propagate(False)
|
||
|
||
# Layout del Frame Central
|
||
frame_central.rowconfigure(0, weight=1)
|
||
frame_central.rowconfigure(1, weight=0)
|
||
frame_central.columnconfigure(0, weight=1)
|
||
|
||
frame_superior = tk.Frame(frame_central, bg="lightyellow")
|
||
frame_inferior = tk.Frame(frame_central, bg="lightgray", height=100)
|
||
|
||
frame_superior.grid(row=0, column=0, sticky="nsew")
|
||
frame_inferior.grid(row=1, column=0, sticky="ew")
|
||
frame_inferior.grid_propagate(False)
|
||
|
||
# --- Implementación del Progressbar (Frame Inferior) ---
|
||
progress_bar = ttk.Progressbar(frame_inferior, orient="horizontal", length=800, mode="determinate")
|
||
progress_bar.pack(pady=10, padx=20, fill="x")
|
||
config.progress_bar = progress_bar
|
||
# -----------------------------------------------
|
||
|
||
# Notebook para las pestañas
|
||
notebook = ttk.Notebook(frame_superior)
|
||
notebook.pack(fill="both", expand=True)
|
||
|
||
# --- PESTAÑA 1: PROGRAMADOR DE ALARMAS ---
|
||
alarma_tab = ttk.Frame(notebook)
|
||
notebook.add(alarma_tab, text="Programador de Alarmas")
|
||
|
||
# --- PESTAÑA 2: BLOC DE NOTAS ---
|
||
editor_tab = ttk.Frame(notebook)
|
||
notebook.add(editor_tab, text="Bloc de Notas")
|
||
|
||
# --- PESTAÑA 3: MONITOR DEL SISTEMA (DEFINICIÓN ÚNICA) ---
|
||
monitor_tab = ttk.Frame(notebook)
|
||
notebook.add(monitor_tab, text="Monitor del Sistema", padding=4)
|
||
|
||
# --- PESTAÑA 4: WEB SCRAPING ---
|
||
scraping_tab = ttk.Frame(notebook)
|
||
notebook.add(scraping_tab, text="Web Scraping")
|
||
|
||
# --- PESTAÑA 5: JUEGOS ---
|
||
games_tab = ttk.Frame(notebook)
|
||
notebook.add(games_tab, text="Juegos 🎲")
|
||
|
||
# --- PESTAÑA 6: MÚSICA (NUEVO) ---
|
||
music_tab = ttk.Frame(notebook)
|
||
notebook.add(music_tab, text="Música 🎵")
|
||
# ---------------------------------------------
|
||
|
||
|
||
# ===============================================
|
||
# FUNCIÓN PARA MOSTRAR EL SELECTOR DE ALARMA FLOTANTE
|
||
# ===============================================
|
||
def mostrar_selector_alarma_flotante(treeview_alarmas, alarma_id=None, data=None):
|
||
"""
|
||
Crea y muestra la interfaz de selección de hora flotante.
|
||
Si se proporciona alarma_id y data, funciona en modo MODIFICACIÓN.
|
||
"""
|
||
|
||
is_modifying = alarma_id is not None
|
||
|
||
popup = tk.Toplevel(root)
|
||
popup.title("Modificar Alarma" if is_modifying else "Añadir Alarma")
|
||
popup.geometry("450x300") # TAMAÑO AJUSTADO PARA VISIBILIDAD
|
||
popup.resizable(False, False)
|
||
popup.transient(root)
|
||
|
||
# CORRECCIÓN CLAVE: Retrasar grab_set para evitar 'grab failed: window not viewable'
|
||
popup.after(10, popup.grab_set)
|
||
|
||
# Variables de entrada (Carga de datos si es modo modificación)
|
||
initial_hora = data['time'].strftime("%H") if is_modifying else datetime.datetime.now().strftime("%H")
|
||
initial_minuto = data['time'].strftime("%M") if is_modifying else datetime.datetime.now().strftime("%M")
|
||
initial_tarea = data['message'] if is_modifying else ""
|
||
initial_sound_file = data['sound_file'] if is_modifying else config.ALERTA_SOUND_FILE
|
||
|
||
# Inicializar con valores cargados o por defecto
|
||
hora_var = tk.StringVar(value=initial_hora)
|
||
minuto_var = tk.StringVar(value=initial_minuto)
|
||
tarea_var = tk.StringVar(value=initial_tarea)
|
||
|
||
# --- Configuración del Label de Sonido ---
|
||
config.ALERTA_SOUND_FILE = initial_sound_file # Seteamos la global para la función seleccionar_archivo_alarma
|
||
initial_sound_text = os.path.basename(config.ALERTA_SOUND_FILE) if config.ALERTA_SOUND_FILE else "[No seleccionado]"
|
||
|
||
main_frame = ttk.Frame(popup, padding="15")
|
||
main_frame.pack(fill='both', expand=True)
|
||
|
||
# Grid para el layout
|
||
main_frame.columnconfigure(1, weight=1)
|
||
main_frame.columnconfigure(2, weight=1)
|
||
|
||
# --- SECCIÓN HORA Y MINUTO ---
|
||
tk.Label(main_frame, text="Hora (HH):").grid(row=0, column=0, pady=5, sticky='w')
|
||
tk.Label(main_frame, text="Minuto (MM):").grid(row=0, column=2, pady=5, sticky='w')
|
||
|
||
horas = [f"{h:02d}" for h in range(24)]
|
||
minutos = [f"{m:02d}" for m in range(60)]
|
||
|
||
hora_cb = ttk.Combobox(main_frame, values=horas, textvariable=hora_var, width=5, state="readonly")
|
||
minuto_cb = ttk.Combobox(main_frame, values=minutos, textvariable=minuto_var, width=5, state="readonly")
|
||
|
||
hora_cb.grid(row=1, column=0, padx=(5, 10), sticky='ew')
|
||
minuto_cb.grid(row=1, column=2, padx=(10, 5), sticky='ew')
|
||
|
||
# --- SECCIÓN SONIDO ---
|
||
tk.Label(main_frame, text="Sonido:", font=('Helvetica', 9, 'bold')).grid(row=2, column=0, pady=(10, 5), sticky='w')
|
||
|
||
label_archivo_seleccionado = tk.Label(main_frame, text=initial_sound_text, anchor='w')
|
||
label_archivo_seleccionado.grid(row=4, column=0, columnspan=3, padx=5, pady=(0, 0), sticky='ew')
|
||
|
||
|
||
# Botón para abrir el diálogo de selección de archivo
|
||
ttk.Button(
|
||
main_frame,
|
||
text="Seleccionar Audio (.wav/.mp3)",
|
||
command=lambda: system_utils.seleccionar_archivo_alarma(root, label_archivo_seleccionado)
|
||
).grid(row=3, column=0, columnspan=3, padx=5, pady=(5, 5), sticky='ew')
|
||
|
||
|
||
# --- SECCIÓN MENSAJE ---
|
||
tk.Label(main_frame, text="Mensaje/Tarea:").grid(row=5, column=0, columnspan=3, pady=(10, 5), sticky='w')
|
||
tarea_entry = ttk.Entry(main_frame, textvariable=tarea_var, width=35)
|
||
tarea_entry.grid(row=6, column=0, columnspan=3, pady=5, sticky='ew')
|
||
|
||
# Frame para botones de control (OK/CANCEL)
|
||
button_frame = ttk.Frame(main_frame)
|
||
button_frame.grid(row=7, column=0, columnspan=3, pady=(15, 0), sticky='e')
|
||
|
||
# --- Lógica de Comando ---
|
||
if is_modifying:
|
||
action_command = lambda: system_utils.modificar_alarma_existente(
|
||
root, alarma_id, hora_var.get(), minuto_var.get(), tarea_var.get(), treeview_alarmas, popup, config.ALERTA_SOUND_FILE
|
||
)
|
||
action_text = "💾 Guardar Cambios"
|
||
else:
|
||
action_command = lambda: system_utils.agregar_alarma(
|
||
root, hora_var.get(), minuto_var.get(), tarea_var.get(), treeview_alarmas, popup, config.ALERTA_SOUND_FILE
|
||
)
|
||
action_text = "➕ Añadir"
|
||
|
||
# Botón OK (Programar/Modificar)
|
||
ttk.Button(
|
||
button_frame,
|
||
text=action_text,
|
||
width=18,
|
||
command=action_command
|
||
).pack(side=tk.LEFT, padx=5)
|
||
|
||
# Botón Cancelar
|
||
ttk.Button(
|
||
button_frame,
|
||
text="Cancelar",
|
||
width=10,
|
||
command=popup.destroy
|
||
).pack(side=tk.LEFT)
|
||
|
||
|
||
# ===============================================
|
||
# CONTENIDO DE LA SOLAPA DE ALARMA
|
||
# ===============================================
|
||
main_alarm_frame = tk.Frame(alarma_tab)
|
||
main_alarm_frame.pack(fill="both", expand=True)
|
||
|
||
# --- Lista de Alarmas (Treeview) ---
|
||
columns = ('ID', 'Hora', 'Tarea', 'Estado', 'Fecha')
|
||
treeview_alarmas = ttk.Treeview(main_alarm_frame, columns=columns, show='headings')
|
||
|
||
for col in columns:
|
||
treeview_alarmas.heading(col, text=col, anchor=tk.W)
|
||
|
||
treeview_alarmas.column('ID', width=40)
|
||
treeview_alarmas.column('Hora', width=40)
|
||
treeview_alarmas.column('Estado', width=70, anchor=tk.CENTER)
|
||
treeview_alarmas.column('Tarea', minwidth=150, stretch=tk.YES)
|
||
treeview_alarmas.column('Fecha', width=80)
|
||
treeview_alarmas.pack(fill='both', expand=True, padx=10, pady=10)
|
||
|
||
# [NUEVO] Llamar a cargar alarmas DESPUÉS de crear el Treeview
|
||
system_utils.cargar_alarmas(treeview_alarmas, root)
|
||
|
||
# --- Evento de Doble Clic para Modificar (CORREGIDO) ---
|
||
def handle_double_click(event):
|
||
abrir_editor_alarma(event, treeview_alarmas)
|
||
|
||
treeview_alarmas.bind('<Double-1>', handle_double_click)
|
||
|
||
# --- Panel de Control de Alarmas (Parte inferior) ---
|
||
control_frame = tk.LabelFrame(main_alarm_frame, text="Control", padx=15, pady=15)
|
||
control_frame.pack(fill='x', padx=10, pady=(0, 10))
|
||
|
||
# Botones principales
|
||
ttk.Button(
|
||
control_frame,
|
||
text="➕ Programar Nueva Alarma",
|
||
command=lambda: mostrar_selector_alarma_flotante(treeview_alarmas) # Llama a la función que abre el popup
|
||
).pack(side=tk.LEFT, padx=10)
|
||
|
||
# Botón Modificar (CORREGIDO: usa la función auxiliar)
|
||
ttk.Button(
|
||
control_frame,
|
||
text="✏️ Modificar Seleccionada",
|
||
command=lambda: abrir_editor_alarma(None, treeview_alarmas)
|
||
).pack(side=tk.LEFT, padx=10)
|
||
|
||
ttk.Button(
|
||
control_frame,
|
||
text="✅ Activar/Desactivar Seleccionada",
|
||
command=lambda: system_utils.toggle_alarma(treeview_alarmas)
|
||
).pack(side=tk.LEFT, padx=10)
|
||
|
||
ttk.Button(
|
||
control_frame,
|
||
text="🗑️ Eliminar Seleccionada",
|
||
command=lambda: system_utils.eliminar_alarma(treeview_alarmas)
|
||
).pack(side=tk.LEFT, padx=10)
|
||
|
||
# NUEVO: Separador
|
||
ttk.Separator(control_frame, orient='vertical').pack(side=tk.LEFT, padx=10, fill='y')
|
||
|
||
# NUEVO: Control de Sonido
|
||
ttk.Button(
|
||
control_frame,
|
||
text="🔇 Detener Sonido Alarma",
|
||
command=system_utils.detener_sonido_alarma
|
||
).pack(side=tk.LEFT, padx=10)
|
||
|
||
tk.Label(control_frame, text="Volumen:").pack(side=tk.LEFT)
|
||
|
||
volumen_scale = ttk.Scale(
|
||
control_frame,
|
||
from_=0, to=100,
|
||
orient='horizontal',
|
||
length=100,
|
||
command=system_utils.ajustar_volumen_alarma
|
||
)
|
||
volumen_scale.set(config.alarma_volumen * 100)
|
||
volumen_scale.pack(side=tk.LEFT, padx=5)
|
||
|
||
|
||
# ===============================================
|
||
# CONTENIDO DE LA SOLAPA BLOC DE NOTAS
|
||
# ===============================================
|
||
|
||
# Frame para los botones de control
|
||
control_frame_editor = tk.Frame(editor_tab)
|
||
control_frame_editor.pack(pady=5)
|
||
|
||
# Botones del Editor
|
||
ttk.Button(control_frame_editor, text="Nuevo", command=system_utils.nuevo_archivo).pack(side=tk.LEFT, padx=5)
|
||
ttk.Button(control_frame_editor, text="Abrir TXT...", command=lambda: system_utils.abrir_archivo(root)).pack(side=tk.LEFT, padx=5)
|
||
ttk.Button(control_frame_editor, text="Guardar TXT", command=lambda: system_utils.guardar_texto(root)).pack(side=tk.LEFT, padx=15)
|
||
ttk.Button(
|
||
control_frame_editor,
|
||
text="📂 Abrir Carpeta Notas", # CAMBIO DE ETIQUETA
|
||
command=system_utils.abrir_carpeta_notas # LLAMA A LA FUNCIÓN DE NOTAS
|
||
).pack(side=tk.LEFT, padx=5)
|
||
|
||
# Widget de Texto
|
||
editor_text_widget = tk.Text(editor_tab, wrap='word', undo=True, font=('Courier New', 10))
|
||
editor_text_widget.pack(fill="both", expand=True, padx=5, pady=5)
|
||
|
||
# Asignar a la variable global
|
||
config.editor_texto = editor_text_widget
|
||
|
||
|
||
# --- Creación de la 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")
|
||
|
||
label_1 = tk.Label(barra_estado, text="Estado Backup", bg="green", anchor="w", width=20)
|
||
label_2 = tk.Label(barra_estado, text="Registro: Detenido", bg="gray", anchor="w", width=20)
|
||
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_1.pack(side="left", fill="x", expand=True)
|
||
label_2.pack(side="left", fill="x", expand=True)
|
||
label_fecha_hora.pack(side="right", fill="x", expand=True)
|
||
|
||
# Asignar los labels a la configuración global para que los hilos los encuentren
|
||
config.label_1 = label_1
|
||
config.label_2 = label_2
|
||
config.label_fecha_hora = label_fecha_hora
|
||
|
||
# --- Inicialización del Panel Lateral ---
|
||
monitor_manager.crear_panel_lateral(frame_izquierdo, root)
|
||
|
||
# --- Creación del Menú Superior (SE MODIFICA) ---
|
||
menu_bar = Menu(root)
|
||
file_menu = Menu(menu_bar, tearoff=0); file_menu.add_command(label="Salir", command=on_closing)
|
||
|
||
# MODIFICADO: Se elimina el comando de YouTube
|
||
launch_menu = Menu(menu_bar, tearoff=0);
|
||
launch_menu.add_command(label="ChatGPT", command=lambda: system_utils.lanzar_url("https://chat.openai.com"))
|
||
launch_menu.add_command(label="Apuntes PSP", command=lambda: system_utils.lanzar_url("https://apuntes-informatica.ieslamar.org/psp/proyecto"))
|
||
launch_menu.add_command(label="Solitario Google", command=lambda: system_utils.lanzar_url("https://www.google.com/logos/fnbx/solitaire/standalone.html"))
|
||
launch_menu.add_command(label="Aules FP", command=lambda: system_utils.lanzar_url("https://aules.edu.gva.es/fp/my/"))
|
||
|
||
# MODIFICADO: Se ELIMINA el comando del juego thread-safe de este menú
|
||
tools_menu = Menu(menu_bar, tearoff=0);
|
||
tools_menu.add_command(label="Ejecutar Copia de Seguridad", command=lambda: system_utils.ejecutar_script_en_hilo(label_1, root))
|
||
tools_menu.add_command(label="Iniciar/Detener Registro CSV", command=lambda: system_utils.manejar_registro_csv(label_2))
|
||
# tools_menu.add_command(label="Simular Juego (Thread-Safe) 🐫", command=lambda: system_utils.simular_juego_camellos(root)) # ELIMINADO
|
||
tools_menu.add_command(label="📂 Abrir Carpeta Scraping", command=lambda: system_utils.abrir_carpeta_especifica(config.SCRAPING_FOLDER, "Scraping"))
|
||
tools_menu.add_command(label="📂 Abrir Config Scraping", command=lambda: system_utils.abrir_carpeta_especifica(config.SCRAPING_CONFIG_FOLDER, "Config Scraping"))
|
||
|
||
menu_bar.add_cascade(label="Archivo", menu=file_menu)
|
||
menu_bar.add_cascade(label="Herramientas", menu=tools_menu)
|
||
menu_bar.add_cascade(label="Lanzadores", menu=launch_menu)
|
||
menu_bar.add_cascade(label="Ayuda", menu=Menu(menu_bar, tearoff=0))
|
||
root.config(menu=menu_bar)
|
||
|
||
|
||
# ===============================================
|
||
# Solapa de Monitor del Sistema (CONTENIDO ÚNICO)
|
||
# ===============================================
|
||
|
||
# Reutilizamos monitor_tab creado arriba para evitar la duplicidad
|
||
main_monitor_frame = tk.Frame(monitor_tab)
|
||
main_monitor_frame.pack(fill=tk.BOTH, expand=True)
|
||
main_monitor_frame.rowconfigure(0, weight=3)
|
||
main_monitor_frame.rowconfigure(1, weight=1)
|
||
main_monitor_frame.columnconfigure(0, weight=1)
|
||
|
||
# --- Fila 0: Gráficos ---
|
||
plot_frame = tk.Frame(main_monitor_frame)
|
||
plot_frame.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
|
||
|
||
fig = plt.figure(figsize=(10, 8))
|
||
gs = fig.add_gridspec(2, 3, hspace=0.6, wspace=0.3)
|
||
|
||
ax_cpu = fig.add_subplot(gs[0, 0])
|
||
ax_mem = fig.add_subplot(gs[0, 1])
|
||
ax_cores = fig.add_subplot(gs[0, 2])
|
||
ax_net = fig.add_subplot(gs[1, 0])
|
||
ax_pie = fig.add_subplot(gs[1, 1], aspect="equal")
|
||
ax_disk_io = fig.add_subplot(gs[1, 2])
|
||
|
||
plt.style.use('ggplot')
|
||
|
||
canvas = FigureCanvasTkAgg(fig, master=plot_frame)
|
||
canvas_widget = canvas.get_tk_widget()
|
||
canvas_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
|
||
|
||
# --- Fila 1: Log y Procesos ---
|
||
bottom_frame = tk.Frame(main_monitor_frame)
|
||
bottom_frame.grid(row=1, column=0, sticky="nsew", padx=5, pady=5)
|
||
bottom_frame.columnconfigure(0, weight=1)
|
||
bottom_frame.columnconfigure(1, weight=1)
|
||
|
||
# 1. Log de Eventos (Sección Izquierda)
|
||
log_frame = tk.LabelFrame(bottom_frame, text="Log de Eventos del Sistema")
|
||
log_frame.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
|
||
log_frame.rowconfigure(0, weight=1)
|
||
log_frame.columnconfigure(0, weight=1)
|
||
|
||
config.system_log = ScrolledText(log_frame, height=8, font=("Courier", 8), bg="#2c3e50", fg="lightgray")
|
||
config.system_log.grid(row=0, column=0, sticky="nsew")
|
||
|
||
# 2. Treeview de Procesos (Sección Derecha)
|
||
process_frame = tk.LabelFrame(bottom_frame, text=f"Top {10} Procesos (Ordenados por CPU)")
|
||
process_frame.grid(row=0, column=1, sticky="nsew", padx=5, pady=5)
|
||
process_frame.columnconfigure(0, weight=1)
|
||
process_frame.rowconfigure(0, weight=1)
|
||
|
||
columns = ('PID', 'CPU', 'MEM', 'HILOS', 'NOMBRE')
|
||
treeview_processes = ttk.Treeview(process_frame, columns=columns, show='headings')
|
||
|
||
for col in columns:
|
||
treeview_processes.heading(col, text=col, anchor=tk.W)
|
||
treeview_processes.column(col, width=50, anchor=tk.W)
|
||
|
||
treeview_processes.column('PID', width=50)
|
||
treeview_processes.column('CPU', width=60)
|
||
treeview_processes.column('MEM', width=70)
|
||
treeview_processes.column('HILOS', width=60)
|
||
treeview_processes.column('NOMBRE', minwidth=150, stretch=tk.YES)
|
||
|
||
treeview_processes.grid(row=0, column=0, sticky="nsew")
|
||
|
||
kill_button = tk.Button(
|
||
process_frame,
|
||
text="Terminar Proceso Seleccionado (⚠️ DANGER)",
|
||
command=lambda: monitor_manager.terminar_proceso(treeview_processes),
|
||
bg='darkred',
|
||
fg='white',
|
||
font=('Helvetica', 10, 'bold')
|
||
)
|
||
kill_button.grid(row=1, column=0, sticky="ew", pady=(5,0))
|
||
|
||
# ===============================================
|
||
# Solapa de Web Scraping (Implementación completa y estética)
|
||
# ===============================================
|
||
|
||
# Marco principal para el scraping
|
||
main_scraping_frame = ttk.Frame(scraping_tab, padding="15")
|
||
main_scraping_frame.pack(fill=tk.BOTH, expand=True)
|
||
|
||
main_scraping_frame.columnconfigure(0, weight=1)
|
||
main_scraping_frame.columnconfigure(1, weight=0)
|
||
main_scraping_frame.rowconfigure(5, weight=1) # Fila 5 es el Text Output
|
||
|
||
# --- Fila 0 & 1: URL, Opciones y Configuración Personalizada ---
|
||
|
||
# 1. Título y Carga de Configuración
|
||
header_frame = ttk.Frame(main_scraping_frame)
|
||
header_frame.grid(row=0, column=0, columnspan=2, sticky='ew', pady=(0, 5))
|
||
header_frame.columnconfigure(0, weight=1)
|
||
|
||
# CORRECCIÓN DE ORTOGRAFÍA: "Scrappear" -> "Scrapear"
|
||
ttk.Label(header_frame, text="🌐 URL a Scrapear:", font=('Helvetica', 10, 'bold')).pack(side=tk.LEFT, padx=(0, 5))
|
||
|
||
config.scraping_config_file_label = ttk.Label(header_frame, text="Config: [Ninguna]", foreground='blue')
|
||
config.scraping_config_file_label.pack(side=tk.RIGHT, padx=5)
|
||
|
||
ttk.Button(
|
||
header_frame,
|
||
text="⚙️ Cargar Config. (.json)",
|
||
command=lambda: system_utils.abrir_archivo_scraping_config(root)
|
||
).pack(side=tk.RIGHT)
|
||
|
||
# 2. Entrada de URL
|
||
url_var = tk.StringVar(value="https://www.example.com")
|
||
url_entry = ttk.Entry(main_scraping_frame, textvariable=url_var, width=80)
|
||
url_entry.grid(row=1, column=0, columnspan=2, sticky='ew', pady=(0, 10))
|
||
|
||
# ASIGNAR A CONFIG para que system_utils pueda actualizarlo
|
||
config.scraping_url_input = url_var
|
||
|
||
# --- Fila 2: Controles Detallados ---
|
||
control_frame_row2 = ttk.Frame(main_scraping_frame)
|
||
control_frame_row2.grid(row=2, column=0, columnspan=2, sticky='ew', pady=5)
|
||
control_frame_row2.columnconfigure(2, weight=1)
|
||
|
||
# Opciones de Extracción
|
||
ttk.Label(control_frame_row2, text="Tipo de Extracción:", font=('Helvetica', 9, 'bold')).grid(row=0, column=0, sticky='w', padx=(0, 5))
|
||
tipo_extraccion_var = tk.StringVar(value="Título y Metadatos")
|
||
extracciones = [
|
||
"Título y Metadatos",
|
||
"Primeros Párrafos",
|
||
"Enlaces (Links)",
|
||
"Imágenes (URLs)",
|
||
"Tablas (Estructura Básica)",
|
||
"Portátiles Gamer (Enlace + Precio)", # NUEVA OPCIÓN COMBINADA
|
||
"-> Texto Específico (CSS Selector)",
|
||
"-> Atributo Específico (CSS Selector + Attr)"
|
||
]
|
||
|
||
extraccion_combobox = ttk.Combobox(
|
||
control_frame_row2,
|
||
values=extracciones,
|
||
textvariable=tipo_extraccion_var,
|
||
state="readonly",
|
||
width=30
|
||
)
|
||
extraccion_combobox.grid(row=0, column=1, sticky='w', padx=10)
|
||
|
||
# Selector CSS
|
||
ttk.Label(control_frame_row2, text="Selector CSS/Tag (avanzado):").grid(row=0, column=3, sticky='w', padx=(10, 5))
|
||
config.scraping_selector_input = ttk.Entry(control_frame_row2, width=40)
|
||
config.scraping_selector_input.grid(row=0, column=4, sticky='ew', padx=(0, 10))
|
||
|
||
# Atributo
|
||
ttk.Label(control_frame_row2, text="Atributo (ej: href/src):").grid(row=0, column=5, sticky='w', padx=(10, 5))
|
||
config.scraping_attr_input = ttk.Entry(control_frame_row2, width=15)
|
||
config.scraping_attr_input.grid(row=0, column=6, sticky='ew')
|
||
|
||
# --- Fila 3: Ejecución y Control ---
|
||
control_execution_frame = ttk.Frame(main_scraping_frame)
|
||
control_execution_frame.grid(row=3, column=0, columnspan=2, sticky='ew', pady=(10, 5))
|
||
control_execution_frame.columnconfigure(0, weight=1)
|
||
|
||
# Botón Scrappear
|
||
btn_scrap = ttk.Button(
|
||
control_execution_frame,
|
||
text="🚀 Iniciar Scrapear",
|
||
command=lambda: monitor_manager.scrappear_pagina_principal(
|
||
url_var.get(),
|
||
tipo_extraccion_var.get(),
|
||
config.scraping_output_text,
|
||
config.scraping_progress_bar,
|
||
config.scraping_selector_input.get(),
|
||
config.scraping_attr_input.get(),
|
||
config.scraping_config_data,
|
||
root
|
||
)
|
||
)
|
||
btn_scrap.pack(side=tk.LEFT, padx=(0, 10))
|
||
|
||
# Barra de Progreso
|
||
config.scraping_progress_bar = ttk.Progressbar(control_execution_frame, orient="horizontal", mode="indeterminate")
|
||
config.scraping_progress_bar.pack(side=tk.LEFT, fill='x', expand=True, padx=(0, 10))
|
||
|
||
# Botón Detener
|
||
ttk.Button(
|
||
control_execution_frame,
|
||
text="🛑 Detener",
|
||
command=lambda: system_utils.detener_scraping()
|
||
).pack(side=tk.LEFT)
|
||
|
||
# --- Fila 4 & 5: Área de Resultado y Guardar ---
|
||
|
||
ttk.Label(main_scraping_frame, text="📊 Resultado de la Extracción:", font=('Helvetica', 10, 'bold')).grid(row=4, column=0, columnspan=2, sticky='w', pady=(10, 5))
|
||
|
||
# Widget de Texto para la salida
|
||
config.scraping_output_text = ScrolledText(main_scraping_frame, wrap='word', font=('Courier New', 9), height=18, bg='#f9f9f9')
|
||
config.scraping_output_text.grid(row=5, column=0, columnspan=2, sticky='nsew', pady=(0, 10))
|
||
|
||
# Botón Guardar Resultado
|
||
ttk.Button(
|
||
main_scraping_frame,
|
||
text="💾 Guardar Resultado en /data/scraping", # CORRECCIÓN DE RUTA
|
||
command=lambda: system_utils.guardar_scraping(config.scraping_output_text.get("1.0", tk.END), root)
|
||
).grid(row=6, column=0, columnspan=2, sticky='ew')
|
||
|
||
|
||
# ===============================================
|
||
# CONTENIDO DE LA SOLAPA DE JUEGOS
|
||
# ===============================================
|
||
|
||
main_games_frame = ttk.Frame(games_tab, padding="20")
|
||
main_games_frame.pack(fill=tk.BOTH, expand=True)
|
||
|
||
ttk.Label(
|
||
main_games_frame,
|
||
text="Simulaciones de Entretenimiento (Thread-Safe)",
|
||
font=('Helvetica', 14, 'bold')
|
||
).pack(pady=15)
|
||
|
||
ttk.Separator(main_games_frame, orient='horizontal').pack(fill='x', pady=5)
|
||
|
||
# Botón de juego de camellos
|
||
ttk.Button(
|
||
main_games_frame,
|
||
text="🏆 Iniciar Carrera de Camellos (Thread-Safe)",
|
||
command=lambda: system_utils.simular_juego_camellos(root),
|
||
cursor="hand2"
|
||
).pack(pady=10)
|
||
|
||
ttk.Label(
|
||
main_games_frame,
|
||
text="Este juego se ejecuta en una ventana 'Toplevel' para garantizar la seguridad del hilo principal.",
|
||
font=('Helvetica', 9, 'italic'),
|
||
foreground='gray'
|
||
).pack(pady=5)
|
||
|
||
# ===============================================
|
||
# CONTENIDO DE LA SOLAPA DE MÚSICA (NUEVO)
|
||
# ===============================================
|
||
|
||
music_frame = ttk.Frame(music_tab, padding="20")
|
||
music_frame.pack(fill=tk.BOTH, expand=True)
|
||
|
||
ttk.Label(
|
||
music_frame,
|
||
text="Reproductor de Audio Local (.mp3 / .wav)",
|
||
font=('Helvetica', 14, 'bold')
|
||
).pack(pady=15)
|
||
|
||
# 1. Archivo Seleccionado
|
||
ttk.Label(music_frame, text="Archivo Seleccionado:").pack(pady=(10, 5))
|
||
label_music_file = ttk.Label(music_frame, text="[Ningún archivo cargado]", anchor='center', foreground='blue')
|
||
label_music_file.pack(fill='x', padx=50)
|
||
|
||
# 2. Botón Seleccionar
|
||
ttk.Button(
|
||
music_frame,
|
||
text="📂 Seleccionar MP3/WAV",
|
||
command=lambda: system_utils.seleccionar_mp3(root, label_music_file)
|
||
).pack(pady=10, padx=50, fill='x')
|
||
|
||
ttk.Separator(music_frame, orient='horizontal').pack(fill='x', pady=10, padx=50)
|
||
|
||
# 3. Controles de Reproducción
|
||
control_music_frame = ttk.Frame(music_frame)
|
||
control_music_frame.pack(pady=10)
|
||
|
||
ttk.Button(
|
||
control_music_frame,
|
||
text="▶️ Reproducir",
|
||
command=lambda: system_utils.reproducir_mp3(root)
|
||
).pack(side=tk.LEFT, padx=10)
|
||
|
||
ttk.Button(
|
||
control_music_frame,
|
||
text="⏹️ Detener",
|
||
command=system_utils.detener_mp3
|
||
).pack(side=tk.LEFT, padx=10)
|
||
|
||
# 4. Control de Volumen
|
||
ttk.Label(music_frame, text="Volumen:").pack(pady=(15, 5))
|
||
|
||
volumen_scale_music = ttk.Scale(
|
||
music_frame,
|
||
from_=0, to=100,
|
||
orient='horizontal',
|
||
length=200,
|
||
command=system_utils.ajustar_volumen_mp3 # Reutilizamos la función que ajusta Pygame Mixer
|
||
)
|
||
volumen_scale_music.set(config.alarma_volumen * 100)
|
||
volumen_scale_music.pack(pady=5)
|
||
|
||
# --- Iniciar Hilos ---
|
||
system_utils.log_event("Monitor de sistema iniciado. Esperando la primera lectura de métricas...")
|
||
monitor_manager.iniciar_monitor_sistema(fig, canvas, ax_cpu, ax_mem, ax_net, ax_cores, ax_pie, ax_disk_io, treeview_processes, root)
|
||
|
||
update_thread = threading.Thread(target=lambda: system_utils.update_time(label_fecha_hora, root))
|
||
update_thread.daemon = True
|
||
update_thread.start() |