feat(vista): Integra alarmas, refactoriza y corrige (main)
Integra T4: Sistema de alarmas y mejoras generales. * Añade pestaña "Alarmas" para programar temporizadores. * Implementa creación, cancelación y visualización de alarmas. * Refactoriza y corrige la lógica de reproducción de radios (T3). * Mueve el editor de notas al panel central (pestaña "Tareas"). * Elimina el editor de notas del panel lateral. * Corrige la gestión de volumen en el reproductor de radios. * Actualiza dependencias y rutas de ficheros.
This commit is contained in:
parent
983836d94c
commit
8911576bb7
|
|
@ -40,6 +40,6 @@ python -m ProyectoGlobal
|
|||
|
||||
4. Scraping
|
||||
|
||||
5. Juego de los camellos / autos de choque / etc. (aplicar resolución de sincronización para evitar problemas de interbloqueos)
|
||||
5. ~~Juego de los camellos~~ / autos de choque / etc. (aplicar resolución de sincronización para evitar problemas de interbloqueos)
|
||||
|
||||
6. Música de fondo (reproducción de mp3 o midi)
|
||||
6. ~~Música de fondo (reproducción de mp3 o midi)~~
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
# Módulo: logica/T2/alarm.py
|
||||
|
||||
import tkinter as tk
|
||||
from datetime import datetime
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
from logica.T2.musicReproductor import MusicReproductor
|
||||
except ImportError:
|
||||
class MusicReproductor:
|
||||
def __init__(self, *args, **kwargs): pass
|
||||
|
||||
def ajustar_volumen(self, valor): pass
|
||||
|
||||
def cargar_y_reproducir(self, url): print(f"🎶 SIMULANDO PLAY: {url}")
|
||||
|
||||
def detener(self): print("🔇 SIMULANDO STOP")
|
||||
|
||||
|
||||
class AlarmManager:
|
||||
"""
|
||||
Gestiona la creación, seguimiento y disparo de múltiples alarmas (temporizadores).
|
||||
"""
|
||||
|
||||
def __init__(self, root, trigger_callback):
|
||||
self.root = root
|
||||
self.active_alarms = {}
|
||||
self.next_id = 1
|
||||
self.trigger_callback = trigger_callback
|
||||
|
||||
self.alarm_sound_player = MusicReproductor()
|
||||
|
||||
self.ALARM_SOUND_PATH = os.path.join(os.path.dirname(__file__), '..', '..', 'res', 'alarm.mp3')
|
||||
|
||||
# === MODIFICACIÓN CLAVE: Acepta total_seconds en lugar de minutes ===
|
||||
def set_alarm(self, total_seconds):
|
||||
"""
|
||||
Programa una nueva alarma para sonar después de un número de segundos.
|
||||
|
||||
:param total_seconds: Número de segundos hasta que suena la alarma.
|
||||
:return: ID único de la alarma.
|
||||
"""
|
||||
if total_seconds <= 0:
|
||||
raise ValueError("El tiempo de alarma debe ser positivo.")
|
||||
|
||||
alarm_id = self.next_id
|
||||
self.next_id += 1
|
||||
|
||||
ms_delay = total_seconds * 1000
|
||||
ms_delay_int = int(ms_delay)
|
||||
|
||||
tiempo_disparo_ts = datetime.now().timestamp() + total_seconds
|
||||
tiempo_disparo_dt = datetime.fromtimestamp(tiempo_disparo_ts)
|
||||
|
||||
name = f"⏰ {tiempo_disparo_dt.strftime('%H:%M:%S')}"
|
||||
|
||||
alarm_data = {
|
||||
'nombre': name,
|
||||
'total_seconds': total_seconds, # Almacenamos el tiempo total en segundos
|
||||
'tiempo_creacion': datetime.now(),
|
||||
'tiempo_disparo': tiempo_disparo_ts,
|
||||
'after_id': None,
|
||||
'sonada': False
|
||||
}
|
||||
|
||||
after_id = self.root.after(ms_delay_int, lambda: self._trigger_alarm(alarm_id))
|
||||
|
||||
alarm_data['after_id'] = after_id
|
||||
self.active_alarms[alarm_id] = alarm_data
|
||||
|
||||
print(f"🔔 Alarma '{name}' ({alarm_id}) programada para sonar en {total_seconds} segundos.")
|
||||
return alarm_id
|
||||
|
||||
def _trigger_alarm(self, alarm_id):
|
||||
# ... (código sin cambios aquí) ...
|
||||
if alarm_id in self.active_alarms:
|
||||
alarm = self.active_alarms[alarm_id]
|
||||
|
||||
print(f"🚨 ¡ALARMA! La alarma '{alarm['nombre']}' ({alarm_id}) ha sonado.")
|
||||
|
||||
alarm['sonada'] = True
|
||||
|
||||
self.alarm_sound_player.cargar_y_reproducir(self.ALARM_SOUND_PATH)
|
||||
|
||||
self.trigger_callback(alarm['nombre'], alarm_id)
|
||||
|
||||
def stop_alarm_sound(self):
|
||||
self.alarm_sound_player.detener()
|
||||
|
||||
def cancel_alarm(self, alarm_id):
|
||||
# ... (código sin cambios aquí) ...
|
||||
if alarm_id in self.active_alarms:
|
||||
alarm = self.active_alarms[alarm_id]
|
||||
|
||||
if not alarm['sonada'] and alarm['after_id']:
|
||||
self.root.after_cancel(alarm['after_id'])
|
||||
print(f"❌ Alarma '{alarm['nombre']}' ({alarm_id}) cancelada.")
|
||||
|
||||
del self.active_alarms[alarm_id]
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_active_alarms(self):
|
||||
"""
|
||||
Retorna una lista de las alarmas activas, incluyendo el tiempo restante.
|
||||
"""
|
||||
alarms_to_show = []
|
||||
current_time = datetime.now().timestamp()
|
||||
|
||||
for alarm_id, alarm in list(self.active_alarms.items()):
|
||||
if not alarm['sonada']:
|
||||
time_diff = alarm['tiempo_disparo'] - current_time
|
||||
|
||||
if time_diff > 0:
|
||||
remaining_sec = int(time_diff)
|
||||
|
||||
hours = remaining_sec // 3600
|
||||
minutes = (remaining_sec % 3600) // 60
|
||||
seconds = remaining_sec % 60
|
||||
|
||||
remaining_str = f"{hours:02d}h:{minutes:02d}m:{seconds:02d}s"
|
||||
|
||||
alarms_to_show.append({
|
||||
'id': alarm_id,
|
||||
'nombre': alarm['nombre'],
|
||||
'restante': remaining_str,
|
||||
'total_seconds': alarm['total_seconds'] # Usar total_seconds
|
||||
})
|
||||
|
||||
return sorted(alarms_to_show, key=lambda x: x['restante'])
|
||||
|
|
@ -1,97 +1,77 @@
|
|||
# Módulo: logica/T2/musicReproductor.py
|
||||
|
||||
import vlc
|
||||
import threading
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
# --- BLOQUE DE CÓDIGO OPCIONAL PARA SOLUCIÓN DE ERRORES DE RUTA DE VLC ---
|
||||
# Si al ejecutar el programa obtienes un error de "ImportError: DLL load failed"
|
||||
# o similar con 'vlc', DESCOMENTA el siguiente bloque y AJUSTA la ruta de vlc_path.
|
||||
# Esto ayuda a que Python encuentre las librerías principales de VLC.
|
||||
#
|
||||
# if sys.platform.startswith('win'):
|
||||
# # RUTA DE EJEMPLO PARA WINDOWS (AJUSTA según tu instalación)
|
||||
# vlc_path = r"C:\Program Files\VideoLAN\VLC"
|
||||
# if vlc_path not in os.environ.get('PATH', ''):
|
||||
# os.environ['PATH'] += os.pathsep + vlc_path
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
class MusicReproductor:
|
||||
"""
|
||||
Gestiona la reproducción de streams de radio usando la librería python-vlc.
|
||||
Clase para gestionar la reproducción de audio utilizando la librería python-vlc.
|
||||
Se asegura de que el objeto 'player' de VLC sea liberado y recreado correctamente
|
||||
para poder manejar múltiples eventos de alarma o cambiar de stream de radio sin fallos.
|
||||
"""
|
||||
|
||||
def __init__(self, initial_volume=50.0):
|
||||
"""Inicializa la instancia de VLC y el reproductor."""
|
||||
|
||||
# Instancia de VLC y objeto Reproductor
|
||||
def __init__(self, initial_volume=50):
|
||||
# 1. Crear la instancia de VLC, que debe ser única por aplicación.
|
||||
self.instance = vlc.Instance()
|
||||
|
||||
# 2. Creamos el player inicial.
|
||||
self.player = self.instance.media_player_new()
|
||||
self.current_media = None
|
||||
self.is_playing = False
|
||||
|
||||
# Configurar volumen inicial
|
||||
self.volumen = initial_volume
|
||||
self.ajustar_volumen(initial_volume)
|
||||
print(f"🎵 [VLC] Reproductor inicializado. Volumen: {self.player.audio_get_volume()}")
|
||||
print(f"🎵 [VLC] Reproductor inicializado. Volumen: {self.volumen}")
|
||||
|
||||
def ajustar_volumen(self, valor_porcentual):
|
||||
def ajustar_volumen(self, valor):
|
||||
"""Ajusta el volumen del reproductor."""
|
||||
self.volumen = int(valor)
|
||||
if self.player:
|
||||
self.player.audio_set_volume(self.volumen)
|
||||
|
||||
def cargar_y_reproducir(self, url):
|
||||
"""
|
||||
Ajusta el volumen del reproductor (0 a 100).
|
||||
Carga el archivo o stream y lo reproduce.
|
||||
|
||||
**Corrección:** Recrea self.player si fue liberado (release) por el método detener().
|
||||
"""
|
||||
volumen_int = int(max(0, min(100, valor_porcentual)))
|
||||
self.player.audio_set_volume(volumen_int)
|
||||
# No imprimimos el volumen aquí para evitar saturar la consola con cada movimiento del Scale
|
||||
print(f"🔄 [VLC] Intentando cargar y reproducir: {url}")
|
||||
|
||||
def cargar_y_reproducir(self, url_stream):
|
||||
"""
|
||||
Carga una nueva URL de stream y comienza la reproducción.
|
||||
"""
|
||||
if not url_stream:
|
||||
print("❌ [VLC] URL del stream vacía.")
|
||||
return
|
||||
# 1. Recrear el reproductor si fue liberado.
|
||||
if not self.player:
|
||||
self.player = self.instance.media_player_new()
|
||||
self.ajustar_volumen(self.volumen) # Restaurar volumen
|
||||
|
||||
print(f"🔄 [VLC] Intentando cargar y reproducir: {url_stream}")
|
||||
# 2. Detener la reproducción actual de forma segura antes de cargar una nueva media.
|
||||
# Esto previene el AttributeError, ya que self.player ahora está garantizado que no es None.
|
||||
if self.player:
|
||||
self.player.stop()
|
||||
|
||||
self.player.stop()
|
||||
# 3. Cargar y reproducir la nueva media.
|
||||
media = self.instance.media_new(url)
|
||||
self.player.set_media(media)
|
||||
|
||||
self.current_media = self.instance.media_new(url_stream)
|
||||
self.player.set_media(self.current_media)
|
||||
|
||||
self.player.play()
|
||||
self.is_playing = True
|
||||
print("✅ [VLC] Reproducción iniciada.")
|
||||
|
||||
def reproducir(self):
|
||||
"""
|
||||
Reanuda la reproducción si está pausada.
|
||||
"""
|
||||
if self.player.get_state() == vlc.State.Paused:
|
||||
self.player.play()
|
||||
self.is_playing = True
|
||||
print("▶️ [VLC] Reproducción reanudada.")
|
||||
if self.player.play() == 0:
|
||||
print("✅ [VLC] Reproducción iniciada.")
|
||||
else:
|
||||
print("ℹ️ [VLC] Ya está reproduciéndose o esperando un stream.")
|
||||
|
||||
def pausar(self):
|
||||
"""
|
||||
Pausa la reproducción.
|
||||
"""
|
||||
if self.player.get_state() == vlc.State.Playing:
|
||||
self.player.pause()
|
||||
self.is_playing = False
|
||||
print("⏸️ [VLC] Reproducción pausada.")
|
||||
else:
|
||||
print("ℹ️ [VLC] No se puede pausar, el reproductor no está en estado de reproducción.")
|
||||
print("❌ [VLC] Error al intentar iniciar la reproducción.")
|
||||
|
||||
def detener(self):
|
||||
"""
|
||||
Detiene la reproducción y libera los recursos. Crucial al cerrar la aplicación.
|
||||
Detiene la reproducción, libera los recursos del player y lo establece a None.
|
||||
Esto es crucial para que el sistema de audio no se quede bloqueado por VLC.
|
||||
"""
|
||||
if self.player:
|
||||
self.player.stop()
|
||||
# Limpiar referencias para asegurar que VLC se libere correctamente
|
||||
del self.player
|
||||
del self.instance
|
||||
print("⏹️ [VLC] Reproductor detenido y recursos liberados.")
|
||||
self.player.release()
|
||||
self.player = None # <--- Obliga a recrear el player en la próxima llamada a cargar_y_reproducir
|
||||
print("⏹️ [VLC] Reproductor detenido y recursos liberados.")
|
||||
|
||||
def pausar(self):
|
||||
"""Pausa la reproducción."""
|
||||
if self.player and self.player.is_playing():
|
||||
self.player.pause()
|
||||
|
||||
def reproducir(self):
|
||||
"""Reanuda la reproducción."""
|
||||
if self.player:
|
||||
self.player.play()
|
||||
Binary file not shown.
|
|
@ -16,5 +16,23 @@
|
|||
"url_stream": "https://stream.serviciospararadios.es/listen/activa_fm/activafm-tunein.mp3",
|
||||
"pais": "ES",
|
||||
"genero": null
|
||||
},
|
||||
{
|
||||
"nombre": "Cope Denia",
|
||||
"url_stream": "https://denia-copesedes-rrcast.flumotion.com/copesedes/denia-low.mp3",
|
||||
"pais": "ES",
|
||||
"genero": "Noticias"
|
||||
},
|
||||
{
|
||||
"nombre": "KPOO",
|
||||
"url_stream": "http://amber.streamguys.com:5220/xstream",
|
||||
"pais": null,
|
||||
"genero": null
|
||||
},
|
||||
{
|
||||
"nombre": "Cope Valencia",
|
||||
"url_stream": "https://valencia-copesedes-rrcast.flumotion.com/copesedes/valencia-low.mp3",
|
||||
"pais": "ES",
|
||||
"genero": "Noticias"
|
||||
}
|
||||
]
|
||||
|
|
@ -3,13 +3,16 @@
|
|||
import random
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from tkinter import messagebox
|
||||
import json
|
||||
from datetime import datetime
|
||||
import os
|
||||
import sys # Necesario para el bloque opcional de VLC
|
||||
import sys
|
||||
|
||||
# --- LÓGICA DE T1 (MONITOR DE RECURSOS) ---
|
||||
from logica.T1.trafficMeter import iniciar_monitor_red
|
||||
from logica.T1.graficos import crear_grafico_recursos, actualizar_historial_datos
|
||||
from logica.T1.textEditor import cargar_contenido_res_notes, guardar_contenido_res_notes # <--- AÑADIDO
|
||||
from matplotlib.figure import Figure
|
||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||
|
||||
|
|
@ -21,26 +24,28 @@ from logica.T2.carreraCamellos import (
|
|||
)
|
||||
|
||||
# --- LÓGICA DE T3 (REPRODUCTOR DE MÚSICA) ---
|
||||
# Se necesita el bloque opcional aquí, ya que el import 'vlc' está en este módulo
|
||||
# Bloque para manejar la dependencia de VLC
|
||||
try:
|
||||
from logica.T2.musicReproductor import MusicReproductor
|
||||
except ImportError:
|
||||
# Bloque OPCIONAL si falla el import de MusicReproductor por problemas de VLC en el entorno
|
||||
print("⚠️ Error al importar MusicReproductor. Asegúrate de tener 'python-vlc' instalado y VLC en tu sistema.")
|
||||
print("⚠️ Error al importar MusicReproductor. Usando simulador.")
|
||||
|
||||
|
||||
class MusicReproductor:
|
||||
def __init__(self, *args, **kwargs): pass
|
||||
|
||||
def ajustar_volumen(self, valor): print(f"Volumen (Simulado): {valor}")
|
||||
def ajustar_volumen(self, valor): pass
|
||||
|
||||
def cargar_y_reproducir(self, url): print(f"Reproduciendo (Simulado): {url}")
|
||||
def cargar_y_reproducir(self, url): print(f"🎶 SIMULANDO PLAY: {url}")
|
||||
|
||||
def reproducir(self): print("Reproducir (Simulado)")
|
||||
def reproducir(self): pass
|
||||
|
||||
def pausar(self): print("Pausar (Simulado)")
|
||||
def pausar(self, *args): pass
|
||||
|
||||
def detener(self): print("Detener (Simulado)")
|
||||
def detener(self): pass
|
||||
|
||||
# 🟢 LÓGICA DE T4 (ALARMAS)
|
||||
from logica.T2.alarm import AlarmManager
|
||||
|
||||
# --- IMPORTACIÓN UNIVERSAL DE CONSTANTES ---
|
||||
from vista.config import *
|
||||
|
|
@ -48,12 +53,11 @@ from vista.config import *
|
|||
|
||||
class PanelCentral(ttk.Frame):
|
||||
"""Contiene el Notebook (subpestañas), el panel de Notas y el panel de Chat,
|
||||
y gestiona directamente la lógica de control de T1, T2 y T3."""
|
||||
y gestiona directamente la lógica de control de T1, T2, T3 y T4."""
|
||||
|
||||
INTERVALO_ACTUALIZACION_MS = INTERVALO_RECURSOS_MS
|
||||
INTERVALO_CARRERA_MS = 200
|
||||
|
||||
# ✅ CORRECCIÓN DE RUTA
|
||||
NOMBRE_FICHERO_RADIOS = "res/radios.json"
|
||||
|
||||
def __init__(self, parent, root, *args, **kwargs):
|
||||
|
|
@ -62,6 +66,9 @@ class PanelCentral(ttk.Frame):
|
|||
|
||||
self.after_id = None
|
||||
self.after_carrera_id = None
|
||||
self.after_alarm_id = None
|
||||
|
||||
# T2
|
||||
self.camellos = []
|
||||
self.progreso_labels = {}
|
||||
self.frame_carrera_controles = None
|
||||
|
|
@ -69,37 +76,56 @@ class PanelCentral(ttk.Frame):
|
|||
self.carrera_info_label = None
|
||||
self.carrera_estado_label = None
|
||||
|
||||
# 1. INICIALIZACIÓN DE VARIABLES (T1)
|
||||
# T1
|
||||
self.net_monitor = iniciar_monitor_red()
|
||||
self.figure = Figure(figsize=(5, 4), dpi=100)
|
||||
self.canvas = None
|
||||
|
||||
# 2. INICIALIZACIÓN DE VARIABLES Y LÓGICA DE RADIO (T3)
|
||||
# T3 (Radios)
|
||||
self.emisoras_cargadas = self.cargar_emisoras()
|
||||
self.radio_seleccionada = tk.StringVar(value="---")
|
||||
self.volumen_var = tk.DoubleVar(value=50.0)
|
||||
self.reproductor = MusicReproductor(initial_volume=self.volumen_var.get())
|
||||
|
||||
# 3. CONFIGURACIÓN DEL LAYOUT
|
||||
# 🟢 T4 (Alarmas) - Inicialización de variables UI.
|
||||
self.alarm_manager = None
|
||||
self.alarm_list_frame = None
|
||||
self.scrollable_frame = None
|
||||
self.alarm_hours_entry = None
|
||||
self.alarm_minutes_entry = None
|
||||
self.alarm_seconds_entry = None
|
||||
|
||||
# 📄 Tareas (res/notes)
|
||||
self.notes_text_editor = None # <--- AÑADIDO
|
||||
|
||||
# 2. CONFIGURACIÓN DEL LAYOUT
|
||||
self.grid_columnconfigure(0, weight=3)
|
||||
self.grid_columnconfigure(1, weight=1)
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
|
||||
self.crear_area_principal_y_notas()
|
||||
self.crear_area_principal() # <--- FUNCIÓN SIMPLIFICADA
|
||||
self.crear_panel_chat_y_alumnos()
|
||||
|
||||
# 4. INICIO DE CICLOS DE ACTUALIZACIÓN
|
||||
# Inicializar el AlarmManager solo después de que show_alarm_popup esté definido.
|
||||
self.inicializar_alarmas()
|
||||
|
||||
# 3. INICIO DE CICLOS DE ACTUALIZACIÓN
|
||||
self.iniciar_actualizacion_automatica()
|
||||
self.iniciar_actualizacion_carrera()
|
||||
self.iniciar_actualizacion_alarmas()
|
||||
|
||||
def inicializar_alarmas(self):
|
||||
"""Inicializa AlarmManager, pasándole el método de callback show_alarm_popup."""
|
||||
self.alarm_manager = AlarmManager(self.root, self.show_alarm_popup)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 📻 LÓGICA Y VISTA DE T3 (REPRODUCTOR DE RADIOS)
|
||||
# ... (El código de cargar_emisoras, crear_interfaz_radios, seleccionar_radio, controlar_reproduccion, cambiar_volumen no tiene cambios)
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def cargar_emisoras(self):
|
||||
"""Carga la lista de emisoras desde el archivo radios.json."""
|
||||
try:
|
||||
# ✅ La ruta ahora apunta correctamente al subdirectorio 'res'
|
||||
with open(self.NOMBRE_FICHERO_RADIOS, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError:
|
||||
|
|
@ -152,11 +178,9 @@ class PanelCentral(ttk.Frame):
|
|||
emisora = self.emisoras_cargadas[indice]
|
||||
url = emisora['url_stream']
|
||||
|
||||
# 1. Actualizar la interfaz
|
||||
self.radio_seleccionada.set(emisora['nombre'])
|
||||
self.url_seleccionada_label.config(text=url)
|
||||
|
||||
# 2. Llamar a la lógica del reproductor
|
||||
self.reproductor.cargar_y_reproducir(url)
|
||||
|
||||
def controlar_reproduccion(self, accion):
|
||||
|
|
@ -168,43 +192,308 @@ class PanelCentral(ttk.Frame):
|
|||
|
||||
def cambiar_volumen(self, valor):
|
||||
"""
|
||||
Llama al método de control de volumen del reproductor,
|
||||
asegurando que el valor sea un entero para evitar saltos del Scale.
|
||||
Ajusta el volumen, asegurando que el valor sea un entero para estabilizar el Scale.
|
||||
"""
|
||||
# ✅ CORRECCIÓN DE LA BARRA DE VOLUMEN
|
||||
# 1. Convertir el valor de punto flotante a entero
|
||||
valor_entero = int(float(valor))
|
||||
|
||||
# 2. Actualizar la variable de control con el valor entero.
|
||||
self.volumen_var.set(valor_entero)
|
||||
|
||||
# 3. Llamar a la lógica del reproductor.
|
||||
self.reproductor.ajustar_volumen(valor_entero)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 🔔 LÓGICA Y VISTA DE T4 (ALARMAS / TEMPORIZADORES)
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def crear_interfaz_alarmas(self, parent_frame):
|
||||
"""Crea la interfaz para programar y visualizar alarmas (H:M:S)."""
|
||||
|
||||
frame = ttk.Frame(parent_frame, padding=10, style='TFrame')
|
||||
frame.pack(expand=True, fill="both")
|
||||
|
||||
ttk.Label(frame, text="Programar Nuevo Temporizador (H:M:S)", font=FUENTE_NEGOCIOS).pack(pady=(0, 10))
|
||||
|
||||
# --- Controles de Nueva Alarma (H:M:S) ---
|
||||
frame_input = ttk.Frame(frame, style='TFrame')
|
||||
frame_input.pack(fill='x', pady=5)
|
||||
|
||||
# Horas
|
||||
ttk.Label(frame_input, text="Horas:").pack(side='left', padx=(0, 2))
|
||||
self.alarm_hours_entry = ttk.Entry(frame_input, width=3)
|
||||
self.alarm_hours_entry.pack(side='left', padx=(0, 10))
|
||||
self.alarm_hours_entry.insert(0, "0")
|
||||
|
||||
# Minutos
|
||||
ttk.Label(frame_input, text="Minutos:").pack(side='left', padx=(0, 2))
|
||||
self.alarm_minutes_entry = ttk.Entry(frame_input, width=3)
|
||||
self.alarm_minutes_entry.pack(side='left', padx=(0, 10))
|
||||
self.alarm_minutes_entry.insert(0, "1")
|
||||
|
||||
# Segundos
|
||||
ttk.Label(frame_input, text="Segundos:").pack(side='left', padx=(0, 2))
|
||||
self.alarm_seconds_entry = ttk.Entry(frame_input, width=3)
|
||||
self.alarm_seconds_entry.pack(side='left', padx=(0, 15))
|
||||
self.alarm_seconds_entry.insert(0, "0")
|
||||
|
||||
ttk.Button(frame_input, text="➕ Crear Alarma", command=self.manejar_nueva_alarma,
|
||||
style='Action.TButton').pack(side='left')
|
||||
|
||||
ttk.Separator(frame, orient='horizontal').pack(fill='x', pady=15)
|
||||
|
||||
# --- Listado de Alarmas Activas ---
|
||||
ttk.Label(frame, text="Alarmas Activas (Tiempo Restante)", font=FUENTE_NEGOCIOS).pack(pady=(0, 5))
|
||||
|
||||
self.alarm_list_frame = ttk.Frame(frame)
|
||||
self.alarm_list_frame.pack(fill="both", expand=True)
|
||||
|
||||
canvas = tk.Canvas(self.alarm_list_frame, borderwidth=0, background=COLOR_BLANCO)
|
||||
vscroll = ttk.Scrollbar(self.alarm_list_frame, orient="vertical", command=canvas.yview)
|
||||
|
||||
self.scrollable_frame = ttk.Frame(canvas)
|
||||
|
||||
self.scrollable_frame.bind(
|
||||
"<Configure>",
|
||||
lambda e: canvas.configure(
|
||||
scrollregion=canvas.bbox("all")
|
||||
)
|
||||
)
|
||||
|
||||
canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
|
||||
canvas.configure(yscrollcommand=vscroll.set)
|
||||
|
||||
canvas.pack(side="left", fill="both", expand=True)
|
||||
vscroll.pack(side="right", fill="y")
|
||||
|
||||
def manejar_nueva_alarma(self):
|
||||
"""Captura los datos del formulario (H:M:S), los convierte a segundos y llama al AlarmManager."""
|
||||
try:
|
||||
# 1. Leer los valores (usando 'or 0' para manejar campos vacíos como 0)
|
||||
hours = int(self.alarm_hours_entry.get() or 0)
|
||||
minutes = int(self.alarm_minutes_entry.get() or 0)
|
||||
seconds = int(self.alarm_seconds_entry.get() or 0)
|
||||
|
||||
# 2. Calcular el total de segundos
|
||||
total_seconds = (hours * 3600) + (minutes * 60) + seconds
|
||||
|
||||
if total_seconds <= 0:
|
||||
print("⚠️ El tiempo de alarma debe ser un número positivo (H:M:S > 0).")
|
||||
return
|
||||
|
||||
# 3. Llamar al AlarmManager con el total de segundos
|
||||
self.alarm_manager.set_alarm(total_seconds)
|
||||
|
||||
# 4. Limpiar y preparar para la siguiente alarma (Default: 1 minuto)
|
||||
self.alarm_hours_entry.delete(0, tk.END)
|
||||
self.alarm_hours_entry.insert(0, "0")
|
||||
self.alarm_minutes_entry.delete(0, tk.END)
|
||||
self.alarm_minutes_entry.insert(0, "1")
|
||||
self.alarm_seconds_entry.delete(0, tk.END)
|
||||
self.alarm_seconds_entry.insert(0, "0")
|
||||
|
||||
self.actualizar_lista_alarmas()
|
||||
|
||||
except ValueError:
|
||||
print("⚠️ Por favor, introduce números enteros válidos para el tiempo.")
|
||||
except AttributeError:
|
||||
print("⚠️ Error: AlarmManager no inicializado.")
|
||||
|
||||
def manejar_cancelar_alarma(self, alarm_id):
|
||||
"""Cancela la alarma usando su ID."""
|
||||
if self.alarm_manager.cancel_alarm(alarm_id):
|
||||
self.actualizar_lista_alarmas()
|
||||
|
||||
def actualizar_lista_alarmas(self):
|
||||
"""Actualiza la visualización de las alarmas activas con botones de cancelación individuales."""
|
||||
if not self.scrollable_frame:
|
||||
self.after_alarm_id = self.after(1000, self.actualizar_lista_alarmas)
|
||||
return
|
||||
|
||||
for widget in self.scrollable_frame.winfo_children():
|
||||
widget.destroy()
|
||||
|
||||
active_alarms = self.alarm_manager.get_active_alarms()
|
||||
|
||||
if not active_alarms:
|
||||
ttk.Label(self.scrollable_frame, text="--- No hay alarmas activas ---", font=('Consolas', 10),
|
||||
foreground=COLOR_TEXTO).pack(padx=10, pady=10)
|
||||
|
||||
for alarm in active_alarms:
|
||||
self.add_alarm_row(self.scrollable_frame, alarm)
|
||||
|
||||
self.after_alarm_id = self.after(1000, self.actualizar_lista_alarmas)
|
||||
|
||||
def add_alarm_row(self, parent, alarm_data):
|
||||
"""Añade una fila con la info de la alarma y su botón de cancelación."""
|
||||
row_frame = ttk.Frame(parent, padding=5, style='Note.TFrame')
|
||||
row_frame.pack(fill='x', padx=5, pady=2)
|
||||
|
||||
# Convertir total_seconds a formato Hh:Mm:Ss para la visualización del tiempo total
|
||||
total_s = alarm_data['total_seconds']
|
||||
h = total_s // 3600
|
||||
m = (total_s % 3600) // 60
|
||||
s = total_s % 60
|
||||
total_time_str = f"{h:02d}h:{m:02d}m:{s:02d}s"
|
||||
|
||||
# Info de la alarma
|
||||
info_text = (f"[ID{alarm_data['id']}] {alarm_data['restante']} -> {alarm_data['nombre']} "
|
||||
f"({total_time_str} total)")
|
||||
ttk.Label(row_frame, text=info_text, font=('Consolas', 10), style='Note.TLabel').pack(side='left', fill='x',
|
||||
expand=True)
|
||||
|
||||
# Botón de Cancelación Individual
|
||||
ttk.Button(row_frame, text="❌ Cancelar", style='Danger.TButton', width=10,
|
||||
command=lambda id=alarm_data['id']: self.manejar_cancelar_alarma(id)).pack(side='right')
|
||||
|
||||
def iniciar_actualizacion_alarmas(self):
|
||||
"""Inicia el ciclo de actualización de la lista de alarmas."""
|
||||
if self.alarm_manager:
|
||||
self.after_alarm_id = self.after(0, self.actualizar_lista_alarmas)
|
||||
|
||||
def detener_actualizacion_alarmas(self):
|
||||
"""Detiene el ciclo de actualización de la lista de alarmas."""
|
||||
if hasattr(self, 'after_alarm_id') and self.after_alarm_id:
|
||||
self.after_cancel(self.after_alarm_id)
|
||||
self.after_alarm_id = None
|
||||
print("Ciclo de actualización de alarmas detenido.")
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 🔔 POPUP DE ALARMA (Notificación)
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def show_alarm_popup(self, alarm_name, alarm_id):
|
||||
"""Muestra una ventana Toplevel sin barra de título ni botón de cierre."""
|
||||
|
||||
# 1. Crear la ventana popup
|
||||
popup = tk.Toplevel(self.root)
|
||||
popup.title("🚨 ¡ALARMA!")
|
||||
popup.geometry("350x150")
|
||||
popup.resizable(False, False)
|
||||
|
||||
# ✅ Eliminar la barra de título y los botones (incluido el de cierre 'X')
|
||||
popup.overrideredirect(True)
|
||||
|
||||
# Hacer que el popup sea modal (siempre encima)
|
||||
popup.transient(self.root)
|
||||
popup.grab_set()
|
||||
|
||||
# 2. Centrar la ventana
|
||||
self.root.update_idletasks()
|
||||
width = popup.winfo_width()
|
||||
height = popup.winfo_height()
|
||||
x = (self.root.winfo_screenwidth() // 2) - (width // 2)
|
||||
y = (self.root.winfo_screenheight() // 2) - (height // 2)
|
||||
popup.geometry(f'{width}x{height}+{x}+{y}')
|
||||
|
||||
# 3. Función para cerrar y detener la música
|
||||
def close_and_stop(event=None):
|
||||
"""Función para cerrar el popup y detener el sonido."""
|
||||
self.alarm_manager.stop_alarm_sound()
|
||||
self.alarm_manager.cancel_alarm(alarm_id)
|
||||
popup.destroy()
|
||||
self.actualizar_lista_alarmas()
|
||||
|
||||
# 4. Contenido
|
||||
frame = ttk.Frame(popup, padding=20, relief='solid', borderwidth=2)
|
||||
frame.pack(expand=True, fill="both")
|
||||
|
||||
ttk.Label(frame, text="¡El Temporizador ha Terminado!", font=FUENTE_TITULO, foreground=COLOR_ACCION).pack(
|
||||
pady=5)
|
||||
ttk.Label(frame, text=f"Hora de Disparo: {alarm_name}", font=FUENTE_NEGOCIOS).pack(pady=5)
|
||||
ttk.Label(frame, text="Haz clic para cerrar.", font=('Arial', 9, 'italic'), foreground=COLOR_TEXTO).pack(pady=0)
|
||||
|
||||
# 5. Configurar el cierre
|
||||
# La forma de cerrar es mediante un clic en la ventana.
|
||||
popup.bind("<Button-1>", close_and_stop)
|
||||
frame.bind("<Button-1>", close_and_stop)
|
||||
|
||||
# 6. Esperar a que se cierre para continuar la ejecución del hilo principal de Tkinter
|
||||
self.root.wait_window(popup)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 📄 LÓGICA Y VISTA DE TAREAS (Editor de Notas)
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def crear_interfaz_tareas(self, parent_frame):
|
||||
"""Crea el editor de texto simple para el archivo res/notes dentro de la pestaña Tareas."""
|
||||
|
||||
frame = ttk.Frame(parent_frame, padding=15, style='TFrame')
|
||||
frame.pack(expand=True, fill="both")
|
||||
|
||||
ttk.Label(frame, text="Editor de Notas ", font=FUENTE_TITULO).pack(pady=(0, 10), anchor="w")
|
||||
ttk.Label(frame, text="Use este panel para tomar notas rápidas sobre la ejecución de tareas.",
|
||||
font=FUENTE_NEGOCIOS).pack(pady=(0, 15), anchor="w")
|
||||
|
||||
# 1. Widget de texto
|
||||
self.notes_text_editor = tk.Text(
|
||||
frame,
|
||||
height=20,
|
||||
wrap="word",
|
||||
bg=COLOR_BLANCO,
|
||||
relief="solid",
|
||||
borderwidth=1,
|
||||
font=FUENTE_MONO
|
||||
)
|
||||
self.notes_text_editor.pack(fill="both", expand=True, pady=(0, 10))
|
||||
|
||||
# 2. Botones de Cargar y Guardar
|
||||
frame_botones = ttk.Frame(frame)
|
||||
frame_botones.pack(fill="x", pady=(5, 0))
|
||||
|
||||
ttk.Button(frame_botones, text="Guardar Cambios", command=self.guardar_res_notes, style='Action.TButton').pack(
|
||||
side=tk.RIGHT)
|
||||
ttk.Button(frame_botones, text="Cargar Archivo", command=self.cargar_res_notes, style='Action.TButton').pack(
|
||||
side=tk.LEFT)
|
||||
|
||||
self.cargar_res_notes(initial_load=True) # Carga inicial al crear la interfaz
|
||||
|
||||
def cargar_res_notes(self, initial_load=False):
|
||||
"""Carga el contenido de res/notes al editor de texto."""
|
||||
if not self.notes_text_editor: return
|
||||
|
||||
contenido = cargar_contenido_res_notes()
|
||||
|
||||
self.notes_text_editor.delete("1.0", tk.END)
|
||||
|
||||
if "Error al cargar:" in contenido:
|
||||
self.notes_text_editor.insert(tk.END, contenido)
|
||||
else:
|
||||
if initial_load and not contenido.strip():
|
||||
self.notes_text_editor.insert(tk.END, "# Escriba aquí sus notas (res/notes)")
|
||||
else:
|
||||
self.notes_text_editor.insert(tk.END, contenido)
|
||||
|
||||
if initial_load:
|
||||
print("Cargado 'res/notes' en la pestaña Tareas.")
|
||||
|
||||
def guardar_res_notes(self):
|
||||
"""Guarda el contenido del editor de texto en res/notes."""
|
||||
if not self.notes_text_editor: return
|
||||
|
||||
contenido = self.notes_text_editor.get("1.0", tk.END)
|
||||
|
||||
success, message = guardar_contenido_res_notes(contenido)
|
||||
|
||||
if success:
|
||||
messagebox.showinfo("✅ Guardado", "Notas guardadas exitosamente.")
|
||||
print(message)
|
||||
else:
|
||||
messagebox.showerror("❌ Error al Guardar", message)
|
||||
print(f"FALLO AL GUARDAR: {message}")
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 📦 ESTRUCTURA PRINCIPAL DEL PANEL
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def crear_area_principal_y_notas(self):
|
||||
"""Crea el contenedor de las subpestañas y el panel de notas."""
|
||||
def crear_area_principal(self):
|
||||
"""Crea el contenedor de las subpestañas."""
|
||||
frame_izquierdo = ttk.Frame(self, style='TFrame')
|
||||
frame_izquierdo.grid(row=0, column=0, sticky="nsew")
|
||||
|
||||
frame_izquierdo.grid_rowconfigure(0, weight=4)
|
||||
frame_izquierdo.grid_rowconfigure(1, weight=1)
|
||||
frame_izquierdo.grid_rowconfigure(0, weight=1)
|
||||
frame_izquierdo.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.crear_notebook_pestañas(frame_izquierdo)
|
||||
|
||||
panel_notas = ttk.Frame(frame_izquierdo, style='Note.TFrame')
|
||||
panel_notas.grid(row=1, column=0, sticky="nsew", pady=(5, 0))
|
||||
|
||||
ttk.Label(panel_notas, text="Panel para notas informativas y mensajes sobre la ejecución de los hilos.",
|
||||
style='Note.TLabel', anchor="nw", justify=tk.LEFT, padding=10, font=FUENTE_NOTA).pack(
|
||||
expand=True, fill="both")
|
||||
|
||||
def crear_notebook_pestañas(self, parent_frame):
|
||||
"""Crea las pestañas internas para las tareas (T1, Carrera, Radios)."""
|
||||
"""Crea las pestañas internas para las tareas (T1, Carrera, Radios, Tareas, Alarmas, etc.)."""
|
||||
sub_notebook = ttk.Notebook(parent_frame)
|
||||
sub_notebook.grid(row=0, column=0, sticky="nsew")
|
||||
|
||||
|
|
@ -230,8 +519,16 @@ class PanelCentral(ttk.Frame):
|
|||
elif sub_tab_text == "Radios":
|
||||
self.crear_interfaz_radios(frame)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
elif sub_tab_text == "Tareas":
|
||||
self.crear_interfaz_tareas(frame) # <--- Llamada a la nueva interfaz
|
||||
|
||||
elif sub_tab_text == "Alarmas":
|
||||
self.crear_interfaz_alarmas(frame)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
|
||||
# 🐪 LÓGICA DE T2 (CARRERA DE CAMELLOS)
|
||||
# ... (El código de carreraCamellos no cambia)
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def crear_interfaz_carrera(self, parent_frame):
|
||||
|
|
@ -414,7 +711,7 @@ class PanelCentral(ttk.Frame):
|
|||
self.after_id = self.after(0, self.actualizar_recursos)
|
||||
|
||||
def detener_actualizacion_automatica(self):
|
||||
"""Detiene el ciclo de actualización periódica y el hilo de red (T1) y T3."""
|
||||
"""Detiene el ciclo de actualización periódica y los hilos/tareas."""
|
||||
if self.after_id:
|
||||
self.after_cancel(self.after_id)
|
||||
self.after_id = None
|
||||
|
|
@ -426,8 +723,8 @@ class PanelCentral(ttk.Frame):
|
|||
print("Hilo de TrafficMeter detenido.")
|
||||
|
||||
self.detener_actualizacion_carrera()
|
||||
self.detener_actualizacion_alarmas()
|
||||
|
||||
# Detener el reproductor al cerrar la aplicación
|
||||
if self.reproductor:
|
||||
self.reproductor.detener()
|
||||
|
||||
|
|
@ -471,7 +768,7 @@ class PanelCentral(ttk.Frame):
|
|||
ttk.Button(frame_alumno, text="↻", width=3, style='Action.TButton').grid(row=0, column=1, rowspan=2, padx=5,
|
||||
sticky="ne")
|
||||
|
||||
# --- FILA 8: Reproductor Música ---
|
||||
# --- FILA 8: Reproductor Música (T3) ---
|
||||
musica_frame = ttk.LabelFrame(panel_chat, text="Reproductor Música", padding=10, style='TFrame')
|
||||
musica_frame.grid(row=8, column=0, sticky="ew", pady=(15, 0))
|
||||
|
||||
|
|
@ -498,6 +795,4 @@ class PanelCentral(ttk.Frame):
|
|||
|
||||
ttk.Label(musica_frame, textvariable=self.volumen_var, style='TLabel').grid(row=2, column=2, sticky="w")
|
||||
|
||||
musica_frame.grid_columnconfigure(1, weight=1)
|
||||
|
||||
panel_chat.grid_rowconfigure(8, weight=0)
|
||||
musica_frame.grid_columnconfigure(1, weight=1)
|
||||
|
|
@ -6,7 +6,8 @@ from tkinter import messagebox
|
|||
from logica.controlador import accion_placeholder
|
||||
from logica.T1.backup import accion_backup_t1
|
||||
from logica.T1.runVScode import abrir_vscode
|
||||
from logica.T1.textEditor import cargar_contenido_res_notes, guardar_contenido_res_notes
|
||||
# NO necesitamos importar cargar/guardar notas aquí, ya que la lógica se mueve al Panel Central
|
||||
# from logica.T1.textEditor import cargar_contenido_res_notes, guardar_contenido_res_notes
|
||||
from logica.T1.openBrowser import navegar_a_url
|
||||
|
||||
# --- IMPORTACIÓN DE CONSTANTES DESDE vista/config.py ---
|
||||
|
|
@ -14,14 +15,14 @@ from vista.config import *
|
|||
|
||||
|
||||
class PanelLateral(ttk.Frame):
|
||||
"""Contiene el menú de botones, entradas para las tareas y el editor simple para res/notes."""
|
||||
"""Contiene el menú de botones y entradas para las tareas."""
|
||||
|
||||
# Usamos la constante importada
|
||||
ANCHO_CARACTERES_FIJO = ANCHO_CARACTERES_PANEL_LATERAL
|
||||
|
||||
def __init__(self, parent, central_panel=None, *args, **kwargs):
|
||||
super().__init__(parent, *args, **kwargs)
|
||||
# La referencia al PanelCentral es esencial para iniciar la carrera
|
||||
# La referencia al PanelCentral es esencial para iniciar la carrera y acceder a sus métodos
|
||||
self.central_panel = central_panel
|
||||
|
||||
self.configurar_estilos_locales(parent)
|
||||
|
|
@ -40,15 +41,11 @@ class PanelLateral(ttk.Frame):
|
|||
self.crear_seccion(self, titulo="", acciones=acciones_extraccion)
|
||||
|
||||
# 3. Área de Aplicaciones
|
||||
|
||||
# --- CAMBIO CLAVE: CONEXIÓN DE APP2 ---
|
||||
|
||||
# Definimos el comando para App2 usando el método que llama a PanelCentral
|
||||
app2_comando = self.manejar_inicio_carrera_t2
|
||||
|
||||
acciones_aplicaciones = [
|
||||
("Visual Code", abrir_vscode),
|
||||
("App2 (Carrera 🏁)", app2_comando), # <--- CONEXIÓN REALIZADA
|
||||
("App2 (Carrera 🏁)", app2_comando),
|
||||
("App3", lambda: accion_placeholder("App3"))
|
||||
]
|
||||
self.crear_seccion(self, titulo="Aplicaciones", acciones=acciones_aplicaciones)
|
||||
|
|
@ -60,19 +57,21 @@ class PanelLateral(ttk.Frame):
|
|||
self.crear_seccion(self, titulo="Procesos batch", acciones=acciones_batch)
|
||||
|
||||
# 5. Espacio expandible
|
||||
# Ahora este marco se expandirá para ocupar todo el espacio restante.
|
||||
tk.Frame(self, height=1).pack(expand=True, fill="both")
|
||||
|
||||
# 6. Panel de Notas
|
||||
self.crear_editor_res_notes()
|
||||
# 6. Panel de Notas - ELIMINADO: Se moverá a la pestaña Tareas del Panel Central.
|
||||
# self.crear_editor_res_notes() # <--- LÍNEA ELIMINADA
|
||||
|
||||
# --- MÉTODOS DE LÓGICA / CONTROL ---
|
||||
|
||||
# --- NUEVO MÉTODO PARA MANEJAR LA CARRERA (App2) ---
|
||||
def manejar_inicio_carrera_t2(self):
|
||||
"""
|
||||
Llama al método 'manejar_inicio_carrera' del Panel Central.
|
||||
"""
|
||||
if self.central_panel:
|
||||
print("Botón App2 presionado. Iniciando Carrera de Camellos en Panel Central...")
|
||||
# Aquí es donde se llama a la función expuesta por PanelCentral
|
||||
# Llamada a la función expuesta por PanelCentral
|
||||
self.central_panel.manejar_inicio_carrera()
|
||||
|
||||
# Opcional: Cambiar automáticamente a la pestaña Resultados
|
||||
|
|
@ -84,7 +83,6 @@ class PanelLateral(ttk.Frame):
|
|||
else:
|
||||
messagebox.showerror("Error", "El Panel Central no está inicializado.")
|
||||
|
||||
# --- MÉTODOS EXISTENTES ---
|
||||
def manejar_navegacion(self, event=None):
|
||||
"""
|
||||
Obtiene el texto de la entrada superior y llama a la función de navegación.
|
||||
|
|
@ -93,67 +91,10 @@ class PanelLateral(ttk.Frame):
|
|||
if navegar_a_url(url):
|
||||
self.entrada_superior.delete(0, tk.END)
|
||||
|
||||
def crear_editor_res_notes(self):
|
||||
"""Crea el editor de texto simple para el archivo res/notes."""
|
||||
|
||||
ttk.Label(self, text="Editor Simple (res/notes)", font=FUENTE_NEGOCIOS).pack(fill="x", pady=(10, 0),
|
||||
padx=5)
|
||||
|
||||
frame_editor = ttk.Frame(self, padding=5)
|
||||
frame_editor.pack(fill="x", padx=5, pady=(0, 10))
|
||||
|
||||
# 1. Widget de texto
|
||||
self.notes_text_editor = tk.Text(
|
||||
frame_editor,
|
||||
height=8,
|
||||
width=self.ANCHO_CARACTERES_FIJO,
|
||||
wrap="word",
|
||||
bg=COLOR_BLANCO,
|
||||
relief="solid",
|
||||
borderwidth=1,
|
||||
font=FUENTE_MONO
|
||||
)
|
||||
self.notes_text_editor.pack(fill="x", expand=False)
|
||||
|
||||
# 2. Botones de Cargar y Guardar
|
||||
frame_botones = ttk.Frame(frame_editor)
|
||||
frame_botones.pack(fill="x", pady=(5, 0))
|
||||
|
||||
ttk.Button(frame_botones, text="Guardar", command=self.guardar_res_notes, style='SmallAction.TButton').pack(
|
||||
side=tk.RIGHT)
|
||||
ttk.Button(frame_botones, text="Cargar", command=self.cargar_res_notes, style='SmallAction.TButton').pack(
|
||||
side=tk.LEFT)
|
||||
|
||||
self.cargar_res_notes(initial_load=True)
|
||||
|
||||
def cargar_res_notes(self, initial_load=False):
|
||||
"""Carga el contenido de res/notes al editor de texto lateral."""
|
||||
contenido = cargar_contenido_res_notes()
|
||||
|
||||
self.notes_text_editor.delete("1.0", tk.END)
|
||||
|
||||
if "Error al cargar:" in contenido:
|
||||
self.notes_text_editor.insert(tk.END, contenido)
|
||||
else:
|
||||
if initial_load and not contenido.strip():
|
||||
self.notes_text_editor.insert(tk.END, "# Escriba aquí sus notas (res/notes)")
|
||||
else:
|
||||
self.notes_text_editor.insert(tk.END, contenido)
|
||||
|
||||
print("Cargado 'res/notes' en el editor lateral.")
|
||||
|
||||
def guardar_res_notes(self):
|
||||
"""Guarda el contenido del editor de texto lateral en res/notes."""
|
||||
contenido = self.notes_text_editor.get("1.0", tk.END)
|
||||
|
||||
success, message = guardar_contenido_res_notes(contenido)
|
||||
|
||||
if success:
|
||||
messagebox.showinfo("✅ Guardado", "Notas guardadas exitosamente.")
|
||||
print(message)
|
||||
else:
|
||||
messagebox.showerror("❌ Error al Guardar", message)
|
||||
print(f"FALLO AL GUARDAR: {message}")
|
||||
# --- MÉTODOS DE NOTAS ELIMINADOS (Se moverán a PanelCentral) ---
|
||||
# def crear_editor_res_notes(self): ...
|
||||
# def cargar_res_notes(self, initial_load=False): ...
|
||||
# def guardar_res_notes(self): ...
|
||||
|
||||
def manejar_extraccion_datos(self):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in New Issue