Refactorizacion de nombres de scripts
This commit is contained in:
parent
e44cc3af8f
commit
bc768e9ca7
|
@ -12,7 +12,7 @@ Este documento describe el estado actual y los objetivos de desarrollo para la *
|
|||
- La interfaz de usuario se divide en 2 columnas, a la izquierda el area de ingreso de datos y equaciones a ser evaluadas, a la derecha el area de resultados que se colorean segun cada tipo de respuesta.
|
||||
|
||||
### 2. **Sintaxis Simplificada con Corchetes (Única)**
|
||||
- Usar **exclusivamente** `Class[args]` en lugar de `Class("args")`
|
||||
- Usar **exclusivamente** `Class[args]` en lugar de `Class("args")` . Cuando se usan los corchetes los parametros se separan por `;`
|
||||
- Los corchetes indican parsing especial (agregar comillas automáticamente)
|
||||
- Mantiene sintaxis limpia y reduce errores de tipeo
|
||||
|
||||
|
@ -91,6 +91,7 @@ x=? → solve(x)
|
|||
|
||||
### 1. **Bracket Parser y Detección Contextual de Ecuaciones**
|
||||
- Preprocesador que convierte `Class[args]` → `Class("args")`
|
||||
- La separacion de argumentos se hace con el ";"
|
||||
- Soporte para clases: `IP4`, `Hex`, `Bin`, `Date`, `Dec`, `Chr`
|
||||
- **Detección contextual inteligente de ecuaciones**:
|
||||
- Ecuaciones standalone: `3+b=5+c` (detectar como ecuación)
|
||||
|
@ -314,6 +315,7 @@ Cada tipo de objeto (por ejemplo, `IP4`, `Hex`, etc.) debe definir una función
|
|||
- Recibe el string de entrada del usuario.
|
||||
- Decide, usando su propia lógica (por ejemplo, expresiones regulares), si puede ofrecer una ayuda relevante.
|
||||
- Devuelve un string de ayuda (ejemplo de uso, sintaxis, sugerencia) o `None` si no aplica.
|
||||
- Las respuestas no pueden tener mas de una linea nunca.
|
||||
|
||||
**Ejemplo:**
|
||||
```python
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"python.analysis.autoImportCompletions": true,
|
||||
"python.analysis.typeCheckingMode": "off"
|
||||
}
|
BIN
MaVCalcv2.lnk
BIN
MaVCalcv2.lnk
Binary file not shown.
|
@ -1,16 +1,16 @@
|
|||
"""
|
||||
Clase híbrida para números binarios
|
||||
"""
|
||||
from hybrid_base import HybridCalcType
|
||||
from sympy_Base import SympyClassBase
|
||||
import re
|
||||
|
||||
|
||||
class HybridBin(HybridCalcType):
|
||||
class Class_Bin(SympyClassBase):
|
||||
"""Clase híbrida para números binarios"""
|
||||
|
||||
def __new__(cls, value_input):
|
||||
"""Crear objeto SymPy válido"""
|
||||
obj = HybridCalcType.__new__(cls)
|
||||
obj = SympyClassBase.__new__(cls)
|
||||
return obj
|
||||
|
||||
def __init__(self, value_input):
|
||||
|
|
|
@ -0,0 +1,255 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Launcher principal para Calculadora MAV - CAS Híbrido
|
||||
Este script maneja la inicialización y ejecución de la aplicación
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox
|
||||
from pathlib import Path
|
||||
import importlib.util
|
||||
import logging
|
||||
import datetime
|
||||
import traceback
|
||||
import platform
|
||||
import platform
|
||||
|
||||
|
||||
def setup_logging():
|
||||
"""Configura el sistema de logging completo"""
|
||||
log_dir = Path("logs")
|
||||
log_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Archivo de log con timestamp
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
log_file = log_dir / f"mav_calc_{timestamp}.log"
|
||||
|
||||
# Configurar logging
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler(log_file, encoding='utf-8'),
|
||||
logging.StreamHandler(sys.stdout)
|
||||
]
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
return logger, log_file
|
||||
|
||||
|
||||
|
||||
def log_error_with_context(logger, error, context=""):
|
||||
"""Registra un error con contexto completo"""
|
||||
logger.error("=" * 50)
|
||||
logger.error("ERROR DETECTADO")
|
||||
logger.error("=" * 50)
|
||||
if context:
|
||||
logger.error(f"Contexto: {context}")
|
||||
logger.error(f"Tipo de error: {type(error).__name__}")
|
||||
logger.error(f"Mensaje: {str(error)}")
|
||||
logger.error("Traceback completo:")
|
||||
logger.error(traceback.format_exc())
|
||||
logger.error("Variables locales en el momento del error:")
|
||||
|
||||
# Intentar capturar variables locales del frame donde ocurrió el error
|
||||
try:
|
||||
tb = traceback.extract_tb(error.__traceback__)
|
||||
if tb:
|
||||
last_frame = tb[-1]
|
||||
logger.error(f" Archivo: {last_frame.filename}")
|
||||
logger.error(f" Línea: {last_frame.lineno}")
|
||||
logger.error(f" Función: {last_frame.name}")
|
||||
logger.error(f" Código: {last_frame.line}")
|
||||
except Exception as frame_error:
|
||||
logger.error(f"No se pudieron obtener detalles del frame: {frame_error}")
|
||||
|
||||
logger.error("=" * 50)
|
||||
|
||||
|
||||
def show_error_with_log_info(error, log_file, context=""):
|
||||
"""Muestra error al usuario con información del log"""
|
||||
error_msg = f"""Error en Calculadora MAV:
|
||||
|
||||
{context}
|
||||
|
||||
Error: {type(error).__name__}: {str(error)}
|
||||
|
||||
INFORMACIÓN DE DEBUGGING:
|
||||
• Log completo guardado en: {log_file}
|
||||
• Para soporte, enviar el archivo de log
|
||||
• Timestamp: {datetime.datetime.now()}
|
||||
|
||||
¿Qué hacer ahora?
|
||||
1. Revisar el archivo de log para más detalles
|
||||
2. Intentar reiniciar la aplicación
|
||||
3. Verificar dependencias con: python launcher.py --setup
|
||||
4. Ejecutar tests con: python launcher.py --test
|
||||
"""
|
||||
|
||||
try:
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
|
||||
# Crear ventana de error personalizada
|
||||
error_window = tk.Toplevel(root)
|
||||
error_window.title("Error - Calculadora MAV")
|
||||
error_window.geometry("600x400")
|
||||
error_window.configure(bg="#2b2b2b")
|
||||
|
||||
# Hacer la ventana modal
|
||||
error_window.transient(root)
|
||||
error_window.grab_set()
|
||||
|
||||
# Centrar ventana
|
||||
error_window.update_idletasks()
|
||||
x = (error_window.winfo_screenwidth() // 2) - (error_window.winfo_width() // 2)
|
||||
y = (error_window.winfo_screenheight() // 2) - (error_window.winfo_height() // 2)
|
||||
error_window.geometry(f"+{x}+{y}")
|
||||
|
||||
# Contenido
|
||||
from tkinter import scrolledtext
|
||||
|
||||
text_widget = scrolledtext.ScrolledText(
|
||||
error_window,
|
||||
font=("Consolas", 10),
|
||||
bg="#1e1e1e",
|
||||
fg="#ff6b6b",
|
||||
wrap=tk.WORD
|
||||
)
|
||||
text_widget.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
text_widget.insert("1.0", error_msg)
|
||||
text_widget.config(state="disabled")
|
||||
|
||||
# Botones
|
||||
button_frame = tk.Frame(error_window, bg="#2b2b2b")
|
||||
button_frame.pack(fill=tk.X, padx=10, pady=5)
|
||||
|
||||
def open_log_folder():
|
||||
try:
|
||||
if platform.system() == "Windows":
|
||||
os.startfile(log_file.parent)
|
||||
elif platform.system() == "Darwin": # macOS
|
||||
subprocess.run(["open", str(log_file.parent)])
|
||||
else: # Linux
|
||||
subprocess.run(["xdg-open", str(log_file.parent)])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def copy_log_path():
|
||||
error_window.clipboard_clear()
|
||||
error_window.clipboard_append(str(log_file))
|
||||
|
||||
tk.Button(
|
||||
button_frame,
|
||||
text="Abrir Carpeta de Logs",
|
||||
command=open_log_folder,
|
||||
bg="#4fc3f7",
|
||||
fg="white"
|
||||
).pack(side=tk.LEFT, padx=5)
|
||||
|
||||
tk.Button(
|
||||
button_frame,
|
||||
text="Copiar Ruta del Log",
|
||||
command=copy_log_path,
|
||||
bg="#82aaff",
|
||||
fg="white"
|
||||
).pack(side=tk.LEFT, padx=5)
|
||||
|
||||
tk.Button(
|
||||
button_frame,
|
||||
text="Cerrar",
|
||||
command=error_window.destroy,
|
||||
bg="#ff6b6b",
|
||||
fg="white"
|
||||
).pack(side=tk.RIGHT, padx=5)
|
||||
|
||||
# Esperar a que se cierre la ventana
|
||||
error_window.wait_window()
|
||||
root.destroy()
|
||||
|
||||
except Exception as gui_error:
|
||||
# Si falla la GUI, mostrar en consola
|
||||
print("ERROR: No se pudo mostrar ventana de error")
|
||||
print(error_msg)
|
||||
print(f"Error adicional: {gui_error}")
|
||||
|
||||
|
||||
# Variable global para el logger
|
||||
logger = None
|
||||
log_file = None
|
||||
|
||||
def launch_application():
|
||||
|
||||
try:
|
||||
# Importar y ejecutar la aplicación
|
||||
from main_calc_app import HybridCalculatorApp
|
||||
root = tk.Tk()
|
||||
|
||||
app = HybridCalculatorApp(root)
|
||||
root.mainloop()
|
||||
|
||||
logger.info("Aplicación cerrada normalmente")
|
||||
|
||||
except ImportError as e:
|
||||
logger.error(f"Error de importación: {e}")
|
||||
log_error_with_context(logger, e, "Importación de módulos de la aplicación")
|
||||
show_error_with_log_info(e, log_file, "Error importando módulos de la aplicación")
|
||||
except Exception as e:
|
||||
logger.error(f"Error durante ejecución de la aplicación: {e}")
|
||||
log_error_with_context(logger, e, "Ejecución de la aplicación principal")
|
||||
show_error_with_log_info(e, log_file, "Error durante la ejecución de la aplicación")
|
||||
|
||||
|
||||
def main():
|
||||
"""Función principal del launcher"""
|
||||
global logger, log_file
|
||||
|
||||
# Configurar logging al inicio
|
||||
try:
|
||||
logger, log_file = setup_logging()
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: No se pudo configurar logging: {e}")
|
||||
# Continuar sin logging si falla
|
||||
logger = None
|
||||
log_file = None
|
||||
|
||||
logger.info("Iniciando verificaciones del sistema...")
|
||||
|
||||
try:
|
||||
|
||||
launch_application()
|
||||
|
||||
logger.info("Aplicación cerrada - fin de sesión")
|
||||
logger.info("=" * 60)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Aplicación interrumpida por el usuario (Ctrl+C)")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
logger.error("Error crítico en main():")
|
||||
log_error_with_context(logger, e, "Función principal del launcher")
|
||||
show_error_with_log_info(e, log_file, "Error crítico durante el inicio")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Configurar logging básico para manejo de argumentos
|
||||
temp_logger = None
|
||||
temp_log_file = None
|
||||
|
||||
# Inicio normal
|
||||
try:
|
||||
main()
|
||||
except Exception as e:
|
||||
if temp_logger:
|
||||
log_error_with_context(temp_logger, e, "Error crítico en __main__")
|
||||
print(f"ERROR CRÍTICO: {e}")
|
||||
if temp_log_file:
|
||||
print(f"Ver log completo en: {temp_log_file}")
|
||||
sys.exit(1)
|
|
@ -1,16 +1,16 @@
|
|||
"""
|
||||
Clase híbrida para caracteres
|
||||
"""
|
||||
from hybrid_base import HybridCalcType
|
||||
from sympy_Base import SympyClassBase
|
||||
import re
|
||||
|
||||
|
||||
class HybridChr(HybridCalcType):
|
||||
class Class_Chr(SympyClassBase):
|
||||
"""Clase híbrida para caracteres"""
|
||||
|
||||
def __new__(cls, str_input):
|
||||
"""Crear objeto SymPy válido"""
|
||||
obj = HybridCalcType.__new__(cls)
|
||||
obj = SympyClassBase.__new__(cls)
|
||||
return obj
|
||||
|
||||
def __init__(self, str_input: str):
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
"""
|
||||
Clase híbrida para números decimales
|
||||
"""
|
||||
from hybrid_base import HybridCalcType
|
||||
from sympy_Base import SympyClassBase
|
||||
import re
|
||||
|
||||
|
||||
class HybridDec(HybridCalcType):
|
||||
class Class_Dec(SympyClassBase):
|
||||
"""Clase híbrida para números decimales"""
|
||||
|
||||
def __new__(cls, value_input):
|
||||
"""Crear objeto SymPy válido"""
|
||||
obj = HybridCalcType.__new__(cls)
|
||||
obj = SympyClassBase.__new__(cls)
|
||||
return obj
|
||||
|
||||
def __init__(self, value_input):
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
"""
|
||||
Clase híbrida para números hexadecimales
|
||||
"""
|
||||
from hybrid_base import HybridCalcType
|
||||
from sympy_Base import SympyClassBase
|
||||
import re
|
||||
|
||||
|
||||
class HybridHex(HybridCalcType):
|
||||
class Class_Hex(SympyClassBase):
|
||||
"""Clase híbrida para números hexadecimales"""
|
||||
|
||||
def __new__(cls, value_input):
|
||||
"""Crear objeto SymPy válido"""
|
||||
obj = HybridCalcType.__new__(cls)
|
||||
obj = SympyClassBase.__new__(cls)
|
||||
return obj
|
||||
|
||||
def __init__(self, value_input):
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
Hex[FF]
|
||||
ip=IP4[192.168.1.100/24]
|
||||
ip=IP4[110.1.30.70/24]
|
||||
|
||||
ip.NetworkAddress()
|
||||
|
||||
|
||||
500/25
|
||||
|
@ -7,3 +9,9 @@ ip=IP4[192.168.1.100/24]
|
|||
Dec[Hex[ff]]
|
||||
|
||||
Hex[ff]
|
||||
|
||||
Hex[ff].toDecimal()
|
||||
|
||||
IP4[110.1.30.70,255.255.255.0]
|
||||
|
||||
IP4[110.1.30.70,255.255.255.0]
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"window_geometry": "1000x700+327+101",
|
||||
"sash_pos_x": 339
|
||||
"window_geometry": "1020x700+2638+160",
|
||||
"sash_pos_x": 303
|
||||
}
|
10
ip4_type.py
10
ip4_type.py
|
@ -1,17 +1,17 @@
|
|||
"""
|
||||
Clase híbrida para direcciones IPv4
|
||||
"""
|
||||
from hybrid_base import HybridCalcType
|
||||
from sympy_Base import SympyClassBase
|
||||
from typing import Optional, Tuple
|
||||
import re
|
||||
|
||||
|
||||
class HybridIP4(HybridCalcType):
|
||||
class Class_IP4(SympyClassBase):
|
||||
"""Clase híbrida para direcciones IPv4"""
|
||||
|
||||
def __new__(cls, *args):
|
||||
"""Crear objeto SymPy válido"""
|
||||
obj = HybridCalcType.__new__(cls)
|
||||
obj = SympyClassBase.__new__(cls)
|
||||
return obj
|
||||
|
||||
def __init__(self, *args):
|
||||
|
@ -148,7 +148,7 @@ class HybridIP4(HybridCalcType):
|
|||
]
|
||||
network_str = '.'.join(str(x) for x in parts)
|
||||
|
||||
return HybridIP4(f"{network_str}/{self._prefix}")
|
||||
return Class_IP4(f"{network_str}/{self._prefix}")
|
||||
|
||||
def BroadcastAddress(self):
|
||||
"""Obtiene la dirección de broadcast"""
|
||||
|
@ -170,7 +170,7 @@ class HybridIP4(HybridCalcType):
|
|||
]
|
||||
broadcast_str = '.'.join(str(x) for x in parts)
|
||||
|
||||
return HybridIP4(f"{broadcast_str}/{self._prefix}")
|
||||
return Class_IP4(f"{broadcast_str}/{self._prefix}")
|
||||
|
||||
def Nodes(self):
|
||||
"""Obtiene el número de nodos disponibles"""
|
||||
|
|
624
main_calc.py
624
main_calc.py
|
@ -1,624 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Launcher principal para Calculadora MAV - CAS Híbrido
|
||||
Este script maneja la inicialización y ejecución de la aplicación
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox
|
||||
from pathlib import Path
|
||||
import importlib.util
|
||||
import logging
|
||||
import datetime
|
||||
import traceback
|
||||
import platform
|
||||
|
||||
|
||||
import platform
|
||||
|
||||
|
||||
def setup_logging():
|
||||
"""Configura el sistema de logging completo"""
|
||||
log_dir = Path("logs")
|
||||
log_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Archivo de log con timestamp
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
log_file = log_dir / f"mav_calc_{timestamp}.log"
|
||||
|
||||
# Configurar logging
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler(log_file, encoding='utf-8'),
|
||||
logging.StreamHandler(sys.stdout)
|
||||
]
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Log información del sistema
|
||||
logger.info("=" * 60)
|
||||
logger.info("CALCULADORA MAV - CAS HÍBRIDO - INICIO DE SESIÓN")
|
||||
logger.info("=" * 60)
|
||||
logger.info(f"Timestamp: {datetime.datetime.now()}")
|
||||
logger.info(f"Python: {sys.version}")
|
||||
logger.info(f"Plataforma: {platform.platform()}")
|
||||
logger.info(f"Directorio de trabajo: {Path.cwd()}")
|
||||
logger.info(f"Argumentos: {sys.argv}")
|
||||
logger.info(f"Archivo de log: {log_file}")
|
||||
logger.info("-" * 60)
|
||||
|
||||
return logger, log_file
|
||||
|
||||
|
||||
def log_system_info(logger):
|
||||
"""Registra información detallada del sistema"""
|
||||
try:
|
||||
logger.info("INFORMACIÓN DEL SISTEMA:")
|
||||
logger.info(f" OS: {platform.system()} {platform.release()}")
|
||||
logger.info(f" Arquitectura: {platform.machine()}")
|
||||
logger.info(f" Procesador: {platform.processor()}")
|
||||
logger.info(f" Python executable: {sys.executable}")
|
||||
logger.info(f" Python path: {sys.path[:3]}...") # Solo primeros 3 elementos
|
||||
|
||||
# Información de memoria si está disponible
|
||||
try:
|
||||
import psutil
|
||||
memory = psutil.virtual_memory()
|
||||
logger.info(f" RAM total: {memory.total // (1024**3)} GB")
|
||||
logger.info(f" RAM disponible: {memory.available // (1024**3)} GB")
|
||||
except ImportError:
|
||||
logger.info(" Información de memoria: No disponible (psutil no instalado)")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error obteniendo información del sistema: {e}")
|
||||
|
||||
|
||||
def log_error_with_context(logger, error, context=""):
|
||||
"""Registra un error con contexto completo"""
|
||||
logger.error("=" * 50)
|
||||
logger.error("ERROR DETECTADO")
|
||||
logger.error("=" * 50)
|
||||
if context:
|
||||
logger.error(f"Contexto: {context}")
|
||||
logger.error(f"Tipo de error: {type(error).__name__}")
|
||||
logger.error(f"Mensaje: {str(error)}")
|
||||
logger.error("Traceback completo:")
|
||||
logger.error(traceback.format_exc())
|
||||
logger.error("Variables locales en el momento del error:")
|
||||
|
||||
# Intentar capturar variables locales del frame donde ocurrió el error
|
||||
try:
|
||||
tb = traceback.extract_tb(error.__traceback__)
|
||||
if tb:
|
||||
last_frame = tb[-1]
|
||||
logger.error(f" Archivo: {last_frame.filename}")
|
||||
logger.error(f" Línea: {last_frame.lineno}")
|
||||
logger.error(f" Función: {last_frame.name}")
|
||||
logger.error(f" Código: {last_frame.line}")
|
||||
except Exception as frame_error:
|
||||
logger.error(f"No se pudieron obtener detalles del frame: {frame_error}")
|
||||
|
||||
logger.error("=" * 50)
|
||||
|
||||
|
||||
def show_error_with_log_info(error, log_file, context=""):
|
||||
"""Muestra error al usuario con información del log"""
|
||||
error_msg = f"""Error en Calculadora MAV:
|
||||
|
||||
{context}
|
||||
|
||||
Error: {type(error).__name__}: {str(error)}
|
||||
|
||||
INFORMACIÓN DE DEBUGGING:
|
||||
• Log completo guardado en: {log_file}
|
||||
• Para soporte, enviar el archivo de log
|
||||
• Timestamp: {datetime.datetime.now()}
|
||||
|
||||
¿Qué hacer ahora?
|
||||
1. Revisar el archivo de log para más detalles
|
||||
2. Intentar reiniciar la aplicación
|
||||
3. Verificar dependencias con: python launcher.py --setup
|
||||
4. Ejecutar tests con: python launcher.py --test
|
||||
"""
|
||||
|
||||
try:
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
|
||||
# Crear ventana de error personalizada
|
||||
error_window = tk.Toplevel(root)
|
||||
error_window.title("Error - Calculadora MAV")
|
||||
error_window.geometry("600x400")
|
||||
error_window.configure(bg="#2b2b2b")
|
||||
|
||||
# Hacer la ventana modal
|
||||
error_window.transient(root)
|
||||
error_window.grab_set()
|
||||
|
||||
# Centrar ventana
|
||||
error_window.update_idletasks()
|
||||
x = (error_window.winfo_screenwidth() // 2) - (error_window.winfo_width() // 2)
|
||||
y = (error_window.winfo_screenheight() // 2) - (error_window.winfo_height() // 2)
|
||||
error_window.geometry(f"+{x}+{y}")
|
||||
|
||||
# Contenido
|
||||
from tkinter import scrolledtext
|
||||
|
||||
text_widget = scrolledtext.ScrolledText(
|
||||
error_window,
|
||||
font=("Consolas", 10),
|
||||
bg="#1e1e1e",
|
||||
fg="#ff6b6b",
|
||||
wrap=tk.WORD
|
||||
)
|
||||
text_widget.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
text_widget.insert("1.0", error_msg)
|
||||
text_widget.config(state="disabled")
|
||||
|
||||
# Botones
|
||||
button_frame = tk.Frame(error_window, bg="#2b2b2b")
|
||||
button_frame.pack(fill=tk.X, padx=10, pady=5)
|
||||
|
||||
def open_log_folder():
|
||||
try:
|
||||
if platform.system() == "Windows":
|
||||
os.startfile(log_file.parent)
|
||||
elif platform.system() == "Darwin": # macOS
|
||||
subprocess.run(["open", str(log_file.parent)])
|
||||
else: # Linux
|
||||
subprocess.run(["xdg-open", str(log_file.parent)])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def copy_log_path():
|
||||
error_window.clipboard_clear()
|
||||
error_window.clipboard_append(str(log_file))
|
||||
|
||||
tk.Button(
|
||||
button_frame,
|
||||
text="Abrir Carpeta de Logs",
|
||||
command=open_log_folder,
|
||||
bg="#4fc3f7",
|
||||
fg="white"
|
||||
).pack(side=tk.LEFT, padx=5)
|
||||
|
||||
tk.Button(
|
||||
button_frame,
|
||||
text="Copiar Ruta del Log",
|
||||
command=copy_log_path,
|
||||
bg="#82aaff",
|
||||
fg="white"
|
||||
).pack(side=tk.LEFT, padx=5)
|
||||
|
||||
tk.Button(
|
||||
button_frame,
|
||||
text="Cerrar",
|
||||
command=error_window.destroy,
|
||||
bg="#ff6b6b",
|
||||
fg="white"
|
||||
).pack(side=tk.RIGHT, padx=5)
|
||||
|
||||
# Esperar a que se cierre la ventana
|
||||
error_window.wait_window()
|
||||
root.destroy()
|
||||
|
||||
except Exception as gui_error:
|
||||
# Si falla la GUI, mostrar en consola
|
||||
print("ERROR: No se pudo mostrar ventana de error")
|
||||
print(error_msg)
|
||||
print(f"Error adicional: {gui_error}")
|
||||
|
||||
|
||||
# Variable global para el logger
|
||||
logger = None
|
||||
log_file = None
|
||||
|
||||
|
||||
def check_dependencies():
|
||||
"""Verifica que todas las dependencias estén disponibles"""
|
||||
logger.info("Verificando dependencias...")
|
||||
|
||||
required_modules = {
|
||||
'sympy': 'SymPy (motor algebraico)',
|
||||
'matplotlib': 'Matplotlib (plotting)',
|
||||
'numpy': 'NumPy (cálculos numéricos)',
|
||||
'tkinter': 'Tkinter (interfaz gráfica)'
|
||||
}
|
||||
|
||||
missing_modules = []
|
||||
|
||||
for module, description in required_modules.items():
|
||||
try:
|
||||
if module == 'tkinter':
|
||||
import tkinter
|
||||
logger.info(f"✅ {module} - {description}")
|
||||
else:
|
||||
importlib.import_module(module)
|
||||
logger.info(f"✅ {module} - {description}")
|
||||
except ImportError as e:
|
||||
missing_modules.append((module, description))
|
||||
logger.error(f"❌ {module} - {description} - Error: {e}")
|
||||
|
||||
if missing_modules:
|
||||
logger.warning(f"Módulos faltantes: {[m[0] for m in missing_modules]}")
|
||||
else:
|
||||
logger.info("Todas las dependencias están disponibles")
|
||||
|
||||
return missing_modules
|
||||
|
||||
|
||||
def install_missing_dependencies(missing_modules):
|
||||
"""Intenta instalar dependencias faltantes"""
|
||||
logger.info("Intentando instalar dependencias faltantes...")
|
||||
|
||||
installable_modules = [
|
||||
('sympy', 'sympy>=1.12'),
|
||||
('matplotlib', 'matplotlib>=3.7.0'),
|
||||
('numpy', 'numpy>=1.24.0')
|
||||
]
|
||||
|
||||
modules_to_install = []
|
||||
for module_name, _ in missing_modules:
|
||||
for inst_name, inst_package in installable_modules:
|
||||
if module_name == inst_name:
|
||||
modules_to_install.append(inst_package)
|
||||
break
|
||||
|
||||
if not modules_to_install:
|
||||
logger.info("No hay módulos para instalar automáticamente")
|
||||
return True
|
||||
|
||||
logger.info(f"Instalando: {', '.join(modules_to_install)}")
|
||||
|
||||
try:
|
||||
cmd = [sys.executable, "-m", "pip", "install"] + modules_to_install
|
||||
logger.info(f"Ejecutando comando: {' '.join(cmd)}")
|
||||
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300 # 5 minutos timeout
|
||||
)
|
||||
|
||||
logger.info(f"Código de salida pip: {result.returncode}")
|
||||
logger.info(f"Stdout pip: {result.stdout}")
|
||||
|
||||
if result.stderr:
|
||||
logger.warning(f"Stderr pip: {result.stderr}")
|
||||
|
||||
return result.returncode == 0
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.error("Timeout durante instalación de dependencias")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Error durante instalación: {e}")
|
||||
log_error_with_context(logger, e, "Instalación de dependencias")
|
||||
return False
|
||||
|
||||
|
||||
def show_dependency_error(missing_modules):
|
||||
"""Muestra error de dependencias faltantes"""
|
||||
error_msg = "Dependencias faltantes:\n\n"
|
||||
for module, description in missing_modules:
|
||||
error_msg += f"• {module}: {description}\n"
|
||||
|
||||
error_msg += "\nPara instalar dependencias:\n"
|
||||
error_msg += "pip install sympy matplotlib numpy\n\n"
|
||||
|
||||
if any(module == 'tkinter' for module, _ in missing_modules):
|
||||
error_msg += "Para tkinter en Linux:\n"
|
||||
error_msg += "sudo apt-get install python3-tk\n"
|
||||
|
||||
error_msg += "\nO ejecute: python launcher.py --setup"
|
||||
|
||||
logger.error("DEPENDENCIAS FALTANTES:")
|
||||
for module, description in missing_modules:
|
||||
logger.error(f" • {module}: {description}")
|
||||
|
||||
show_error_with_log_info(
|
||||
Exception("Dependencias faltantes"),
|
||||
log_file,
|
||||
"Faltan dependencias requeridas para ejecutar la aplicación"
|
||||
)
|
||||
|
||||
|
||||
def check_app_files():
|
||||
"""Verifica que todos los archivos de la aplicación estén presentes"""
|
||||
logger.info("Verificando archivos de la aplicación...")
|
||||
|
||||
required_files = [
|
||||
'tl_bracket_parser.py',
|
||||
'hybrid_base.py',
|
||||
'ip4_type.py',
|
||||
'hex_type.py',
|
||||
'bin_type.py',
|
||||
'dec_type.py',
|
||||
'chr_type.py',
|
||||
'main_evaluation.py',
|
||||
'tl_popup.py',
|
||||
'main_calc_app.py'
|
||||
]
|
||||
|
||||
current_dir = Path(__file__).parent
|
||||
missing_files = []
|
||||
|
||||
for filename in required_files:
|
||||
file_path = current_dir / filename
|
||||
if file_path.exists():
|
||||
logger.info(f"✅ {filename} - Tamaño: {file_path.stat().st_size} bytes")
|
||||
else:
|
||||
missing_files.append(filename)
|
||||
logger.error(f"❌ {filename} - No encontrado")
|
||||
|
||||
if missing_files:
|
||||
logger.error(f"Archivos faltantes: {missing_files}")
|
||||
else:
|
||||
logger.info("Todos los archivos de la aplicación están presentes")
|
||||
|
||||
return missing_files
|
||||
|
||||
|
||||
def show_file_error(missing_files):
|
||||
"""Muestra error de archivos faltantes"""
|
||||
error_msg = "Archivos de aplicación faltantes:\n\n"
|
||||
for filename in missing_files:
|
||||
error_msg += f"• {filename}\n"
|
||||
|
||||
error_msg += "\nAsegúrese de tener todos los archivos del proyecto."
|
||||
|
||||
logger.error("ARCHIVOS FALTANTES:")
|
||||
for filename in missing_files:
|
||||
logger.error(f" • {filename}")
|
||||
|
||||
show_error_with_log_info(
|
||||
Exception("Archivos faltantes"),
|
||||
log_file,
|
||||
"Faltan archivos necesarios para la aplicación"
|
||||
)
|
||||
|
||||
|
||||
def launch_application():
|
||||
"""Lanza la aplicación principal"""
|
||||
logger.info("Iniciando aplicación principal...")
|
||||
|
||||
try:
|
||||
# Importar y ejecutar la aplicación
|
||||
logger.info("Importando módulo principal...")
|
||||
from main_calc_app import HybridCalculatorApp
|
||||
|
||||
logger.info("Creando ventana principal...")
|
||||
root = tk.Tk()
|
||||
|
||||
logger.info("Inicializando aplicación...")
|
||||
app = HybridCalculatorApp(root)
|
||||
|
||||
logger.info("✅ Calculadora MAV - CAS Híbrido iniciada correctamente")
|
||||
logger.info("Iniciando loop principal de tkinter...")
|
||||
|
||||
root.mainloop()
|
||||
|
||||
logger.info("Aplicación cerrada normalmente")
|
||||
|
||||
except ImportError as e:
|
||||
logger.error(f"Error de importación: {e}")
|
||||
log_error_with_context(logger, e, "Importación de módulos de la aplicación")
|
||||
show_error_with_log_info(e, log_file, "Error importando módulos de la aplicación")
|
||||
except Exception as e:
|
||||
logger.error(f"Error durante ejecución de la aplicación: {e}")
|
||||
log_error_with_context(logger, e, "Ejecución de la aplicación principal")
|
||||
show_error_with_log_info(e, log_file, "Error durante la ejecución de la aplicación")
|
||||
|
||||
|
||||
def main():
|
||||
"""Función principal del launcher"""
|
||||
global logger, log_file
|
||||
|
||||
# Configurar logging al inicio
|
||||
try:
|
||||
logger, log_file = setup_logging()
|
||||
log_system_info(logger)
|
||||
except Exception as e:
|
||||
print(f"ERROR: No se pudo configurar logging: {e}")
|
||||
# Continuar sin logging si falla
|
||||
logger = None
|
||||
log_file = None
|
||||
|
||||
logger.info("Iniciando verificaciones del sistema...")
|
||||
|
||||
try:
|
||||
# Verificar archivos de aplicación
|
||||
logger.info("Verificando archivos de aplicación...")
|
||||
missing_files = check_app_files()
|
||||
if missing_files:
|
||||
logger.error("❌ Archivos faltantes detectados")
|
||||
show_file_error(missing_files)
|
||||
logger.info("Cerrando debido a archivos faltantes")
|
||||
sys.exit(1)
|
||||
|
||||
logger.info("✅ Todos los archivos están presentes")
|
||||
|
||||
# Verificar dependencias
|
||||
logger.info("Verificando dependencias...")
|
||||
missing_deps = check_dependencies()
|
||||
if missing_deps:
|
||||
logger.warning("❌ Dependencias faltantes detectadas")
|
||||
|
||||
# Intentar instalación automática para módulos pip
|
||||
installable_deps = [
|
||||
(module, desc) for module, desc in missing_deps
|
||||
if module in ['sympy', 'matplotlib', 'numpy']
|
||||
]
|
||||
|
||||
if installable_deps:
|
||||
logger.info("Preguntando al usuario sobre instalación automática...")
|
||||
response = input("¿Instalar dependencias automáticamente? (s/n): ").lower().strip()
|
||||
logger.info(f"Respuesta del usuario: {response}")
|
||||
|
||||
if response in ['s', 'si', 'y', 'yes']:
|
||||
logger.info("Iniciando instalación automática...")
|
||||
if install_missing_dependencies(installable_deps):
|
||||
logger.info("✅ Dependencias instaladas correctamente")
|
||||
# Re-verificar después de instalación
|
||||
missing_deps = check_dependencies()
|
||||
else:
|
||||
logger.error("❌ Error durante instalación automática")
|
||||
else:
|
||||
logger.info("Usuario declinó instalación automática")
|
||||
|
||||
if missing_deps:
|
||||
logger.error("Dependencias aún faltantes, mostrando error al usuario")
|
||||
show_dependency_error(missing_deps)
|
||||
logger.info("Cerrando debido a dependencias faltantes")
|
||||
sys.exit(1)
|
||||
|
||||
logger.info("✅ Todas las dependencias disponibles")
|
||||
|
||||
# Splash screen eliminado
|
||||
logger.info("Splash screen ha sido eliminado.")
|
||||
|
||||
# Lanzar aplicación
|
||||
logger.info("Lanzando aplicación principal...")
|
||||
launch_application()
|
||||
|
||||
logger.info("Aplicación cerrada - fin de sesión")
|
||||
logger.info("=" * 60)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Aplicación interrumpida por el usuario (Ctrl+C)")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
logger.error("Error crítico en main():")
|
||||
log_error_with_context(logger, e, "Función principal del launcher")
|
||||
show_error_with_log_info(e, log_file, "Error crítico durante el inicio")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def show_help():
|
||||
"""Muestra ayuda del launcher"""
|
||||
help_text = """
|
||||
Calculadora MAV - CAS Híbrido Launcher
|
||||
|
||||
Uso: python launcher.py [opciones]
|
||||
|
||||
Opciones:
|
||||
--help Muestra esta ayuda
|
||||
--no-splash Inicia sin splash screen
|
||||
--test Ejecuta tests antes de iniciar
|
||||
--setup Ejecuta setup de dependencias
|
||||
--debug Activa logging detallado
|
||||
|
||||
Descripción:
|
||||
Sistema de álgebra computacional híbrido que combina
|
||||
SymPy con clases especializadas para networking,
|
||||
programación y cálculos numéricos.
|
||||
|
||||
Dependencias:
|
||||
- Python 3.8+
|
||||
- SymPy (motor algebraico)
|
||||
- Matplotlib (plotting)
|
||||
- NumPy (cálculos numéricos)
|
||||
- Tkinter (interfaz gráfica)
|
||||
|
||||
Logging:
|
||||
- Los logs se guardan automáticamente en: ./logs/
|
||||
- Cada ejecución genera un archivo con timestamp
|
||||
- En caso de error, se muestra la ubicación del log
|
||||
- Los logs incluyen información del sistema y debugging
|
||||
|
||||
Resolución de problemas:
|
||||
1. Revisar logs en ./logs/ para errores detallados
|
||||
2. Ejecutar: python launcher.py --test
|
||||
3. Verificar dependencias: python launcher.py --setup
|
||||
4. Modo debug: python launcher.py --debug
|
||||
|
||||
Para más información, consulte la documentación.
|
||||
"""
|
||||
print(help_text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Configurar logging básico para manejo de argumentos
|
||||
temp_logger = None
|
||||
temp_log_file = None
|
||||
|
||||
try:
|
||||
temp_logger, temp_log_file = setup_logging()
|
||||
temp_logger.info("Procesando argumentos de línea de comandos...")
|
||||
except:
|
||||
pass # Si falla el logging, continuar sin él
|
||||
|
||||
# Manejar argumentos de línea de comandos
|
||||
if "--help" in sys.argv or "-h" in sys.argv:
|
||||
if temp_logger:
|
||||
temp_logger.info("Mostrando ayuda")
|
||||
show_help()
|
||||
sys.exit(0)
|
||||
|
||||
if "--test" in sys.argv:
|
||||
if temp_logger:
|
||||
temp_logger.info("Ejecutando tests...")
|
||||
try:
|
||||
from test_suite import run_all_tests
|
||||
if not run_all_tests():
|
||||
error_msg = "❌ Tests fallaron - no se iniciará la aplicación"
|
||||
print(error_msg)
|
||||
if temp_logger:
|
||||
temp_logger.error(error_msg)
|
||||
sys.exit(1)
|
||||
success_msg = "✅ Tests pasaron - iniciando aplicación"
|
||||
print(success_msg)
|
||||
if temp_logger:
|
||||
temp_logger.info(success_msg)
|
||||
except ImportError as e:
|
||||
warning_msg = "⚠️ Tests no disponibles - continuando con inicio"
|
||||
print(warning_msg)
|
||||
if temp_logger:
|
||||
temp_logger.warning(f"{warning_msg} - Error: {e}")
|
||||
except Exception as e:
|
||||
if temp_logger:
|
||||
log_error_with_context(temp_logger, e, "Ejecución de tests")
|
||||
print(f"❌ Error ejecutando tests: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if "--setup" in sys.argv:
|
||||
if temp_logger:
|
||||
temp_logger.info("Ejecutando setup...")
|
||||
try:
|
||||
import setup_script
|
||||
setup_script.main()
|
||||
sys.exit(0)
|
||||
except ImportError:
|
||||
error_msg = "❌ Setup script no disponible"
|
||||
print(error_msg)
|
||||
if temp_logger:
|
||||
temp_logger.error(error_msg)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
if temp_logger:
|
||||
log_error_with_context(temp_logger, e, "Ejecución de setup")
|
||||
print(f"❌ Error en setup: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Logging adicional para debug
|
||||
if "--debug" in sys.argv:
|
||||
if temp_logger:
|
||||
temp_logger.setLevel(logging.DEBUG)
|
||||
temp_logger.info("Modo debug activado")
|
||||
|
||||
# Inicio normal
|
||||
try:
|
||||
main()
|
||||
except Exception as e:
|
||||
if temp_logger:
|
||||
log_error_with_context(temp_logger, e, "Error crítico en __main__")
|
||||
print(f"ERROR CRÍTICO: {e}")
|
||||
if temp_log_file:
|
||||
print(f"Ver log completo en: {temp_log_file}")
|
||||
sys.exit(1)
|
189
main_calc_app.py
189
main_calc_app.py
|
@ -6,6 +6,7 @@ from tkinter import scrolledtext, messagebox, Menu, filedialog
|
|||
import tkinter.font as tkFont
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path # Added for robust path handling
|
||||
import threading
|
||||
from typing import List, Dict, Any, Optional
|
||||
import re
|
||||
|
@ -13,13 +14,13 @@ import re
|
|||
# Importar componentes del CAS híbrido
|
||||
from main_evaluation import HybridEvaluationEngine, EvaluationResult
|
||||
from tl_popup import InteractiveResultManager
|
||||
from ip4_type import HybridIP4 as IP4
|
||||
from hex_type import HybridHex as Hex
|
||||
from bin_type import HybridBin as Bin
|
||||
from dec_type import HybridDec as Dec
|
||||
from chr_type import HybridChr as Chr
|
||||
from ip4_type import Class_IP4
|
||||
from hex_type import Class_Hex
|
||||
from bin_type import Class_Bin
|
||||
from dec_type import Class_Dec
|
||||
from chr_type import Class_Chr
|
||||
import sympy
|
||||
from sympy_helper import Helper as SympyHelper
|
||||
from sympy_helper import SympyTools as SympyHelper
|
||||
|
||||
|
||||
class HybridCalculatorApp:
|
||||
|
@ -29,12 +30,12 @@ class HybridCalculatorApp:
|
|||
HISTORY_FILE = "hybrid_calc_history.txt"
|
||||
|
||||
HELPERS = [
|
||||
IP4.Helper,
|
||||
Hex.Helper,
|
||||
Bin.Helper,
|
||||
Dec.Helper,
|
||||
Chr.Helper,
|
||||
SympyHelper,
|
||||
Class_IP4.Helper,
|
||||
Class_Hex.Helper,
|
||||
Class_Bin.Helper,
|
||||
Class_Dec.Helper,
|
||||
Class_Chr.Helper,
|
||||
SympyHelper.Helper,
|
||||
]
|
||||
|
||||
def __init__(self, root: tk.Tk):
|
||||
|
@ -70,10 +71,23 @@ class HybridCalculatorApp:
|
|||
def _setup_icon(self):
|
||||
"""Configura el ícono de la aplicación"""
|
||||
try:
|
||||
self.app_icon = tk.PhotoImage(file="icon.png")
|
||||
# Construct path relative to this script file (main_calc_app.py)
|
||||
script_dir = Path(__file__).resolve().parent
|
||||
icon_path = script_dir / "icon.png"
|
||||
|
||||
if not icon_path.is_file():
|
||||
print(f"Advertencia: Archivo de ícono no encontrado en '{icon_path}'.")
|
||||
# Optionally, set a default Tk icon or simply return
|
||||
return
|
||||
|
||||
self.app_icon = tk.PhotoImage(file=str(icon_path))
|
||||
self.root.iconphoto(True, self.app_icon)
|
||||
except tk.TclError:
|
||||
print("Advertencia: No se pudo cargar 'icon.png' como ícono.")
|
||||
except tk.TclError as e:
|
||||
# Provide more specific error, including the path and Tkinter's error message
|
||||
print(f"Advertencia: No se pudo cargar el ícono desde '{icon_path}'. Error de Tkinter: {e}")
|
||||
except Exception as e:
|
||||
# Catch other potential errors during icon loading
|
||||
print(f"Advertencia: Ocurrió un error inesperado al cargar el ícono desde '{icon_path}': {e}")
|
||||
|
||||
def _load_settings(self) -> Dict[str, Any]:
|
||||
"""Carga configuración de la aplicación"""
|
||||
|
@ -261,6 +275,8 @@ class HybridCalculatorApp:
|
|||
self.output_text.tag_configure("ip", foreground="#fff176")
|
||||
self.output_text.tag_configure("date", foreground="#ff8a80")
|
||||
self.output_text.tag_configure("chr_type", foreground="#80cbc4")
|
||||
# Agregar tag para ayuda contextual
|
||||
self.output_text.tag_configure("helper", foreground="#ffd700", font=("Consolas", 11, "italic"))
|
||||
|
||||
def on_key_release(self, event=None):
|
||||
"""Maneja eventos de teclado"""
|
||||
|
@ -279,9 +295,58 @@ class HybridCalculatorApp:
|
|||
cursor_index_str = self.input_text.index(tk.INSERT)
|
||||
line_num_str, char_num_str = cursor_index_str.split('.')
|
||||
current_line_num = int(line_num_str)
|
||||
char_idx_of_dot = int(char_num_str)
|
||||
obj_expr_str = self.input_text.get(f"{current_line_num}.0", f"{current_line_num}.{char_idx_of_dot -1}").strip()
|
||||
char_idx_after_dot = int(char_num_str)
|
||||
|
||||
if char_idx_after_dot == 0: # Should not happen if a dot was typed
|
||||
print("DEBUG: _handle_dot_autocomplete called with cursor at beginning of line somehow.")
|
||||
return
|
||||
|
||||
text_before_dot = self.input_text.get(f"{current_line_num}.0", f"{current_line_num}.{char_idx_after_dot - 1}")
|
||||
|
||||
# Si no hay nada o solo espacios antes del punto, ofrecer sugerencias globales
|
||||
if not text_before_dot.strip():
|
||||
print("DEBUG: Dot on empty line or after spaces. Offering global suggestions.")
|
||||
suggestions = []
|
||||
custom_classes_suggestions = [
|
||||
("Hex", "Tipo Hexadecimal. Ej: Hex[FF]"),
|
||||
("Bin", "Tipo Binario. Ej: Bin[1010]"),
|
||||
("Dec", "Tipo Decimal. Ej: Dec[42]"),
|
||||
("IP4", "Tipo Dirección IPv4. Ej: IP4[1.2.3.4/24]"),
|
||||
("Chr", "Tipo Carácter. Ej: Chr[A]"),
|
||||
]
|
||||
suggestions.extend(custom_classes_suggestions)
|
||||
|
||||
try:
|
||||
sympy_functions = SympyHelper.PopupFunctionList()
|
||||
if sympy_functions:
|
||||
suggestions.extend(sympy_functions)
|
||||
except Exception as e:
|
||||
print(f"DEBUG: Error calling SympyHelper.PopupFunctionList() for global: {e}")
|
||||
|
||||
if suggestions:
|
||||
self._show_autocomplete_popup(suggestions, is_global_popup=True)
|
||||
return
|
||||
|
||||
# Si hay texto antes del punto, es para autocompletado de métodos de un objeto
|
||||
obj_expr_str = text_before_dot.strip()
|
||||
print(f"DEBUG: Autocomplete triggered for object. Expression: '{obj_expr_str}'")
|
||||
|
||||
if not obj_expr_str:
|
||||
# Esto no debería ocurrir si la lógica anterior para popup global es correcta
|
||||
print("DEBUG: Object expression is empty. No autocomplete.")
|
||||
return
|
||||
|
||||
# Caso especial para el módulo sympy
|
||||
if obj_expr_str == "sympy":
|
||||
print(f"DEBUG: Detected 'sympy.', using SympyHelper for suggestions.")
|
||||
try:
|
||||
methods = SympyHelper.PopupFunctionList()
|
||||
if methods:
|
||||
self._show_autocomplete_popup(methods, is_global_popup=False)
|
||||
else:
|
||||
print(f"DEBUG: SympyHelper.PopupFunctionList returned no methods.")
|
||||
except Exception as e:
|
||||
print(f"DEBUG: Error calling SympyHelper.PopupFunctionList(): {e}")
|
||||
return
|
||||
|
||||
# Preprocesar para convertir sintaxis de corchetes a llamada de clase
|
||||
|
@ -289,24 +354,28 @@ class HybridCalculatorApp:
|
|||
bracket_match = re.match(r"([A-Za-z_][A-Za-z0-9_]*)\[(.*)\]$", obj_expr_str)
|
||||
if bracket_match:
|
||||
class_name, arg = bracket_match.groups()
|
||||
# Si el argumento es un número, no poner comillas
|
||||
if arg.isdigit():
|
||||
obj_expr_str = f"{class_name}({arg})"
|
||||
else:
|
||||
obj_expr_str = f"{class_name}('{arg}')"
|
||||
print(f"DEBUG: Preprocessed bracket syntax to: '{obj_expr_str}'")
|
||||
|
||||
eval_context = self.engine._get_full_context() if hasattr(self.engine, '_get_full_context') else {}
|
||||
obj = None
|
||||
try:
|
||||
print(f"DEBUG: Attempting to eval: '{obj_expr_str}'")
|
||||
obj = eval(obj_expr_str, eval_context)
|
||||
except Exception:
|
||||
print(f"DEBUG: Eval successful. Object: {type(obj)}, Value: {obj}")
|
||||
except Exception as e:
|
||||
print(f"DEBUG: Error evaluating object expression '{obj_expr_str}': {e}")
|
||||
return
|
||||
|
||||
if obj is not None and hasattr(obj, 'PopupFunctionList'):
|
||||
methods = obj.PopupFunctionList()
|
||||
if methods:
|
||||
self._show_autocomplete_popup(methods)
|
||||
self._show_autocomplete_popup(methods, is_global_popup=False)
|
||||
|
||||
def _show_autocomplete_popup(self, suggestions):
|
||||
def _show_autocomplete_popup(self, suggestions, is_global_popup=False):
|
||||
# suggestions: lista de tuplas (nombre, hint)
|
||||
cursor_bbox = self.input_text.bbox(tk.INSERT)
|
||||
if not cursor_bbox:
|
||||
|
@ -330,18 +399,29 @@ class HybridCalculatorApp:
|
|||
self._autocomplete_listbox.select_set(0)
|
||||
self._autocomplete_listbox.pack(expand=True, fill=tk.BOTH)
|
||||
self._autocomplete_listbox.bind("<Return>", self._on_autocomplete_select)
|
||||
self._autocomplete_listbox.bind("<Tab>", self._on_autocomplete_select)
|
||||
self._autocomplete_listbox.bind("<Escape>", lambda e: self._close_autocomplete_popup())
|
||||
self._autocomplete_listbox.bind("<Double-Button-1>", self._on_autocomplete_select)
|
||||
self._autocomplete_listbox.bind("<Double-Button-1>", lambda e, g=is_global_popup: self._on_autocomplete_select(e, is_global=g))
|
||||
self._autocomplete_listbox.focus_set()
|
||||
self._autocomplete_listbox.bind("<Up>", lambda e: self._navigate_autocomplete(e, -1))
|
||||
self._autocomplete_listbox.bind("<Down>", lambda e: self._navigate_autocomplete(e, 1))
|
||||
self.input_text.bind("<FocusOut>", lambda e: self._close_autocomplete_popup(), add=True)
|
||||
|
||||
# self.input_text.bind("<FocusOut>", lambda e: self._close_autocomplete_popup(), add=True) # Removed: Caused popup to close immediately
|
||||
self.input_text.bind("<Button-1>", lambda e: self._close_autocomplete_popup(), add=True)
|
||||
self.root.bind("<Button-1>", lambda e: self._close_autocomplete_popup(), add=True)
|
||||
self.input_text.bind("<Key>", lambda e: self._close_autocomplete_popup(), add=True)
|
||||
# self.input_text.bind("<Key>", lambda e: self._close_autocomplete_popup(), add=True) # Removed: Too aggressive
|
||||
|
||||
# Pasar el flag is_global_popup a los bindings que llaman a _on_autocomplete_select
|
||||
self._autocomplete_listbox.bind("<Return>", lambda e, g=is_global_popup: self._on_autocomplete_select(e, is_global=g))
|
||||
self._autocomplete_listbox.bind("<Tab>", lambda e, g=is_global_popup: self._on_autocomplete_select(e, is_global=g))
|
||||
|
||||
max_len = max(len(name) for name, _ in suggestions) if suggestions else 10
|
||||
width = max(15, min(max_len + 10, 50))
|
||||
height = min(len(suggestions), 10)
|
||||
# Calcular el ancho basado en el texto completo que se muestra en el listbox
|
||||
full_text_suggestions = [f"{name} — {hint}" for name, hint in suggestions]
|
||||
max_full_len = max(len(text) for text in full_text_suggestions) if full_text_suggestions else 20
|
||||
width = max(20, min(max_full_len + 5, 80)) # Ajustar el +5 y el límite 80 según sea necesario
|
||||
self._autocomplete_listbox.config(width=width, height=height)
|
||||
else:
|
||||
self._close_autocomplete_popup()
|
||||
|
@ -363,17 +443,39 @@ class HybridCalculatorApp:
|
|||
self._autocomplete_listbox.see(new_idx)
|
||||
return "break"
|
||||
|
||||
def _on_autocomplete_select(self, event):
|
||||
def _on_autocomplete_select(self, event, is_global=False):
|
||||
if not hasattr(self, '_autocomplete_listbox') or not self._autocomplete_listbox:
|
||||
return "break"
|
||||
selection = self._autocomplete_listbox.curselection()
|
||||
if not selection:
|
||||
self._close_autocomplete_popup()
|
||||
return "break"
|
||||
selected = self._autocomplete_listbox.get(selection[0])
|
||||
method_name = selected.split()[0]
|
||||
self.input_text.insert(tk.INSERT, method_name + "()")
|
||||
self.input_text.mark_set(tk.INSERT, f"{tk.INSERT}-1c") # Cursor dentro de los paréntesis
|
||||
|
||||
selected_text_with_hint = self._autocomplete_listbox.get(selection[0])
|
||||
# Extraer solo el nombre del ítem, antes de " —"
|
||||
item_name = selected_text_with_hint.split(" —")[0].strip()
|
||||
|
||||
if is_global:
|
||||
# Eliminar el punto que activó el popup y luego insertar el nombre
|
||||
cursor_pos_str = self.input_text.index(tk.INSERT) # Posición actual (después del punto)
|
||||
line_num, char_num = map(int, cursor_pos_str.split('.'))
|
||||
|
||||
# El punto está en char_num - 1 en la línea actual
|
||||
dot_pos_on_line = char_num - 1
|
||||
dot_index_str = f"{line_num}.{dot_pos_on_line}"
|
||||
|
||||
self.input_text.delete(dot_index_str)
|
||||
|
||||
# Insertar el nombre de la función/clase seguido de "()"
|
||||
insert_text = item_name + "()"
|
||||
self.input_text.insert(dot_index_str, insert_text)
|
||||
# Colocar cursor dentro de los paréntesis: después del nombre y el '('
|
||||
self.input_text.mark_set(tk.INSERT, f"{dot_index_str}+{len(item_name)+1}c")
|
||||
else:
|
||||
# Comportamiento existente para métodos de objeto
|
||||
self.input_text.insert(tk.INSERT, item_name + "()")
|
||||
self.input_text.mark_set(tk.INSERT, f"{tk.INSERT}-1c") # Cursor dentro de los paréntesis
|
||||
|
||||
self._close_autocomplete_popup()
|
||||
self.input_text.focus_set()
|
||||
self.on_key_release() # Trigger re-evaluation
|
||||
|
@ -383,6 +485,17 @@ class HybridCalculatorApp:
|
|||
if hasattr(self, '_autocomplete_popup') and self._autocomplete_popup:
|
||||
self._autocomplete_popup.destroy()
|
||||
self._autocomplete_popup = None
|
||||
# Consider unbinding the Button-1 events if they were stored with IDs,
|
||||
# for now, their guard condition `if self._autocomplete_popup:` handles multiple calls.
|
||||
# Example of how to unbind if IDs were stored:
|
||||
# if hasattr(self, '_input_text_b1_bind_id'):
|
||||
# self.input_text.unbind("<Button-1>", self._input_text_b1_bind_id)
|
||||
# del self._input_text_b1_bind_id
|
||||
# if hasattr(self, '_root_b1_bind_id'):
|
||||
# self.root.unbind("<Button-1>", self._root_b1_bind_id)
|
||||
# del self._root_b1_bind_id
|
||||
|
||||
if hasattr(self, '_autocomplete_listbox') and self._autocomplete_listbox:
|
||||
self._autocomplete_listbox = None
|
||||
|
||||
def _evaluate_and_update(self):
|
||||
|
@ -428,7 +541,11 @@ class HybridCalculatorApp:
|
|||
if result.is_error:
|
||||
ayuda = self.obtener_ayuda(result.original_line)
|
||||
if ayuda:
|
||||
output_parts.append(("helper", ayuda))
|
||||
# Mostrar ayuda en un solo renglón, truncando si es necesario
|
||||
ayuda_linea = ayuda.replace("\n", " ").replace("\r", " ")
|
||||
if len(ayuda_linea) > 120:
|
||||
ayuda_linea = ayuda_linea[:117] + "..."
|
||||
output_parts.append(("helper", ayuda_linea))
|
||||
else:
|
||||
output_parts.append(("error", f"Error: {result.error}"))
|
||||
elif result.result_type == "comment":
|
||||
|
@ -445,9 +562,7 @@ class HybridCalculatorApp:
|
|||
|
||||
# Verificar si es resultado interactivo
|
||||
if self.interactive_manager and result.is_interactive:
|
||||
interactive_tag, display_text = self.interactive_manager.create_interactive_tag(
|
||||
result.result, self.output_text, "1.0"
|
||||
)
|
||||
interactive_tag, display_text = self.interactive_manager.create_interactive_tag(result.result, self.output_text, "1.0")
|
||||
if interactive_tag:
|
||||
output_parts.append((interactive_tag, display_text))
|
||||
else:
|
||||
|
@ -467,13 +582,13 @@ class HybridCalculatorApp:
|
|||
|
||||
def _get_result_tag(self, result: Any) -> str:
|
||||
"""Determina el tag de color para un resultado"""
|
||||
if isinstance(result, Hex):
|
||||
if isinstance(result, Class_Hex):
|
||||
return "hex"
|
||||
elif isinstance(result, Bin):
|
||||
elif isinstance(result, Class_Bin):
|
||||
return "bin"
|
||||
elif isinstance(result, IP4):
|
||||
elif isinstance(result, Class_IP4):
|
||||
return "ip"
|
||||
elif isinstance(result, Chr):
|
||||
elif isinstance(result, Class_Chr):
|
||||
return "chr_type"
|
||||
elif isinstance(result, sympy.Basic):
|
||||
return "symbolic"
|
||||
|
|
|
@ -10,12 +10,12 @@ from contextlib import contextmanager
|
|||
|
||||
from tl_bracket_parser import BracketParser
|
||||
from tl_popup import PlotResult
|
||||
from hybrid_base import HybridCalcType
|
||||
from ip4_type import HybridIP4 as IP4
|
||||
from hex_type import HybridHex as Hex
|
||||
from bin_type import HybridBin as Bin
|
||||
from dec_type import HybridDec as Dec
|
||||
from chr_type import HybridChr as Chr
|
||||
from sympy_Base import SympyClassBase
|
||||
from ip4_type import Class_IP4 as Class_IP4
|
||||
from hex_type import Class_Hex as Class_Hex
|
||||
from bin_type import Class_Bin as Class_Bin
|
||||
from dec_type import Class_Dec as Class_Dec
|
||||
from chr_type import Class_Chr as Class_Chr
|
||||
|
||||
|
||||
class HybridEvaluationEngine:
|
||||
|
@ -85,17 +85,17 @@ class HybridEvaluationEngine:
|
|||
|
||||
# Clases especializadas
|
||||
specialized_classes = {
|
||||
'Hex': Hex,
|
||||
'Bin': Bin,
|
||||
'Dec': Dec,
|
||||
'IP4': IP4,
|
||||
'Chr': Chr,
|
||||
'Hex': Class_Hex,
|
||||
'Bin': Class_Bin,
|
||||
'Dec': Class_Dec,
|
||||
'IP4': Class_IP4,
|
||||
'Chr': Class_Chr,
|
||||
# Alias en minúsculas
|
||||
'hex': Hex,
|
||||
'bin': Bin,
|
||||
'dec': Dec,
|
||||
'ip4': IP4,
|
||||
'chr': Chr,
|
||||
'hex': Class_Hex,
|
||||
'bin': Class_Bin,
|
||||
'dec': Class_Dec,
|
||||
'ip4': Class_IP4,
|
||||
'chr': Class_Chr,
|
||||
}
|
||||
|
||||
# Funciones de utilidad
|
||||
|
@ -314,7 +314,7 @@ class HybridEvaluationEngine:
|
|||
result = eval(expression, {"__builtins__": {}}, context)
|
||||
|
||||
# Si el resultado es un objeto híbrido, integrarlo con SymPy si es necesario
|
||||
if isinstance(result, HybridCalcType):
|
||||
if isinstance(result, SympyClassBase):
|
||||
return result
|
||||
elif isinstance(result, PlotResult):
|
||||
if self.debug:
|
||||
|
|
|
@ -7,7 +7,7 @@ from typing import Any, Optional, Dict
|
|||
import re
|
||||
|
||||
|
||||
class HybridCalcType(Basic):
|
||||
class SympyClassBase(Basic):
|
||||
"""
|
||||
Clase base híbrida que combina SymPy Basic con funcionalidad de calculadora
|
||||
Todas las clases especializadas deben heredar de esta
|
|
@ -1,25 +1,59 @@
|
|||
import re
|
||||
|
||||
def Helper(input_str):
|
||||
"""Ayuda contextual para funciones SymPy comunes"""
|
||||
sympy_funcs = {
|
||||
class SympyTools:
|
||||
"""
|
||||
Utilidades para la integración con SymPy, incluyendo ayuda contextual
|
||||
y listas de funciones para autocompletado.
|
||||
"""
|
||||
_sympy_func_details = {
|
||||
"diff": "Derivada: diff(expr, var). Ej: diff(sin(x), x)",
|
||||
"integrate": "Integral: integrate(expr, var). Ej: integrate(x**2, x)",
|
||||
"solve": "Resolver ecuaciones: solve(expr, var). Ej: solve(x**2-1, x)",
|
||||
"limit": "Límite: limit(expr, var, valor). Ej: limit(sin(x)/x, x, 0)",
|
||||
"series": "Serie de Taylor: series(expr, var, punto, n). Ej: series(exp(x), x, 0, 5)",
|
||||
"Matrix": "Matrices: Matrix([[a, b], [c, d]]). Ej: Matrix([[1,2],[3,4]])",
|
||||
"plot": "Gráfica: plot(expr, (var, a, b)). Ej: plot(sin(x), (x, 0, 2*pi))",
|
||||
"plot": "Gráfica: plot(expr, (var, a, b)). Ej: plot(sin(x), (x, -2*pi, 2*pi))", # Corregido ejemplo de plot
|
||||
"plot3d": "Gráfica 3D: plot3d(expr, (x, a, b), (y, c, d))",
|
||||
"simplify": "Simplificar: simplify(expr). Ej: simplify((x**2 + 2*x + 1))",
|
||||
"expand": "Expandir: expand(expr). Ej: expand((x+1)**2)",
|
||||
"factor": "Factorizar: factor(expr). Ej: factor(x**2 + 2*x + 1)",
|
||||
"collect": "Agrupar: collect(expr, var). Ej: collect(x*y + x, x)",
|
||||
"cancel": "Cancelar: cancel(expr). Ej: cancel((x**2 + 2*x + 1)/(x+1))",
|
||||
"apart": "Fracciones parciales: apart(expr, var). Ej: apart(1/(x*(x+1)), x)",
|
||||
"apart": "Fracciones parciales: apart(expr). Ej: apart(1/(x*(x+1)))", # Var opcional en apart
|
||||
"together": "Unir fracciones: together(expr). Ej: together(1/x + 1/y)",
|
||||
}
|
||||
for func, ayuda in sympy_funcs.items():
|
||||
if input_str.strip().startswith(func):
|
||||
return ayuda
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def Helper(input_str: str) -> str | None:
|
||||
"""
|
||||
Ayuda contextual para funciones SymPy comunes.
|
||||
Se activa si alguna palabra en input_str es un prefijo de un nombre de función conocido.
|
||||
"""
|
||||
cleaned_input = input_str.strip()
|
||||
if not cleaned_input:
|
||||
return None
|
||||
|
||||
# Extraer palabras (posibles identificadores de función) de la entrada
|
||||
words_in_input = re.findall(r'[a-zA-Z_][a-zA-Z0-9_]*', cleaned_input)
|
||||
if not words_in_input:
|
||||
return None
|
||||
|
||||
# Iterar sobre las funciones de SymPy definidas
|
||||
for func_name, help_text in SympyTools._sympy_func_details.items():
|
||||
# Comprobar si alguna palabra de la entrada es un prefijo del nombre de la función
|
||||
for word in words_in_input:
|
||||
# len(word) > 0 para cumplir "no hay minimo" (una palabra no vacía)
|
||||
if func_name.startswith(word) and len(word) > 0:
|
||||
# Ej: func_name="solve", word="solv" -> True. Devuelve ayuda para "solve".
|
||||
# Ej: func_name="solve", word="s" -> True. Devuelve ayuda para "solve"
|
||||
# (o la primera función en el diccionario que empiece con "s").
|
||||
return help_text
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def PopupFunctionList():
|
||||
"""Lista de métodos y sus descripciones breves para el popup de autocompletado."""
|
||||
return [
|
||||
(name, desc.split(". Ej:")[0] if ". Ej:" in desc else desc)
|
||||
for name, desc in SympyTools._sympy_func_details.items()
|
||||
]
|
Loading…
Reference in New Issue