add simple text editor for res/notes with load and save functionality

This commit is contained in:
BYolivia 2025-12-03 18:39:17 +01:00
parent 48ed65d91c
commit f9cfafe3fe
5 changed files with 166 additions and 20 deletions

View File

@ -16,14 +16,16 @@ python -m ProyectoGlobal
1. Lanzar aplicaciones externas con parámetros (por ejemplo navegadores externos con url) 1. Lanzar aplicaciones externas con parámetros (por ejemplo navegadores externos con url)
2. Copias de seguridad realizadas con scripts powershell (.ps1) 2. ~~Copias de seguridad realizadas con scripts powershell (.ps1)~~
3. Ver los recursos del sistema (memoria, procesador, hilos, etc.) utilizando gráficas (matplotlib) gráficos de barras, de áreas, líneas, etc. 3. ~~Ver los recursos del sistema (memoria, procesador, hilos, etc.) utilizando gráficas (matplotlib) gráficos de barras, de áreas, líneas, etc.~~
4. Editor de texto (estilo notepad). 4. Editor de texto (estilo notepad).
5. Hilo que cuente en kilobytes el tráfico de entrada y de salida de nuestra conexión de red. psutil.net_io_counters() 5. Hilo que cuente en kilobytes el tráfico de entrada y de salida de nuestra conexión de red. psutil.net_io_counters()
6. ~~Abrir VScode desde el programa~~
### T2. Multihilos ### ### T2. Multihilos ###
1. Hora del sistema / Fecha del sistema 1. Hora del sistema / Fecha del sistema

View File

@ -0,0 +1,48 @@
# Módulo: logica/T1/textEditor.py
import os
# Definimos la ruta del archivo de notas (asumiendo que res/ está un nivel arriba)
# Esto calcula la ruta absoluta: .../ProyectoGlobal/res/notes
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # logica/T1 -> logica -> ProyectoGlobal
ARCHIVO_NOTAS_RES = os.path.join(BASE_DIR, "res", "notes")
def cargar_contenido_res_notes():
"""
Carga el contenido del archivo 'res/notes'. Si no existe, lo crea y devuelve una cadena vacía.
"""
try:
if not os.path.exists(os.path.dirname(ARCHIVO_NOTAS_RES)):
os.makedirs(os.path.dirname(ARCHIVO_NOTAS_RES), exist_ok=True)
if not os.path.exists(ARCHIVO_NOTAS_RES):
with open(ARCHIVO_NOTAS_RES, 'w', encoding='utf-8') as f:
f.write("") # Crear vacío si no existe
return ""
with open(ARCHIVO_NOTAS_RES, 'r', encoding='utf-8') as f:
return f.read()
except Exception as e:
print(f"Error al cargar el archivo {ARCHIVO_NOTAS_RES}: {e}")
return f"Error al cargar: {e}"
def guardar_contenido_res_notes(contenido: str):
"""
Guarda el contenido proporcionado en el archivo 'res/notes'.
"""
try:
# Aseguramos que la carpeta exista antes de intentar escribir
os.makedirs(os.path.dirname(ARCHIVO_NOTAS_RES), exist_ok=True)
with open(ARCHIVO_NOTAS_RES, 'w', encoding='utf-8') as f:
f.write(contenido)
return True, f"Contenido guardado exitosamente en {ARCHIVO_NOTAS_RES}"
except Exception as e:
error_msg = f"Error al guardar en {ARCHIVO_NOTAS_RES}: {e}"
print(error_msg)
return False, error_msg

2
res/notes Normal file
View File

@ -0,0 +1,2 @@
'oijp;
hfgdhfgdh

View File

