feat(vista): Refactoriza ventana principal (main)
Refactoriza la ventana principal para modularizar y mejorar la estructura. * Reestructura ventana principal con módulos. * Integra clases modulares para cada pestaña. * Corrige errores de inicialización y dependencias. * Agrega view_scrapping.py con NavegadorPanel. * Refactoriza musicReproductor.py. * Modifica trafficMeter.py. * Modifica getWeather.py.
This commit is contained in:
parent
9a47fe104d
commit
731050242a
|
|
@ -9,21 +9,29 @@ def obtener_datos_cpu_ram():
|
|||
:return: Diccionario con métricas.
|
||||
"""
|
||||
|
||||
cpu_percent_total = psutil.cpu_percent(interval=None)
|
||||
# --- 1. MEDICIÓN DE CPU (CORRECCIÓN CLAVE) ---
|
||||
# La primera llamada 'primea' el cálculo de psutil.
|
||||
psutil.cpu_percent(interval=None)
|
||||
|
||||
# La segunda llamada devuelve el porcentaje real calculado desde la primera.
|
||||
# Aquí obtenemos el total y los núcleos en la misma llamada para consistencia.
|
||||
cpu_percent_per_core = psutil.cpu_percent(interval=None, percpu=True)
|
||||
cpu_percent_total = sum(cpu_percent_per_core) / len(cpu_percent_per_core)
|
||||
# Alternativamente, psutil.cpu_percent(interval=None) - pero calculando la media es más seguro.
|
||||
|
||||
|
||||
# --- 2. RESTO DE DATOS (Sin cambios) ---
|
||||
mem = psutil.virtual_memory()
|
||||
|
||||
num_procesos = len(psutil.pids())
|
||||
|
||||
cpu_freq = psutil.cpu_freq()
|
||||
|
||||
datos = {
|
||||
'cpu_total': cpu_percent_total,
|
||||
'cpu_cores': cpu_percent_per_core,
|
||||
'cpu_total': round(cpu_percent_total, 1),
|
||||
'cpu_cores': [round(p, 1) for p in cpu_percent_per_core],
|
||||
'ram_total_gb': round(mem.total / (1024 ** 3), 2),
|
||||
'ram_uso_gb': round(mem.used / (1024 ** 3), 2),
|
||||
'ram_percent': mem.percent,
|
||||
# Nota: 'num_hilos' es técnicamente el número de PIDs (procesos), no hilos
|
||||
'num_hilos': num_procesos,
|
||||
'cpu_freq_mhz': cpu_freq.current if cpu_freq else 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ def actualizar_historial_datos(net_in_kb, net_out_kb, cpu_percent, ram_percent):
|
|||
"""
|
||||
Recopila los datos actuales de CPU, RAM y Red pasados como argumento a sus historiales.
|
||||
"""
|
||||
# 🎯 CORRECCIÓN: Los datos de CPU/RAM ahora vienen como argumentos, no se calculan aquí.
|
||||
|
||||
# 1. Añadir CPU y gestionar la longitud
|
||||
historial_cpu.append(cpu_percent)
|
||||
if len(historial_cpu) > MAX_PUNTOS:
|
||||
|
|
@ -40,7 +38,7 @@ def crear_grafico_recursos(figure):
|
|||
"""
|
||||
Crea o actualiza un gráfico que muestre la evolución de CPU, RAM y Red.
|
||||
"""
|
||||
# Limpiar la figura antes de dibujar
|
||||
# Limpiar la figura antes de dibujar (CRUCIAL para redibujo)
|
||||
figure.clear()
|
||||
|
||||
# Configuramos el fondo de la figura para que coincida con el estilo de la aplicación
|
||||
|
|
@ -50,7 +48,7 @@ def crear_grafico_recursos(figure):
|
|||
# 3 filas para CPU, RAM, Red con espaciado vertical
|
||||
gs = figure.add_gridspec(3, 1, hspace=0.6, top=0.95, bottom=0.05, left=0.1, right=0.95)
|
||||
|
||||
# --- Función Helper para el estilo btop ---
|
||||
# --- Función Helper para el estilo ---
|
||||
def configurar_ejes_historial(ax, title, color, data, y_limit=100, y_ticks=None):
|
||||
ax.set_facecolor('#f0f0f0') # Fondo del área de dibujo
|
||||
ax.set_title(title, fontsize=9, loc='left', pad=10)
|
||||
|
|
|
|||
|
|
@ -21,24 +21,30 @@ class NetIOMonitor(threading.Thread):
|
|||
|
||||
# Almacenamiento seguro para los últimos datos de tráfico
|
||||
self.lock = threading.Lock()
|
||||
self.data_in_kb = 0.0 # Tráfico de entrada en KB/s (Recibido)
|
||||
self.data_out_kb = 0.0 # Tráfico de salida en KB/s (Enviado)
|
||||
self.cpu_percent = 0.0 # Nuevo
|
||||
self.ram_percent = 0.0 # Nuevo
|
||||
self.data_in_kb = 0.0
|
||||
self.data_out_kb = 0.0
|
||||
self.cpu_percent = 0.0
|
||||
self.ram_percent = 0.0
|
||||
|
||||
# Almacena el contador anterior para calcular la diferencia (tasa)
|
||||
self.last_counters = psutil.net_io_counters()
|
||||
|
||||
# Necesario para inicializar la medición de CPU/RAM al inicio
|
||||
# Inicializar la medición de CPU (sin intervalo) para la primera lectura.
|
||||
# Esto 'primea' el cálculo de psutil para la primera vez que se llama en run().
|
||||
psutil.cpu_percent(interval=None)
|
||||
|
||||
def run(self):
|
||||
"""Método principal del hilo."""
|
||||
try:
|
||||
# Esperar el intervalo antes de la primera lectura para tener una base
|
||||
# y que el cálculo de la tasa sea correcto desde el inicio.
|
||||
time.sleep(self.intervalo)
|
||||
|
||||
while not self._stop_event.is_set():
|
||||
# Esperar el intervalo antes de la lectura para calcular la tasa
|
||||
time.sleep(self.intervalo)
|
||||
self._actualizar_datos()
|
||||
# Pausa al final, simplifica la lógica de _actualizar_datos
|
||||
time.sleep(self.intervalo)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error fatal en el hilo NetIOMonitor: {e}")
|
||||
self._stop_event.set()
|
||||
|
|
@ -52,8 +58,9 @@ class NetIOMonitor(threading.Thread):
|
|||
|
||||
current_counters = psutil.net_io_counters()
|
||||
|
||||
# Medición de CPU y RAM (usando interval=0.0 ya que el sleep garantiza el intervalo)
|
||||
current_cpu = psutil.cpu_percent(interval=0.0)
|
||||
# 🎯 CORRECCIÓN: Llamar sin intervalo. psutil.cpu_percent() devolverá el uso
|
||||
# desde la última llamada (que fue hace 'self.intervalo' segundos).
|
||||
current_cpu = psutil.cpu_percent()
|
||||
current_ram = psutil.virtual_memory().percent
|
||||
|
||||
# Calcular la diferencia de bytes recibidos y enviados desde la última lectura
|
||||
|
|
|
|||
|
|
@ -30,19 +30,72 @@ class MusicReproductor:
|
|||
self.instance = vlc.Instance()
|
||||
self.player = self.instance.media_player_new()
|
||||
self.current_media = None
|
||||
self.is_playing = False
|
||||
|
||||
# 🔑 Variables de estado/control
|
||||
self._current_url = None # Guarda la última URL cargada
|
||||
self._is_playing = False # Indica si se está reproduciendo activamente (no pausado)
|
||||
|
||||
# Configurar volumen inicial
|
||||
self.ajustar_volumen(initial_volume)
|
||||
print(f"🎵 [VLC] Reproductor inicializado. Volumen: {self.player.audio_get_volume()}")
|
||||
self.set_volumen(initial_volume) # 🔑 Renombrado a set_volumen
|
||||
print(f"🎵 [VLC] Reproductor inicializado. Volumen: {self.get_volumen()}")
|
||||
|
||||
def ajustar_volumen(self, valor_porcentual):
|
||||
# -------------------------------------------------------------
|
||||
# 🔑 MÉTODOS REQUERIDOS POR LA VISTA
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def get_volumen(self):
|
||||
"""
|
||||
Ajusta el volumen del reproductor (0 a 100).
|
||||
[REQUERIDO] Devuelve el nivel de volumen actual (0-100).
|
||||
Este método es esencial para inicializar el slider de la interfaz.
|
||||
"""
|
||||
# VLC proporciona el volumen actual directamente
|
||||
return self.player.audio_get_volume()
|
||||
|
||||
def set_volumen(self, valor_porcentual):
|
||||
"""
|
||||
[REQUERIDO - Antes ajustar_volumen] Ajusta el volumen del reproductor (0 a 100).
|
||||
"""
|
||||
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
|
||||
|
||||
def esta_reproduciendo(self):
|
||||
"""
|
||||
[REQUERIDO] Devuelve True si el reproductor está en estado Playing o Paused
|
||||
y nosotros lo consideramos 'activo'.
|
||||
Usaremos el estado interno _is_playing para indicar el estado activo/pausado.
|
||||
"""
|
||||
return self._is_playing
|
||||
|
||||
def continuar(self):
|
||||
"""
|
||||
[REQUERIDO] Reanuda la reproducción si está pausada, o inicia el stream si está detenido.
|
||||
Devuelve True si la reproducción se inició/continuó.
|
||||
"""
|
||||
# Si está pausado, reanuda
|
||||
if self.player.get_state() == vlc.State.Paused:
|
||||
self.player.play()
|
||||
self._is_playing = True
|
||||
print("▶️ [VLC] Reproducción reanudada.")
|
||||
return True
|
||||
|
||||
# Si está detenido y hay un medio cargado, intenta reproducir
|
||||
elif self.player.get_state() == vlc.State.Stopped and self.current_media:
|
||||
self.player.play()
|
||||
self._is_playing = True
|
||||
print("▶️ [VLC] Reproducción iniciada desde stream cargado.")
|
||||
return True
|
||||
|
||||
# Si no hay medio cargado, no puede continuar
|
||||
elif not self.current_media:
|
||||
print("ℹ️ [VLC] No hay stream cargado para continuar.")
|
||||
return False
|
||||
|
||||
# Si ya está reproduciendo, lo ignoramos
|
||||
return True
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# MÉTODOS DE CONTROL DE VLC
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def cargar_y_reproducir(self, url_stream):
|
||||
"""
|
||||
|
|
@ -50,29 +103,33 @@ class MusicReproductor:
|
|||
"""
|
||||
if not url_stream:
|
||||
print("❌ [VLC] URL del stream vacía.")
|
||||
return
|
||||
return False, "URL del stream vacía."
|
||||
|
||||
print(f"🔄 [VLC] Intentando cargar y reproducir: {url_stream}")
|
||||
|
||||
# Detener la reproducción anterior
|
||||
self.player.stop()
|
||||
|
||||
self.current_media = self.instance.media_new(url_stream)
|
||||
self.player.set_media(self.current_media)
|
||||
self._current_url = url_stream
|
||||
|
||||
# Iniciar reproducción
|
||||
self.player.play()
|
||||
self.is_playing = True
|
||||
print("✅ [VLC] Reproducción iniciada.")
|
||||
self._is_playing = True
|
||||
|
||||
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.")
|
||||
# Esperar un poco para confirmar el estado de reproducción
|
||||
# En entornos reales, se usaría un callback de evento para esto.
|
||||
import time
|
||||
time.sleep(0.1)
|
||||
|
||||
if self.player.get_state() in [vlc.State.Playing, vlc.State.Opening]:
|
||||
print("✅ [VLC] Reproducción iniciada.")
|
||||
return True, url_stream
|
||||
else:
|
||||
print("ℹ️ [VLC] Ya está reproduciéndose o esperando un stream.")
|
||||
print(f"❌ [VLC] Fallo al iniciar la reproducción. Estado: {self.player.get_state()}")
|
||||
self._is_playing = False
|
||||
return False, "Fallo al iniciar el stream (Revisa la URL)."
|
||||
|
||||
def pausar(self):
|
||||
"""
|
||||
|
|
@ -80,18 +137,20 @@ class MusicReproductor:
|
|||
"""
|
||||
if self.player.get_state() == vlc.State.Playing:
|
||||
self.player.pause()
|
||||
self.is_playing = False
|
||||
self._is_playing = False
|
||||
print("⏸️ [VLC] Reproducción pausada.")
|
||||
return True
|
||||
else:
|
||||
print("ℹ️ [VLC] No se puede pausar, el reproductor no está en estado de reproducción.")
|
||||
return False
|
||||
|
||||
def detener(self):
|
||||
"""
|
||||
Detiene la reproducción y libera los recursos. Crucial al cerrar la aplicación.
|
||||
Detiene la reproducción y el estado activo.
|
||||
"""
|
||||
if self.player:
|
||||
self.player.stop()
|
||||
# 🎯 Solo liberamos el reproductor. No eliminamos self.instance.
|
||||
self.player.release()
|
||||
self.player = None # Esto asegura que el player se recree si es necesario
|
||||
print("⏹️ [VLC] Reproductor detenido y recursos liberados.")
|
||||
self._is_playing = False
|
||||
print("⏹️ [VLC] Reproductor detenido.")
|
||||
return True
|
||||
return False
|
||||
|
|
@ -5,51 +5,38 @@ from tkinter import ttk
|
|||
import json
|
||||
from vista.config import *
|
||||
|
||||
# Bloque para manejar la dependencia de VLC
|
||||
try:
|
||||
from logica.T2.musicReproductor import MusicReproductor
|
||||
except ImportError:
|
||||
print("⚠️ Error al importar MusicReproductor. Usando simulador.")
|
||||
|
||||
|
||||
# CLASE SIMULADA
|
||||
class MusicReproductor:
|
||||
def __init__(self, *args, **kwargs): pass
|
||||
|
||||
def ajustar_volumen(self, valor): print(f"🎶 SIMULANDO VOLUMEN: {valor}")
|
||||
|
||||
def cargar_y_reproducir(self, url): print(f"🎶 SIMULANDO PLAY: {url}")
|
||||
|
||||
def reproducir(self): print("🎶 SIMULANDO PLAY")
|
||||
|
||||
def pausar(self, *args): print("🎶 SIMULANDO PAUSA")
|
||||
|
||||
def detener(self): print("🎶 SIMULANDO DETENER")
|
||||
# -------------------------------------------------------------
|
||||
# ❌ ELIMINAMOS EL BLOQUE try/except CON LA SIMULACIÓN DE VLC
|
||||
# Ya que esta clase no debe interactuar directamente con MusicReproductor.
|
||||
# -------------------------------------------------------------
|
||||
|
||||
|
||||
class RadioPanel(ttk.Frame):
|
||||
"""
|
||||
Panel de la pestaña Radios (T3).
|
||||
Gestiona la selección de emisoras y los controles de reproducción.
|
||||
Gestiona únicamente la selección de emisoras y DELEGA la reproducción
|
||||
al ReproductorController en el Panel Lateral.
|
||||
"""
|
||||
|
||||
NOMBRE_FICHERO_RADIOS = "res/radios.json"
|
||||
|
||||
def __init__(self, parent_notebook, root, *args, **kwargs):
|
||||
def __init__(self, parent_notebook, root, reproductor_controller_instance=None, *args, **kwargs):
|
||||
super().__init__(parent_notebook, *args, **kwargs)
|
||||
self.root = root
|
||||
|
||||
# 🔑 REFERENCIA AL CONTROLADOR DE AUDIO DEL PANEL LATERAL
|
||||
# Este controlador tiene los métodos cargar_stream() y manejar_stop().
|
||||
self.reproductor_controller = reproductor_controller_instance
|
||||
|
||||
self.emisoras_cargadas = self.cargar_emisoras()
|
||||
self.radio_seleccionada = tk.StringVar(value="---")
|
||||
self.volumen_var = tk.DoubleVar(value=50.0)
|
||||
|
||||
# Inicialización de la lógica del reproductor
|
||||
self.reproductor = MusicReproductor(initial_volume=self.volumen_var.get())
|
||||
# ❌ Se eliminaron: self.volumen_var y la inicialización de MusicReproductor.
|
||||
|
||||
self.crear_interfaz_radios(self)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 📻 VISTA Y LÓGICA DE RADIO
|
||||
# 📻 LÓGICA DE DATOS
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def cargar_emisoras(self):
|
||||
|
|
@ -64,8 +51,12 @@ class RadioPanel(ttk.Frame):
|
|||
print(f"⚠️ Error al leer el archivo {self.NOMBRE_FICHERO_RADIOS}. Está mal formado.")
|
||||
return []
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 🖼️ VISTA (SOLO SELECCIONADOR)
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def crear_interfaz_radios(self, parent_frame):
|
||||
"""Crea la interfaz para seleccionar la emisora de radio."""
|
||||
"""Crea la interfaz para seleccionar la emisora de radio (SIN CONTROLES DE AUDIO)."""
|
||||
|
||||
frame_radio = ttk.Frame(parent_frame, padding=10, style='TFrame')
|
||||
frame_radio.pack(expand=True, fill="both")
|
||||
|
|
@ -99,25 +90,16 @@ class RadioPanel(ttk.Frame):
|
|||
self.url_seleccionada_label = ttk.Label(frame_radio, text="N/A", wraplength=400, foreground=COLOR_TEXTO)
|
||||
self.url_seleccionada_label.pack(anchor="w")
|
||||
|
||||
# Controles de Volumen y Play/Pause
|
||||
frame_controles = ttk.Frame(frame_radio, padding=5)
|
||||
frame_controles.pack(fill="x", pady=10)
|
||||
# ❌ Se eliminaron los controles de volumen y Play/Pause/Stop.
|
||||
|
||||
ttk.Button(frame_controles, text="▶️ Play", command=lambda: self.controlar_reproduccion('play'),
|
||||
style='Action.TButton').pack(side='left', padx=5)
|
||||
ttk.Button(frame_controles, text="⏸️ Pause", command=lambda: self.controlar_reproduccion('pause'),
|
||||
style='Action.TButton').pack(side='left', padx=5)
|
||||
|
||||
ttk.Label(frame_controles, textvariable=self.radio_seleccionada, font=FUENTE_NEGOCIOS).pack(side='left',
|
||||
padx=15)
|
||||
|
||||
ttk.Label(frame_controles, text="Volumen:").pack(side='right', padx=5)
|
||||
volumen_slider = ttk.Scale(frame_controles, from_=0, to=100, orient=tk.HORIZONTAL,
|
||||
variable=self.volumen_var, command=self.cambiar_volumen, length=100)
|
||||
volumen_slider.pack(side='right', padx=5)
|
||||
# -------------------------------------------------------------
|
||||
# ⏯️ DELEGACIÓN DE LA LÓGICA DE AUDIO
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def seleccionar_radio(self, listbox):
|
||||
"""Captura la selección y llama al reproductor para iniciar la reproducción."""
|
||||
"""
|
||||
Captura la selección y DELEGA la reproducción al ReproductorController.
|
||||
"""
|
||||
seleccion = listbox.curselection()
|
||||
if seleccion:
|
||||
indice = seleccion[0]
|
||||
|
|
@ -126,22 +108,21 @@ class RadioPanel(ttk.Frame):
|
|||
|
||||
self.radio_seleccionada.set(emisora['nombre'])
|
||||
self.url_seleccionada_label.config(text=url)
|
||||
self.reproductor.cargar_y_reproducir(url)
|
||||
|
||||
def controlar_reproduccion(self, accion):
|
||||
"""Llama al método de control del reproductor (play/pause)."""
|
||||
if accion == 'play':
|
||||
self.reproductor.reproducir()
|
||||
elif accion == 'pause':
|
||||
self.reproductor.pausar()
|
||||
# 🔑 DELEGACIÓN: Llamamos al controlador de audio del Panel Lateral
|
||||
if self.reproductor_controller:
|
||||
self.reproductor_controller.cargar_stream(url)
|
||||
else:
|
||||
# El error indica que falta conectar en panel_central.py
|
||||
print("❌ Error: ReproductorController no está asignado.")
|
||||
|
||||
def cambiar_volumen(self, valor):
|
||||
"""Ajusta el volumen."""
|
||||
valor_entero = int(float(valor))
|
||||
self.volumen_var.set(valor_entero)
|
||||
self.reproductor.ajustar_volumen(valor_entero)
|
||||
# ❌ Se eliminaron los métodos controlar_reproduccion, cambiar_volumen.
|
||||
|
||||
def detener_actualizacion(self):
|
||||
"""Método llamado por PanelCentral al cerrar la aplicación."""
|
||||
if self.reproductor:
|
||||
self.reproductor.detener()
|
||||
"""Método llamado por PanelCentral al cerrar la aplicación (solo delega la detención)."""
|
||||
# 🔑 DELEGACIÓN: El PanelCentral llama a esto al cerrar.
|
||||
if self.reproductor_controller:
|
||||
self.reproductor_controller.manejar_stop()
|
||||
else:
|
||||
# Si el controlador no existe, no hacemos nada, pero el PanelLateral debería manejar su propio cierre.
|
||||
pass
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg # <-- ¡IMPORTACIÓN NECESARIA!
|
||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||
from logica.T1.graficos import crear_grafico_recursos, actualizar_historial_datos
|
||||
from vista.config import * # Asumiendo que las constantes de estilo/fondo están aquí
|
||||
from vista.config import *
|
||||
|
||||
|
||||
class RecursosPanel(ttk.Frame):
|
||||
|
|
@ -13,54 +13,56 @@ class RecursosPanel(ttk.Frame):
|
|||
Muestra el gráfico de Matplotlib con el consumo de recursos de red.
|
||||
"""
|
||||
|
||||
# NOTA: Los parámetros 'canvas' y 'canvas_widget' ya no son necesarios
|
||||
# en el constructor, pero los mantenemos para no romper el flujo de PanelCentral.
|
||||
def __init__(self, parent_notebook, figure, canvas, *args, **kwargs):
|
||||
super().__init__(parent_notebook, *args, **kwargs)
|
||||
self.figure = figure
|
||||
# Aseguramos que el frame se expanda para llenar la pestaña
|
||||
# Asegura que el frame del panel se expande
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.canvas = None # Lo crearemos aquí
|
||||
self.canvas_widget = None # Lo crearemos aquí
|
||||
self.canvas = None
|
||||
self.canvas_widget = None
|
||||
self.grafico_frame = None
|
||||
|
||||
self.crear_interfaz_recursos(self)
|
||||
|
||||
# Estado inicial del gráfico
|
||||
# Estado inicial del gráfico (limpia y configura la figura)
|
||||
crear_grafico_recursos(self.figure)
|
||||
|
||||
# Después de la creación, nos aseguramos de que PanelCentral obtenga las referencias
|
||||
# que espera si las necesita.
|
||||
# ✅ DIBUJO INICIAL: Asegura que el gráfico vacío se muestre inmediatamente.
|
||||
if self.canvas:
|
||||
self.canvas.draw()
|
||||
|
||||
def crear_interfaz_recursos(self, parent_frame):
|
||||
"""Prepara el Frame para contener el gráfico de Matplotlib."""
|
||||
|
||||
# 1. Crear el Frame que contendrá el Canvas (contenedor interno)
|
||||
self.grafico_frame = ttk.Frame(parent_frame, style='TFrame')
|
||||
# Ubicar el frame contenedor del gráfico
|
||||
self.grafico_frame.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
|
||||
# Asegura que el contenido del gráfico_frame se expande
|
||||
self.grafico_frame.grid_rowconfigure(0, weight=1)
|
||||
self.grafico_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# 2. 🎯 CREAR Y UBICAR EL CANVAS DENTRO DEL FRAME (SOLUCIÓN DEL PROBLEMA)
|
||||
# El canvas debe crearse AQUÍ y usar 'self.grafico_frame' como master
|
||||
# CREAR Y UBICAR EL CANVAS
|
||||
self.canvas = FigureCanvasTkAgg(self.figure, master=self.grafico_frame)
|
||||
self.canvas_widget = self.canvas.get_tk_widget()
|
||||
|
||||
# Usamos .grid() para llenar el frame_contenedor
|
||||
# ✅ CRÍTICO: El widget del canvas debe expandirse para llenar el gráfico_frame
|
||||
self.canvas_widget.grid(row=0, column=0, sticky="nsew")
|
||||
|
||||
# -------------------------------------------------------------
|
||||
|
||||
# 📞 MÉTODOS DE CONEXIÓN (Llamados por PanelCentral)
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def actualizar_datos(self, net_in, net_out):
|
||||
"""Recibe los datos del monitor de red y actualiza el historial."""
|
||||
actualizar_historial_datos(net_in, net_out)
|
||||
def actualizar_datos(self, net_in, net_out, cpu_percent, ram_percent):
|
||||
"""Recibe los datos del monitor y actualiza el historial de datos en la lógica."""
|
||||
# Recibe los 4 datos validados por la prueba de terminal
|
||||
actualizar_historial_datos(net_in, net_out, cpu_percent, ram_percent)
|
||||
|
||||
def dibujar_grafico(self):
|
||||
"""Llama a la función de redibujo del gráfico."""
|
||||
"""Llama a la función de redibujo del gráfico y fuerza la actualización del canvas."""
|
||||
if self.figure:
|
||||
crear_grafico_recursos(self.figure)
|
||||
# 1. Modifica la figura usando los datos del historial
|
||||
crear_grafico_recursos(self.figure)
|
||||
|
||||
# 2. ✅ FORZAR EL REDIBUJADO DE TKINTER
|
||||
if self.canvas:
|
||||
self.canvas.draw()
|
||||
|
|
@ -5,6 +5,7 @@ from tkinter import ttk
|
|||
from tkinter import messagebox
|
||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||
from matplotlib.figure import Figure
|
||||
import psutil # <--- IMPORTACIÓN REQUERIDA PARA CPU/RAM
|
||||
|
||||
# --- LÓGICA DE CONTROL UNIVERSAL ---
|
||||
from logica.T1.trafficMeter import iniciar_monitor_red
|
||||
|
|
@ -34,9 +35,12 @@ class PanelCentral(ttk.Frame):
|
|||
|
||||
INTERVALO_ACTUALIZACION_MS = INTERVALO_RECURSOS_MS
|
||||
|
||||
def __init__(self, parent, root, *args, **kwargs):
|
||||
# 🔑 CORRECCIÓN CRUCIAL: Añadir 'panel_lateral' al constructor para acceder al controlador de música.
|
||||
def __init__(self, parent, root, panel_lateral, *args, **kwargs):
|
||||
super().__init__(parent, *args, **kwargs)
|
||||
self.root = root
|
||||
# 🔑 Guardamos la referencia para poder acceder al ReproductorController
|
||||
self.panel_lateral = panel_lateral
|
||||
|
||||
# --- Variables de Estado y Lógica Central ---
|
||||
self.after_id = None
|
||||
|
|
@ -56,12 +60,10 @@ class PanelCentral(ttk.Frame):
|
|||
self.modulos = {}
|
||||
|
||||
# Configuración de Layout Principal
|
||||
# 🎯 CORRECCIÓN 1: Se elimina la columna 1. La columna 0 ocupa todo el espacio.
|
||||
self.grid_columnconfigure(0, weight=1) # La Columna de Pestañas ahora es la única y principal
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
|
||||
self.crear_area_principal()
|
||||
# ❌ Se elimina la llamada a self.crear_panel_chat_y_alumnos()
|
||||
|
||||
# 📦 ESTRUCTURA PRINCIPAL Y CREACIÓN DE PESTAÑAS
|
||||
# -------------------------------------------------------------
|
||||
|
|
@ -69,7 +71,7 @@ class PanelCentral(ttk.Frame):
|
|||
def crear_area_principal(self):
|
||||
"""Crea el contenedor de las subpestañas (Notebook), columna izquierda (0)."""
|
||||
frame_izquierdo = ttk.Frame(self, style='TFrame')
|
||||
frame_izquierdo.grid(row=0, column=0, sticky="nsew") # Ocupa toda la ventana
|
||||
frame_izquierdo.grid(row=0, column=0, sticky="nsew")
|
||||
|
||||
frame_izquierdo.grid_rowconfigure(0, weight=1)
|
||||
frame_izquierdo.grid_columnconfigure(0, weight=1)
|
||||
|
|
@ -123,6 +125,14 @@ class PanelCentral(ttk.Frame):
|
|||
vista_instancia = AlarmaPanel(frame, self.root, self.alarm_manager)
|
||||
self.modulos[sub_tab_text] = vista_instancia
|
||||
|
||||
# 🔑 CORRECCIÓN CLAVE: Inyectar la dependencia del controlador de música en RadioPanel
|
||||
elif sub_tab_text == "Radios":
|
||||
# Accedemos al controlador de música a través de la referencia al PanelLateral
|
||||
reproductor_controller = getattr(self.panel_lateral, 'controles_musica', None)
|
||||
vista_instancia = RadioPanel(frame, self.root,
|
||||
reproductor_controller_instance=reproductor_controller)
|
||||
self.modulos[sub_tab_text] = vista_instancia
|
||||
|
||||
else:
|
||||
vista_instancia = ClasePanel(frame, self.root)
|
||||
self.modulos[sub_tab_text] = vista_instancia
|
||||
|
|
@ -197,22 +207,29 @@ class PanelCentral(ttk.Frame):
|
|||
def actualizar_recursos(self):
|
||||
"""Obtiene los datos del sistema y delega el dibujo al módulo RecursosPanel."""
|
||||
try:
|
||||
if self.net_monitor is None:
|
||||
raise Exception("TrafficMeter no inicializado.")
|
||||
if 'Recursos' not in self.modulos or self.net_monitor is None:
|
||||
# Si el módulo Recursos o el monitor no están listos, reprogramamos.
|
||||
self.after_id = self.after(self.INTERVALO_ACTUALIZACION_MS, self.actualizar_recursos)
|
||||
return
|
||||
|
||||
# 1. OBTENER DATOS (Red, CPU y RAM)
|
||||
# 🔑 CORRECCIÓN CLAVE: Desempaquetar los 4 valores devueltos por TrafficMeter.
|
||||
net_in, net_out, cpu_percent, ram_percent = self.net_monitor.get_io_data_kb()
|
||||
|
||||
if 'Recursos' in self.modulos:
|
||||
self.modulos['Recursos'].actualizar_datos(net_in, net_out, cpu_percent, ram_percent)
|
||||
self.modulos['Recursos'].dibujar_grafico()
|
||||
# 2. ACTUALIZAR Y DIBUJAR
|
||||
recursos_panel = self.modulos['Recursos']
|
||||
|
||||
if self.canvas:
|
||||
self.canvas.draw()
|
||||
# Llama a actualizar_datos en la lógica de graficos.py
|
||||
recursos_panel.actualizar_datos(net_in, net_out, cpu_percent, ram_percent)
|
||||
|
||||
# Llama a dibujar_grafico en la vista (que ahora incluye self.canvas.draw())
|
||||
recursos_panel.dibujar_grafico()
|
||||
|
||||
except Exception as e:
|
||||
# Captura y muestra el error, pero no detiene la tarea
|
||||
print(f"Error en la actualización de recursos T1: {e}")
|
||||
pass
|
||||
|
||||
# 3. REPROGRAMAR TAREA (Crucial para el bucle)
|
||||
self.after_id = self.after(self.INTERVALO_ACTUALIZACION_MS, self.actualizar_recursos)
|
||||
|
||||
def iniciar_actualizacion_automatica(self):
|
||||
|
|
@ -230,6 +247,12 @@ class PanelCentral(ttk.Frame):
|
|||
return
|
||||
|
||||
print("Iniciando actualización automática de recursos.")
|
||||
|
||||
# 🔑 CORRECCIÓN: Realizar una llamada inicial a psutil.cpu_percent()
|
||||
# para establecer el punto de partida del intervalo de medición en el hilo principal.
|
||||
psutil.cpu_percent(interval=None)
|
||||
|
||||
# Iniciar el ciclo de actualización.
|
||||
self.after_id = self.after(0, self.actualizar_recursos)
|
||||
|
||||
def detener_actualizacion_automatica(self):
|
||||
|
|
@ -247,5 +270,4 @@ class PanelCentral(ttk.Frame):
|
|||
# Llama a los métodos de detención de los módulos (si existen)
|
||||
for nombre, modulo in self.modulos.items():
|
||||
if hasattr(modulo, 'detener_actualizacion'):
|
||||
modulo.detener_actualizacion()
|
||||
|
||||
modulo.detener_actualizacion()
|
||||
|
|
@ -5,16 +5,18 @@ from tkinter import ttk
|
|||
from tkinter import messagebox
|
||||
|
||||
# --- Módulos de Lógica Existente ---
|
||||
# Asumiendo que estos módulos existen en la estructura lógica del proyecto
|
||||
from logica.controlador import accion_placeholder
|
||||
from logica.T1.backup import accion_backup_t1
|
||||
from logica.T1.runVScode import abrir_vscode
|
||||
from logica.T1.openBrowser import navegar_a_url
|
||||
from logica.T2.scraping import hacer_scraping
|
||||
# 🔑 NUEVA IMPORTACIÓN DE LÓGICA T2
|
||||
from logica.T2.musicReproductor import MusicReproductor
|
||||
|
||||
# --- Módulos de Vistas ---
|
||||
# Importamos la clase RadioPanel, que contiene los controles de música (Play/Pause y Volumen).
|
||||
from vista.central_panel.view_radio import RadioPanel
|
||||
# ❌ Eliminamos: from vista.central_panel.view_radio import RadioPanel
|
||||
# 🔑 NUEVA IMPORTACIÓN DE VISTA MODULAR
|
||||
from vista.reproductor_controller import ReproductorController
|
||||
from vista.config import *
|
||||
|
||||
|
||||
|
|
@ -33,9 +35,12 @@ class PanelLateral(ttk.Frame):
|
|||
self.root = root
|
||||
self.panel_central = panel_central
|
||||
self.controles_musica = None
|
||||
|
||||
self.entrada_superior = None
|
||||
|
||||
# 🔑 INSTANCIA DE LÓGICA DE MÚSICA T2
|
||||
# Inicializamos el objeto de la lógica de reproducción aquí
|
||||
self.music_reproductor = MusicReproductor()
|
||||
|
||||
self.configurar_estilos_locales(root)
|
||||
|
||||
# Configuración de Layout Principal
|
||||
|
|
@ -52,6 +57,7 @@ class PanelLateral(ttk.Frame):
|
|||
ttk.Separator(self, orient='horizontal').grid(row=4, column=0, sticky="ew", pady=(10, 0))
|
||||
tk.Frame(self, height=1).grid(row=99, column=0, sticky="nsew")
|
||||
|
||||
# 🔑 LLAMADA AL NUEVO CONTROLADOR
|
||||
self.crear_controles_musica() # Fila 100
|
||||
|
||||
# -------------------------------------------------------------
|
||||
|
|
@ -82,7 +88,7 @@ class PanelLateral(ttk.Frame):
|
|||
|
||||
acciones_aplicaciones = [
|
||||
("Visual Code", abrir_vscode),
|
||||
("App2 (Carrera 🏁)", app2_comando),
|
||||
("Carrera 🏁", app2_comando),
|
||||
("App3", lambda: accion_placeholder("App3"))
|
||||
]
|
||||
self._crear_bloque_botones(self, titulo="Aplicaciones", acciones=acciones_aplicaciones, grid_row=2)
|
||||
|
|
@ -95,13 +101,17 @@ class PanelLateral(ttk.Frame):
|
|||
self._crear_bloque_botones(self, titulo="Procesos batch", acciones=acciones_batch, grid_row=3)
|
||||
|
||||
def crear_controles_musica(self):
|
||||
"""Crea el área para alojar los controles de música/radio."""
|
||||
"""Crea el área para alojar los controles de música/radio usando el nuevo controlador."""
|
||||
frame_musica = ttk.Frame(self, style='TFrame', padding="15 10")
|
||||
frame_musica.grid(row=100, column=0, sticky="ew")
|
||||
frame_musica.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Instancia la clase RadioPanel, que ahora contiene solo Play/Pause y Volumen
|
||||
self.controles_musica = RadioPanel(frame_musica, self.root)
|
||||
# 🔑 REEMPLAZO CLAVE: Usamos ReproductorController y le pasamos la instancia de la lógica.
|
||||
self.controles_musica = ReproductorController(
|
||||
frame_musica,
|
||||
self.root,
|
||||
music_reproductor_instance=self.music_reproductor # Pasamos la instancia de la lógica T2
|
||||
)
|
||||
self.controles_musica.grid(row=0, column=0, sticky="nsew")
|
||||
|
||||
frame_musica.grid_rowconfigure(0, weight=1)
|
||||
|
|
@ -199,4 +209,12 @@ class PanelLateral(ttk.Frame):
|
|||
ttk.Label(frame_titulo, text=titulo, font=FUENTE_NEGOCIOS).pack(anchor="w", padx=5)
|
||||
|
||||
for texto_boton, comando in acciones:
|
||||
ttk.Button(frame_seccion, text=texto_boton, command=comando, style='Green.TButton').pack(fill="x", pady=5)
|
||||
ttk.Button(frame_seccion, text=texto_boton, command=comando, style='Green.TButton').pack(fill="x", pady=5)
|
||||
|
||||
def set_panel_central_reference(self, panel_central_instance):
|
||||
"""
|
||||
Asigna la referencia al PanelCentral una vez que ambos paneles han sido inicializados.
|
||||
Esto resuelve la dependencia circular.
|
||||
"""
|
||||
self.panel_central = panel_central_instance
|
||||
print("✅ [PanelLateral] Referencia a Panel Central establecida.")
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
# Módulo: vista/reproductor_controller.py
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
# ⚠️ Asegúrate de que esta ruta es correcta: 'vista/config' o 'config'
|
||||
from vista.config import *
|
||||
|
||||
|
||||
class ReproductorController(ttk.Frame):
|
||||
"""
|
||||
Controlador de la interfaz de reproducción de música (Panel Lateral).
|
||||
Delega todas las acciones de audio a la instancia MusicReproductor.
|
||||
"""
|
||||
|
||||
def __init__(self, parent, root, music_reproductor_instance=None, *args, **kwargs):
|
||||
super().__init__(parent, *args, **kwargs)
|
||||
self.root = root
|
||||
self.reproductor = music_reproductor_instance # Instancia de MusicReproductor (Lógica T2)
|
||||
|
||||
# Variables de control de UI
|
||||
# Intentar obtener el volumen inicial de la lógica, si está disponible
|
||||
initial_volume = self.reproductor.get_volumen() if self.reproductor and hasattr(self.reproductor,
|
||||
'get_volumen') else 50
|
||||
self.volumen_var = tk.DoubleVar(value=initial_volume)
|
||||
|
||||
# Estado inicial del botón Play/Pause
|
||||
self.play_pause_text = tk.StringVar(value="▶️")
|
||||
if self.reproductor and hasattr(self.reproductor, 'esta_reproduciendo') and self.reproductor.esta_reproduciendo():
|
||||
self.play_pause_text.set("⏸️")
|
||||
|
||||
# Configuración de Layout
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
|
||||
self.crear_controles()
|
||||
|
||||
def crear_controles(self):
|
||||
"""Crea el marco con el slider de volumen y los botones de control."""
|
||||
|
||||
main_frame = ttk.Frame(self, padding=10, style='TFrame')
|
||||
main_frame.grid(row=0, column=0, sticky="nsew")
|
||||
|
||||
main_frame.grid_columnconfigure(0, weight=1)
|
||||
main_frame.grid_columnconfigure(1, weight=1)
|
||||
|
||||
# --- Título ---
|
||||
ttk.Label(main_frame, text="🎵 Control de Música", font=FUENTE_NEGOCIOS).grid(
|
||||
row=0, column=0, columnspan=2, pady=(0, 10), sticky="w")
|
||||
|
||||
# --- Slider de Volumen ---
|
||||
ttk.Label(main_frame, text="Volumen:", font=FUENTE_NORMAL).grid(
|
||||
row=1, column=0, columnspan=2, pady=(5, 0), sticky="w")
|
||||
|
||||
self.slider_volumen = ttk.Scale(
|
||||
main_frame,
|
||||
from_=0,
|
||||
to=100,
|
||||
orient="horizontal",
|
||||
variable=self.volumen_var,
|
||||
command=self.manejar_ajuste_volumen
|
||||
)
|
||||
self.slider_volumen.grid(row=2, column=0, columnspan=2, sticky="ew", pady=(5, 10))
|
||||
|
||||
# --- Botones de Control ---
|
||||
|
||||
self.boton_play_pause = ttk.Button(
|
||||
main_frame,
|
||||
textvariable=self.play_pause_text,
|
||||
style='Action.TButton',
|
||||
command=self.manejar_play_pause
|
||||
)
|
||||
self.boton_play_pause.grid(row=3, column=0, sticky="ew", padx=(0, 5), pady=5)
|
||||
|
||||
self.boton_stop = ttk.Button(main_frame, text="⏹️", style='Action.TButton', command=self.manejar_stop)
|
||||
self.boton_stop.grid(row=3, column=1, sticky="ew", padx=(5, 0), pady=5)
|
||||
|
||||
def manejar_play_pause(self):
|
||||
"""Alterna entre reproducir y pausar llamando al reproductor de la lógica."""
|
||||
if not self.reproductor: return
|
||||
if self.reproductor.esta_reproduciendo():
|
||||
self.reproductor.pausar()
|
||||
self.play_pause_text.set("▶️")
|
||||
else:
|
||||
# Llama a continuar, que intenta reanudar o iniciar el último medio
|
||||
if self.reproductor.continuar():
|
||||
self.play_pause_text.set("⏸️")
|
||||
else:
|
||||
self.play_pause_text.set("▶️")
|
||||
|
||||
def manejar_stop(self):
|
||||
"""Detiene completamente la reproducción."""
|
||||
if not self.reproductor: return
|
||||
self.reproductor.detener()
|
||||
self.play_pause_text.set("▶️")
|
||||
|
||||
def manejar_ajuste_volumen(self, valor):
|
||||
"""Ajusta el volumen del reproductor basado en el slider."""
|
||||
if not self.reproductor: return
|
||||
volumen = int(float(valor))
|
||||
# 🔑 Delega el ajuste de volumen a la lógica (MusicReproductor.set_volumen)
|
||||
self.reproductor.set_volumen(volumen)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
|
||||
# 📡 FUNCIÓN EXPORTADA PARA RECIBIR EL STREAM DEL view_radio
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def cargar_stream(self, url_stream):
|
||||
"""
|
||||
Recibe la URL de la radio seleccionada y la pasa a la lógica para su reproducción.
|
||||
"""
|
||||
if not self.reproductor:
|
||||
print("Error: Instancia de reproductor no asignada.")
|
||||
return
|
||||
|
||||
# 🔑 Llama a la lógica para detener lo anterior, cargar y reproducir el nuevo stream
|
||||
success, message = self.reproductor.cargar_y_reproducir(url_stream)
|
||||
|
||||
# Actualiza la UI basada en el resultado de la carga
|
||||
if success:
|
||||
self.play_pause_text.set("⏸️")
|
||||
else:
|
||||
self.play_pause_text.set("▶️")
|
||||
print(f"⚠️ Fallo en la carga del stream: {message}")
|
||||
|
|
@ -24,7 +24,8 @@ class VentanaPrincipal(tk.Tk):
|
|||
self.reloj_after_id = None
|
||||
self.label_clima = None
|
||||
self.clima_after_id = None
|
||||
self.panel_central = None # Inicializar a None para evitar errores en on_closing
|
||||
self.panel_central = None
|
||||
self.panel_lateral = None # 🔑 Añadir inicialización a None
|
||||
|
||||
style = ttk.Style()
|
||||
style.theme_use('clam')
|
||||
|
|
@ -48,6 +49,9 @@ class VentanaPrincipal(tk.Tk):
|
|||
self.iniciar_actualizacion_reloj()
|
||||
self.iniciar_actualizacion_clima()
|
||||
|
||||
# 🔑 CORRECCIÓN CRÍTICA: Iniciar el bucle de actualización del Panel Central (T1)
|
||||
self.iniciar_actualizacion_graficos()
|
||||
|
||||
def configurar_estilos(self, s: ttk.Style):
|
||||
"""Define estilos visuales personalizados."""
|
||||
|
||||
|
|
@ -77,20 +81,37 @@ class VentanaPrincipal(tk.Tk):
|
|||
s.configure('TNotebook.Tab', padding=[10, 5], font=FUENTE_NEGOCIOS)
|
||||
s.map('TNotebook.Tab', background=[('selected', COLOR_FONDO)], foreground=[('selected', COLOR_ACCION)])
|
||||
|
||||
# 🔑 MÉTODO CORREGIDO
|
||||
def crear_paneles_principales(self):
|
||||
"""Ensambla el panel lateral y el panel central."""
|
||||
"""Ensambla el panel lateral y el panel central, resolviendo la dependencia circular."""
|
||||
|
||||
# Panel Central debe inicializarse primero para pasar la referencia al lateral
|
||||
# self es parent
|
||||
self.panel_central = PanelCentral(self, self)
|
||||
# 1. Crear Panel Lateral: Inicialmente no necesita el PanelCentral, solo necesita saber que existirá.
|
||||
# PanelLateral espera: PanelLateral(parent, root, panel_central_ref, ...)
|
||||
# Usamos None para 'panel_central_ref' por ahora.
|
||||
self.panel_lateral = PanelLateral(self, self, None, width=ANCHO_PANEL_LATERAL)
|
||||
self.panel_lateral.grid(row=0, column=0, sticky="nswe", padx=(10, 5), pady=10)
|
||||
self.panel_lateral.grid_propagate(False)
|
||||
|
||||
|
||||
# 2. Crear Panel Central: Ahora sí necesita la referencia al PanelLateral para inyectar el controlador de audio.
|
||||
# PanelCentral espera: PanelCentral(parent, root, panel_lateral)
|
||||
self.panel_central = PanelCentral(self, self, panel_lateral=self.panel_lateral)
|
||||
self.panel_central.grid(row=0, column=1, sticky="nswe", padx=(5, 10), pady=10)
|
||||
|
||||
# 🎯 CORRECCIÓN CLAVE: Pasar 'self' como argumento 'root' y 'self.panel_central' como argumento posicional.
|
||||
# PanelLateral espera: PanelLateral(parent, root, panel_central, ...)
|
||||
self.panel_lateral = PanelLateral(self, self, self.panel_central, width=ANCHO_PANEL_LATERAL)
|
||||
self.panel_lateral.grid(row=0, column=0, sticky="nswe", padx=(10, 5), pady=10)
|
||||
# 3. Finalizar la dependencia circular: Asignar la referencia del Panel Central al Panel Lateral.
|
||||
# El PanelLateral requiere la referencia de PanelCentral para manejar eventos (ej. Scrapping).
|
||||
self.panel_lateral.set_panel_central_reference(self.panel_central)
|
||||
|
||||
self.panel_lateral.grid_propagate(False)
|
||||
|
||||
# --- LÓGICA DE ACTUALIZACIÓN DE GRÁFICOS (T1) ---
|
||||
def iniciar_actualizacion_graficos(self):
|
||||
"""Inicia el ciclo de actualización de recursos del PanelCentral."""
|
||||
if self.panel_central:
|
||||
print("Iniciando actualización automática de gráficos del Panel Central.")
|
||||
# Llama al método que inicializa el TrafficMeter y el self.after()
|
||||
self.panel_central.iniciar_actualizacion_automatica()
|
||||
else:
|
||||
print("Error: Panel Central no inicializado para iniciar gráficos.")
|
||||
|
||||
# --- LÓGICA DE ACTUALIZACIÓN DE RELOJ ---
|
||||
def actualizar_reloj(self):
|
||||
|
|
@ -103,9 +124,6 @@ class VentanaPrincipal(tk.Tk):
|
|||
if self.label_reloj:
|
||||
self.label_reloj.config(text="Error al obtener la hora")
|
||||
print(f"Error en el reloj: {e}")
|
||||
# CORRECCIÓN: Eliminamos la detención para que el after continúe intentando
|
||||
# self.detener_actualizacion_reloj()
|
||||
# return
|
||||
|
||||
self.reloj_after_id = self.after(INTERVALO_RELOJ_MS, self.actualizar_reloj)
|
||||
|
||||
|
|
@ -132,9 +150,6 @@ class VentanaPrincipal(tk.Tk):
|
|||
if self.label_clima:
|
||||
self.label_clima.config(text="Error al obtener el clima")
|
||||
print(f"Error en la actualización del clima: {e}")
|
||||
# CORRECCIÓN: Eliminamos la detención para que el after continúe intentando
|
||||
# self.detener_actualizacion_clima()
|
||||
# return
|
||||
|
||||
self.clima_after_id = self.after(INTERVALO_CLIMA_MS, self.actualizar_clima)
|
||||
|
||||
|
|
@ -156,7 +171,7 @@ class VentanaPrincipal(tk.Tk):
|
|||
self.detener_actualizacion_reloj()
|
||||
self.detener_actualizacion_clima()
|
||||
|
||||
# Solo intenta detener el panel central si fue inicializado
|
||||
# Detiene el hilo de TrafficMeter y el ciclo de repintado del gráfico
|
||||
if self.panel_central:
|
||||
self.panel_central.detener_actualizacion_automatica()
|
||||
|
||||
|
|
@ -188,135 +203,4 @@ class VentanaPrincipal(tk.Tk):
|
|||
frame_fecha.grid(row=0, column=2, sticky="e")
|
||||
|
||||
self.label_reloj = ttk.Label(frame_fecha, text="Día y Hora: --/--/--", style='TLabel')
|
||||
self.label_reloj.pack(side="left")# Módulo: vista/central_panel/view_radio.py
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from tkinter import messagebox
|
||||
|
||||
# 🎯 Asumo que MusicReproductor está importado aquí.
|
||||
from logica.T2.musicReproductor import MusicReproductor
|
||||
from vista.config import *
|
||||
|
||||
|
||||
class RadioPanel(ttk.Frame):
|
||||
"""
|
||||
Panel de controles de Radio/Música.
|
||||
Gestiona la interfaz de reproducción y volumen.
|
||||
"""
|
||||
|
||||
def __init__(self, parent_frame_musica, root, *args, **kwargs):
|
||||
super().__init__(parent_frame_musica, *args, **kwargs)
|
||||
self.root = root
|
||||
|
||||
# 🎯 Instanciar la lógica del reproductor al inicializar la vista
|
||||
self.reproductor = MusicReproductor()
|
||||
|
||||
# Variables de control de UI
|
||||
self.volumen_var = tk.DoubleVar(value=self.reproductor.get_volumen()) # Inicializa al volumen actual (por defecto 50)
|
||||
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.crear_interfaz_radio(self)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 🖼️ INTERFAZ DE USUARIO
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def crear_interfaz_radio(self, parent_frame):
|
||||
"""Crea los controles de reproducción y el slider de volumen."""
|
||||
|
||||
main_frame = ttk.Frame(parent_frame, padding=5, style='TFrame')
|
||||
main_frame.grid(row=0, column=0, sticky="nsew")
|
||||
main_frame.grid_columnconfigure(0, weight=1) # Columna de botones
|
||||
main_frame.grid_columnconfigure(1, weight=1) # Columna de botones
|
||||
main_frame.grid_columnconfigure(2, weight=1) # Columna de botones
|
||||
main_frame.grid_columnconfigure(3, weight=1) # Columna de volumen
|
||||
|
||||
# --- Título ---
|
||||
ttk.Label(main_frame, text="Controles de Música", font=FUENTE_NEGOCIOS).grid(
|
||||
row=0, column=0, columnspan=4, pady=(0, 10), sticky="w")
|
||||
|
||||
|
||||
# --- Botones de Control (Fila 1) ---
|
||||
|
||||
# Botón Play/Pause
|
||||
self.boton_play_pause = ttk.Button(main_frame, text="▶️", style='Action.TButton', command=self.manejar_play_pause)
|
||||
self.boton_play_pause.grid(row=1, column=1, sticky="ew", padx=5)
|
||||
|
||||
# Botón Stop
|
||||
ttk.Button(main_frame, text="⏹️", style='Action.TButton', command=self.manejar_stop).grid(
|
||||
row=1, column=2, sticky="ew", padx=5)
|
||||
|
||||
# Botón de Prueba de Carga (Para probar la reproducción de una URL)
|
||||
ttk.Button(main_frame, text="📡 Cargar Stream", command=self.cargar_stream_prueba).grid(
|
||||
row=1, column=0, sticky="ew", padx=5)
|
||||
|
||||
# --- Slider de Volumen (Fila 2) ---
|
||||
ttk.Label(main_frame, text="Volumen:", font=FUENTE_NORMAL).grid(
|
||||
row=2, column=0, columnspan=4, pady=(10, 0), sticky="w")
|
||||
|
||||
self.slider_volumen = ttk.Scale(
|
||||
main_frame,
|
||||
from_=0,
|
||||
to=100,
|
||||
orient="horizontal",
|
||||
variable=self.volumen_var,
|
||||
command=self.manejar_ajuste_volumen
|
||||
)
|
||||
self.slider_volumen.grid(row=3, column=0, columnspan=4, sticky="ew", pady=(5, 0))
|
||||
|
||||
# Etiqueta de la estación actual (para estado)
|
||||
self.label_estado = ttk.Label(main_frame, text="Estado: Detenido", anchor="center", font=('Arial', 9))
|
||||
self.label_estado.grid(row=4, column=0, columnspan=4, pady=(5, 0), sticky="ew")
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# ⏯️ MANEJO DE LA LÓGICA
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def manejar_play_pause(self):
|
||||
"""Alterna entre reproducir y pausar."""
|
||||
if self.reproductor.esta_reproduciendo():
|
||||
self.reproductor.pausar()
|
||||
self.boton_play_pause.config(text="▶️")
|
||||
self.label_estado.config(text="Estado: Pausado")
|
||||
else:
|
||||
# Si está pausado o detenido, intenta reproducir el último stream cargado.
|
||||
if self.reproductor.continuar():
|
||||
self.boton_play_pause.config(text="⏸️")
|
||||
self.label_estado.config(text="Estado: Reproduciendo")
|
||||
else:
|
||||
# Si no hay stream cargado, se mantiene detenido o se puede mostrar un error.
|
||||
messagebox.showwarning("Advertencia", "No hay stream cargado para reproducir.")
|
||||
|
||||
|
||||
def manejar_stop(self):
|
||||
"""Detiene completamente la reproducción."""
|
||||
self.reproductor.detener()
|
||||
self.boton_play_pause.config(text="▶️")
|
||||
self.label_estado.config(text="Estado: Detenido")
|
||||
|
||||
def manejar_ajuste_volumen(self, valor):
|
||||
"""Ajusta el volumen del reproductor basado en el slider."""
|
||||
volumen = int(float(valor))
|
||||
self.reproductor.set_volumen(volumen)
|
||||
# Puedes añadir una pequeña etiqueta para ver el volumen si es necesario.
|
||||
print(f"Volumen ajustado a: {volumen}%")
|
||||
|
||||
def cargar_stream_prueba(self):
|
||||
"""
|
||||
Carga y reproduce una URL de prueba o la última guardada.
|
||||
Aquí usamos una URL de ejemplo que puede ser más estable.
|
||||
"""
|
||||
# Nota: La URL de tu log falló. Usamos una de prueba conocida (Radio Paradise)
|
||||
URL_STREAM_PRUEBA = "http://stream.radioparadise.com/flac"
|
||||
|
||||
success, mensaje = self.reproductor.cargar_y_reproducir(URL_STREAM_PRUEBA)
|
||||
|
||||
if success:
|
||||
self.boton_play_pause.config(text="⏸️")
|
||||
self.label_estado.config(text=f"Estado: Reproduciendo {mensaje}")
|
||||
else:
|
||||
self.label_estado.config(text=f"Error: {mensaje}")
|
||||
messagebox.showerror("Error de Stream", f"No se pudo cargar el stream. Revisa la URL y la conexión.\nDetalle: {mensaje}")
|
||||
self.label_reloj.pack(side="left")
|
||||
Loading…
Reference in New Issue