ProyectoPHP/ui_layout.py

683 lines
28 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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()