@ -6,23 +6,28 @@ from tkinter import messagebox
from logica.controlador import accion_placeholder, getPlataforma from logica.controlador import accion_placeholder, getPlataforma
from logica.T1.backup import accion_backup_t1 from logica.T1.backup import accion_backup_t1
from logica.T1.runVScode import abrir_vscode from logica.T1.runVScode import abrir_vscode
from logica.T1.textEditor import cargar_contenido_res_notes, guardar_contenido_res_notes
import os
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, entradas para las tareas y el editor simple para res/notes."""
# Definimos un ancho fijo en caracteres. Esto es crucial para que Tkinter
# no intente expandir el panel lateral más allá de lo deseado.
ANCHO_CARACTERES_FIJO = 35
def __init__(self, parent, central_panel=None, *args, **kwargs): def __init__(self, parent, central_panel=None, *args, **kwargs):
super().__init__(parent, *args, **kwargs) super().__init__(parent, *args, **kwargs)
self.central_panel = central_panel # Guardamos la referencia self.central_panel = central_panel
# ❌ ELIMINAR ESTA LÍNEA: self.pack(fill="y", padx=5, pady=5)
self.configurar_estilos_locales(parent) self.configurar_estilos_locales(parent)
# Entrada superior (amarilla) # 1. Entrada superior (amarilla) - Aplicamos el ancho fijo
ttk.Entry(self, width=25, style='Yellow.TEntry').pack(fill="x", pady=10, padx=5, ipady=3) ttk.Entry(self, width=self.ANCHO_CARACTERES_FIJO, style='Yellow.TEntry').pack(fill="x", pady=10, padx=5,
ipady=3)
# 1. Área de Extracción/Navegación # 2. Área de Extracción/Navegación
acciones_extraccion = [ acciones_extraccion = [
("Extraer datos", self.manejar_extraccion_datos), ("Extraer datos", self.manejar_extraccion_datos),
("Navegar", lambda: accion_placeholder("Navegar")), ("Navegar", lambda: accion_placeholder("Navegar")),
@ -30,7 +35,7 @@ class PanelLateral(ttk.Frame):
] ]
self.crear_seccion(self, titulo="", acciones=acciones_extraccion) self.crear_seccion(self, titulo="", acciones=acciones_extraccion)
# 2. Área de Aplicaciones # 3. Área de Aplicaciones
acciones_aplicaciones = [ acciones_aplicaciones = [
("Visual Code", abrir_vscode), ("Visual Code", abrir_vscode),
("App2", lambda: accion_placeholder("App2")), ("App2", lambda: accion_placeholder("App2")),
@ -38,14 +43,87 @@ class PanelLateral(ttk.Frame):
] ]
self.crear_seccion(self, titulo="Aplicaciones", acciones=acciones_aplicaciones) self.crear_seccion(self, titulo="Aplicaciones", acciones=acciones_aplicaciones)
# 3. Área de Procesos Batch # 4. Área de Procesos Batch
acciones_batch = [ acciones_batch = [
("Copias de seguridad", self.manejar_backup) ("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)
# 5. Espacio expandible (Empuja los elementos superiores hacia arriba)
tk.Frame(self, height=1).pack(expand=True, fill="both") tk.Frame(self, height=1).pack(expand=True, fill="both")
# 6. Panel de Notas (Editor res/notes, ubicado abajo)
self.crear_editor_res_notes()
# --- LÓGICA DEL EDITOR res/notes ---
def crear_editor_res_notes(self):
"""Crea el editor de texto simple para el archivo res/notes."""
ttk.Label(self, text="Editor Simple (res/notes)", font=('Arial', 11, 'bold')).pack(fill="x", pady=(10, 0),
padx=5)
frame_editor = ttk.Frame(self, padding=5)
frame_editor.pack(fill="x", padx=5, pady=(0, 10))
# 1. Widget de texto - Aplicamos el ancho fijo
self.notes_text_editor = tk.Text(
frame_editor,
height=8,
width=self.ANCHO_CARACTERES_FIJO,
wrap="word",
bg='white',
relief="solid",
borderwidth=1,
font=('Consolas', 9) # Fuente tipo terminal
)
self.notes_text_editor.pack(fill="x", expand=False)
# 2. Botones de Cargar y Guardar
frame_botones = ttk.Frame(frame_editor)
frame_botones.pack(fill="x", pady=(5, 0))
# Se usa 'SmallAction.TButton' para reducir el padding y asegurar que quepan
ttk.Button(frame_botones, text="Guardar", command=self.guardar_res_notes, style='SmallAction.TButton').pack(
side=tk.RIGHT)
ttk.Button(frame_botones, text="Cargar", command=self.cargar_res_notes, style='SmallAction.TButton').pack(
side=tk.LEFT)
self.cargar_res_notes(initial_load=True) # Carga inicial
def cargar_res_notes(self, initial_load=False):
"""Carga el contenido de res/notes al editor de texto lateral."""
contenido = cargar_contenido_res_notes()
self.notes_text_editor.delete("1.0", tk.END) # Limpiar contenido actual
if "Error al cargar:" in contenido:
self.notes_text_editor.insert(tk.END, contenido)
else:
if initial_load and not contenido.strip():
self.notes_text_editor.insert(tk.END, "# Escriba aquí sus notas (res/notes)")
else:
self.notes_text_editor.insert(tk.END, contenido)
print("Cargado 'res/notes' en el editor lateral.")
def guardar_res_notes(self):
"""Guarda el contenido del editor de texto lateral en res/notes."""
# CORRECCIÓN: Quitamos .strip() para que el guardado refleje fielmente el contenido
# del widget, incluyendo saltos de línea finales, lo que soluciona la confusión
# de que "se guarda, pero el archivo está vacío".
contenido = self.notes_text_editor.get("1.0", tk.END)
success, message = guardar_contenido_res_notes(contenido)
if success:
messagebox.showinfo("✅ Guardado", "Notas guardadas exitosamente.")
print(message)
else:
messagebox.showerror("❌ Error al Guardar", message)
print(f"FALLO AL GUARDAR: {message}")
# --- MÉTODOS EXISTENTES ---
def manejar_extraccion_datos(self): def manejar_extraccion_datos(self):
""" """
Llama a la lógica de actualización del gráfico de recursos Llama a la lógica de actualización del gráfico de recursos
@ -63,19 +141,30 @@ class PanelLateral(ttk.Frame):
success, message = accion_backup_t1() success, message = accion_backup_t1()
if success: if success:
messagebox.showinfo("Backup Completado", message) messagebox.showinfo("Backup Completado", message)
else: else:
messagebox.showerror("Error en el Backup", message) messagebox.showerror("Error en el Backup", message)
def configurar_estilos_locales(self, parent): def configurar_estilos_locales(self, parent):
"""Configura estilos para los widgets del panel lateral.""" """Configura estilos para los widgets del panel lateral."""
style = ttk.Style(parent) style = ttk.Style(parent)
# Estilos existentes
style.configure('Yellow.TEntry', fieldbackground='#fff8e1', foreground='#333333', padding=[5, 5], style.configure('Yellow.TEntry', fieldbackground='#fff8e1', foreground='#333333', padding=[5, 5],
relief='solid', borderwidth=1) relief='solid', borderwidth=1)
style.configure('Green.TButton', background='#4CAF50', foreground='white', font=('Arial', 10, 'bold'), style.configure('Green.TButton', background='#4CAF50', foreground='white', font=('Arial', 10, 'bold'),
relief='flat', padding=[10, 5]) relief='flat', padding=[10, 5])
style.map('Green.TButton', background=[('active', '#388E3C'), ('pressed', '#1B5E20')]) style.map('Green.TButton', background=[('active', '#388E3C'), ('pressed', '#1B5E20')])
style.configure('Action.TButton', background='#0078d4', foreground='white', font=('Arial', 10, 'bold'),
relief='flat', padding=[10, 5])
style.map('Action.TButton', background=[('active', '#005a9e'), ('pressed', '#003c6e')])
# NUEVO ESTILO: Botones pequeños para el editor de notas
style.configure('SmallAction.TButton', background='#0078d4', foreground='white', font=('Arial', 9, 'bold'),
relief='flat', padding=[5, 3]) # <-- Padding reducido
style.map('SmallAction.TButton', background=[('active', '#005a9e'), ('pressed', '#003c6e')])
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."""
if titulo: if titulo:

