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)
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).
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 ###
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.T1.backup import accion_backup_t1
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):
"""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):
super().__init__(parent, *args, **kwargs)
self.central_panel = central_panel # Guardamos la referencia
# ❌ ELIMINAR ESTA LÍNEA: self.pack(fill="y", padx=5, pady=5)
self.central_panel = central_panel
self.configurar_estilos_locales(parent)
# Entrada superior (amarilla)
ttk.Entry(self, width=25, style='Yellow.TEntry').pack(fill="x", pady=10, padx=5, ipady=3)
# 1. Entrada superior (amarilla) - Aplicamos el ancho fijo
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 = [
("Extraer datos", self.manejar_extraccion_datos),
("Navegar", lambda: accion_placeholder("Navegar")),
@ -30,7 +35,7 @@ class PanelLateral(ttk.Frame):
]
self.crear_seccion(self, titulo="", acciones=acciones_extraccion)
# 2. Área de Aplicaciones
# 3. Área de Aplicaciones
acciones_aplicaciones = [
("Visual Code", abrir_vscode),
("App2", lambda: accion_placeholder("App2")),
@ -38,14 +43,87 @@ class PanelLateral(ttk.Frame):
]
self.crear_seccion(self, titulo="Aplicaciones", acciones=acciones_aplicaciones)
# 3. Área de Procesos Batch
# 4. Área de Procesos Batch
acciones_batch = [
("Copias de seguridad", self.manejar_backup)
]
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")
# 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):
"""
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()
if success:
messagebox.showinfo("Backup Completado", message)
messagebox.showinfo("Backup Completado", message)
else:
messagebox.showerror("Error en el Backup", message)
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)
# Estilos existentes
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.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):
"""Función helper para crear secciones de etiquetas y botones."""
if titulo:

View File

@ -9,6 +9,9 @@ from vista.panel_central import PanelCentral
class VentanaPrincipal(tk.Tk):
"""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):
super().__init__()
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)
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(1, weight=0)
self.grid_columnconfigure(0, weight=0)
@ -31,7 +35,6 @@ class VentanaPrincipal(tk.Tk):
self.crear_paneles_principales()
self.crear_barra_inferior()
# ... (código de configurar_estilos) ...
def configurar_estilos(self, s: ttk.Style):
"""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)])
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.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)
# 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, width=self.ANCHO_PANEL_LATERAL)
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 ---
def on_closing(self):
"""
@ -78,14 +84,13 @@ class VentanaPrincipal(tk.Tk):
y cierra la ventana principal de forma limpia.
"""
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()
# 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):
"""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)