```
feat(vista): Integra radios y refactoriza (main) Integra T3: Reproductor de radios con control de volumen. * Añade pestaña "Radios" con lista de emisoras. * Implementa selección y reproducción de streams. * Permite ajustar el volumen. * Refactoriza panel central para integrar la app. * Agrega controles de reproductor al panel lateral. * Corrige rutas de ficheros y nombres de variables. * Actualiza readme con dependencias. ```
This commit is contained in:
parent
813b2b7d65
commit
983836d94c
16
Readme.md
16
Readme.md
|
|
@ -2,7 +2,11 @@
|
|||
tkinter => hay que instalar python3-tk
|
||||
|
||||
matplotlib => pip install matplotlib
|
||||
|
||||
psutil => pip install psutil
|
||||
|
||||
python-vlc => pip install python-vlc
|
||||
|
||||
## Como Ejecutar ##
|
||||
> [!NOTE]
|
||||
> Desde la carpeta anterior
|
||||
|
|
@ -28,12 +32,14 @@ python -m ProyectoGlobal
|
|||
|
||||
### T2. Multihilos ###
|
||||
|
||||
1. Hora del sistema / Fecha del sistema
|
||||
1. ~~Hora del sistema / Fecha del sistema~~
|
||||
|
||||
2. Programar Alarma (aviso visual y sonoro al pasar X minutos)
|
||||
2. ~~Temperatura local~~
|
||||
|
||||
3. Scraping
|
||||
3. Programar Alarma (aviso visual y sonoro al pasar X minutos)
|
||||
|
||||
4. Juego de los camellos / autos de choque / etc. (aplicar resolución de sincronización para evitar problemas de interbloqueos)
|
||||
4. Scraping
|
||||
|
||||
5. Música de fondo (reproducción de mp3 o midi)
|
||||
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)
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
# 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.
|
||||
"""
|
||||
|
||||
def __init__(self, initial_volume=50.0):
|
||||
"""Inicializa la instancia de VLC y el reproductor."""
|
||||
|
||||
# Instancia de VLC y objeto Reproductor
|
||||
self.instance = vlc.Instance()
|
||||
self.player = self.instance.media_player_new()
|
||||
self.current_media = None
|
||||
self.is_playing = False
|
||||
|
||||
# Configurar volumen inicial
|
||||
self.ajustar_volumen(initial_volume)
|
||||
print(f"🎵 [VLC] Reproductor inicializado. Volumen: {self.player.audio_get_volume()}")
|
||||
|
||||
def ajustar_volumen(self, valor_porcentual):
|
||||
"""
|
||||
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 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
|
||||
|
||||
print(f"🔄 [VLC] Intentando cargar y reproducir: {url_stream}")
|
||||
|
||||
self.player.stop()
|
||||
|
||||
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.")
|
||||
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.")
|
||||
|
||||
def detener(self):
|
||||
"""
|
||||
Detiene la reproducción y libera los recursos. Crucial al cerrar la aplicación.
|
||||
"""
|
||||
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.")
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
# --- NOMBRE DEL ARCHIVO ---
|
||||
NOMBRE_FICHERO = "radios.json"
|
||||
|
||||
|
||||
def cargar_emisoras():
|
||||
"""Carga las emisoras existentes desde el archivo, o retorna una lista vacía si no existe."""
|
||||
if os.path.exists(NOMBRE_FICHERO):
|
||||
try:
|
||||
with open(NOMBRE_FICHERO, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
print(f"⚠️ Error al leer el archivo {NOMBRE_FICHERO}. Creando una lista nueva.")
|
||||
return []
|
||||
return []
|
||||
|
||||
|
||||
def guardar_emisoras(emisoras):
|
||||
"""Guarda la lista de emisoras en formato JSON en el archivo."""
|
||||
try:
|
||||
with open(NOMBRE_FICHERO, 'w', encoding='utf-8') as f:
|
||||
# Usamos indent=4 para que el JSON sea legible
|
||||
json.dump(emisoras, f, indent=4, ensure_ascii=False)
|
||||
print(f"\n✅ ¡Éxito! El archivo '{NOMBRE_FICHERO}' ha sido guardado.")
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error al intentar guardar el archivo: {e}")
|
||||
|
||||
|
||||
def crear_nueva_emisora(emisoras):
|
||||
"""Pide al usuario los datos de una nueva emisora y la añade a la lista."""
|
||||
print("\n--- Crear Nueva Emisora ---")
|
||||
|
||||
# --- CAMPOS OBLIGATORIOS ---
|
||||
nombre = input("▶️ Nombre de la radio (ej: Jazz Clásico): ").strip()
|
||||
url = input("▶️ URL del Stream (ej: http://stream.com/live.mp3): ").strip()
|
||||
|
||||
if not nombre or not url:
|
||||
print("❌ El nombre y la URL son obligatorios. Inténtalo de nuevo.")
|
||||
return
|
||||
|
||||
# --- CAMPOS OPCIONALES ---
|
||||
pais = input("▶️ País (ej: ES, DE) [Opcional]: ").strip()
|
||||
genero = input("▶️ Género (ej: Pop, Noticias) [Opcional]: ").strip()
|
||||
|
||||
nueva_emisora = {
|
||||
"nombre": nombre,
|
||||
"url_stream": url,
|
||||
"pais": pais if pais else None,
|
||||
"genero": genero if genero else None
|
||||
}
|
||||
|
||||
emisoras.append(nueva_emisora)
|
||||
print(f"\n✅ Emisora '{nombre}' añadida a la lista en memoria.")
|
||||
|
||||
|
||||
def mostrar_emisoras(emisoras):
|
||||
"""Muestra la lista de emisoras actuales en memoria."""
|
||||
if not emisoras:
|
||||
print("\nLista de emisoras vacía. Utiliza la opción 1 para añadir una.")
|
||||
return
|
||||
|
||||
print(f"\n--- Emisoras Actuales en Memoria ({len(emisoras)} en total) ---")
|
||||
for i, e in enumerate(emisoras):
|
||||
print(f"\nID: {i + 1}")
|
||||
print(f" Nombre: {e.get('nombre', 'N/D')}")
|
||||
print(f" URL: {e.get('url_stream', 'N/D')}")
|
||||
print(f" País: {e.get('pais', 'N/D')}")
|
||||
print(f" Género: {e.get('genero', 'N/D')}")
|
||||
print("--------------------------------------------------")
|
||||
|
||||
|
||||
def menu_principal():
|
||||
"""Función principal que ejecuta el menú interactivo."""
|
||||
|
||||
# Cargar las emisoras al iniciar (si el archivo existe)
|
||||
emisoras_en_memoria = cargar_emisoras()
|
||||
|
||||
while True:
|
||||
print("\n" + "=" * 30)
|
||||
print("📻 EDITOR DE CONFIGURACIÓN DE RADIOS 📻")
|
||||
print("=" * 30)
|
||||
print("1. Crear y Añadir Nueva Emisora")
|
||||
print("2. Mostrar Emisoras en Memoria")
|
||||
print(f"3. Guardar Lista en '{NOMBRE_FICHERO}'")
|
||||
print("4. Salir (Sin guardar los cambios en memoria)")
|
||||
print("-" * 30)
|
||||
|
||||
opcion = input("Elige una opción (1-4): ").strip()
|
||||
|
||||
if opcion == '1':
|
||||
crear_nueva_emisora(emisoras_en_memoria)
|
||||
elif opcion == '2':
|
||||
mostrar_emisoras(emisoras_en_memoria)
|
||||
elif opcion == '3':
|
||||
guardar_emisoras(emisoras_en_memoria)
|
||||
elif opcion == '4':
|
||||
print("\nSaliendo del editor. ¡Hasta pronto!")
|
||||
break
|
||||
else:
|
||||
print("Opción no válida. Por favor, selecciona un número del 1 al 4.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
menu_principal()
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
Aqui se alojan pequeños programas que no tienen que ver con el
|
||||
programa pero pueden simplificar procesos como
|
||||
es crear un fichero con datos que tiene que leer el programa
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
[
|
||||
{
|
||||
"nombre": "Esencia FM",
|
||||
"url_stream": "https://stream.serviciospararadios.es/listen/esencia_fm/esenciafm.mp3",
|
||||
"pais": "ES",
|
||||
"genero": null
|
||||
},
|
||||
{
|
||||
"nombre": "Bikini FM",
|
||||
"url_stream": "https://stream.emisorasmusicales.net/listen/bikini_fm/bikinifm-vlc.mp3",
|
||||
"pais": "ES",
|
||||
"genero": "La radio remember"
|
||||
},
|
||||
{
|
||||
"nombre": "Activa FM",
|
||||
"url_stream": "https://stream.serviciospararadios.es/listen/activa_fm/activafm-tunein.mp3",
|
||||
"pais": "ES",
|
||||
"genero": null
|
||||
}
|
||||
]
|
||||
|
|
@ -3,32 +3,63 @@
|
|||
import random
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
import json
|
||||
import os
|
||||
import sys # Necesario para el bloque opcional de VLC
|
||||
|
||||
# --- 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 matplotlib.figure import Figure
|
||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||
|
||||
# --- IMPORTACIÓN DE LA LÓGICA DE T2 (CARRERA DE CAMELLOS) ---
|
||||
# --- LÓGICA DE T2 (CARRERA DE CAMELLOS) ---
|
||||
from logica.T2.carreraCamellos import (
|
||||
iniciar_carrera,
|
||||
obtener_estado_carrera,
|
||||
detener_carrera,
|
||||
RESULTADO_ULTIMO # Variable de estado persistente
|
||||
RESULTADO_ULTIMO
|
||||
)
|
||||
|
||||
# --- LÓGICA DE T3 (REPRODUCTOR DE MÚSICA) ---
|
||||
# Se necesita el bloque opcional aquí, ya que el import 'vlc' está en este módulo
|
||||
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.")
|
||||
|
||||
|
||||
class MusicReproductor:
|
||||
def __init__(self, *args, **kwargs): pass
|
||||
|
||||
def ajustar_volumen(self, valor): print(f"Volumen (Simulado): {valor}")
|
||||
|
||||
def cargar_y_reproducir(self, url): print(f"Reproduciendo (Simulado): {url}")
|
||||
|
||||
def reproducir(self): print("Reproducir (Simulado)")
|
||||
|
||||
def pausar(self): print("Pausar (Simulado)")
|
||||
|
||||
def detener(self): print("Detener (Simulado)")
|
||||
|
||||
# --- IMPORTACIÓN UNIVERSAL DE CONSTANTES ---
|
||||
# Asume que todas las constantes de estilo y colores necesarias están definidas en vista.config
|
||||
from vista.config import *
|
||||
|
||||
|
||||
class PanelCentral(ttk.Frame):
|
||||
"""Contiene el Notebook (subpestañas de T1, T2 en Resultados), el panel de Notas y el panel de Chat."""
|
||||
"""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."""
|
||||
|
||||
INTERVALO_ACTUALIZACION_MS = INTERVALO_RECURSOS_MS
|
||||
INTERVALO_CARRERA_MS = 200
|
||||
|
||||
def __init__(self, parent, *args, **kwargs):
|
||||
# ✅ CORRECCIÓN DE RUTA
|
||||
NOMBRE_FICHERO_RADIOS = "res/radios.json"
|
||||
|
||||
def __init__(self, parent, root, *args, **kwargs):
|
||||
super().__init__(parent, *args, **kwargs)
|
||||
self.root = root
|
||||
|
||||
self.after_id = None
|
||||
self.after_carrera_id = None
|
||||
self.camellos = []
|
||||
|
|
@ -43,7 +74,13 @@ class PanelCentral(ttk.Frame):
|
|||
self.figure = Figure(figsize=(5, 4), dpi=100)
|
||||
self.canvas = None
|
||||
|
||||
# 2. CONFIGURACIÓN DEL LAYOUT
|
||||
# 2. INICIALIZACIÓN DE VARIABLES Y LÓGICA DE RADIO (T3)
|
||||
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
|
||||
self.grid_columnconfigure(0, weight=3)
|
||||
self.grid_columnconfigure(1, weight=1)
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
|
|
@ -51,9 +88,99 @@ class PanelCentral(ttk.Frame):
|
|||
self.crear_area_principal_y_notas()
|
||||
self.crear_panel_chat_y_alumnos()
|
||||
|
||||
# 4. INICIO DE CICLOS DE ACTUALIZACIÓN
|
||||
self.iniciar_actualizacion_automatica()
|
||||
self.iniciar_actualizacion_carrera()
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 📻 LÓGICA Y VISTA DE T3 (REPRODUCTOR DE RADIOS)
|
||||
# -------------------------------------------------------------
|
||||
|
||||
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:
|
||||
print(f"❌ Archivo de emisoras no encontrado en: '{self.NOMBRE_FICHERO_RADIOS}'.")
|
||||
return []
|
||||
except json.JSONDecodeError:
|
||||
print(f"⚠️ Error al leer el archivo {self.NOMBRE_FICHERO_RADIOS}. Está mal formado.")
|
||||
return []
|
||||
|
||||
def crear_interfaz_radios(self, parent_frame):
|
||||
"""Crea la interfaz para seleccionar la emisora de radio."""
|
||||
|
||||
frame_radio = ttk.Frame(parent_frame, padding=10, style='TFrame')
|
||||
frame_radio.pack(expand=True, fill="both")
|
||||
|
||||
ttk.Label(frame_radio, text="Selección de Emisoras de Radio", font=FUENTE_TITULO).pack(pady=10)
|
||||
|
||||
if not self.emisoras_cargadas:
|
||||
ttk.Label(frame_radio,
|
||||
text=f"No se encontraron emisoras en '{self.NOMBRE_FICHERO_RADIOS}'.",
|
||||
foreground='red').pack(pady=20)
|
||||
return
|
||||
|
||||
frame_listado = ttk.Frame(frame_radio)
|
||||
frame_listado.pack(fill="both", expand=True)
|
||||
|
||||
listbox = tk.Listbox(frame_listado, height=15, width=60, font=('Arial', 10),
|
||||
bg=COLOR_BLANCO, fg=COLOR_TEXTO, selectbackground=COLOR_ACCION)
|
||||
listbox.pack(side="left", fill="both", expand=True, padx=5, pady=5)
|
||||
|
||||
scrollbar = ttk.Scrollbar(frame_listado, orient="vertical", command=listbox.yview)
|
||||
scrollbar.pack(side="right", fill="y")
|
||||
listbox.config(yscrollcommand=scrollbar.set)
|
||||
|
||||
for emisora in self.emisoras_cargadas:
|
||||
nombre_display = f"{emisora['nombre']} ({emisora.get('genero', 'N/D')})"
|
||||
listbox.insert(tk.END, nombre_display)
|
||||
|
||||
listbox.bind('<<ListboxSelect>>', lambda e: self.seleccionar_radio(listbox))
|
||||
|
||||
ttk.Label(frame_radio, text="URL del Stream:", font=FUENTE_NEGOCIOS).pack(pady=(10, 0), anchor="w")
|
||||
self.url_seleccionada_label = ttk.Label(frame_radio, text="N/A", wraplength=400, foreground=COLOR_TEXTO)
|
||||
self.url_seleccionada_label.pack(anchor="w")
|
||||
|
||||
def seleccionar_radio(self, listbox):
|
||||
"""Captura la selección y llama al reproductor para iniciar la reproducción."""
|
||||
seleccion = listbox.curselection()
|
||||
if seleccion:
|
||||
indice = seleccion[0]
|
||||
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):
|
||||
"""Llama al método de control del reproductor (play/pause)."""
|
||||
if accion == 'play':
|
||||
self.reproductor.reproducir()
|
||||
elif accion == 'pause':
|
||||
self.reproductor.pausar()
|
||||
|
||||
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.
|
||||
"""
|
||||
# ✅ 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)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 📦 ESTRUCTURA PRINCIPAL DEL PANEL
|
||||
# -------------------------------------------------------------
|
||||
|
|
@ -77,11 +204,11 @@ class PanelCentral(ttk.Frame):
|
|||
expand=True, fill="both")
|
||||
|
||||
def crear_notebook_pestañas(self, parent_frame):
|
||||
"""Crea las pestañas internas para las tareas (T1, Carrera en Resultados)."""
|
||||
"""Crea las pestañas internas para las tareas (T1, Carrera, Radios)."""
|
||||
sub_notebook = ttk.Notebook(parent_frame)
|
||||
sub_notebook.grid(row=0, column=0, sticky="nsew")
|
||||
|
||||
sub_tabs = ["Recursos", "Resultados", "Navegador", "Correos", "Tareas", "Alarmas", "Enlaces"]
|
||||
sub_tabs = ["Recursos", "Resultados", "Radios", "Navegador", "Correos", "Tareas", "Alarmas", "Enlaces"]
|
||||
self.tabs = {}
|
||||
|
||||
for i, sub_tab_text in enumerate(sub_tabs):
|
||||
|
|
@ -90,7 +217,6 @@ class PanelCentral(ttk.Frame):
|
|||
self.tabs[sub_tab_text] = frame
|
||||
|
||||
if sub_tab_text == "Recursos":
|
||||
# Lógica de T1 - Gráfico de recursos
|
||||
self.grafico_frame = ttk.Frame(frame, style='TFrame')
|
||||
self.grafico_frame.pack(expand=True, fill="both", padx=10, pady=10)
|
||||
|
||||
|
|
@ -98,18 +224,18 @@ class PanelCentral(ttk.Frame):
|
|||
self.canvas_widget = self.canvas.get_tk_widget()
|
||||
self.canvas_widget.pack(expand=True, fill="both")
|
||||
|
||||
# --- INTEGRACIÓN DE LA CARRERA EN LA PESTAÑA "Resultados" ---
|
||||
elif sub_tab_text == "Resultados":
|
||||
self.crear_interfaz_carrera(frame)
|
||||
|
||||
elif sub_tab_text == "Radios":
|
||||
self.crear_interfaz_radios(frame)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 🐪 LÓGICA DE T2 (CARRERA DE CAMELLOS)
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def crear_interfaz_carrera(self, parent_frame):
|
||||
"""Crea los controles y la visualización de la Carrera de Camellos."""
|
||||
|
||||
# 1. Título y Controles (Solo título, el botón de inicio va en el lateral)
|
||||
frame_controles = ttk.Frame(parent_frame, style='TFrame', padding=10)
|
||||
frame_controles.pack(fill="x")
|
||||
self.frame_carrera_controles = frame_controles
|
||||
|
|
@ -120,7 +246,6 @@ class PanelCentral(ttk.Frame):
|
|||
self.carrera_estado_label = ttk.Label(frame_controles, text="Estado.", style='TLabel', font=FUENTE_NEGOCIOS)
|
||||
self.carrera_estado_label.pack(side="right", padx=10)
|
||||
|
||||
# 2. Marco de visualización de progreso
|
||||
self.frame_progreso = ttk.Frame(parent_frame, style='TFrame', padding=10)
|
||||
self.frame_progreso.pack(fill="both", expand=True)
|
||||
|
||||
|
|
@ -136,18 +261,15 @@ class PanelCentral(ttk.Frame):
|
|||
|
||||
print("Iniciando Carrera de Camellos con número variable de participantes...")
|
||||
|
||||
# Genera un número aleatorio de camellos entre 10 y 20
|
||||
num_camellos = random.randint(10, 20)
|
||||
nombres = [f"Camello {i + 1}" for i in range(num_camellos)]
|
||||
|
||||
# Limpiar la visualización anterior
|
||||
for widget in self.frame_progreso.winfo_children():
|
||||
widget.destroy()
|
||||
self.progreso_labels = {}
|
||||
|
||||
self.camellos = iniciar_carrera(nombres)
|
||||
|
||||
# --- MENSAJE SIMPLIFICADO ---
|
||||
self.carrera_estado_label.config(text=f"¡Carrera en marcha! 💨 ({num_camellos} participantes)")
|
||||
|
||||
self.crear_visualizacion_carrera(nombres)
|
||||
|
|
@ -199,7 +321,6 @@ class PanelCentral(ttk.Frame):
|
|||
if etiqueta_posicion and estado['posicion']:
|
||||
etiqueta_posicion.config(text=f"POS: {estado['posicion']} 🏆", foreground=COLOR_ACCION)
|
||||
|
||||
# --- MENSAJE SIMPLIFICADO ---
|
||||
self.carrera_estado_label.config(text="Carrera en curso...")
|
||||
self.carrera_info_label.config(text="")
|
||||
|
||||
|
|
@ -226,7 +347,6 @@ class PanelCentral(ttk.Frame):
|
|||
etiqueta_posicion.config(text=f"POS: {estado['posicion']} {'🏆' if estado['posicion'] == 1 else ''}",
|
||||
foreground=COLOR_ACCION)
|
||||
|
||||
# --- MENSAJE SIMPLIFICADO ---
|
||||
self.carrera_estado_label.config(text="✅ Carrera Terminada.")
|
||||
self.carrera_info_label.config(text=f"¡El ganador es: {resultado_final['ganador']}!",
|
||||
font=FUENTE_TITULO, foreground=COLOR_EXITO)
|
||||
|
|
@ -253,7 +373,6 @@ class PanelCentral(ttk.Frame):
|
|||
if RESULTADO_ULTIMO and not RESULTADO_ULTIMO.get('activa', True) and self.frame_progreso:
|
||||
self.mostrar_resultado_final(RESULTADO_ULTIMO)
|
||||
else:
|
||||
# --- MENSAJE SIMPLIFICADO AL CARGAR ---
|
||||
self.carrera_estado_label.config(text="Carrera lista para empezar.")
|
||||
self.after_carrera_id = self.after(0, self.actualizar_carrera)
|
||||
|
||||
|
|
@ -295,7 +414,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 la carrera (T2)."""
|
||||
"""Detiene el ciclo de actualización periódica y el hilo de red (T1) y T3."""
|
||||
if self.after_id:
|
||||
self.after_cancel(self.after_id)
|
||||
self.after_id = None
|
||||
|
|
@ -308,31 +427,31 @@ class PanelCentral(ttk.Frame):
|
|||
|
||||
self.detener_actualizacion_carrera()
|
||||
|
||||
# Detener el reproductor al cerrar la aplicación
|
||||
if self.reproductor:
|
||||
self.reproductor.detener()
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# 💬 PANEL CHAT, ALUMNOS Y APPS (PANEL LATERAL)
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def crear_panel_chat_y_alumnos(self, ):
|
||||
"""Crea el panel de chat y lista de Alumnos (columna derecha)."""
|
||||
"""Crea el panel de chat y lista de Alumnos (columna derecha), incluyendo el reproductor."""
|
||||
panel_chat = ttk.Frame(self, style='TFrame', padding="10")
|
||||
panel_chat.grid(row=0, column=1, sticky="nsew")
|
||||
|
||||
# Configuración de expansión (La fila 5, que contiene los alumnos, es la que se expande)
|
||||
panel_chat.grid_rowconfigure(5, weight=1)
|
||||
panel_chat.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# --- FILA 0: Título ---
|
||||
ttk.Label(panel_chat, text="Chat", foreground=COLOR_ACCION, font=FUENTE_TITULO, style='TLabel').grid(row=0,
|
||||
column=0,
|
||||
pady=(
|
||||
0,
|
||||
pady=(0,
|
||||
10),
|
||||
sticky="w")
|
||||
|
||||
# --- FILAS 1-3: Chat Input ---
|
||||
ttk.Label(panel_chat, text="Mensaje", style='TLabel').grid(row=1, column=0, sticky="w")
|
||||
|
||||
# Usando COLOR_BLANCO para el fondo del Text
|
||||
chat_text = tk.Text(panel_chat, height=6, width=30, bg=COLOR_BLANCO, relief="solid", borderwidth=1,
|
||||
font=('Arial', 10))
|
||||
chat_text.grid(row=2, column=0, sticky="ew", pady=(0, 5))
|
||||
|
|
@ -352,26 +471,33 @@ 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: Música ---
|
||||
# --- FILA 8: Reproductor Música ---
|
||||
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))
|
||||
ttk.Label(musica_frame, text="[ Botones de Play/Stop y Control de Volumen ]", anchor="center",
|
||||
style='TLabel').pack(fill="x", padx=5, pady=5)
|
||||
|
||||
# --- FILA 9: Botones de Aplicación (Minijuegos) ---
|
||||
frame_apps = ttk.LabelFrame(panel_chat, text="Minijuegos / Apps", padding=10, style='TFrame')
|
||||
frame_apps.grid(row=9, column=0, sticky="ew", pady=(15, 0))
|
||||
# 8a: Emisora Actual
|
||||
ttk.Label(musica_frame, text="Actual: ", style='TLabel').grid(row=0, column=0, sticky="w")
|
||||
ttk.Label(musica_frame, textvariable=self.radio_seleccionada, font=('Arial', 10, 'bold'), style='TLabel').grid(
|
||||
row=0, column=1, columnspan=2, sticky="w")
|
||||
|
||||
# Botón 1: Placeholder
|
||||
ttk.Button(frame_apps, text="App1 (T3-Red)", width=15, style='Action.TButton').pack(side="left", padx=5)
|
||||
# 8b: Controles de Reproducción (Play/Pause)
|
||||
frame_controles_musica = ttk.Frame(musica_frame, style='TFrame')
|
||||
frame_controles_musica.grid(row=1, column=0, columnspan=3, pady=(5, 5))
|
||||
|
||||
# Botón 2: Inicia la Carrera de Camellos (T2 Sincronización)
|
||||
ttk.Button(frame_apps,
|
||||
text="App2 (T2-Carrera 🏁)",
|
||||
command=self.manejar_inicio_carrera,
|
||||
width=15,
|
||||
style='Action.TButton').pack(side="left", padx=5)
|
||||
ttk.Button(frame_controles_musica, text="▶️ Iniciar", command=lambda: self.controlar_reproduccion('play'),
|
||||
style='Action.TButton', width=8).pack(side="left", padx=5)
|
||||
ttk.Button(frame_controles_musica, text="⏸️ Pausar", command=lambda: self.controlar_reproduccion('pause'),
|
||||
style='Action.TButton', width=8).pack(side="left", padx=5)
|
||||
|
||||
# 8c: Control de Volumen (Scale/Deslizable)
|
||||
ttk.Label(musica_frame, text="Volumen:", style='TLabel').grid(row=2, column=0, sticky="w")
|
||||
|
||||
volumen_scale = ttk.Scale(musica_frame, from_=0, to=100, orient="horizontal",
|
||||
variable=self.volumen_var, command=self.cambiar_volumen)
|
||||
volumen_scale.grid(row=2, column=1, sticky="ew", padx=(0, 5))
|
||||
|
||||
ttk.Label(musica_frame, textvariable=self.volumen_var, style='TLabel').grid(row=2, column=2, sticky="w")
|
||||
|
||||
musica_frame.grid_columnconfigure(1, weight=1)
|
||||
|
||||
# Asegurar que las filas de abajo no se expandan
|
||||
panel_chat.grid_rowconfigure(8, weight=0)
|
||||
panel_chat.grid_rowconfigure(9, weight=0)
|
||||
|
|
@ -16,7 +16,7 @@ class VentanaPrincipal(tk.Tk):
|
|||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.title("Proyecto Integrado - PSP (Estilo Moderno Nativo)")
|
||||
self.title("Proyecto Integrado - PSP")
|
||||
self.geometry("1200x800")
|
||||
|
||||
self.label_reloj = None
|
||||
|
|
@ -79,7 +79,7 @@ class VentanaPrincipal(tk.Tk):
|
|||
def crear_paneles_principales(self):
|
||||
"""Ensambla el panel lateral y el panel central en la rejilla, asegurando el ancho del lateral."""
|
||||
|
||||
self.panel_central = PanelCentral(self)
|
||||
self.panel_central = PanelCentral(self, self)
|
||||
self.panel_central.grid(row=0, column=1, sticky="nswe", padx=(5, 10), pady=10)
|
||||
|
||||
# Usando la constante importada
|
||||
|
|
|
|||
Loading…
Reference in New Issue