From b1e6f550208088bc465df618c8127cdc4767fd4d Mon Sep 17 00:00:00 2001 From: BYolivia Date: Thu, 4 Dec 2025 00:15:48 +0100 Subject: [PATCH] update t1 finish it's posible change the button for open a navegator with URL --- Readme.md | 6 +- logica/T1/TrafficMeter.py | 0 logica/T1/graficos.py | 159 +++++++++++++++++++++++++++++--------- logica/T1/openBrowser.py | 28 +++++++ logica/T1/trafficMeter.py | 75 ++++++++++++++++++ res/notes | 2 - vista/panel_central.py | 109 +++++++++++++++++--------- vista/panel_lateral.py | 32 +++++--- 8 files changed, 324 insertions(+), 87 deletions(-) delete mode 100644 logica/T1/TrafficMeter.py create mode 100644 logica/T1/openBrowser.py create mode 100644 logica/T1/trafficMeter.py diff --git a/Readme.md b/Readme.md index 8005606..770f074 100644 --- a/Readme.md +++ b/Readme.md @@ -14,15 +14,15 @@ python -m ProyectoGlobal ### T1. Multiprocesos ### -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)~~ 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~~ diff --git a/logica/T1/TrafficMeter.py b/logica/T1/TrafficMeter.py deleted file mode 100644 index e69de29..0000000 diff --git a/logica/T1/graficos.py b/logica/T1/graficos.py index 7780a55..cd887e3 100644 --- a/logica/T1/graficos.py +++ b/logica/T1/graficos.py @@ -1,55 +1,138 @@ # Módulo: logica/T1/graficos.py +import psutil import matplotlib.pyplot as plt -from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg -import tkinter as tk +import numpy as np -COLOR_PRINCIPAL = '#0078d4' -COLOR_RAM = '#4CAF50' +# --- Datos Históricos --- +MAX_PUNTOS = 60 # Mantener los últimos 60 puntos (segundos) +historial_cpu = [] +historial_ram = [] +historial_net_in = [] +historial_net_out = [] -def crear_grafico_recursos(parent_frame: tk.Frame, datos: dict): +def actualizar_historial_datos(net_in_kb, net_out_kb): """ - Genera un gráfico de matplotlib que muestra el uso de CPU y RAM, - e integra este gráfico en un Frame de Tkinter. + Recopila los datos actuales de CPU, RAM y añade los datos de Red + pasados como argumento a sus historiales. """ + # 1. Obtener datos básicos (CPU y RAM) + # interval=None asegura que se use el tiempo transcurrido desde la última llamada + # a psutil.cpu_percent (o 0.0 si es la primera vez en este proceso) + cpu_percent = psutil.cpu_percent(interval=None) + ram_percent = psutil.virtual_memory().percent - # Limpiamos el frame padre para redibujar - for widget in parent_frame.winfo_children(): - widget.destroy() + # 2. Añadir CPU y gestionar la longitud + historial_cpu.append(cpu_percent) + if len(historial_cpu) > MAX_PUNTOS: + historial_cpu.pop(0) - # 1. Crear la figura (2 subplots) - fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4)) - fig.patch.set_facecolor('white') + # 3. Añadir RAM y gestionar la longitud + historial_ram.append(ram_percent) + if len(historial_ram) > MAX_PUNTOS: + historial_ram.pop(0) - # --- 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"]}%') + # 4. Añadir Red y gestionar la longitud + historial_net_in.append(net_in_kb) + historial_net_out.append(net_out_kb) - 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') + if len(historial_net_in) > MAX_PUNTOS: + historial_net_in.pop(0) + historial_net_out.pop(0) - # --- 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') +def crear_grafico_recursos(figure): + """ + Crea o actualiza un gráfico que muestre la evolución de CPU, RAM y Red. + """ + # Limpiar la figura antes de dibujar + figure.clear() - ram_title = f'RAM Total: {datos["ram_total_gb"]} GB\nUso: {datos["ram_uso_gb"]} GB' - ax2.set_title(ram_title, fontsize=10) + # Configuramos el fondo de la figura para que coincida con el estilo de la aplicación + figure.patch.set_facecolor('#f9f9f9') - # 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) + # --- Configuración General del Layout --- + # 3 filas para CPU, RAM, Red con espaciado vertical + gs = figure.add_gridspec(3, 1, hspace=0.6, top=0.95, bottom=0.05, left=0.1, right=0.95) - return canvas_widget \ No newline at end of file + # --- Función Helper para el estilo btop --- + def configurar_ejes_historial(ax, title, color, data, y_limit=100, y_ticks=None): + ax.set_facecolor('#f0f0f0') # Fondo del área de dibujo + ax.set_title(title, fontsize=9, loc='left', pad=10) + ax.set_ylim(0, y_limit) + + if y_ticks: + ax.set_yticks(y_ticks) + + ax.tick_params(axis='x', labelbottom=False, length=0) + ax.tick_params(axis='y', labelsize=8) + ax.spines['top'].set_visible(False) + ax.spines['right'].set_visible(False) + ax.spines['left'].set_visible(False) + ax.spines['bottom'].set_visible(False) + ax.grid(axis='y', linestyle='--', alpha=0.5) + + # Dibujar línea y relleno + ax.plot(data, color=color, linewidth=1.5) + ax.fill_between(range(len(data)), data, color=color, alpha=0.3) + + # --- 1. Gráfico de CPU --- + ax_cpu = figure.add_subplot(gs[0, 0]) + configurar_ejes_historial( + ax_cpu, 'Uso de CPU (%) - Ultimo: {:.1f}%'.format(historial_cpu[-1] if historial_cpu else 0), + 'red', historial_cpu, 100, [0, 50, 100] + ) + + # --- 2. Gráfico de RAM --- + ax_ram = figure.add_subplot(gs[1, 0]) + configurar_ejes_historial( + ax_ram, 'Uso de RAM (%) - Ultimo: {:.1f}%'.format(historial_ram[-1] if historial_ram else 0), + 'cyan', historial_ram, 100, [0, 50, 100] + ) + + # --- 3. Gráfico de Red --- + ax_net = figure.add_subplot(gs[2, 0]) + + # Calcular el límite Y dinámico para la red (ajusta el gráfico al tráfico real) + max_in = max(historial_net_in) if historial_net_in else 0 + max_out = max(historial_net_out) if historial_net_out else 0 + y_limit_net = max(max_in, max_out) * 1.2 # 20% de margen + y_limit_net = max(y_limit_net, 10) # Mínimo de 10 KB/s + + configurar_ejes_historial( + ax_net, + 'Tráfico de Red (KB/s) - IN: {:.1f} KB/s | OUT: {:.1f} KB/s'.format( + historial_net_in[-1] if historial_net_in else 0, + historial_net_out[-1] if historial_net_out else 0 + ), + 'gray', [0] * MAX_PUNTOS, # Usamos un color de base para la configuración + y_limit_net, + [0, y_limit_net * 0.5, y_limit_net * 0.9] + ) + + # Sobreescribir las líneas para mostrar IN y OUT + ax_net.clear() # Limpiamos para redibujar con las dos líneas + ax_net.set_ylim(0, y_limit_net) + + # Dibujar Entrada (Recibido) + ax_net.plot(historial_net_in, label='IN (Recibido)', color='green', linewidth=1.5) + ax_net.fill_between(range(len(historial_net_in)), historial_net_in, color='green', alpha=0.2) + + # Dibujar Salida (Enviado) + ax_net.plot(historial_net_out, label='OUT (Enviado)', color='yellow', linewidth=1.5) + ax_net.fill_between(range(len(historial_net_out)), historial_net_out, color='yellow', alpha=0.2) + + # Reconfigurar los títulos y estilos después de limpiar el eje + configurar_ejes_historial( + ax_net, + 'Tráfico de Red (KB/s) - IN: {:.1f} KB/s | OUT: {:.1f} KB/s'.format( + historial_net_in[-1] if historial_net_in else 0, + historial_net_out[-1] if historial_net_out else 0 + ), + 'gray', [0] * MAX_PUNTOS, + y_limit_net, + [0, round(y_limit_net * 0.5, 1), round(y_limit_net * 0.9, 1)] + ) + + figure.tight_layout() \ No newline at end of file diff --git a/logica/T1/openBrowser.py b/logica/T1/openBrowser.py new file mode 100644 index 0000000..d6cc96b --- /dev/null +++ b/logica/T1/openBrowser.py @@ -0,0 +1,28 @@ +# Módulo: logica/T1/openBrowser.py + +import webbrowser + +def navegar_a_url(url: str): + """ + Abre la URL proporcionada en el navegador web predeterminado del sistema. + Utiliza el método .open(new=1) para solicitar una nueva ventana. + """ + url = url.strip() + + if not url: + print("Error de Navegación: URL vacía.") + return False + + # Restauramos la verificación de esquema para la estabilidad de la URI + if not url.startswith(('http://', 'https://')): + url = 'http://' + url + + try: + # Se usa new=1 para solicitar una nueva ventana. + # El comportamiento final depende del SO y del navegador por defecto del usuario. + webbrowser.open(url, new=1) + print(f"Lanzando navegador externo (solicitando nueva ventana) con URL: {url}") + return True + except Exception as e: + print(f"Error crítico al intentar abrir el navegador para {url}: {e}") + return False \ No newline at end of file diff --git a/logica/T1/trafficMeter.py b/logica/T1/trafficMeter.py new file mode 100644 index 0000000..db2f532 --- /dev/null +++ b/logica/T1/trafficMeter.py @@ -0,0 +1,75 @@ +# Módulo: logica/T1/trafficMeter.py + +import psutil +import time +import threading + +# Constante de conversión: 1 KB = 1024 bytes +KB = 1024 + + +class NetIOMonitor(threading.Thread): + """ + Hilo que monitorea y almacena el tráfico de red (bytes por segundo) + convirtiéndolo a Kilobytes por segundo (KB/s). + """ + + def __init__(self, intervalo=1): + super().__init__() + self._stop_event = threading.Event() + self.intervalo = intervalo + + # Almacenamiento seguro para los últimos datos de tráfico + self.lock = threading.Lock() + self.data_in_kb = 0.0 # Tráfico de entrada en KB/s (Recibido) + self.data_out_kb = 0.0 # Tráfico de salida en KB/s (Enviado) + + # Almacena el contador anterior para calcular la diferencia (tasa) + self.last_counters = psutil.net_io_counters() + # Nota: La primera lectura es solo para inicializar, se requiere una segunda para la tasa. + + def run(self): + """Método principal del hilo.""" + while not self._stop_event.is_set(): + # Esperar el intervalo antes de la lectura para calcular la tasa + time.sleep(self.intervalo) + self._actualizar_datos() + + def stop(self): + """Detiene el hilo de forma segura.""" + self._stop_event.set() + + def _actualizar_datos(self): + """Calcula el tráfico de red en KB/s.""" + + current_counters = psutil.net_io_counters() + + # Calcular la diferencia de bytes recibidos y enviados desde la última lectura + bytes_recv_diff = current_counters.bytes_recv - self.last_counters.bytes_recv + bytes_sent_diff = current_counters.bytes_sent - self.last_counters.bytes_sent + + # Calcular la tasa (bytes/segundo) y convertir a KB/s + # El tiempo transcurrido es igual a self.intervalo + rate_in_kb_s = (bytes_recv_diff / self.intervalo) / KB + rate_out_kb_s = (bytes_sent_diff / self.intervalo) / KB + + # Actualizar el contador anterior + self.last_counters = current_counters + + # Guardar los resultados de forma segura + with self.lock: + self.data_in_kb = rate_in_kb_s + self.data_out_kb = rate_out_kb_s + + def get_io_data_kb(self): + """Devuelve el tráfico de entrada y salida actual en KB/s.""" + with self.lock: + return self.data_in_kb, self.data_out_kb + + +# --- FUNCIÓN DE INICIO --- +def iniciar_monitor_red(): + """Inicializa y comienza el monitor de red.""" + monitor = NetIOMonitor(intervalo=1) # 1 segundo de intervalo + monitor.start() + return monitor \ No newline at end of file diff --git a/res/notes b/res/notes index daedf58..e69de29 100644 --- a/res/notes +++ b/res/notes @@ -1,2 +0,0 @@ -'oijp; -hfgdhfgdh \ No newline at end of file diff --git a/vista/panel_central.py b/vista/panel_central.py index a8717b4..85185cd 100644 --- a/vista/panel_central.py +++ b/vista/panel_central.py @@ -2,21 +2,32 @@ import tkinter as tk 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 + + +from logica.T1.trafficMeter import iniciar_monitor_red +from logica.T1.graficos import crear_grafico_recursos, actualizar_historial_datos +from matplotlib.figure import Figure +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg class PanelCentral(ttk.Frame): """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 + # Reducimos el intervalo de actualización a 1000ms (1 segundo) para gráficos fluidos + INTERVALO_ACTUALIZACION_MS = 1000 def __init__(self, parent, *args, **kwargs): super().__init__(parent, *args, **kwargs) self.after_id = None # ID para controlar el timer de tk.after + # 1. INICIALIZACIÓN DE VARIABLES + self.net_monitor = iniciar_monitor_red() # <-- INICIAR EL HILO DE RED + + # Objeto Figure de Matplotlib (debe crearse antes de crear_sub_pestañas_t1) + self.figure = Figure(figsize=(5, 4), dpi=100) + self.canvas = None # Se inicializará en crear_sub_pestañas_t1 + + # 2. CONFIGURACIÓN DEL LAYOUT self.grid_columnconfigure(0, weight=3) self.grid_columnconfigure(1, weight=1) self.grid_rowconfigure(0, weight=1) @@ -24,6 +35,7 @@ class PanelCentral(ttk.Frame): self.crear_area_principal_y_notas() self.crear_panel_chat_y_alumnos() + # La actualización se inicia al final de __init__ self.iniciar_actualizacion_automatica() def crear_area_principal_y_notas(self): @@ -43,7 +55,7 @@ class PanelCentral(ttk.Frame): 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( + 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): @@ -64,48 +76,74 @@ class PanelCentral(ttk.Frame): 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() + # Inicialización del Canvas de Matplotlib + self.canvas = FigureCanvasTkAgg(self.figure, master=self.grafico_frame) + self.canvas_widget = self.canvas.get_tk_widget() + self.canvas_widget.pack(expand=True, fill="both") + # La actualización automática iniciada en __init__ se encarga de esto sub_notebook.select(i) # 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") + 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.") + ">>> Á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.""" + """ + Obtiene los datos del sistema (incluyendo Red) y dibuja/redibuja el gráfico. + """ try: - datos_sistema = obtener_datos_cpu_ram() - crear_grafico_recursos(self.grafico_frame, datos_sistema) + # 1. Obtener datos de red del hilo (en KB/s) + net_in, net_out = self.net_monitor.get_io_data_kb() + + # 2. Actualizar el historial global (CPU y RAM se obtienen dentro de esta función) + actualizar_historial_datos(net_in, net_out) + + # 3. Redibujar el gráfico + crear_grafico_recursos(self.figure) + self.canvas.draw() + 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) + # Manejo de errores visual en la pestaña (limpiar y mostrar error) + if self.canvas_widget.winfo_exists(): + self.canvas_widget.pack_forget() + + error_label = ttk.Label(self.grafico_frame, text=error_msg, foreground='red', style='TLabel') + error_label.pack(pady=20) + + # Detener la actualización para evitar bucle de error + self.detener_actualizacion_automatica() + + # 4. Programar la siguiente llamada (solo si no hay error crítico que detenga la actualización) + self.after_id = self.after(self.INTERVALO_ACTUALIZACION_MS, self.actualizar_grafico_recursos) + + # --- Control del Ciclo de Vida --- 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) + """Inicia el ciclo de actualización del gráfico de recursos.""" + print("Iniciando actualización automática de recursos.") + # Programar la primera llamada inmediatamente + self.after_id = self.after(0, self.actualizar_grafico_recursos) def detener_actualizacion_automatica(self): - """Detiene el ciclo de actualización periódica del gráfico.""" + """Detiene el ciclo de actualización periódica y el hilo de red.""" if self.after_id: self.after_cancel(self.after_id) + self.after_id = None print("Ciclo de actualización de gráficos detenido.") + # Detener el monitor de red al cerrar la aplicación + if self.net_monitor: + self.net_monitor.stop() + self.net_monitor.join() # Esperar a que el hilo termine + print("Hilo de TrafficMeter detenido.") + def crear_panel_chat_y_alumnos(self, ): """Crea el panel de chat, lista de Alumnos y Reproductor de Música (columna derecha).""" panel_chat = ttk.Frame(self, style='TFrame', padding="10") @@ -117,17 +155,17 @@ class PanelCentral(ttk.Frame): # 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") + column=0, + pady=( + 0, + 10), + sticky="w") # 2. Área de Mensaje ttk.Label(panel_chat, text="Mensaje", style='TLabel').grid(row=1, column=0, sticky="w") 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)) ttk.Button(panel_chat, text="Enviar", style='Action.TButton').grid(row=3, column=0, pady=(0, 15), sticky="e") @@ -141,14 +179,15 @@ class PanelCentral(ttk.Frame): 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="Lorem ipsum dolor sit amet, consectetur adipiscing elit.", wraplength=250, - justify=tk.LEFT, style='Alumno.TLabel').grid(row=1, column=0, sticky="w") + justify=tk.LEFT, style='Alumno.TLabel').grid(row=1, column=0, sticky="w") 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) 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)) ttk.Label(musica_frame, text="[ Botones de Play/Stop y Control de Volumen ]", anchor="center", - style='TLabel').pack(fill="x", padx=5, pady=5) \ No newline at end of file + style='TLabel').pack(fill="x", padx=5, pady=5) \ No newline at end of file diff --git a/vista/panel_lateral.py b/vista/panel_lateral.py index 630f4eb..477cf5c 100644 --- a/vista/panel_lateral.py +++ b/vista/panel_lateral.py @@ -3,11 +3,12 @@ import tkinter as tk from tkinter import ttk from tkinter import messagebox -from logica.controlador import accion_placeholder, getPlataforma +from logica.controlador import accion_placeholder 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 +from logica.T1.openBrowser import navegar_a_url + class PanelLateral(ttk.Frame): @@ -17,20 +18,24 @@ class PanelLateral(ttk.Frame): # no intente expandir el panel lateral más allá de lo deseado. ANCHO_CARACTERES_FIJO = 35 + ANCHO_CARACTERES_FIJO = 35 + def __init__(self, parent, central_panel=None, *args, **kwargs): super().__init__(parent, *args, **kwargs) self.central_panel = central_panel self.configurar_estilos_locales(parent) - # 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. Entrada superior (amarilla) - ¡Guardamos la referencia! + self.entrada_superior = ttk.Entry(self, width=self.ANCHO_CARACTERES_FIJO, style='Yellow.TEntry') + self.entrada_superior.pack(fill="x", pady=10, padx=5, ipady=3) + self.entrada_superior.bind('', self.manejar_navegacion) # Opcional: Ejecutar con Enter # 2. Área de Extracción/Navegación acciones_extraccion = [ ("Extraer datos", self.manejar_extraccion_datos), - ("Navegar", lambda: accion_placeholder("Navegar")), + # 2. Asignamos el nuevo método de manejo a este botón + ("Navegar", self.manejar_navegacion), ("Buscar API Google", lambda: accion_placeholder("Buscar API Google")) ] self.crear_seccion(self, titulo="", acciones=acciones_extraccion) @@ -49,19 +54,28 @@ class PanelLateral(ttk.Frame): ] self.crear_seccion(self, titulo="Procesos batch", acciones=acciones_batch) - # 5. Espacio expandible (Empuja los elementos superiores hacia arriba) + # 5. Espacio expandible tk.Frame(self, height=1).pack(expand=True, fill="both") - # 6. Panel de Notas (Editor res/notes, ubicado abajo) + # 6. Panel de Notas self.crear_editor_res_notes() + # --- NUEVO MÉTODO PARA MANEJAR LA NAVEGACIÓN --- + def manejar_navegacion(self, event=None): + """ + Obtiene el texto de la entrada superior y llama a la función de navegación. + """ + url = self.entrada_superior.get() + if navegar_a_url(url): + # Limpiar la casilla si la navegación fue exitosa + self.entrada_superior.delete(0, tk.END) # --- 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) + padx=5) frame_editor = ttk.Frame(self, padding=5) frame_editor.pack(fill="x", padx=5, pady=(0, 10))