proyecto-global-psp/vista/chat/chat_cliente.py

154 lines
5.7 KiB
Python

import tkinter as tk
from tkinter import ttk
import threading
from vista.chat.chat_base import ChatBase
from logica.red.cliente import conectar_servidor
from vista.config import *
PREFIJO_NOMBRE = "__NOMBRE__:"
class ChatClientePanel(ChatBase):
"""Vista de chat para el rol de cliente."""
def __init__(self, parent, root, ip, puerto, clave, on_auth_error=None, *args, **kwargs):
super().__init__(parent, root, *args, **kwargs)
self.ip = ip
self.puerto = puerto
self.clave = clave
self.nombre = None
self.on_auth_error = on_auth_error
self.crear_interfaz_chat(
self, titulo="Chat - Cliente",
boton_accion_texto="Desconectar",
boton_accion_callback=self.desconectar_y_volver
)
self.conectar()
def conectar(self):
print(f"[DEBUG CLI-GUI] Iniciando conexion a {self.ip}:{self.puerto} con clave={self.clave!r}")
self.agregar_mensaje_sistema(f"Conectando a {self.ip}:{self.puerto}...")
def hilo_conexion():
cliente, extra = conectar_servidor(self.ip, self.puerto, self.clave)
if cliente:
self.socket = cliente
print(f"[DEBUG CLI-GUI] Conexion exitosa, extra={extra!r}")
self.root.after(0, self.agregar_mensaje_sistema, f"Conectado a {self.ip}:{self.puerto}")
self.recibir_mensajes(extra)
else:
print(f"[DEBUG CLI-GUI] Conexion fallida")
if self.on_auth_error:
self.root.after(0, self.on_auth_error)
else:
self.root.after(0, self.agregar_mensaje_sistema, "Error: no se pudo conectar o clave incorrecta")
hilo = threading.Thread(target=hilo_conexion, daemon=True)
hilo.start()
def _procesar_mensaje(self, mensaje):
"""Procesa un mensaje recibido (nombre, chat o remoto)."""
print(f"[DEBUG CLI-GUI] Procesando mensaje: {mensaje!r}")
if mensaje.startswith(PREFIJO_NOMBRE):
self.nombre = mensaje[len(PREFIJO_NOMBRE):]
print(f"[DEBUG CLI-GUI] Nombre asignado: {self.nombre!r}")
self.root.after(0, self.agregar_mensaje_sistema, f"Tu nombre: {self.nombre}")
self.root.after(0, self._actualizar_titulo)
elif ": " in mensaje:
remitente, texto = mensaje.split(": ", 1)
print(f"[DEBUG CLI-GUI] Mensaje de chat: {remitente!r} -> {texto!r}")
self.root.after(0, self.agregar_mensaje, remitente, texto)
else:
print(f"[DEBUG CLI-GUI] Mensaje remoto sin formato: {mensaje!r}")
self.root.after(0, self.agregar_mensaje, "REMOTO", mensaje)
def recibir_mensajes(self, extra=""):
# Procesar datos que llegaron junto con el "OK" de autenticacion
if extra:
print(f"[DEBUG CLI-GUI] Procesando datos extra del handshake: {extra!r}")
self._procesar_mensaje(extra)
print("[DEBUG CLI-GUI] Bucle de recepcion iniciado")
try:
while self.socket:
datos = self.socket.recv(4096)
if not datos:
print("[DEBUG CLI-GUI] Datos vacios recibidos (servidor cerro)")
self.root.after(0, self._servidor_desconectado)
break
mensaje = datos.decode("utf-8")
print(f"[DEBUG CLI-GUI] Datos crudos recibidos: {datos!r}")
self._procesar_mensaje(mensaje)
except (ConnectionResetError, OSError) as e:
print(f"[DEBUG CLI-GUI] Error en recepcion: {e}")
self.root.after(0, self._servidor_desconectado)
def _actualizar_titulo(self):
if self.nombre:
self.info_label.config(text=f"Chat - {self.nombre}")
def enviar_mensaje(self, event=None):
mensaje = self.chat_input_entry.get().strip()
if not mensaje or not self.socket:
return "break" if event else None
self.chat_input_entry.delete(0, "end")
self.agregar_mensaje("TU", mensaje)
try:
print(f"[DEBUG CLI-GUI] Enviando mensaje: {mensaje!r}")
self.socket.sendall(mensaje.encode("utf-8"))
except OSError as e:
print(f"[DEBUG CLI-GUI] Error al enviar: {e}")
self.agregar_mensaje_sistema("Error al enviar mensaje")
return "break" if event else None
def _servidor_desconectado(self):
"""Bloquea la entrada y muestra popup cuando el servidor cierra la conexion."""
self.cerrar_conexion()
self.bloquear_entrada()
dialogo = tk.Toplevel(self.root)
dialogo.title("Servidor desconectado")
dialogo.geometry("320x140")
dialogo.resizable(False, False)
dialogo.transient(self.root)
dialogo.grab_set()
frame = ttk.Frame(dialogo, padding=20)
frame.pack(expand=True, fill="both")
ttk.Label(
frame, text="El servidor se ha cerrado.",
font=FUENTE_NEGOCIOS
).pack(pady=(0, 15))
frame_btns = ttk.Frame(frame)
frame_btns.pack()
def volver():
dialogo.destroy()
self.desconectar_y_volver()
def ver_chat():
dialogo.destroy()
ttk.Button(
frame_btns, text="Volver al inicio",
command=volver, style='Action.TButton'
).pack(side="left", padx=5)
ttk.Button(
frame_btns, text="Ver el chat",
command=ver_chat
).pack(side="left", padx=5)
def desconectar_y_volver(self):
"""Desconecta del servidor y vuelve al selector."""
self.cerrar_conexion()
parent = self.master
self.destroy()
if hasattr(parent, 'volver_al_selector'):
parent.volver_al_selector()