View File

@ -9,6 +9,9 @@ from vista.panel_central import PanelCentral
class VentanaPrincipal(tk.Tk): class VentanaPrincipal(tk.Tk):
"""Clase principal de la aplicación que monta la interfaz con estilos nativos mejorados.""" """Clase principal de la aplicación que monta la interfaz con estilos nativos mejorados."""
# Definimos el ancho deseado para el panel lateral
ANCHO_PANEL_LATERAL = 300
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.title("Proyecto Integrado - PSP (Estilo Moderno Nativo)") self.title("Proyecto Integrado - PSP (Estilo Moderno Nativo)")
@ -23,6 +26,7 @@ class VentanaPrincipal(tk.Tk):
# Configuración del manejador de protocolo para el botón de cierre (X) # Configuración del manejador de protocolo para el botón de cierre (X)
self.protocol("WM_DELETE_WINDOW", self.on_closing) self.protocol("WM_DELETE_WINDOW", self.on_closing)
# Columna 0 (Lateral) no se expande (weight=0), Columna 1 (Central) sí se expande (weight=1)
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) self.grid_columnconfigure(0, weight=0)
@ -31,7 +35,6 @@ class VentanaPrincipal(tk.Tk):
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."""
@ -61,16 +64,19 @@ class VentanaPrincipal(tk.Tk):
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, asegurando el ancho del lateral."""
# Panel Central (debe crearse primero) # Panel Central (se expande en 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) # Panel Lateral (se le aplica un ancho fijo para que no se expanda con el contenido)
self.panel_lateral = PanelLateral(self, central_panel=self.panel_central) self.panel_lateral = PanelLateral(self, central_panel=self.panel_central, width=self.ANCHO_PANEL_LATERAL)
self.panel_lateral.grid(row=0, column=0, sticky="nswe", padx=(10, 5), pady=10) self.panel_lateral.grid(row=0, column=0, sticky="nswe", padx=(10, 5), pady=10)
# Forzamos que el widget base respete el ancho definido, ignorando el tamaño del contenido (propagate=False)
self.panel_lateral.grid_propagate(False)
# --- FUNCIÓN DE CIERRE --- # --- FUNCIÓN DE CIERRE ---
def on_closing(self): def on_closing(self):
""" """
@ -78,14 +84,13 @@ class VentanaPrincipal(tk.Tk):
y cierra la ventana principal de forma limpia. y cierra la ventana principal de forma limpia.
""" """
if self.panel_central: if self.panel_central:
# Llamar al método de limpieza del Panel Central # Llamar al método de limpieza del Panel Central (ciclo tk.after)
self.panel_central.detener_actualizacion_automatica() self.panel_central.detener_actualizacion_automatica()
# Destruir el objeto Tkinter y terminar mainloop # Destruir el objeto Tkinter y terminar mainloop
self.destroy() self.destroy()
print("Aplicación cerrada limpiamente.") 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)