add simple text editor for res/notes with load and save functionality
This commit is contained in:
parent
48ed65d91c
commit
f9cfafe3fe
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue