add backup functionality with platform-specific scripts and resource monitoring
This commit is contained in:
parent
e9c1a52116
commit
d6a39f3307
|
|
@ -1,6 +1,8 @@
|
||||||
## Requisitos ##
|
## Requisitos ##
|
||||||
tkinter => hay que instalar python3-tk
|
tkinter => hay que instalar python3-tk
|
||||||
|
|
||||||
|
matplotlib => pip install matplotlib
|
||||||
|
psutil => pip install psutil
|
||||||
## Como Ejecutar ##
|
## Como Ejecutar ##
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> Desde la carpeta anterior
|
> Desde la carpeta anterior
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,120 @@
|
||||||
|
# Módulo: logica/T1/backup.py
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import os # Necesario para la verificación y, si es necesario, cambio de permisos
|
||||||
|
|
||||||
|
# Importamos las funciones necesarias del controlador
|
||||||
|
from logica.controlador import getPlataforma, get_scripts_dir
|
||||||
|
|
||||||
|
_scripts = None
|
||||||
|
|
||||||
|
|
||||||
|
# --- FUNCIONES AUXILIARES DE DETECCIÓN ---
|
||||||
|
|
||||||
|
def _systemToExtension(plataforma_set):
|
||||||
|
"""Mapea el set de plataforma a la extensión de script necesaria."""
|
||||||
|
if 'WINDOWS' in plataforma_set:
|
||||||
|
return 'ps1'
|
||||||
|
else:
|
||||||
|
# Incluye 'LINUX' y 'MACOS'
|
||||||
|
return 'sh'
|
||||||
|
|
||||||
|
|
||||||
|
def _get_scripts():
|
||||||
|
"""Carga la lista de nombres de scripts disponibles en la carpeta 'res/scripts'."""
|
||||||
|
global _scripts
|
||||||
|
if _scripts is None:
|
||||||
|
script_dir_path = get_scripts_dir()
|
||||||
|
_scripts = [document.name for document in script_dir_path.iterdir() if document.is_file()]
|
||||||
|
return _scripts
|
||||||
|
|
||||||
|
|
||||||
|
def _get_valid_script_path():
|
||||||
"""
|
"""
|
||||||
hay que crear el script de backup
|
Busca el script de backup adecuado y devuelve su ruta absoluta.
|
||||||
ver el sistema operativo
|
|
||||||
ejecutar el script segun SO
|
|
||||||
"""
|
"""
|
||||||
|
plataforma = getPlataforma()
|
||||||
|
extension = _systemToExtension(plataforma)
|
||||||
|
scripts = _get_scripts()
|
||||||
|
script_name = None
|
||||||
|
|
||||||
|
for script in scripts:
|
||||||
|
if script.endswith(extension):
|
||||||
|
script_name = script
|
||||||
|
break
|
||||||
|
|
||||||
|
if script_name:
|
||||||
|
return get_scripts_dir() / script_name
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# --- FUNCIÓN PRINCIPAL DE ACCIÓN Y EJECUCIÓN ---
|
||||||
|
|
||||||
|
def accion_backup_t1():
|
||||||
|
"""
|
||||||
|
Función de acción para el botón de backup.
|
||||||
|
1. Obtiene la ruta del script a ejecutar.
|
||||||
|
2. Ejecuta el script con subprocess.
|
||||||
|
|
||||||
|
:return: Una tupla (bool, str) que indica éxito/fracaso y el mensaje.
|
||||||
|
"""
|
||||||
|
|
||||||
|
script_path = _get_valid_script_path()
|
||||||
|
|
||||||
|
# 1. VERIFICACIÓN DE EXISTENCIA DEL SCRIPT
|
||||||
|
if script_path is None or not script_path.exists():
|
||||||
|
return False, f"ERROR: No se encontró ningún script válido (.ps1 o .sh) en la carpeta de recursos."
|
||||||
|
|
||||||
|
plataforma = getPlataforma()
|
||||||
|
system = None
|
||||||
|
|
||||||
|
if 'WINDOWS' in plataforma:
|
||||||
|
system = "Windows"
|
||||||
|
elif 'LINUX' in plataforma:
|
||||||
|
# En Linux, aseguramos el permiso de ejecución antes de intentar ejecutar.
|
||||||
|
try:
|
||||||
|
os.chmod(script_path, 0o755) # 0o755: rwxr-xr-x
|
||||||
|
system = "Linux"
|
||||||
|
except Exception as e:
|
||||||
|
return False, f"ERROR de Permiso (Linux): No se pudo establecer el permiso de ejecución para {script_path.name}. Intente ejecutar 'chmod +x {script_path}' manualmente. Detalle: {e}"
|
||||||
|
elif 'MACOS' in plataforma:
|
||||||
|
system = "macOS"
|
||||||
|
else:
|
||||||
|
return False, f"Sistema operativo '{plataforma}' no soportado para la ejecución."
|
||||||
|
|
||||||
|
print(f"Backup T1: Ejecutando script: {script_path.name} en {system}")
|
||||||
|
|
||||||
|
# 2. DEFINICIÓN DEL COMANDO
|
||||||
|
command = []
|
||||||
|
if system == "Windows":
|
||||||
|
command = [
|
||||||
|
"powershell.exe",
|
||||||
|
"-ExecutionPolicy", "Bypass",
|
||||||
|
"-File", str(script_path)
|
||||||
|
]
|
||||||
|
elif system == "Linux" or system == "macOS":
|
||||||
|
command = [str(script_path)]
|
||||||
|
|
||||||
|
# 3. EJECUCIÓN DEL COMANDO
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
command,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
encoding='utf-8',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copia exitosa
|
||||||
|
return True, result.stdout
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
error_output = e.stderr if e.stderr else e.stdout
|
||||||
|
return False, f"El script falló (Código {e.returncode}). Mensaje del script:\n{error_output}"
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
interpeter = 'powershell.exe' if system == 'Windows' else 'el intérprete de shell'
|
||||||
|
return False, f"ERROR: El intérprete de comandos ({interpeter}) no se encontró en el PATH del sistema."
|
||||||
|
except Exception as e:
|
||||||
|
return False, f"Error inesperado al ejecutar el script: {e}"
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Módulo: logica/T1/geterSystemRecource.py
|
||||||
|
|
||||||
|
import psutil
|
||||||
|
|
||||||
|
|
||||||
|
def obtener_datos_cpu_ram():
|
||||||
|
"""
|
||||||
|
Función que utiliza psutil para recopilar información de CPU, RAM y procesos.
|
||||||
|
:return: Diccionario con métricas.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cpu_percent_total = psutil.cpu_percent(interval=None)
|
||||||
|
cpu_percent_per_core = psutil.cpu_percent(interval=None, percpu=True)
|
||||||
|
|
||||||
|
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,
|
||||||
|
'ram_total_gb': round(mem.total / (1024 ** 3), 2),
|
||||||
|
'ram_uso_gb': round(mem.used / (1024 ** 3), 2),
|
||||||
|
'ram_percent': mem.percent,
|
||||||
|
'num_hilos': num_procesos,
|
||||||
|
'cpu_freq_mhz': cpu_freq.current if cpu_freq else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return datos
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Módulo: logica/T1/graficos.py
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
COLOR_PRINCIPAL = '#0078d4'
|
||||||
|
COLOR_RAM = '#4CAF50'
|
||||||
|
|
||||||
|
|
||||||
|
def crear_grafico_recursos(parent_frame: tk.Frame, datos: dict):
|
||||||
|
"""
|
||||||
|
Genera un gráfico de matplotlib que muestra el uso de CPU y RAM,
|
||||||
|
e integra este gráfico en un Frame de Tkinter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Limpiamos el frame padre para redibujar
|
||||||
|
for widget in parent_frame.winfo_children():
|
||||||
|
widget.destroy()
|
||||||
|
|
||||||
|
# 1. Crear la figura (2 subplots)
|
||||||
|
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4))
|
||||||
|
fig.patch.set_facecolor('white')
|
||||||
|
|
||||||
|
# --- GRÁFICO 1: USO DE CPU (Gráfico de Barras) ---
|
||||||
|
core_labels = [f'Núcleo {i + 1}' for i in range(len(datos['cpu_cores']))]
|
||||||
|
ax1.bar(core_labels, datos['cpu_cores'], color=COLOR_PRINCIPAL)
|
||||||
|
ax1.axhline(datos['cpu_total'], color='red', linestyle='--', linewidth=1, label=f'Total: {datos["cpu_total"]}%')
|
||||||
|
|
||||||
|
ax1.set_title(f'Uso de CPU por Núcleo (Total: {datos["cpu_total"]}%)', fontsize=10)
|
||||||
|
ax1.set_ylabel('Uso (%)')
|
||||||
|
ax1.set_ylim(0, 100)
|
||||||
|
ax1.tick_params(axis='x', rotation=45)
|
||||||
|
ax1.legend(loc='upper right')
|
||||||
|
|
||||||
|
# --- GRÁFICO 2: USO DE RAM (Gráfico Circular/Pie) ---
|
||||||
|
labels = ['Usada', 'Libre']
|
||||||
|
sizes = [datos['ram_percent'], 100 - datos['ram_percent']]
|
||||||
|
colors = [COLOR_RAM, '#d3d3d3']
|
||||||
|
explode = (0.1, 0)
|
||||||
|
|
||||||
|
ax2.pie(sizes, explode=explode, labels=labels, colors=colors,
|
||||||
|
autopct='%1.1f%%', shadow=False, startangle=90)
|
||||||
|
ax2.axis('equal')
|
||||||
|
|
||||||
|
ram_title = f'RAM Total: {datos["ram_total_gb"]} GB\nUso: {datos["ram_uso_gb"]} GB'
|
||||||
|
ax2.set_title(ram_title, fontsize=10)
|
||||||
|
|
||||||
|
# 3. Integración en Tkinter
|
||||||
|
canvas = FigureCanvasTkAgg(fig, master=parent_frame)
|
||||||
|
canvas_widget = canvas.get_tk_widget()
|
||||||
|
fig.tight_layout(pad=3.0)
|
||||||
|
canvas_widget.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
return canvas_widget
|
||||||
|
|
@ -5,10 +5,6 @@ import platform
|
||||||
def abrir_vscode():
|
def abrir_vscode():
|
||||||
"""
|
"""
|
||||||
Intenta abrir Visual Studio Code utilizando el comando 'code'.
|
Intenta abrir Visual Studio Code utilizando el comando 'code'.
|
||||||
|
|
||||||
Este comando asume que VS Code ha sido instalado correctamente y que
|
|
||||||
el ejecutable 'code' está disponible en el PATH del sistema (lo cual
|
|
||||||
suele ser el caso si se habilita la opción al instalar VS Code).
|
|
||||||
"""
|
"""
|
||||||
comando = 'code'
|
comando = 'code'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,44 @@
|
||||||
import platform
|
import platform
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
_os = None
|
_os = None
|
||||||
|
|
||||||
|
_root_dir = Path(__file__).parent.parent
|
||||||
|
_scripts_dir = _root_dir / "res" / "scripts"
|
||||||
|
|
||||||
|
# --- Funciones de Configuración y Sistema ---
|
||||||
|
|
||||||
|
def get_scripts_dir():
|
||||||
|
"""Devuelve la ruta absoluta al directorio de scripts."""
|
||||||
|
return _scripts_dir
|
||||||
|
|
||||||
def getPlataforma():
|
def getPlataforma():
|
||||||
|
"""Detecta y devuelve el conjunto que identifica el SO ('WINDOWS', 'LINUX', 'MACOS')."""
|
||||||
global _os
|
global _os
|
||||||
if _os==None:
|
if _os is None:
|
||||||
_os = _obtener_datos_sistema()
|
_os = _obtener_datos_sistema()
|
||||||
return _os
|
return _os
|
||||||
|
|
||||||
|
|
||||||
def accion_placeholder(nombre_accion):
|
|
||||||
"""
|
|
||||||
Función placeholder temporal para acciones que aún no tienen implementación.
|
|
||||||
Simplemente imprime un mensaje en la consola.
|
|
||||||
"""
|
|
||||||
print(f"Acción pendiente de implementación: {nombre_accion}")
|
|
||||||
|
|
||||||
def _obtener_datos_sistema():
|
def _obtener_datos_sistema():
|
||||||
"""
|
"""Lógica para detectar el sistema operativo."""
|
||||||
Función placeholder para la tarea T1.3 (recursos del sistema).
|
|
||||||
Esta función se llenará con la lógica para obtener datos de CPU/RAM.
|
|
||||||
"""
|
|
||||||
print("Iniciando la recopilación de datos del sistema...")
|
|
||||||
# Lógica a añadir aquí en el futuro (usando psutil, por ejemplo)
|
|
||||||
tmpVar = platform.system().lower()
|
tmpVar = platform.system().lower()
|
||||||
if tmpVar.__contains__("windows"):
|
|
||||||
|
if "windows" in tmpVar:
|
||||||
print("Sistema operativo detectado: Windows")
|
print("Sistema operativo detectado: Windows")
|
||||||
return {'WINDOWS'}
|
return {'WINDOWS'}
|
||||||
elif tmpVar.__contains__("darwin"):
|
elif "darwin" in tmpVar:
|
||||||
print("Sistema operativo detectado: MacOS")
|
print("Sistema operativo detectado: MacOS")
|
||||||
return {'MACOS'}
|
return {'MACOS'}
|
||||||
else:
|
else:
|
||||||
print("Sistema operativo detectado: Linux/Unix")
|
print("Sistema operativo detectado: Linux/Unix")
|
||||||
return {'LINUX'}
|
return {'LINUX'}
|
||||||
|
|
||||||
|
# --- Función Placeholder ---
|
||||||
|
|
||||||
|
def accion_placeholder(nombre_accion):
|
||||||
|
"""
|
||||||
|
Función placeholder temporal para acciones que aún no tienen implementación.
|
||||||
|
"""
|
||||||
|
print(f"Acción pendiente de implementación: {nombre_accion}")
|
||||||
|
|
||||||
|
# Nota: La lógica de 'subprocess.run' se encuentra ahora en logica/T1/backup.py.
|
||||||
|
|
@ -1,5 +1,55 @@
|
||||||
out-null
|
out-null
|
||||||
cls
|
cls
|
||||||
|
|
||||||
$saludo='Hola Usuario'
|
# SCRIPT: backup_script.ps1
|
||||||
Write-Host $saludo
|
|
||||||
|
# --- CONFIGURACIÓN DE RUTAS ---
|
||||||
|
# La carpeta de ORIGEN es la carpeta 'Pictures' (Imágenes) del usuario
|
||||||
|
$SourceFolder = Join-Path $env:USERPROFILE "Pictures"
|
||||||
|
|
||||||
|
# La carpeta base de DESTINO es 'BACKUPS' dentro del directorio raíz del usuario
|
||||||
|
$DestinationBasePath = Join-Path $env:USERPROFILE "BACKUPS"
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
# 1. Verificar que la carpeta de origen exista
|
||||||
|
if (-not (Test-Path $SourceFolder)) {
|
||||||
|
Write-Error "ERROR: La carpeta de origen '$SourceFolder' (Imágenes) no existe."
|
||||||
|
exit 1 # Código de error
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. Generar el nombre de la carpeta con la fecha y hora (Formato: backup-AAAA-MM-DD_HH-MM-SS)
|
||||||
|
$Timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
|
||||||
|
$BackupFolderName = "backup-$Timestamp"
|
||||||
|
|
||||||
|
# 3. Crear la ruta final de destino
|
||||||
|
$FinalDestinationPath = Join-Path $DestinationBasePath $BackupFolderName
|
||||||
|
|
||||||
|
# 4. Crear la carpeta base 'BACKUPS' si no existe y luego la carpeta de la copia
|
||||||
|
try {
|
||||||
|
# New-Item -ItemType Directory -Path $FinalDestinationPath -Force:
|
||||||
|
# Crea la estructura completa ($DestinationBasePath/backup-...)
|
||||||
|
Write-Host "Creando carpeta de destino: $FinalDestinationPath"
|
||||||
|
$null = New-Item -ItemType Directory -Path $FinalDestinationPath -Force
|
||||||
|
} catch {
|
||||||
|
Write-Error "ERROR: No se pudo crear la carpeta de destino: $($_.Exception.Message)"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 5. Ejecutar la copia recursiva (Copiando el *contenido* de la carpeta de Imágenes)
|
||||||
|
Write-Host "Iniciando copia de seguridad de '$SourceFolder' a '$FinalDestinationPath'..."
|
||||||
|
try {
|
||||||
|
# Copia todo el contenido de $SourceFolder (el asterisco es importante)
|
||||||
|
Copy-Item -Path "$SourceFolder\*" -Destination $FinalDestinationPath -Recurse -Force
|
||||||
|
|
||||||
|
# 6. Mensaje de finalización
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "✅ Copia de seguridad completada con éxito."
|
||||||
|
Write-Host " Origen: $SourceFolder"
|
||||||
|
Write-Host " Destino: $FinalDestinationPath"
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
Write-Error "ERROR: La copia de seguridad falló durante la copia de archivos: $($_.Exception.Message)"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
exit 0 # Código de éxito
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# SCRIPT: res/scripts/sript.sh
|
||||||
|
|
||||||
|
# --- CONFIGURACIÓN DE RUTAS ---
|
||||||
|
USER_HOME=~
|
||||||
|
# CAMBIO CLAVE: Usamos 'Imágenes' en lugar de 'Pictures'
|
||||||
|
SOURCE_FOLDER="$USER_HOME/Imágenes"
|
||||||
|
DESTINATION_BASE_PATH="$USER_HOME/BACKUPS"
|
||||||
|
# ------------------------------
|
||||||
|
|
||||||
|
# 1. Verificar que la carpeta de o2rigen exista
|
||||||
|
if [ ! -d "$SOURCE_FOLDER" ]; then
|
||||||
|
echo "ERROR: La carpeta de origen '$SOURCE_FOLDER' no existe." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Generar el nombre de la carpeta con la fecha y hora (Formato: backup-AAAA-MM-DD_HH-MM-SS)
|
||||||
|
TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S)
|
||||||
|
BACKUP_FOLDER_NAME="backup-$TIMESTAMP"
|
||||||
|
|
||||||
|
# 3. Crear la ruta final de destino
|
||||||
|
FINAL_DESTINATION_PATH="$DESTINATION_BASE_PATH/$BACKUP_FOLDER_NAME"
|
||||||
|
|
||||||
|
# 4. Crear la carpeta base 'BACKUPS' si no existe y luego la carpeta de la copia
|
||||||
|
mkdir -p "$FINAL_DESTINATION_PATH"
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "ERROR: No se pudo crear la carpeta de destino: $FINAL_DESTINATION_PATH" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. Ejecutar la copia recursiva con rsync
|
||||||
|
echo "Iniciando copia de seguridad de '$SOURCE_FOLDER' a '$FINAL_DESTINATION_PATH'..."
|
||||||
|
# -a: modo archivo (preserva permisos, dueño, timestamps, etc.)
|
||||||
|
# -v: modo verbose (muestra progreso/qué está copiando)
|
||||||
|
# -z: comprime los datos durante la transferencia (útil si copias a red, pero no hace daño aquí)
|
||||||
|
# --exclude: ignora la carpeta de destino si por alguna razón ya existe dentro del origen (seguridad)
|
||||||
|
rsync -avz --exclude 'BACKUPS' "$SOURCE_FOLDER/" "$FINAL_DESTINATION_PATH/"
|
||||||
|
|
||||||
|
# $?: guarda el código de salida del último comando ejecutado (rsync)
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "✅ Copia de seguridad completada con éxito."
|
||||||
|
echo " Origen: $SOURCE_FOLDER"
|
||||||
|
echo " Destino: $FINAL_DESTINATION_PATH"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "ERROR: La copia de seguridad falló durante rsync." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
echo "Hello, World!"
|
|
||||||
|
|
@ -1,13 +1,22 @@
|
||||||
|
# Módulo: vista/panel_central.py
|
||||||
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from logica.T1.geterSystemRecource import obtener_datos_cpu_ram
|
||||||
|
from logica.T1.graficos import crear_grafico_recursos
|
||||||
|
from logica.controlador import accion_placeholder
|
||||||
|
|
||||||
|
|
||||||
class PanelCentral(ttk.Frame):
|
class PanelCentral(ttk.Frame):
|
||||||
"""Contiene el Notebook (subpestañas de T1), el panel de Notas y el panel de Chat."""
|
"""Contiene el Notebook (subpestañas de T1), el panel de Notas y el panel de Chat."""
|
||||||
|
|
||||||
|
# Definimos el intervalo de actualización en milisegundos (5000 ms = 5 segundos)
|
||||||
|
INTERVALO_ACTUALIZACION_MS = 5000
|
||||||
|
|
||||||
def __init__(self, parent, *args, **kwargs):
|
def __init__(self, parent, *args, **kwargs):
|
||||||
super().__init__(parent, *args, **kwargs)
|
super().__init__(parent, *args, **kwargs)
|
||||||
# Columna 0: Área Principal (3/4) | Columna 1: Chat (1/4)
|
self.after_id = None # ID para controlar el timer de tk.after
|
||||||
|
|
||||||
self.grid_columnconfigure(0, weight=3)
|
self.grid_columnconfigure(0, weight=3)
|
||||||
self.grid_columnconfigure(1, weight=1)
|
self.grid_columnconfigure(1, weight=1)
|
||||||
self.grid_rowconfigure(0, weight=1)
|
self.grid_rowconfigure(0, weight=1)
|
||||||
|
|
@ -15,12 +24,13 @@ class PanelCentral(ttk.Frame):
|
||||||
self.crear_area_principal_y_notas()
|
self.crear_area_principal_y_notas()
|
||||||
self.crear_panel_chat_y_alumnos()
|
self.crear_panel_chat_y_alumnos()
|
||||||
|
|
||||||
|
self.iniciar_actualizacion_automatica()
|
||||||
|
|
||||||
def crear_area_principal_y_notas(self):
|
def crear_area_principal_y_notas(self):
|
||||||
"""Crea el contenedor de las subpestañas de T1 y el panel de notas (inferior izquierda)."""
|
"""Crea el contenedor de las subpestañas de T1 y el panel de notas (inferior izquierda)."""
|
||||||
frame_izquierdo = ttk.Frame(self, style='TFrame')
|
frame_izquierdo = ttk.Frame(self, style='TFrame')
|
||||||
frame_izquierdo.grid(row=0, column=0, sticky="nsew")
|
frame_izquierdo.grid(row=0, column=0, sticky="nsew")
|
||||||
|
|
||||||
# Fila 0: Subpestañas (4 partes) | Fila 1: Notas (1 parte)
|
|
||||||
frame_izquierdo.grid_rowconfigure(0, weight=4)
|
frame_izquierdo.grid_rowconfigure(0, weight=4)
|
||||||
frame_izquierdo.grid_rowconfigure(1, weight=1)
|
frame_izquierdo.grid_rowconfigure(1, weight=1)
|
||||||
frame_izquierdo.grid_columnconfigure(0, weight=1)
|
frame_izquierdo.grid_columnconfigure(0, weight=1)
|
||||||
|
|
@ -28,51 +38,98 @@ class PanelCentral(ttk.Frame):
|
||||||
# 1. El Notebook de T1 (Sub-pestañas)
|
# 1. El Notebook de T1 (Sub-pestañas)
|
||||||
self.crear_sub_pestañas_t1(frame_izquierdo)
|
self.crear_sub_pestañas_t1(frame_izquierdo)
|
||||||
|
|
||||||
# 2. El Panel de Notas (área verde clara inferior izquierda)
|
# 2. El Panel de Notas
|
||||||
panel_notas = ttk.Frame(frame_izquierdo, style='Note.TFrame')
|
panel_notas = ttk.Frame(frame_izquierdo, style='Note.TFrame')
|
||||||
panel_notas.grid(row=1, column=0, sticky="nsew", pady=(5, 0))
|
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=('Arial', 9, 'italic')).pack(expand=True, fill="both")
|
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=('Arial', 9, 'italic')).pack(
|
||||||
|
expand=True, fill="both")
|
||||||
|
|
||||||
def crear_sub_pestañas_t1(self, parent_frame):
|
def crear_sub_pestañas_t1(self, parent_frame):
|
||||||
"""Crea las pestañas internas para la tarea T1 y las empaqueta en la rejilla."""
|
"""Crea las pestañas internas para la tarea T1 y las empaqueta en la rejilla."""
|
||||||
sub_notebook = ttk.Notebook(parent_frame)
|
sub_notebook = ttk.Notebook(parent_frame)
|
||||||
sub_notebook.grid(row=0, column=0, sticky="nsew") # Ocupa Fila 0, Columna 0
|
sub_notebook.grid(row=0, column=0, sticky="nsew")
|
||||||
|
|
||||||
|
sub_tabs = ["Recursos", "Resultados", "Navegador", "Correos", "Tareas", "Alarmas", "Enlaces"]
|
||||||
|
self.tabs = {}
|
||||||
|
|
||||||
sub_tabs = ["Resultados", "Navegador", "Correos", "Tareas", "Alarmas", "Enlaces"]
|
|
||||||
for i, sub_tab_text in enumerate(sub_tabs):
|
for i, sub_tab_text in enumerate(sub_tabs):
|
||||||
frame = ttk.Frame(sub_notebook, style='TFrame')
|
frame = ttk.Frame(sub_notebook, style='TFrame')
|
||||||
sub_notebook.add(frame, text=sub_tab_text)
|
sub_notebook.add(frame, text=sub_tab_text)
|
||||||
|
self.tabs[sub_tab_text] = frame
|
||||||
|
|
||||||
|
# LÓGICA DE LA PESTAÑA DE RECURSOS
|
||||||
|
if sub_tab_text == "Recursos":
|
||||||
|
self.grafico_frame = ttk.Frame(frame, style='TFrame')
|
||||||
|
self.grafico_frame.pack(expand=True, fill="both", padx=10, pady=10)
|
||||||
|
|
||||||
|
# Llamada inicial, la auto-actualización tomará el control
|
||||||
|
self.actualizar_grafico_recursos()
|
||||||
|
|
||||||
# Contenido del área central (simulando el panel rayado)
|
|
||||||
if sub_tab_text == "Navegador":
|
|
||||||
# Usamos un widget Text con fondo blanco y un borde sutil para que destaque
|
|
||||||
contenido_area = tk.Text(frame, wrap="word", padx=15, pady=15,bg='white', relief="groove", borderwidth=1,font=('Consolas', 10), foreground="#555555")
|
|
||||||
contenido_area.insert(tk.END,">>> ÁREA DE CONTENIDO / VISOR DE NAVEGADOR (Para mostrar resultados o web scraping)\n\n""Este es el espacio dedicado a la visualización de datos o interfaces específicas de cada tarea.")
|
|
||||||
contenido_area.pack(expand=True, fill="both", padx=5, pady=5)
|
|
||||||
sub_notebook.select(i)
|
sub_notebook.select(i)
|
||||||
|
|
||||||
def crear_panel_chat_y_alumnos(self):
|
# Contenido de otras pestañas
|
||||||
|
elif sub_tab_text == "Navegador":
|
||||||
|
contenido_area = tk.Text(frame, wrap="word", padx=15, pady=15, bg='white', relief="groove",
|
||||||
|
borderwidth=1, font=('Consolas', 10), foreground="#555555")
|
||||||
|
contenido_area.insert(tk.END,
|
||||||
|
">>> ÁREA DE CONTENIDO / VISOR DE NAVEGADOR (Para mostrar resultados o web scraping)\n\n""Este es el espacio dedicado a la visualización de datos o interfaces específicas de cada tarea.")
|
||||||
|
contenido_area.pack(expand=True, fill="both", padx=5, pady=5)
|
||||||
|
|
||||||
|
def actualizar_grafico_recursos(self):
|
||||||
|
"""Obtiene los datos del sistema y dibuja/redibuja el gráfico."""
|
||||||
|
try:
|
||||||
|
datos_sistema = obtener_datos_cpu_ram()
|
||||||
|
crear_grafico_recursos(self.grafico_frame, datos_sistema)
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Error al generar el gráfico de recursos: {e}"
|
||||||
|
print(error_msg)
|
||||||
|
for widget in self.grafico_frame.winfo_children():
|
||||||
|
widget.destroy()
|
||||||
|
ttk.Label(self.grafico_frame, text=error_msg, foreground='red', style='TLabel').pack(pady=20)
|
||||||
|
|
||||||
|
def iniciar_actualizacion_automatica(self):
|
||||||
|
"""
|
||||||
|
Programa la actualización periódica del gráfico de recursos usando tk.after,
|
||||||
|
y almacena el ID para poder cancelarlo.
|
||||||
|
"""
|
||||||
|
# 1. Ejecuta la actualización
|
||||||
|
self.actualizar_grafico_recursos()
|
||||||
|
|
||||||
|
# 2. Programa la siguiente llamada y almacena el ID
|
||||||
|
self.after_id = self.after(self.INTERVALO_ACTUALIZACION_MS, self.iniciar_actualizacion_automatica)
|
||||||
|
|
||||||
|
def detener_actualizacion_automatica(self):
|
||||||
|
"""Detiene el ciclo de actualización periódica del gráfico."""
|
||||||
|
if self.after_id:
|
||||||
|
self.after_cancel(self.after_id)
|
||||||
|
print("Ciclo de actualización de gráficos detenido.")
|
||||||
|
|
||||||
|
def crear_panel_chat_y_alumnos(self, ):
|
||||||
"""Crea el panel de chat, lista de Alumnos y Reproductor de Música (columna derecha)."""
|
"""Crea el panel de chat, lista de Alumnos y Reproductor de Música (columna derecha)."""
|
||||||
panel_chat = ttk.Frame(self, style='TFrame', padding="10")
|
panel_chat = ttk.Frame(self, style='TFrame', padding="10")
|
||||||
panel_chat.grid(row=0, column=1, sticky="nsew")
|
panel_chat.grid(row=0, column=1, sticky="nsew")
|
||||||
|
|
||||||
# Configuración interna del panel de chat
|
|
||||||
panel_chat.grid_rowconfigure(5, weight=1)
|
panel_chat.grid_rowconfigure(5, weight=1)
|
||||||
panel_chat.grid_rowconfigure(7, weight=0)
|
panel_chat.grid_rowconfigure(7, weight=0)
|
||||||
panel_chat.grid_columnconfigure(0, weight=1)
|
panel_chat.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
# 1. Título "Chat"
|
# 1. Título "Chat"
|
||||||
ttk.Label(panel_chat, text="Chat", foreground="#0078d4", font=("Arial", 18, "bold"), style='TLabel').grid(row=0,column=0,pady=(0,10),sticky="w")
|
ttk.Label(panel_chat, text="Chat", foreground="#0078d4", font=("Arial", 18, "bold"), style='TLabel').grid(row=0,
|
||||||
|
column=0,
|
||||||
|
pady=(
|
||||||
|
0,
|
||||||
|
10),
|
||||||
|
sticky="w")
|
||||||
|
|
||||||
# 2. Área de Mensaje
|
# 2. Área de Mensaje
|
||||||
ttk.Label(panel_chat, text="Mensaje", style='TLabel').grid(row=1, column=0, sticky="w")
|
ttk.Label(panel_chat, text="Mensaje", style='TLabel').grid(row=1, column=0, sticky="w")
|
||||||
|
|
||||||
# Área de texto para el mensaje (el recuadro amarillo)
|
chat_text = tk.Text(panel_chat, height=6, width=30, bg='#fff8e1', relief="solid", borderwidth=1,
|
||||||
chat_text = tk.Text(panel_chat, height=6, width=30, bg='#fff8e1', relief="solid", borderwidth=1,font=('Arial', 10))
|
font=('Arial', 10))
|
||||||
chat_text.grid(row=2, column=0, sticky="ew", pady=(0, 5))
|
chat_text.grid(row=2, column=0, sticky="ew", pady=(0, 5))
|
||||||
|
|
||||||
# Botón Enviar
|
|
||||||
ttk.Button(panel_chat, text="Enviar", style='Action.TButton').grid(row=3, column=0, pady=(0, 15), sticky="e")
|
ttk.Button(panel_chat, text="Enviar", style='Action.TButton').grid(row=3, column=0, pady=(0, 15), sticky="e")
|
||||||
|
|
||||||
# 3. Lista de Alumnos (Simulación)
|
# 3. Lista de Alumnos (Simulación)
|
||||||
|
|
@ -81,13 +138,17 @@ class PanelCentral(ttk.Frame):
|
||||||
frame_alumno.grid(row=3 + i, column=0, sticky="ew", pady=5)
|
frame_alumno.grid(row=3 + i, column=0, sticky="ew", pady=5)
|
||||||
frame_alumno.grid_columnconfigure(0, weight=1)
|
frame_alumno.grid_columnconfigure(0, weight=1)
|
||||||
|
|
||||||
ttk.Label(frame_alumno, text=f"Alumno {i}", font=("Arial", 11, "bold"), style='Alumno.TLabel').grid(row=0,column=0,sticky="w")
|
ttk.Label(frame_alumno, text=f"Alumno {i}", font=("Arial", 11, "bold"), style='Alumno.TLabel').grid(row=0,
|
||||||
ttk.Label(frame_alumno, text="Lorem ipsum dolor sit amet, consectetur adipiscing elit.",wraplength=250, justify=tk.LEFT, style='Alumno.TLabel').grid(row=1, column=0, sticky="w")
|
column=0,
|
||||||
|
sticky="w")
|
||||||
|
ttk.Label(frame_alumno, text="Lorem ipsum dolor sit amet, consectetur adipiscing elit.", wraplength=250,
|
||||||
|
justify=tk.LEFT, style='Alumno.TLabel').grid(row=1, column=0, sticky="w")
|
||||||
|
|
||||||
# Botón de Recargar/Actualizar
|
ttk.Button(frame_alumno, text="↻", width=3, style='Action.TButton').grid(row=0, column=1, rowspan=2, padx=5,
|
||||||
ttk.Button(frame_alumno, text="↻", width=3, style='Action.TButton').grid(row=0, column=1, rowspan=2, padx=5,sticky="ne")
|
sticky="ne")
|
||||||
|
|
||||||
# 4. Reproductor de Música (Simulado)
|
# 4. Reproductor de Música (Simulado)
|
||||||
musica_frame = ttk.LabelFrame(panel_chat, text="Reproductor Música", padding=10, style='TFrame')
|
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))
|
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)
|
ttk.Label(musica_frame, text="[ Botones de Play/Stop y Control de Volumen ]", anchor="center",
|
||||||
|
style='TLabel').pack(fill="x", padx=5, pady=5)
|
||||||
|
|
@ -1,27 +1,30 @@
|
||||||
|
# Módulo: vista/panel_lateral.py
|
||||||
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
# Importación de la lógica específica para abrir VS Code
|
from tkinter import messagebox
|
||||||
|
from logica.controlador import accion_placeholder, getPlataforma
|
||||||
|
from logica.T1.backup import accion_backup_t1
|
||||||
from logica.T1.runVScode import abrir_vscode
|
from logica.T1.runVScode import abrir_vscode
|
||||||
# Importación de las acciones generales (placeholders y futuras funciones)
|
|
||||||
from logica.controlador import accion_placeholder, obtener_datos_sistema
|
|
||||||
|
|
||||||
|
|
||||||
class PanelLateral(ttk.Frame):
|
class PanelLateral(ttk.Frame):
|
||||||
"""Contiene el menú de botones y entradas para las tareas de T1."""
|
"""Contiene el menú de botones y entradas para las tareas de T1."""
|
||||||
|
|
||||||
def __init__(self, parent, *args, **kwargs):
|
def __init__(self, parent, central_panel=None, *args, **kwargs):
|
||||||
super().__init__(parent, *args, **kwargs)
|
super().__init__(parent, *args, **kwargs)
|
||||||
self.pack(fill="y", padx=5, pady=5)
|
self.central_panel = central_panel # Guardamos la referencia
|
||||||
|
|
||||||
self.configurar_estilos_locales(parent) # Asegura que los estilos funcionen
|
# ❌ ELIMINAR ESTA LÍNEA: self.pack(fill="y", padx=5, pady=5)
|
||||||
|
|
||||||
|
self.configurar_estilos_locales(parent)
|
||||||
|
|
||||||
# Entrada superior (amarilla)
|
# Entrada superior (amarilla)
|
||||||
ttk.Entry(self, width=25, style='Yellow.TEntry').pack(fill="x", pady=10, padx=5, ipady=3)
|
ttk.Entry(self, width=25, style='Yellow.TEntry').pack(fill="x", pady=10, padx=5, ipady=3)
|
||||||
|
|
||||||
# 1. Área de Extracción/Navegación
|
# 1. Área de Extracción/Navegación
|
||||||
acciones_extraccion = [
|
acciones_extraccion = [
|
||||||
# T1.3 - Conectamos el botón de extracción a la función de obtención de datos del sistema
|
("Extraer datos", self.manejar_extraccion_datos),
|
||||||
("Extraer datos", lambda: obtener_datos_sistema()),
|
|
||||||
("Navegar", lambda: accion_placeholder("Navegar")),
|
("Navegar", lambda: accion_placeholder("Navegar")),
|
||||||
("Buscar API Google", lambda: accion_placeholder("Buscar API Google"))
|
("Buscar API Google", lambda: accion_placeholder("Buscar API Google"))
|
||||||
]
|
]
|
||||||
|
|
@ -29,7 +32,6 @@ class PanelLateral(ttk.Frame):
|
||||||
|
|
||||||
# 2. Área de Aplicaciones
|
# 2. Área de Aplicaciones
|
||||||
acciones_aplicaciones = [
|
acciones_aplicaciones = [
|
||||||
# CONEXIÓN: Usa la función específica abrir_vscode
|
|
||||||
("Visual Code", abrir_vscode),
|
("Visual Code", abrir_vscode),
|
||||||
("App2", lambda: accion_placeholder("App2")),
|
("App2", lambda: accion_placeholder("App2")),
|
||||||
("App3", lambda: accion_placeholder("App3"))
|
("App3", lambda: accion_placeholder("App3"))
|
||||||
|
|
@ -38,45 +40,49 @@ class PanelLateral(ttk.Frame):
|
||||||
|
|
||||||
# 3. Área de Procesos Batch
|
# 3. Área de Procesos Batch
|
||||||
acciones_batch = [
|
acciones_batch = [
|
||||||
("Copias de seguridad", lambda: accion_placeholder("Copias de seguridad"))
|
("Copias de seguridad", self.manejar_backup)
|
||||||
]
|
]
|
||||||
self.crear_seccion(self, titulo="Procesos batch", acciones=acciones_batch)
|
self.crear_seccion(self, titulo="Procesos batch", acciones=acciones_batch)
|
||||||
|
|
||||||
# Espaciador para empujar los elementos inferiores si los hubiera
|
|
||||||
# CORRECCIÓN: Eliminamos el argumento 'bg' problemático y permitimos la herencia de color.
|
|
||||||
tk.Frame(self, height=1).pack(expand=True, fill="both")
|
tk.Frame(self, height=1).pack(expand=True, fill="both")
|
||||||
|
|
||||||
def configurar_estilos_locales(self, parent):
|
def manejar_extraccion_datos(self):
|
||||||
"""Configura estilos que deberían estar en la ventana principal, o crea placeholders."""
|
"""
|
||||||
style = ttk.Style(parent)
|
Llama a la lógica de actualización del gráfico de recursos
|
||||||
# Estilo de la entrada amarilla
|
en el panel central (actualización manual).
|
||||||
style.configure('Yellow.TEntry',
|
"""
|
||||||
fieldbackground='#fff8e1',
|
if self.central_panel:
|
||||||
foreground='#333333',
|
print("Activando actualización del gráfico de Recursos (Manual)...")
|
||||||
padding=[5, 5],
|
self.central_panel.actualizar_grafico_recursos()
|
||||||
relief='solid',
|
else:
|
||||||
borderwidth=1)
|
messagebox.showerror("Error", "El Panel Central no está inicializado.")
|
||||||
|
|
||||||
# Estilo de los botones (Verde)
|
def manejar_backup(self):
|
||||||
style.configure('Green.TButton',
|
"""Llama a la lógica de backup de T1 e informa al usuario del resultado."""
|
||||||
background='#4CAF50',
|
print("Iniciando proceso de Copia de Seguridad...")
|
||||||
foreground='white',
|
success, message = accion_backup_t1()
|
||||||
font=('Arial', 10, 'bold'),
|
|
||||||
relief='flat',
|
if success:
|
||||||
padding=[10, 5])
|
messagebox.showinfo("Backup Completado", message)
|
||||||
|
else:
|
||||||
|
messagebox.showerror("Error en el Backup", message)
|
||||||
|
|
||||||
|
def configurar_estilos_locales(self, parent):
|
||||||
|
"""Configura estilos para los widgets del panel lateral."""
|
||||||
|
style = ttk.Style(parent)
|
||||||
|
style.configure('Yellow.TEntry', fieldbackground='#fff8e1', foreground='#333333', padding=[5, 5],
|
||||||
|
relief='solid', borderwidth=1)
|
||||||
|
style.configure('Green.TButton', background='#4CAF50', foreground='white', font=('Arial', 10, 'bold'),
|
||||||
|
relief='flat', padding=[10, 5])
|
||||||
style.map('Green.TButton', background=[('active', '#388E3C'), ('pressed', '#1B5E20')])
|
style.map('Green.TButton', background=[('active', '#388E3C'), ('pressed', '#1B5E20')])
|
||||||
|
|
||||||
def crear_seccion(self, parent_frame, titulo, acciones):
|
def crear_seccion(self, parent_frame, titulo, acciones):
|
||||||
"""
|
"""Función helper para crear secciones de etiquetas y botones."""
|
||||||
Función helper para crear secciones de etiquetas y botones.
|
|
||||||
'acciones' es una lista de tuplas: (texto_boton, comando_a_ejecutar)
|
|
||||||
"""
|
|
||||||
if titulo:
|
if titulo:
|
||||||
ttk.Label(parent_frame, text=titulo, font=('Arial', 11, 'bold')).pack(fill="x", pady=(10, 0), padx=5)
|
ttk.Label(parent_frame, text=titulo, font=('Arial', 11, 'bold')).pack(fill="x", pady=(10, 0), padx=5)
|
||||||
|
|
||||||
frame_botones = ttk.Frame(parent_frame, style='TFrame') # Usando TFrame para el contenedor de botones
|
frame_botones = ttk.Frame(parent_frame, style='TFrame')
|
||||||
frame_botones.pack(fill="x", pady=5, padx=5)
|
frame_botones.pack(fill="x", pady=5, padx=5)
|
||||||
|
|
||||||
for texto_boton, comando in acciones:
|
for texto_boton, comando in acciones:
|
||||||
# Conexión del botón:
|
|
||||||
ttk.Button(frame_botones, text=texto_boton, command=comando, style='Green.TButton').pack(fill="x", pady=5)
|
ttk.Button(frame_botones, text=texto_boton, command=comando, style='Green.TButton').pack(fill="x", pady=5)
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
# Módulo: ventana_principal.py
|
||||||
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from vista.panel_lateral import PanelLateral
|
from vista.panel_lateral import PanelLateral
|
||||||
|
|
@ -12,69 +14,78 @@ class VentanaPrincipal(tk.Tk):
|
||||||
self.title("Proyecto Integrado - PSP (Estilo Moderno Nativo)")
|
self.title("Proyecto Integrado - PSP (Estilo Moderno Nativo)")
|
||||||
self.geometry("1200x800")
|
self.geometry("1200x800")
|
||||||
|
|
||||||
# 1. Usar el tema 'clam' para un aspecto más plano y moderno
|
|
||||||
style = ttk.Style()
|
style = ttk.Style()
|
||||||
style.theme_use('clam')
|
style.theme_use('clam')
|
||||||
|
|
||||||
self.config(bg="#f9f9f9") # Fondo global muy claro
|
self.config(bg="#f9f9f9")
|
||||||
|
|
||||||
self.configurar_estilos(style)
|
self.configurar_estilos(style)
|
||||||
|
|
||||||
# Configuración de la rejilla principal de la ventana
|
# Configuración del manejador de protocolo para el botón de cierre (X)
|
||||||
|
self.protocol("WM_DELETE_WINDOW", self.on_closing)
|
||||||
|
|
||||||
self.grid_rowconfigure(0, weight=1)
|
self.grid_rowconfigure(0, weight=1)
|
||||||
self.grid_rowconfigure(1, weight=0)
|
self.grid_rowconfigure(1, weight=0)
|
||||||
self.grid_columnconfigure(0, weight=0) # Panel lateral es de ancho fijo
|
self.grid_columnconfigure(0, weight=0)
|
||||||
self.grid_columnconfigure(1, weight=1) # Panel central se expande
|
self.grid_columnconfigure(1, weight=1)
|
||||||
|
|
||||||
self.crear_paneles_principales()
|
self.crear_paneles_principales()
|
||||||
self.crear_barra_inferior()
|
self.crear_barra_inferior()
|
||||||
|
|
||||||
|
# ... (código de configurar_estilos) ...
|
||||||
def configurar_estilos(self, s: ttk.Style):
|
def configurar_estilos(self, s: ttk.Style):
|
||||||
"""Define estilos visuales personalizados sin dependencias externas."""
|
"""Define estilos visuales personalizados sin dependencias externas."""
|
||||||
|
|
||||||
# Paleta de Colores
|
|
||||||
COLOR_FONDO = "#f9f9f9"
|
COLOR_FONDO = "#f9f9f9"
|
||||||
COLOR_ACCION = "#0078d4" # Azul principal
|
COLOR_ACCION = "#0078d4"
|
||||||
COLOR_EXITO = "#4CAF50" # Verde para éxito/notas
|
COLOR_EXITO = "#4CAF50"
|
||||||
COLOR_ADVERTENCIA = "#ffc107" # Amarillo para chat
|
COLOR_ADVERTENCIA = "#ffc107"
|
||||||
COLOR_TEXTO = "#333333"
|
COLOR_TEXTO = "#333333"
|
||||||
|
|
||||||
# TFrame y TLabel base (fondo claro)
|
|
||||||
s.configure('TFrame', background=COLOR_FONDO)
|
s.configure('TFrame', background=COLOR_FONDO)
|
||||||
s.configure('TLabel', background=COLOR_FONDO, foreground=COLOR_TEXTO, font=('Arial', 9))
|
s.configure('TLabel', background=COLOR_FONDO, foreground=COLOR_TEXTO, font=('Arial', 9))
|
||||||
|
|
||||||
# Botones de Acción (Simulando el color azul/verde de tu diseño)
|
|
||||||
s.configure('Action.TButton', background=COLOR_ACCION, foreground='white', font=('Arial', 10, 'bold'),
|
s.configure('Action.TButton', background=COLOR_ACCION, foreground='white', font=('Arial', 10, 'bold'),
|
||||||
relief='flat', padding=[10, 5])
|
relief='flat', padding=[10, 5])
|
||||||
s.map('Action.TButton', background=[('active', '#005a9e'), ('pressed', '#003c6e')])
|
s.map('Action.TButton', background=[('active', '#005a9e'), ('pressed', '#003c6e')])
|
||||||
|
|
||||||
# Estilo para el área de notas (simulando el recuadro verde claro)
|
|
||||||
s.configure('Note.TFrame', background=COLOR_EXITO, borderwidth=0, relief="solid")
|
s.configure('Note.TFrame', background=COLOR_EXITO, borderwidth=0, relief="solid")
|
||||||
s.configure('Note.TLabel', background=COLOR_EXITO, foreground='white', font=('Arial', 9, 'italic'))
|
s.configure('Note.TLabel', background=COLOR_EXITO, foreground='white', font=('Arial', 9, 'italic'))
|
||||||
|
|
||||||
# Estilo para el área de chat/entrada (simulando el recuadro amarillo)
|
|
||||||
s.configure('Chat.TFrame', background=COLOR_ADVERTENCIA)
|
s.configure('Chat.TFrame', background=COLOR_ADVERTENCIA)
|
||||||
s.configure('Chat.TLabel', background=COLOR_ADVERTENCIA)
|
s.configure('Chat.TLabel', background=COLOR_ADVERTENCIA)
|
||||||
|
|
||||||
# Estilo para los recuadros de Alumnos (fondo blanco con borde)
|
|
||||||
s.configure('Alumno.TFrame', background='white', borderwidth=1, relief='solid')
|
s.configure('Alumno.TFrame', background='white', borderwidth=1, relief='solid')
|
||||||
s.configure('Alumno.TLabel', background='white', foreground=COLOR_TEXTO)
|
s.configure('Alumno.TLabel', background='white', foreground=COLOR_TEXTO)
|
||||||
|
|
||||||
# Configuración de las pestañas (Notebook interno - subpestañas)
|
|
||||||
s.configure('TNotebook.Tab', padding=[10, 5], font=('Arial', 10, 'bold'))
|
s.configure('TNotebook.Tab', padding=[10, 5], font=('Arial', 10, 'bold'))
|
||||||
s.map('TNotebook.Tab', background=[('selected', COLOR_FONDO)], foreground=[('selected', COLOR_ACCION)])
|
s.map('TNotebook.Tab', background=[('selected', COLOR_FONDO)], foreground=[('selected', COLOR_ACCION)])
|
||||||
|
|
||||||
def crear_paneles_principales(self):
|
def crear_paneles_principales(self):
|
||||||
"""Ensambla el panel lateral y el panel central en la rejilla."""
|
"""Ensambla el panel lateral y el panel central en la rejilla."""
|
||||||
|
|
||||||
# Panel Lateral (columna 0)
|
# Panel Central (debe crearse primero)
|
||||||
self.panel_lateral = PanelLateral(self)
|
|
||||||
self.panel_lateral.grid(row=0, column=0, sticky="nswe", padx=(10, 5), pady=10)
|
|
||||||
|
|
||||||
# Panel Central (columna 1)
|
|
||||||
self.panel_central = PanelCentral(self)
|
self.panel_central = PanelCentral(self)
|
||||||
self.panel_central.grid(row=0, column=1, sticky="nswe", padx=(5, 10), pady=10)
|
self.panel_central.grid(row=0, column=1, sticky="nswe", padx=(5, 10), pady=10)
|
||||||
|
|
||||||
|
# Panel Lateral (se le pasa la referencia del Central)
|
||||||
|
self.panel_lateral = PanelLateral(self, central_panel=self.panel_central)
|
||||||
|
self.panel_lateral.grid(row=0, column=0, sticky="nswe", padx=(10, 5), pady=10)
|
||||||
|
|
||||||
|
# --- FUNCIÓN DE CIERRE ---
|
||||||
|
def on_closing(self):
|
||||||
|
"""
|
||||||
|
Se ejecuta cuando se presiona el botón 'X'. Detiene el ciclo de actualización
|
||||||
|
y cierra la ventana principal de forma limpia.
|
||||||
|
"""
|
||||||
|
if self.panel_central:
|
||||||
|
# Llamar al método de limpieza del Panel Central
|
||||||
|
self.panel_central.detener_actualizacion_automatica()
|
||||||
|
|
||||||
|
# Destruir el objeto Tkinter y terminar mainloop
|
||||||
|
self.destroy()
|
||||||
|
print("Aplicación cerrada limpiamente.")
|
||||||
|
|
||||||
|
# ... (código de crear_barra_inferior) ...
|
||||||
def crear_barra_inferior(self):
|
def crear_barra_inferior(self):
|
||||||
"""Crea la barra de estado o información inferior."""
|
"""Crea la barra de estado o información inferior."""
|
||||||
frame_inferior = ttk.Frame(self, relief="flat", padding=[10, 5, 10, 5], style='TFrame', borderwidth=0)
|
frame_inferior = ttk.Frame(self, relief="flat", padding=[10, 5, 10, 5], style='TFrame', borderwidth=0)
|
||||||
|
|
@ -83,21 +94,16 @@ class VentanaPrincipal(tk.Tk):
|
||||||
frame_inferior.grid_columnconfigure(1, weight=1)
|
frame_inferior.grid_columnconfigure(1, weight=1)
|
||||||
frame_inferior.grid_columnconfigure(2, weight=1)
|
frame_inferior.grid_columnconfigure(2, weight=1)
|
||||||
|
|
||||||
# Elementos de la barra inferior (más discretos)
|
|
||||||
|
|
||||||
# Correos sin leer
|
|
||||||
frame_correo = ttk.Frame(frame_inferior, style='TFrame')
|
frame_correo = ttk.Frame(frame_inferior, style='TFrame')
|
||||||
frame_correo.grid(row=0, column=0, sticky="w")
|
frame_correo.grid(row=0, column=0, sticky="w")
|
||||||
ttk.Label(frame_correo, text="Correos sin leer: 0", style='TLabel').pack(side="left")
|
ttk.Label(frame_correo, text="Correos sin leer: 0", style='TLabel').pack(side="left")
|
||||||
ttk.Button(frame_correo, text="↻", width=3, style='Action.TButton').pack(side="left", padx=5)
|
ttk.Button(frame_correo, text="↻", width=3, style='Action.TButton').pack(side="left", padx=5)
|
||||||
|
|
||||||
# Temperatura local
|
|
||||||
frame_temp = ttk.Frame(frame_inferior, style='TFrame')
|
frame_temp = ttk.Frame(frame_inferior, style='TFrame')
|
||||||
frame_temp.grid(row=0, column=1)
|
frame_temp.grid(row=0, column=1)
|
||||||
ttk.Button(frame_temp, text="↻", width=3, style='Action.TButton').pack(side="left", padx=5)
|
ttk.Button(frame_temp, text="↻", width=3, style='Action.TButton').pack(side="left", padx=5)
|
||||||
ttk.Label(frame_temp, text="Temperatura local: --", style='TLabel').pack(side="left")
|
ttk.Label(frame_temp, text="Temperatura local: --", style='TLabel').pack(side="left")
|
||||||
|
|
||||||
# Fecha y Hora
|
|
||||||
frame_fecha = ttk.Frame(frame_inferior, style='TFrame')
|
frame_fecha = ttk.Frame(frame_inferior, style='TFrame')
|
||||||
frame_fecha.grid(row=0, column=2, sticky="e")
|
frame_fecha.grid(row=0, column=2, sticky="e")
|
||||||
ttk.Label(frame_fecha, text="Fecha Día y Hora: --/--/--", style='TLabel').pack(side="left")
|
ttk.Label(frame_fecha, text="Fecha Día y Hora: --/--/--", style='TLabel').pack(side="left")
|
||||||
Loading…
Reference in New Issue