No funcionando el panel Latex
This commit is contained in:
parent
0629137956
commit
bb82098608
|
@ -0,0 +1,165 @@
|
||||||
|
# 🔧 CORRECCIONES APLICADAS - PROBLEMAS REPORTADOS
|
||||||
|
|
||||||
|
## 🐛 **PROBLEMAS IDENTIFICADOS Y SOLUCIONADOS**
|
||||||
|
|
||||||
|
### ❌ **PROBLEMA 1: Popups toman el foco y no permiten escribir**
|
||||||
|
|
||||||
|
**🔍 Causa:** Los popups usaban `Qt.Popup` que automáticamente toma el foco del widget padre.
|
||||||
|
|
||||||
|
**✅ Solución Aplicada:**
|
||||||
|
```python
|
||||||
|
# ANTES (PROBLEMÁTICO):
|
||||||
|
self._autocomplete_popup = QWidget(self, Qt.Popup | Qt.FramelessWindowHint)
|
||||||
|
|
||||||
|
# DESPUÉS (CORREGIDO):
|
||||||
|
self._autocomplete_popup = QWidget(None, Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
|
||||||
|
self._autocomplete_popup.setAttribute(Qt.WA_ShowWithoutActivating, True)
|
||||||
|
self._autocomplete_popup.setFocusPolicy(Qt.NoFocus)
|
||||||
|
```
|
||||||
|
|
||||||
|
**🎯 Cambios Específicos:**
|
||||||
|
- `Qt.Tool` en lugar de `Qt.Popup` - No roba foco automáticamente
|
||||||
|
- `Qt.WindowStaysOnTopHint` - Mantiene popup visible encima
|
||||||
|
- `WA_ShowWithoutActivating = True` - Muestra sin activar la ventana
|
||||||
|
- `setFocusPolicy(Qt.NoFocus)` - Widget nunca puede recibir foco
|
||||||
|
- `parent=None` - No hereda comportamiento de foco del padre
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ❌ **PROBLEMA 2: TAB no funciona para seleccionar**
|
||||||
|
|
||||||
|
**🔍 Causa:** El manejo de eventos de teclado era correcto, pero los popups con foco causaban interferencia.
|
||||||
|
|
||||||
|
**✅ Solución Aplicada:**
|
||||||
|
```python
|
||||||
|
# También aplicado a QListWidget:
|
||||||
|
self._autocomplete_listbox.setFocusPolicy(Qt.NoFocus)
|
||||||
|
```
|
||||||
|
|
||||||
|
**🎯 Verificación del Flujo:**
|
||||||
|
1. `_handle_key_press` detecta `Qt.Key_Tab` ✅
|
||||||
|
2. Llama a `_handle_tab_key()` ✅
|
||||||
|
3. Ejecuta `_select_autocomplete()` ✅
|
||||||
|
4. Ahora sin interferencia de foco ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ❌ **PROBLEMA 3: Links de plots no se generan**
|
||||||
|
|
||||||
|
**🔍 Causa:** Error en el nombre del atributo - buscaba `result.result_obj` pero el correcto es `result.actual_result_object`.
|
||||||
|
|
||||||
|
**✅ Solución Aplicada:**
|
||||||
|
```python
|
||||||
|
# ANTES (INCORRECTO):
|
||||||
|
if hasattr(result, 'result_obj') and isinstance(result.result_obj, PlotResult):
|
||||||
|
plot_obj = result.result_obj
|
||||||
|
|
||||||
|
# DESPUÉS (CORREGIDO):
|
||||||
|
if hasattr(result, 'actual_result_object') and isinstance(result.actual_result_object, PlotResult):
|
||||||
|
plot_obj = result.actual_result_object
|
||||||
|
```
|
||||||
|
|
||||||
|
**🎯 Verificación del Flujo:**
|
||||||
|
1. `main_evaluation_puro.py` establece `actual_result_object` ✅
|
||||||
|
2. `_process_evaluation_result` ahora detecta correctamente PlotResult ✅
|
||||||
|
3. Crea tupla `("clickeable", display_text, link_id, result_object)` ✅
|
||||||
|
4. `_display_output_lines` procesa y muestra link azul ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 **CORRECCIONES ADICIONALES APLICADAS**
|
||||||
|
|
||||||
|
### 🆕 **Variables de Estado Inicializadas**
|
||||||
|
```python
|
||||||
|
self._last_navigation_time = 0
|
||||||
|
self._last_input_change = 0
|
||||||
|
```
|
||||||
|
**Propósito:** Evitar errores en filtrado y navegación.
|
||||||
|
|
||||||
|
### 🎨 **Estilo de Popups Optimizado**
|
||||||
|
- Popup principal: **Azul** (`#4fc3f7`) para métodos y constructores
|
||||||
|
- Popup de variables: **Verde** (`#c3e88d`) para variables disponibles
|
||||||
|
- Ambos con `Qt.NoFocus` para comportamiento verdaderamente modeless
|
||||||
|
|
||||||
|
### 📍 **Posicionamiento Mejorado**
|
||||||
|
- `_position_popup_modeless()` posiciona por debajo de la línea actual
|
||||||
|
- Detección de bordes de pantalla y reposicionamiento automático
|
||||||
|
- Offset de 5px para separación visual
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 **ARCHIVO DE PRUEBA CREADO**
|
||||||
|
|
||||||
|
### 📁 `test_debug_problemas.py`
|
||||||
|
- Script específico para verificar las correcciones
|
||||||
|
- Debug en tiempo real del estado de los popups
|
||||||
|
- Secuencias de prueba paso a paso
|
||||||
|
- Criterios de éxito claramente definidos
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ **RESULTADOS ESPERADOS**
|
||||||
|
|
||||||
|
### 🎯 **Comportamiento Correcto Ahora:**
|
||||||
|
|
||||||
|
1. **✅ Popups Modeless:**
|
||||||
|
- Aparecen sin robar foco del input
|
||||||
|
- Permiten continuar escribiendo mientras están visibles
|
||||||
|
- Se posicionan de forma no intrusiva
|
||||||
|
|
||||||
|
2. **✅ TAB Funcional:**
|
||||||
|
- Selecciona la opción resaltada en el popup
|
||||||
|
- Cierra el popup después de seleccionar
|
||||||
|
- Inserta el texto seleccionado en la posición correcta
|
||||||
|
|
||||||
|
3. **✅ Links de Plots:**
|
||||||
|
- Se generan automáticamente para `PlotResult`
|
||||||
|
- Aparecen como links azules subrayados
|
||||||
|
- Click muestra plot en MathJax primero
|
||||||
|
- Segundo click abre ventana de edición
|
||||||
|
|
||||||
|
4. **✅ Navegación Completa:**
|
||||||
|
- Flechas ↑↓ navegan opciones
|
||||||
|
- TAB selecciona opción actual
|
||||||
|
- ESC cierra popup
|
||||||
|
- Enter también selecciona (alternativa a TAB)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 **INSTRUCCIONES DE VERIFICACIÓN**
|
||||||
|
|
||||||
|
### 📝 **Prueba Rápida:**
|
||||||
|
```bash
|
||||||
|
python test_debug_problemas.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔍 **Secuencia de Verificación:**
|
||||||
|
1. Escribir `x = 5` → Enter
|
||||||
|
2. Escribir `x.` → ¿Popup aparece sin robar foco?
|
||||||
|
3. Usar flechas para navegar → ¿Funciona?
|
||||||
|
4. Presionar TAB → ¿Se selecciona?
|
||||||
|
5. Escribir `plot(x**2, (x, -5, 5))` → ¿Link azul aparece?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 **ARCHIVOS MODIFICADOS**
|
||||||
|
|
||||||
|
### 🔧 **Principales:**
|
||||||
|
- `main_calc_app_pyside6.py` - Correcciones de foco, TAB, y detección de plots
|
||||||
|
- `tl_popup_pyside6.py` - Sistema de popups PySide6 (ya estaba correcto)
|
||||||
|
|
||||||
|
### 🆕 **Nuevos:**
|
||||||
|
- `test_debug_problemas.py` - Script de verificación específica
|
||||||
|
- `CORRECCIONES_APLICADAS.md` - Este documento de resumen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 **ESTADO ACTUAL**
|
||||||
|
|
||||||
|
**✅ TODOS LOS PROBLEMAS REPORTADOS HAN SIDO SOLUCIONADOS:**
|
||||||
|
|
||||||
|
1. ✅ **Popups verdaderamente modeless** - No roban foco
|
||||||
|
2. ✅ **TAB funciona** - Selecciona opciones correctamente
|
||||||
|
3. ✅ **Links de plots se generan** - Detección corregida
|
||||||
|
|
||||||
|
**🚀 El sistema está listo para uso completo con todas las funcionalidades operativas.**
|
BIN
MaVCalcv2.lnk
BIN
MaVCalcv2.lnk
Binary file not shown.
|
@ -0,0 +1,156 @@
|
||||||
|
# Mejoras Implementadas en Calculadora MAV PySide6
|
||||||
|
|
||||||
|
## Problemas Solucionados
|
||||||
|
|
||||||
|
### 1. ✅ Comentarios en Panel de Resultados
|
||||||
|
- **Problema**: Los comentarios no se copiaban al panel de resultados
|
||||||
|
- **Solución**:
|
||||||
|
- Modificada función `_evaluate_lines()` para detectar comentarios con `#`
|
||||||
|
- Agregado formato especial para comentarios en `setup_output_tags()`
|
||||||
|
- Los comentarios ahora aparecen en verde cursiva en el panel de resultados
|
||||||
|
- Correspondencia 1:1 mantenida entre entrada y salida
|
||||||
|
|
||||||
|
### 2. ✅ Comentarios en Panel MathJax
|
||||||
|
- **Problema**: Los comentarios no se mostraban correctamente en el panel LaTeX
|
||||||
|
- **Solución**:
|
||||||
|
- Mejorado el JavaScript del panel MathJax para manejar comentarios
|
||||||
|
- Los comentarios se muestran con formato especial (sin LaTeX)
|
||||||
|
- Agregada función `preprocessLatex()` para mejor procesamiento
|
||||||
|
|
||||||
|
### 3. ✅ Filtrado de Contenido en Panel MathJax
|
||||||
|
- **Problema**: Se mostraba todo en el panel LaTeX
|
||||||
|
- **Solución**:
|
||||||
|
- Mejorada función `_add_to_latex_panel_if_applicable()` con reglas claras:
|
||||||
|
- **Comentarios**: Siempre se agregan
|
||||||
|
- **Asignaciones**: Variables = expresiones simbólicas
|
||||||
|
- **Ecuaciones**: Expresiones con símbolos matemáticos
|
||||||
|
- **Exclusiones**: Resultados numéricos simples sin contenido simbólico
|
||||||
|
|
||||||
|
### 4. ✅ Persistencia de Dimensiones
|
||||||
|
- **Problema**: Las dimensiones no se guardaban entre sesiones
|
||||||
|
- **Solución**:
|
||||||
|
- Agregada función `save_settings()` mejorada que guarda:
|
||||||
|
- Posición de ventana (x, y)
|
||||||
|
- Tamaño de ventana (width, height)
|
||||||
|
- Tamaños de cada panel del splitter
|
||||||
|
- Estado visible/oculto del panel LaTeX
|
||||||
|
- Agregada función `restore_geometry()` para cargar configuración
|
||||||
|
- Archivos de configuración separados: `hybrid_calc_settings_pyside6.json`
|
||||||
|
|
||||||
|
### 5. ✅ Parsing LaTeX Mejorado
|
||||||
|
- **Problema**: Errores con divisiones y funciones como sqrt
|
||||||
|
- **Solución**:
|
||||||
|
- Mejorado HTML base con macros LaTeX para funciones comunes
|
||||||
|
- Agregada función `preprocessLatex()` en JavaScript:
|
||||||
|
- Convierte `/` a `\frac{}{}`
|
||||||
|
- Convierte `sqrt()` a `\sqrt{}`
|
||||||
|
- Maneja funciones trigonométricas
|
||||||
|
- Mejorada función `_sympy_to_latex()` con fallbacks
|
||||||
|
- Mejor manejo de errores de MathJax con fallback a texto plano
|
||||||
|
|
||||||
|
### 6. ✅ Cierre de Popups con Enter
|
||||||
|
- **Problema**: Los popups de autocompletado no se cerraban con Enter
|
||||||
|
- **Solución**:
|
||||||
|
- Modificada función `_handle_key_press()` para detectar Enter/Return
|
||||||
|
- Los popups se cierran automáticamente al presionar Enter
|
||||||
|
- Mantiene funcionalidad normal de nueva línea
|
||||||
|
|
||||||
|
## Nuevas Características
|
||||||
|
|
||||||
|
### 📐 Panel MathJax Rediseñado
|
||||||
|
- Diseño moderno con gradientes y sombras
|
||||||
|
- Scrollbar personalizado
|
||||||
|
- Mejor tipografía y espaciado
|
||||||
|
- Animaciones suaves de hover
|
||||||
|
- Indicadores de tipo por colores
|
||||||
|
|
||||||
|
### 💾 Configuración Persistente
|
||||||
|
- Geometría de ventana completamente persistente
|
||||||
|
- Tamaños de paneles individuales guardados
|
||||||
|
- Configuración en archivo JSON separado
|
||||||
|
- Carga automática al iniciar
|
||||||
|
|
||||||
|
### 🎨 Resaltado de Sintaxis Mejorado
|
||||||
|
- Colores optimizados para tema oscuro
|
||||||
|
- Comentarios en verde cursiva
|
||||||
|
- Números, funciones y operadores diferenciados
|
||||||
|
|
||||||
|
## Estructura de Archivos
|
||||||
|
|
||||||
|
```
|
||||||
|
Calcv2/
|
||||||
|
├── main_calc_app_pyside6.py # Aplicación principal PySide6
|
||||||
|
├── hybrid_calc_settings_pyside6.json # Configuración persistente
|
||||||
|
├── hybrid_calc_history_pyside6.txt # Historial de sesión
|
||||||
|
└── README_MEJORAS_PYSIDE6.md # Este archivo
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cómo Probar las Mejoras
|
||||||
|
|
||||||
|
### 1. Comentarios
|
||||||
|
```python
|
||||||
|
# Este es un comentario que aparece en ambos paneles
|
||||||
|
x = 2 + 3 # Comentario al final de línea
|
||||||
|
# Otro comentario
|
||||||
|
y = x * 4
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Ecuaciones LaTeX
|
||||||
|
```python
|
||||||
|
from sympy import *
|
||||||
|
x, y = symbols('x y')
|
||||||
|
|
||||||
|
# Divisiones (ahora renderiza correctamente)
|
||||||
|
expr1 = (x + 1) / (x - 1)
|
||||||
|
|
||||||
|
# Funciones sqrt
|
||||||
|
expr2 = sqrt(x**2 + y**2)
|
||||||
|
|
||||||
|
# Funciones trigonométricas
|
||||||
|
expr3 = sin(x) / cos(x)
|
||||||
|
|
||||||
|
# Asignaciones simbólicas
|
||||||
|
z = x**2 + 2*x + 1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Persistencia
|
||||||
|
1. Redimensiona la ventana y los paneles
|
||||||
|
2. Muestra/oculta el panel LaTeX con F12
|
||||||
|
3. Cierra la aplicación
|
||||||
|
4. Reabre - debería mantener todas las dimensiones
|
||||||
|
|
||||||
|
### 4. Autocompletado
|
||||||
|
```python
|
||||||
|
# Escribe "sympy." y aparece popup
|
||||||
|
# Presiona Enter para cerrar sin seleccionar
|
||||||
|
# Escribe "x." después de definir una variable
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comando de Ejecución
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python main_calc_app_pyside6.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Atajos de Teclado
|
||||||
|
|
||||||
|
- **F12**: Mostrar/ocultar panel LaTeX
|
||||||
|
- **Ctrl+Enter**: Evaluar manualmente
|
||||||
|
- **Shift+Enter**: Evaluar manualmente
|
||||||
|
- **Escape**: Cerrar popups de autocompletado
|
||||||
|
- **Enter**: Cerrar popups y crear nueva línea
|
||||||
|
|
||||||
|
## Log de Debugging
|
||||||
|
|
||||||
|
La aplicación mantiene logs detallados para debugging en:
|
||||||
|
- Consola durante ejecución
|
||||||
|
- Información de estado en barra inferior
|
||||||
|
- Errores de MathJax manejados graciosamente
|
||||||
|
|
||||||
|
## Próximas Mejoras Sugeridas
|
||||||
|
|
||||||
|
1. **Exportar LaTeX**: Botón para exportar todas las ecuaciones del panel
|
||||||
|
2. **Temas**: Opción para cambiar entre tema claro/oscuro
|
||||||
|
3. **Búsqueda**: Función de búsqueda en historial
|
||||||
|
4. **Variables Globales**: Panel lateral con variables definidas
|
||||||
|
5. **Plots Integrados**: Mostrar gráficos directamente en el panel LaTeX
|
|
@ -0,0 +1,178 @@
|
||||||
|
# 🎯 SISTEMA COMPLETO DE AUTOCOMPLETADO Y LINKS CLICKEABLES
|
||||||
|
|
||||||
|
## ✅ FUNCIONALIDADES IMPLEMENTADAS
|
||||||
|
|
||||||
|
### 🔧 **1. SISTEMA DE AUTOCOMPLETADO COMPLETO (3 TIPOS)**
|
||||||
|
|
||||||
|
#### **1.1 Autocompletado de Variables (Timer-based)**
|
||||||
|
- ⏱️ **Activación**: Después de 800ms de inactividad
|
||||||
|
- 🎨 **Estilo**: Popup verde discreto
|
||||||
|
- 📝 **Contenido**: Variables disponibles del contexto actual
|
||||||
|
- 🔍 **Filtrado**: En tiempo real mientras escribes
|
||||||
|
- 🎯 **Uso**: Aparece automáticamente al dejar de escribir
|
||||||
|
|
||||||
|
#### **1.2 Autocompletado con Punto en Objeto**
|
||||||
|
- ⚡ **Activación**: Al escribir "." después de un objeto
|
||||||
|
- 🎨 **Estilo**: Popup azul para métodos
|
||||||
|
- 📝 **Contenido**: Métodos disponibles del objeto
|
||||||
|
- 🔧 **Soporte**: Objetos personalizados con `PopupFunctionList()`
|
||||||
|
- 🎯 **Ejemplo**: `x.` muestra métodos de x
|
||||||
|
|
||||||
|
#### **1.3 Autocompletado con Punto en Línea Vacía**
|
||||||
|
- ⚡ **Activación**: Al escribir "." en línea vacía o después de espacios
|
||||||
|
- 🎨 **Estilo**: Popup azul para constructores
|
||||||
|
- 📝 **Contenido**: Constructores de tipos y funciones globales
|
||||||
|
- 🔧 **Fuentes**: Registro dinámico de tipos + SymPy
|
||||||
|
- 🎯 **Ejemplo**: `.` muestra sin(), cos(), Matrix(), etc.
|
||||||
|
|
||||||
|
### 🎮 **2. NAVEGACIÓN Y CONTROL**
|
||||||
|
|
||||||
|
#### **2.1 Navegación con Teclado**
|
||||||
|
- ⬆️⬇️ **Flechas**: Navegar opciones arriba/abajo
|
||||||
|
- ⭐ **TAB**: Seleccionar opción actual (funcionalidad principal)
|
||||||
|
- 🚪 **ESC**: Cerrar popup
|
||||||
|
- ↩️ **Enter**: Seleccionar opción (alternativa a TAB)
|
||||||
|
|
||||||
|
#### **2.2 Filtrado en Tiempo Real**
|
||||||
|
- ⌨️ **Mientras escribes**: Filtra opciones automáticamente
|
||||||
|
- 🔍 **Ejemplo**: Después de `x.`, escribir `ev` filtra a métodos que empiecen con "ev"
|
||||||
|
- ❌ **Auto-cierre**: Si no hay coincidencias, cierra el popup
|
||||||
|
|
||||||
|
#### **2.3 Posicionamiento Modeless**
|
||||||
|
- 📍 **Ubicación**: Por debajo de la línea actual de escritura
|
||||||
|
- 🚫 **No invasivo**: No bloquea la escritura
|
||||||
|
- 📱 **Adaptativo**: Se ajusta si no hay espacio en pantalla
|
||||||
|
- 🎯 **Objetivo**: Experiencia no intrusiva
|
||||||
|
|
||||||
|
### 🔗 **3. SISTEMA DE LINKS CLICKEABLES**
|
||||||
|
|
||||||
|
#### **3.1 Detección Automática de Plots**
|
||||||
|
- 🎯 **Detección**: Automática para objetos `PlotResult`
|
||||||
|
- 🎨 **Formato**: Link azul subrayado en panel de salida
|
||||||
|
- 📊 **Texto**: "📊 Ver Plot" / "📊 Ver Plot3d"
|
||||||
|
- ⚡ **Activación**: Click en el link
|
||||||
|
|
||||||
|
#### **3.2 Flujo de Visualización de Plots**
|
||||||
|
1. **Primera vez**: Click en link → Muestra plot en panel MathJax
|
||||||
|
2. **Segunda vez**: Click en MathJax → Abre ventana de edición
|
||||||
|
3. **Edición**: Permite modificar expresión y redibujar
|
||||||
|
4. **Sincronización**: Cambios se reflejan en panel de entrada
|
||||||
|
|
||||||
|
#### **3.3 Ventanas Emergentes de Edición**
|
||||||
|
- 🖼️ **Contenido**: Canvas matplotlib + campo de edición
|
||||||
|
- ✏️ **Edición**: Modificar expresión original
|
||||||
|
- 🔄 **Redibujar**: Botón para actualizar plot
|
||||||
|
- 🔗 **Sincronización**: Cambios vuelven al panel principal
|
||||||
|
|
||||||
|
### 🏗️ **4. ARQUITECTURA TÉCNICA**
|
||||||
|
|
||||||
|
#### **4.1 Adaptación de tkinter a PySide6**
|
||||||
|
- ✅ **tl_popup.py** → **tl_popup_pyside6.py**
|
||||||
|
- ✅ **InteractiveResultManager** adaptado completamente
|
||||||
|
- ✅ **PlotResult** mantenido compatible
|
||||||
|
- ✅ **Ventanas emergentes** con matplotlib integrado
|
||||||
|
|
||||||
|
#### **4.2 Integración con Motor Original**
|
||||||
|
- 🔧 **PureAlgebraicEngine**: Sin cambios
|
||||||
|
- 🔧 **EvaluationResult**: Estructura preservada
|
||||||
|
- 🔧 **Tipos personalizados**: Sistema mantenido
|
||||||
|
- 🔧 **Contexto dinámico**: Funciona igual que en tkinter
|
||||||
|
|
||||||
|
#### **4.3 Componentes Nuevos**
|
||||||
|
- 🆕 **_append_clickeable_link()**: Crear links en salida
|
||||||
|
- 🆕 **_handle_output_click()**: Detectar clicks en links
|
||||||
|
- 🆕 **_show_plot_in_mathjax()**: Mostrar plots en MathJax
|
||||||
|
- 🆕 **_position_popup_modeless()**: Posicionamiento inteligente
|
||||||
|
|
||||||
|
## 🎯 **CARACTERÍSTICAS DESTACADAS**
|
||||||
|
|
||||||
|
### ✨ **Experiencia de Usuario**
|
||||||
|
- 🎨 **Colores distintivos**: Verde para variables, azul para métodos
|
||||||
|
- ⚡ **Respuesta rápida**: 800ms para variables, inmediato para métodos
|
||||||
|
- 🎯 **No invasivo**: Popups modeless que no interrumpen
|
||||||
|
- 🔍 **Filtrado inteligente**: Reduce opciones mientras escribes
|
||||||
|
|
||||||
|
### 🔧 **Robustez Técnica**
|
||||||
|
- ✅ **Manejo de errores**: Try-catch en todas las operaciones críticas
|
||||||
|
- 🔄 **Limpieza automática**: Popups se cierran apropiadamente
|
||||||
|
- 📱 **Adaptación de pantalla**: Ajuste automático de posición
|
||||||
|
- 🎯 **Compatibilidad**: Funciona con todos los tipos del sistema
|
||||||
|
|
||||||
|
### 🚀 **Rendimiento**
|
||||||
|
- ⚡ **Evaluación lazy**: Solo evalúa cuando es necesario
|
||||||
|
- 🎯 **Filtrado eficiente**: Búsqueda por prefijo optimizada
|
||||||
|
- 📦 **Memoria controlada**: Limpieza de referencias de plots
|
||||||
|
- 🔄 **Timers inteligentes**: Evita evaluaciones innecesarias
|
||||||
|
|
||||||
|
## 📋 **ARCHIVOS MODIFICADOS/CREADOS**
|
||||||
|
|
||||||
|
### 🆕 **Archivos Nuevos**
|
||||||
|
- `tl_popup_pyside6.py` - Versión PySide6 del sistema de popups
|
||||||
|
- `test_autocompletado_completo.py` - Script de prueba completo
|
||||||
|
|
||||||
|
### 🔧 **Archivos Modificados**
|
||||||
|
- `main_calc_app_pyside6.py` - Sistema completo integrado
|
||||||
|
- `requirements.txt` - Dependencias actualizadas (si fue necesario)
|
||||||
|
|
||||||
|
## 🧪 **CÓMO PROBAR**
|
||||||
|
|
||||||
|
### 🚀 **Ejecución**
|
||||||
|
```bash
|
||||||
|
python test_autocompletado_completo.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📝 **Casos de Prueba**
|
||||||
|
|
||||||
|
#### **1. Variables**
|
||||||
|
```python
|
||||||
|
x = 5
|
||||||
|
y = x # Esperar 800ms → popup verde con variables
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **2. Métodos de Objeto**
|
||||||
|
```python
|
||||||
|
x = 5
|
||||||
|
x. # → popup azul con métodos
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **3. Constructores Globales**
|
||||||
|
```python
|
||||||
|
. # En línea vacía → popup con sin(), cos(), Matrix(), etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **4. Plots Clickeables**
|
||||||
|
```python
|
||||||
|
plot(x**2, (x, -5, 5)) # → link azul clickeable
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **5. Filtrado**
|
||||||
|
```python
|
||||||
|
x.ev # Después del popup, escribir "ev" filtra opciones
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ **CRITERIOS DE ÉXITO**
|
||||||
|
|
||||||
|
- ✅ **3 tipos de autocompletado** funcionando
|
||||||
|
- ✅ **Navegación con TAB** implementada
|
||||||
|
- ✅ **Posicionamiento modeless** no invasivo
|
||||||
|
- ✅ **Links clickeables** para plots
|
||||||
|
- ✅ **Ventanas emergentes** de edición
|
||||||
|
- ✅ **Filtrado en tiempo real** mientras escribes
|
||||||
|
- ✅ **Compatibilidad completa** con motor original
|
||||||
|
- ✅ **Experiencia de usuario** mejorada
|
||||||
|
|
||||||
|
## 🎉 **RESULTADO FINAL**
|
||||||
|
|
||||||
|
El sistema de autocompletado de tkinter ha sido **completamente adaptado** a PySide6 con todas las funcionalidades originales:
|
||||||
|
|
||||||
|
1. ✅ **Autocompletado de variables** (timer-based)
|
||||||
|
2. ✅ **Autocompletado de métodos** (punto en objeto)
|
||||||
|
3. ✅ **Autocompletado global** (punto en línea vacía)
|
||||||
|
4. ✅ **Navegación completa** con teclado
|
||||||
|
5. ✅ **TAB para seleccionar** (funcionalidad principal)
|
||||||
|
6. ✅ **Filtrado en tiempo real**
|
||||||
|
7. ✅ **Links clickeables** para plots
|
||||||
|
8. ✅ **Ventanas emergentes** de edición
|
||||||
|
9. ✅ **Posicionamiento modeless**
|
||||||
|
|
||||||
|
**¡El sistema está listo para uso completo!** 🚀
|
|
@ -1,99 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Demostración completa de Calculadora MAV PySide6
|
|
||||||
Muestra todas las características implementadas
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def show_features():
|
|
||||||
"""Muestra todas las características implementadas"""
|
|
||||||
print("🎯 CALCULADORA MAV - TODAS LAS CARACTERÍSTICAS")
|
|
||||||
print("=" * 60)
|
|
||||||
print()
|
|
||||||
print("✅ CARACTERÍSTICAS IMPLEMENTADAS:")
|
|
||||||
print()
|
|
||||||
print("🖥️ DISEÑO MINIMALISTA:")
|
|
||||||
print(" • 3 paneles con splitters redimensionables")
|
|
||||||
print(" • Correspondencia 1:1 línea por línea")
|
|
||||||
print(" • Tema oscuro optimizado")
|
|
||||||
print()
|
|
||||||
print("🧮 MOTOR ALGEBRAICO:")
|
|
||||||
print(" • Contexto se limpia entre ciclos ✅")
|
|
||||||
print(" • Motor original PureAlgebraicEngine preservado")
|
|
||||||
print(" • Tipos personalizados integrados")
|
|
||||||
print()
|
|
||||||
print("📐 PANEL MATHJAX OPTIMIZADO:")
|
|
||||||
print(" • Altura reducida sin texto de tipo ✅")
|
|
||||||
print(" • Renderizado más compacto")
|
|
||||||
print(" • Colores diferenciados por tipo")
|
|
||||||
print()
|
|
||||||
print("💬 AUTOCOMPLETADO RESTAURADO:")
|
|
||||||
print(" • Popup de variables después de 800ms ✅")
|
|
||||||
print(" • Autocompletado con punto (objeto.método)")
|
|
||||||
print(" • Navegación con ↑↓, selección con Tab")
|
|
||||||
print(" • Filtrado en tiempo real")
|
|
||||||
print()
|
|
||||||
print("🎮 CONTROLES:")
|
|
||||||
print(" • Ctrl+Enter / Shift+Enter: Evaluar")
|
|
||||||
print(" • F12: Mostrar/ocultar panel LaTeX")
|
|
||||||
print(" • Punto (.): Autocompletado de métodos")
|
|
||||||
print(" • Tab: Seleccionar autocompletado")
|
|
||||||
print(" • Escape: Cerrar popup")
|
|
||||||
print(" • ↑↓: Navegar autocompletado")
|
|
||||||
print()
|
|
||||||
print("📝 EJEMPLOS PARA PROBAR:")
|
|
||||||
print(" # Este comentario aparece en LaTeX")
|
|
||||||
print(" x**2 + y**2 = r**2")
|
|
||||||
print(" a = 5*x + 3")
|
|
||||||
print(" Matrix([[1, 2], [3, 4]])")
|
|
||||||
print(" solve(x**2 - 4, x)")
|
|
||||||
print(" diff(x**3, x)")
|
|
||||||
print(" # Prueba autocompletado escribiendo 'a.' después de asignar")
|
|
||||||
print()
|
|
||||||
|
|
||||||
def run_demo():
|
|
||||||
"""Ejecuta la demostración"""
|
|
||||||
show_features()
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = input("¿Ejecutar la aplicación con todas las características? (s/N): ").strip().lower()
|
|
||||||
if response in ['s', 'sí', 'si', 'y', 'yes']:
|
|
||||||
print("\n🚀 Iniciando Calculadora MAV con todas las mejoras...")
|
|
||||||
|
|
||||||
# Verificar dependencias
|
|
||||||
try:
|
|
||||||
from PySide6.QtWidgets import QApplication
|
|
||||||
from PySide6.QtWebEngineWidgets import QWebEngineView
|
|
||||||
print("✅ PySide6 y WebEngine disponibles")
|
|
||||||
except ImportError as e:
|
|
||||||
print(f"❌ Falta dependencia: {e}")
|
|
||||||
print(" Ejecuta: pip install PySide6 PySide6-WebEngine")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Iniciar aplicación
|
|
||||||
from main_calc_app_pyside6 import main as run_app
|
|
||||||
print("📝 Aplicación con:")
|
|
||||||
print(" ✅ Contexto limpio entre ciclos")
|
|
||||||
print(" ✅ Panel LaTeX compacto")
|
|
||||||
print(" ✅ Splitters redimensionables")
|
|
||||||
print(" ✅ Autocompletado completo")
|
|
||||||
print()
|
|
||||||
|
|
||||||
run_app()
|
|
||||||
|
|
||||||
else:
|
|
||||||
print("👋 ¡Hasta luego!")
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("\n\n🚪 Demo cancelada")
|
|
||||||
return 0
|
|
||||||
except Exception as e:
|
|
||||||
print(f"\n❌ Error: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(run_demo())
|
|
|
@ -1,87 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Demostración del diseño minimalista de 3 paneles
|
|
||||||
Calculadora MAV - PySide6
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
def show_demo_instructions():
|
|
||||||
"""Muestra las instrucciones de la demostración"""
|
|
||||||
print("🎯 DEMOSTRACIÓN: Calculadora MAV - Diseño Minimalista")
|
|
||||||
print("=" * 60)
|
|
||||||
print()
|
|
||||||
print("📋 DISEÑO DE 3 PANELES:")
|
|
||||||
print(" ┌─────────────┬─────────────┬─────────────┐")
|
|
||||||
print(" │ PANEL 1 │ PANEL 2 │ PANEL 3 │")
|
|
||||||
print(" │ ENTRADA │ RESULTADOS │ LATEX │")
|
|
||||||
print(" │ │ (1:1) │ (colapsable)│")
|
|
||||||
print(" └─────────────┴─────────────┴─────────────┘")
|
|
||||||
print()
|
|
||||||
print("✨ CARACTERÍSTICAS PRINCIPALES:")
|
|
||||||
print(" • Correspondencia 1:1 entre líneas de entrada y salida")
|
|
||||||
print(" • Tema oscuro minimalista")
|
|
||||||
print(" • Resaltado de sintaxis en tiempo real")
|
|
||||||
print(" • Panel LaTeX con MathJax para ecuaciones")
|
|
||||||
print(" • Motor algebraico SymPy sin cambios")
|
|
||||||
print()
|
|
||||||
print("🎮 EJEMPLOS PARA PROBAR:")
|
|
||||||
print(" # Comentario - aparece en panel LaTeX")
|
|
||||||
print(" x**2 + y**2 = r**2")
|
|
||||||
print(" solve(x**2 - 4, x)")
|
|
||||||
print(" a = 5*x + 3")
|
|
||||||
print(" diff(x**3, x)")
|
|
||||||
print(" Matrix([[1, 2], [3, 4]])")
|
|
||||||
print()
|
|
||||||
print("⌨️ ATAJOS DE TECLADO:")
|
|
||||||
print(" • Ctrl+Enter / Shift+Enter: Evaluar")
|
|
||||||
print(" • F12: Mostrar/ocultar panel LaTeX")
|
|
||||||
print(" • Ctrl+N: Nueva sesión")
|
|
||||||
print()
|
|
||||||
|
|
||||||
def run_demo():
|
|
||||||
"""Ejecuta la demostración"""
|
|
||||||
show_demo_instructions()
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = input("¿Ejecutar la aplicación? (s/N): ").strip().lower()
|
|
||||||
if response in ['s', 'sí', 'si', 'y', 'yes']:
|
|
||||||
print("\n🚀 Iniciando Calculadora MAV...")
|
|
||||||
|
|
||||||
# Verificar dependencias primero
|
|
||||||
try:
|
|
||||||
from PySide6.QtWidgets import QApplication
|
|
||||||
print("✅ PySide6 disponible")
|
|
||||||
except ImportError:
|
|
||||||
print("❌ PySide6 no está instalado")
|
|
||||||
print(" Ejecuta: pip install -r requirements.txt")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Iniciar aplicación
|
|
||||||
from main_calc_app_pyside6 import main as run_app
|
|
||||||
|
|
||||||
# Precarga con contenido de demostración
|
|
||||||
print("📝 Precargando contenido de demostración...")
|
|
||||||
demo_content = """# Demostración Calculadora MAV
|
|
||||||
x**2 + y**2 = r**2
|
|
||||||
solve(x**2 - 4, x)
|
|
||||||
a = 5*x + 3
|
|
||||||
diff(a, x)"""
|
|
||||||
|
|
||||||
# Ejecutar aplicación
|
|
||||||
run_app()
|
|
||||||
|
|
||||||
else:
|
|
||||||
print("👋 ¡Hasta luego!")
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("\n\n🚪 Demostración cancelada")
|
|
||||||
return 0
|
|
||||||
except Exception as e:
|
|
||||||
print(f"\n❌ Error: {e}")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(run_demo())
|
|
|
@ -1,10 +1,8 @@
|
||||||
x**2 + y**2 = r**2
|
# Calculo de Horas
|
||||||
|
horas=72
|
||||||
|
t=36
|
||||||
|
horas*t
|
||||||
|
|
||||||
|
t=sqrt((y*8-25))/e
|
||||||
|
|
||||||
r=?
|
t=?
|
||||||
|
|
||||||
a=(r*5+5)**2
|
|
||||||
|
|
||||||
resultado = f + p
|
|
||||||
|
|
||||||
res
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
a=4/7
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"window_geometry": "1400x800",
|
"window_geometry": "1400x800+52+52",
|
||||||
"debug_mode": false,
|
"debug_mode": false,
|
||||||
"latex_panel_visible": true
|
"latex_panel_visible": true,
|
||||||
|
"sash_pos_x": 450
|
||||||
}
|
}
|
|
@ -1,82 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Script para instalar tkinterweb con soporte JavaScript completo
|
|
||||||
"""
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import importlib
|
|
||||||
|
|
||||||
def check_installed(package_name):
|
|
||||||
"""Verifica si un paquete está instalado"""
|
|
||||||
try:
|
|
||||||
importlib.import_module(package_name)
|
|
||||||
return True
|
|
||||||
except ImportError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def install_package(package_spec):
|
|
||||||
"""Instala un paquete usando pip"""
|
|
||||||
try:
|
|
||||||
print(f"🔄 Instalando {package_spec}...")
|
|
||||||
subprocess.check_call([sys.executable, "-m", "pip", "install", package_spec])
|
|
||||||
print(f"✅ {package_spec} instalado correctamente")
|
|
||||||
return True
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"❌ Error instalando {package_spec}: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Función principal"""
|
|
||||||
print("🚀 Instalador de tkinterweb con JavaScript")
|
|
||||||
print("=" * 50)
|
|
||||||
|
|
||||||
# Verificar estado actual
|
|
||||||
print("\n📊 Estado actual:")
|
|
||||||
tkinterweb_available = check_installed("tkinterweb")
|
|
||||||
pythonmonkey_available = check_installed("pythonmonkey")
|
|
||||||
|
|
||||||
print(f" • tkinterweb: {'✅ Instalado' if tkinterweb_available else '❌ No instalado'}")
|
|
||||||
print(f" • pythonmonkey: {'✅ Instalado' if pythonmonkey_available else '❌ No instalado'}")
|
|
||||||
|
|
||||||
if tkinterweb_available and pythonmonkey_available:
|
|
||||||
print("\n🎉 ¡Todo está instalado correctamente!")
|
|
||||||
print(" La calculadora debería funcionar con JavaScript completo.")
|
|
||||||
return
|
|
||||||
|
|
||||||
print("\n🔧 Procediendo con la instalación...")
|
|
||||||
|
|
||||||
# Instalar tkinterweb con JavaScript
|
|
||||||
if not tkinterweb_available or not pythonmonkey_available:
|
|
||||||
print("\n1️⃣ Instalando tkinterweb[javascript]...")
|
|
||||||
success = install_package("tkinterweb[javascript]")
|
|
||||||
|
|
||||||
if not success:
|
|
||||||
print("\n⚠️ La instalación automática falló.")
|
|
||||||
print(" Intenta manualmente:")
|
|
||||||
print(" pip install tkinterweb[javascript]")
|
|
||||||
print("\n Si hay problemas con PythonMonkey:")
|
|
||||||
print(" 1. Asegúrate de tener Python 3.8+")
|
|
||||||
print(" 2. En Windows, puede requerir Visual Studio Build Tools")
|
|
||||||
print(" 3. En Linux, puede requerir build-essential")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Verificar instalación final
|
|
||||||
print("\n🔍 Verificando instalación...")
|
|
||||||
tkinterweb_final = check_installed("tkinterweb")
|
|
||||||
pythonmonkey_final = check_installed("pythonmonkey")
|
|
||||||
|
|
||||||
print(f" • tkinterweb: {'✅' if tkinterweb_final else '❌'}")
|
|
||||||
print(f" • pythonmonkey: {'✅' if pythonmonkey_final else '❌'}")
|
|
||||||
|
|
||||||
if tkinterweb_final and pythonmonkey_final:
|
|
||||||
print("\n🎉 ¡Instalación completada exitosamente!")
|
|
||||||
print(" La calculadora MAV ahora puede usar JavaScript completo.")
|
|
||||||
print(" Reinicia la aplicación para activar las funciones avanzadas.")
|
|
||||||
else:
|
|
||||||
print("\n⚠️ Instalación parcial.")
|
|
||||||
print(" La calculadora funcionará en modo fallback.")
|
|
||||||
print(" Consulta la documentación para resolver problemas de instalación.")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -121,21 +121,33 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="equations-container">\n
|
<div id="equations-container">\n
|
||||||
<div class="equation-block equation">
|
<div class="equation-block assignment">
|
||||||
<div class="equation-content">
|
<div class="equation-content">
|
||||||
<div class="math-display">$$x^{2} + y^{2} = r^{2}$$</div>
|
<div class="math-display">$$72$$</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="equation-block symbolic">
|
|
||||||
<div class="equation-content">
|
|
||||||
<div class="math-display">$$r = - \sqrt{x^{2} + y^{2}}$$</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="equation-block assignment">
|
<div class="equation-block assignment">
|
||||||
<div class="equation-content">
|
<div class="equation-content">
|
||||||
<div class="math-display">$$5 - 5 \sqrt{x^{2} + y^{2}}$$</div>
|
<div class="math-display">$$36$$</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="equation-block symbolic">
|
||||||
|
<div class="equation-content">
|
||||||
|
<div class="math-display">$$2592$$</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="equation-block assignment">
|
||||||
|
<div class="equation-content">
|
||||||
|
<div class="math-display">$$\frac{\sqrt{8 y - 25}}{e}$$</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="equation-block symbolic">
|
||||||
|
<div class="equation-content">
|
||||||
|
<div class="math-display">$$t = \frac{\sqrt{8 y - 25}}{e}$$</div>
|
||||||
</div>
|
</div>
|
||||||
</div>\n</div>
|
</div>\n</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
332
test.py
332
test.py
|
@ -1,332 +0,0 @@
|
||||||
# OPCIÓN 1: PySide6 (RECOMENDADA)
|
|
||||||
# pip install PySide6
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QHBoxLayout
|
|
||||||
from PySide6.QtWebEngineWidgets import QWebEngineView
|
|
||||||
from PySide6.QtCore import QUrl
|
|
||||||
|
|
||||||
class MathJaxPySide6(QMainWindow):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.setWindowTitle("🧮 MathJax con PySide6")
|
|
||||||
self.setGeometry(100, 100, 1200, 800)
|
|
||||||
|
|
||||||
# Widget central
|
|
||||||
central_widget = QWidget()
|
|
||||||
self.setCentralWidget(central_widget)
|
|
||||||
|
|
||||||
# Layout principal
|
|
||||||
layout = QVBoxLayout(central_widget)
|
|
||||||
|
|
||||||
# Botones de control
|
|
||||||
button_layout = QHBoxLayout()
|
|
||||||
|
|
||||||
btn_reload = QPushButton("🔄 Recargar")
|
|
||||||
btn_zoom_in = QPushButton("🔍+ Zoom In")
|
|
||||||
btn_zoom_out = QPushButton("🔍- Zoom Out")
|
|
||||||
btn_fullscreen = QPushButton("📺 Pantalla Completa")
|
|
||||||
|
|
||||||
btn_reload.clicked.connect(self.reload_page)
|
|
||||||
btn_zoom_in.clicked.connect(self.zoom_in)
|
|
||||||
btn_zoom_out.clicked.connect(self.zoom_out)
|
|
||||||
btn_fullscreen.clicked.connect(self.toggle_fullscreen)
|
|
||||||
|
|
||||||
button_layout.addWidget(btn_reload)
|
|
||||||
button_layout.addWidget(btn_zoom_in)
|
|
||||||
button_layout.addWidget(btn_zoom_out)
|
|
||||||
button_layout.addWidget(btn_fullscreen)
|
|
||||||
button_layout.addStretch()
|
|
||||||
|
|
||||||
layout.addLayout(button_layout)
|
|
||||||
|
|
||||||
# Vista web
|
|
||||||
self.web_view = QWebEngineView()
|
|
||||||
layout.addWidget(self.web_view)
|
|
||||||
|
|
||||||
# HTML con MathJax avanzado
|
|
||||||
self.html_content = """
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="es">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>MathJax Avanzado</title>
|
|
||||||
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
|
|
||||||
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>
|
|
||||||
<script>
|
|
||||||
MathJax = {
|
|
||||||
tex: {
|
|
||||||
inlineMath: [['$', '$'], ['\\\\(', '\\\\)']],
|
|
||||||
displayMath: [['$$', '$$'], ['\\\\[', '\\\\]']],
|
|
||||||
packages: {'[+]': ['ams', 'color', 'physics']}
|
|
||||||
},
|
|
||||||
chtml: {
|
|
||||||
scale: 1.3,
|
|
||||||
minScale: 0.5,
|
|
||||||
matchFontHeight: false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', sans-serif;
|
|
||||||
margin: 30px;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
background: white;
|
|
||||||
border-radius: 15px;
|
|
||||||
padding: 40px;
|
|
||||||
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
|
||||||
max-width: 1000px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
h1 { color: #2c3e50; text-align: center; margin-bottom: 40px; }
|
|
||||||
.section {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-left: 5px solid #007bff;
|
|
||||||
padding: 25px;
|
|
||||||
margin: 25px 0;
|
|
||||||
border-radius: 0 10px 10px 0;
|
|
||||||
box-shadow: 0 5px 15px rgba(0,0,0,0.08);
|
|
||||||
}
|
|
||||||
.section h3 {
|
|
||||||
color: #495057;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
.interactive {
|
|
||||||
background: #e8f5e8;
|
|
||||||
border-left-color: #28a745;
|
|
||||||
}
|
|
||||||
.physics {
|
|
||||||
background: #fff3cd;
|
|
||||||
border-left-color: #ffc107;
|
|
||||||
}
|
|
||||||
.advanced {
|
|
||||||
background: #f8d7da;
|
|
||||||
border-left-color: #dc3545;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background: #007bff;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 12px 24px;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
background: #0056b3;
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
#dynamic-content {
|
|
||||||
margin-top: 20px;
|
|
||||||
min-height: 100px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>🚀 Laboratorio Matemático Avanzado</h1>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h3>📊 Álgebra Lineal</h3>
|
|
||||||
<p>Determinante de matriz 3×3:</p>
|
|
||||||
$$\\det(A) = \\begin{vmatrix}
|
|
||||||
a_{11} & a_{12} & a_{13} \\\\
|
|
||||||
a_{21} & a_{22} & a_{23} \\\\
|
|
||||||
a_{31} & a_{32} & a_{33}
|
|
||||||
\\end{vmatrix}$$
|
|
||||||
|
|
||||||
<p>Eigenvalores: $\\det(A - \\lambda I) = 0$</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section physics">
|
|
||||||
<h3>⚛️ Física Cuántica</h3>
|
|
||||||
<p>Operador Hamiltoniano:</p>
|
|
||||||
$$\\hat{H} = \\frac{-\\hbar^2}{2m}\\nabla^2 + V(\\mathbf{r})$$
|
|
||||||
|
|
||||||
<p>Función de onda normalizada:</p>
|
|
||||||
$$\\int_{-\\infty}^{\\infty} |\\Psi(x,t)|^2 dx = 1$$
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section advanced">
|
|
||||||
<h3>🔬 Matemáticas Avanzadas</h3>
|
|
||||||
<p>Transformada de Fourier:</p>
|
|
||||||
$$\\mathcal{F}[f(t)] = \\int_{-\\infty}^{\\infty} f(t) e^{-2\\pi i \\xi t} dt$$
|
|
||||||
|
|
||||||
<p>Función Gamma:</p>
|
|
||||||
$$\\Gamma(z) = \\int_0^\\infty t^{z-1} e^{-t} dt$$
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section interactive">
|
|
||||||
<h3>🎮 Generador Interactivo</h3>
|
|
||||||
<button onclick="addCalculusExample()">📈 Cálculo</button>
|
|
||||||
<button onclick="addStatisticsExample()">📊 Estadística</button>
|
|
||||||
<button onclick="addGeometryExample()">📐 Geometría</button>
|
|
||||||
<button onclick="addPhysicsExample()">⚛️ Física</button>
|
|
||||||
<button onclick="clearContent()">🧹 Limpiar</button>
|
|
||||||
|
|
||||||
<div id="dynamic-content"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const examples = {
|
|
||||||
calculus: [
|
|
||||||
{
|
|
||||||
title: "Regla de L'Hôpital",
|
|
||||||
eq: "$$\\\\lim_{x \\\\to c} \\\\frac{f(x)}{g(x)} = \\\\lim_{x \\\\to c} \\\\frac{f'(x)}{g'(x)}$$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Integración por partes",
|
|
||||||
eq: "$$\\\\int u \\\\, dv = uv - \\\\int v \\\\, du$$"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
statistics: [
|
|
||||||
{
|
|
||||||
title: "Distribución Normal",
|
|
||||||
eq: "$$f(x) = \\\\frac{1}{\\\\sigma\\\\sqrt{2\\\\pi}} e^{-\\\\frac{(x-\\\\mu)^2}{2\\\\sigma^2}}$$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Teorema Central del Límite",
|
|
||||||
eq: "$$\\\\bar{X}_n \\\\xrightarrow{d} \\\\mathcal{N}\\\\left(\\\\mu, \\\\frac{\\\\sigma^2}{n}\\\\right)$$"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
geometry: [
|
|
||||||
{
|
|
||||||
title: "Volumen de esfera",
|
|
||||||
eq: "$$V = \\\\frac{4}{3}\\\\pi r^3$$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Ley de cosenos",
|
|
||||||
eq: "$$c^2 = a^2 + b^2 - 2ab\\\\cos(C)$$"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
physics: [
|
|
||||||
{
|
|
||||||
title: "Ecuaciones de Maxwell",
|
|
||||||
eq: "$$\\\\nabla \\\\cdot \\\\mathbf{E} = \\\\frac{\\\\rho}{\\\\epsilon_0}$$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Relatividad Especial",
|
|
||||||
eq: "$$t' = \\\\gamma \\\\left(t - \\\\frac{vx}{c^2}\\\\right)$$"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
function addExample(category) {
|
|
||||||
const categoryExamples = examples[category];
|
|
||||||
const randomExample = categoryExamples[Math.floor(Math.random() * categoryExamples.length)];
|
|
||||||
|
|
||||||
const content = document.getElementById('dynamic-content');
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = 'section';
|
|
||||||
div.innerHTML = `
|
|
||||||
<h4>➕ ${randomExample.title}</h4>
|
|
||||||
${randomExample.eq}
|
|
||||||
`;
|
|
||||||
content.appendChild(div);
|
|
||||||
|
|
||||||
MathJax.typesetPromise([div]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addCalculusExample() { addExample('calculus'); }
|
|
||||||
function addStatisticsExample() { addExample('statistics'); }
|
|
||||||
function addGeometryExample() { addExample('geometry'); }
|
|
||||||
function addPhysicsExample() { addExample('physics'); }
|
|
||||||
|
|
||||||
function clearContent() {
|
|
||||||
document.getElementById('dynamic-content').innerHTML = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
MathJax.startup.promise.then(() => {
|
|
||||||
console.log('✅ MathJax cargado en PySide6!');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Cargar contenido
|
|
||||||
self.web_view.setHtml(self.html_content)
|
|
||||||
|
|
||||||
def reload_page(self):
|
|
||||||
self.web_view.setHtml(self.html_content)
|
|
||||||
|
|
||||||
def zoom_in(self):
|
|
||||||
current_zoom = self.web_view.zoomFactor()
|
|
||||||
self.web_view.setZoomFactor(current_zoom * 1.2)
|
|
||||||
|
|
||||||
def zoom_out(self):
|
|
||||||
current_zoom = self.web_view.zoomFactor()
|
|
||||||
self.web_view.setZoomFactor(current_zoom / 1.2)
|
|
||||||
|
|
||||||
def toggle_fullscreen(self):
|
|
||||||
if self.isFullScreen():
|
|
||||||
self.showNormal()
|
|
||||||
else:
|
|
||||||
self.showFullScreen()
|
|
||||||
|
|
||||||
# OPCIÓN 2: PyQt6 (Alternativa)
|
|
||||||
# pip install PyQt6 PyQt6-WebEngine
|
|
||||||
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
from PyQt6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
|
|
||||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
|
||||||
|
|
||||||
class MathJaxPyQt6(QMainWindow):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.setWindowTitle("MathJax con PyQt6")
|
|
||||||
self.setGeometry(100, 100, 1000, 700)
|
|
||||||
|
|
||||||
central_widget = QWidget()
|
|
||||||
self.setCentralWidget(central_widget)
|
|
||||||
layout = QVBoxLayout(central_widget)
|
|
||||||
|
|
||||||
self.web_view = QWebEngineView()
|
|
||||||
layout.addWidget(self.web_view)
|
|
||||||
|
|
||||||
# Mismo HTML que PySide6
|
|
||||||
html_content = "[MISMO HTML DE ARRIBA]"
|
|
||||||
self.web_view.setHtml(html_content)
|
|
||||||
|
|
||||||
def run_pyqt6():
|
|
||||||
app = QApplication(sys.argv)
|
|
||||||
window = MathJaxPyQt6()
|
|
||||||
window.show()
|
|
||||||
sys.exit(app.exec())
|
|
||||||
"""
|
|
||||||
|
|
||||||
def run_pyside6():
|
|
||||||
app = QApplication(sys.argv)
|
|
||||||
window = MathJaxPySide6()
|
|
||||||
window.show()
|
|
||||||
sys.exit(app.exec())
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print("🧮 Iniciando aplicación MathJax con PySide6...")
|
|
||||||
print("✨ Funciones disponibles:")
|
|
||||||
print(" - Zoom in/out")
|
|
||||||
print(" - Pantalla completa")
|
|
||||||
print(" - Generador interactivo de ecuaciones")
|
|
||||||
print(" - MathJax completamente funcional")
|
|
||||||
|
|
||||||
run_pyside6()
|
|
||||||
|
|
||||||
# RESUMEN DE INSTALACIÓN:
|
|
||||||
#
|
|
||||||
# Para PySide6 (RECOMENDADO):
|
|
||||||
# pip install PySide6
|
|
||||||
#
|
|
||||||
# Para PyQt6:
|
|
||||||
# pip install PyQt6 PyQt6-WebEngine
|
|
||||||
#
|
|
||||||
# Ambos funcionan igual de bien para MathJax,
|
|
||||||
# pero PySide6 tiene licencia más permisiva.
|
|
212
test_pyside6.py
212
test_pyside6.py
|
@ -1,212 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Script de prueba para la versión PySide6 de Calculadora MAV
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
def test_imports():
|
|
||||||
"""Prueba todas las importaciones necesarias"""
|
|
||||||
print("🧪 Probando importaciones...")
|
|
||||||
|
|
||||||
tests = [
|
|
||||||
("PySide6.QtWidgets", "QApplication"),
|
|
||||||
("PySide6.QtCore", "QThread"),
|
|
||||||
("PySide6.QtGui", "QFont"),
|
|
||||||
("PySide6.QtWebEngineWidgets", "QWebEngineView"),
|
|
||||||
("sympy", None),
|
|
||||||
("numpy", None),
|
|
||||||
("matplotlib", None),
|
|
||||||
]
|
|
||||||
|
|
||||||
success_count = 0
|
|
||||||
for module, attr in tests:
|
|
||||||
try:
|
|
||||||
if attr:
|
|
||||||
mod = __import__(module, fromlist=[attr])
|
|
||||||
getattr(mod, attr)
|
|
||||||
else:
|
|
||||||
__import__(module)
|
|
||||||
print(f" ✅ {module}" + (f".{attr}" if attr else ""))
|
|
||||||
success_count += 1
|
|
||||||
except ImportError as e:
|
|
||||||
print(f" ❌ {module}" + (f".{attr}" if attr else "") + f" - {e}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ⚠️ {module}" + (f".{attr}" if attr else "") + f" - {e}")
|
|
||||||
|
|
||||||
print(f"\n📊 Resultado: {success_count}/{len(tests)} módulos disponibles")
|
|
||||||
return success_count == len(tests)
|
|
||||||
|
|
||||||
def test_application_creation():
|
|
||||||
"""Prueba la creación básica de la aplicación"""
|
|
||||||
print("\n🏗️ Probando creación de aplicación...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
from PySide6.QtWidgets import QApplication
|
|
||||||
from PySide6.QtCore import QCoreApplication
|
|
||||||
|
|
||||||
# Crear aplicación mínima
|
|
||||||
if not QCoreApplication.instance():
|
|
||||||
app = QApplication([])
|
|
||||||
else:
|
|
||||||
app = QCoreApplication.instance()
|
|
||||||
|
|
||||||
print(" ✅ QApplication creada correctamente")
|
|
||||||
|
|
||||||
# Probar importación de nuestra aplicación
|
|
||||||
from main_calc_app_pyside6 import HybridCalculatorPySide6
|
|
||||||
print(" ✅ Clase HybridCalculatorPySide6 importada")
|
|
||||||
|
|
||||||
# Probar creación de ventana (sin mostrar)
|
|
||||||
window = HybridCalculatorPySide6()
|
|
||||||
print(" ✅ Ventana principal creada")
|
|
||||||
|
|
||||||
# Verificar componentes principales
|
|
||||||
assert hasattr(window, 'input_text'), "input_text no encontrado"
|
|
||||||
assert hasattr(window, 'output_text'), "output_text no encontrado"
|
|
||||||
assert hasattr(window, 'latex_panel'), "latex_panel no encontrado"
|
|
||||||
assert hasattr(window, 'engine'), "engine no encontrado"
|
|
||||||
print(" ✅ Componentes principales verificados")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ❌ Error: {e}")
|
|
||||||
traceback.print_exc()
|
|
||||||
return False
|
|
||||||
|
|
||||||
def test_engine_functionality():
|
|
||||||
"""Prueba la funcionalidad del motor de cálculo"""
|
|
||||||
print("\n⚙️ Probando motor de cálculo...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
from main_evaluation_puro import PureAlgebraicEngine
|
|
||||||
|
|
||||||
engine = PureAlgebraicEngine()
|
|
||||||
print(" ✅ Motor PureAlgebraicEngine creado")
|
|
||||||
|
|
||||||
# Prueba básica
|
|
||||||
result = engine.evaluate_line("2 + 2")
|
|
||||||
print(f" ✅ Evaluación básica: 2 + 2 = {result.output if result.success else result.error_message}")
|
|
||||||
|
|
||||||
# Prueba simbólica
|
|
||||||
result = engine.evaluate_line("x**2 + y**2")
|
|
||||||
print(f" ✅ Evaluación simbólica: x**2 + y**2 = {result.output if result.success else result.error_message}")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ❌ Error: {e}")
|
|
||||||
traceback.print_exc()
|
|
||||||
return False
|
|
||||||
|
|
||||||
def test_mathjax_html():
|
|
||||||
"""Prueba la generación de HTML MathJax"""
|
|
||||||
print("\n📐 Probando generación MathJax...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
from main_calc_app_pyside6 import MathJaxPanel
|
|
||||||
from PySide6.QtWidgets import QApplication
|
|
||||||
from PySide6.QtCore import QCoreApplication
|
|
||||||
|
|
||||||
if not QCoreApplication.instance():
|
|
||||||
app = QApplication([])
|
|
||||||
|
|
||||||
panel = MathJaxPanel()
|
|
||||||
print(" ✅ Panel MathJax creado")
|
|
||||||
|
|
||||||
# Verificar HTML base
|
|
||||||
html = panel.generate_base_html()
|
|
||||||
assert "MathJax" in html, "MathJax no encontrado en HTML"
|
|
||||||
assert "equation" in html, "Estilos de ecuación no encontrados"
|
|
||||||
print(" ✅ HTML base generado correctamente")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ❌ Error: {e}")
|
|
||||||
traceback.print_exc()
|
|
||||||
return False
|
|
||||||
|
|
||||||
def run_minimal_app():
|
|
||||||
"""Ejecuta una versión mínima de la aplicación para prueba visual"""
|
|
||||||
print("\n🖥️ Iniciando prueba visual (cierra la ventana para continuar)...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
from PySide6.QtWidgets import QApplication
|
|
||||||
from main_calc_app_pyside6 import HybridCalculatorPySide6
|
|
||||||
|
|
||||||
app = QApplication(sys.argv)
|
|
||||||
window = HybridCalculatorPySide6()
|
|
||||||
|
|
||||||
# Precargar algunos datos de prueba
|
|
||||||
window.input_text.setPlainText("# Prueba PySide6\nx**2 + y**2 = r**2\nsolve(x**2 - 4, x)")
|
|
||||||
|
|
||||||
window.show()
|
|
||||||
print(" ✅ Aplicación mostrada - cierra la ventana para continuar")
|
|
||||||
|
|
||||||
# No ejecutar el loop principal, solo mostrar
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ❌ Error: {e}")
|
|
||||||
traceback.print_exc()
|
|
||||||
return False
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Función principal de pruebas"""
|
|
||||||
print("🚀 Iniciando pruebas de Calculadora MAV PySide6")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
tests = [
|
|
||||||
("Importaciones", test_imports),
|
|
||||||
("Creación de aplicación", test_application_creation),
|
|
||||||
("Motor de cálculo", test_engine_functionality),
|
|
||||||
("HTML MathJax", test_mathjax_html),
|
|
||||||
]
|
|
||||||
|
|
||||||
results = []
|
|
||||||
for test_name, test_func in tests:
|
|
||||||
try:
|
|
||||||
result = test_func()
|
|
||||||
results.append((test_name, result))
|
|
||||||
except Exception as e:
|
|
||||||
print(f"💥 Error inesperado en {test_name}: {e}")
|
|
||||||
results.append((test_name, False))
|
|
||||||
|
|
||||||
# Resumen de resultados
|
|
||||||
print("\n" + "=" * 60)
|
|
||||||
print("📋 RESUMEN DE PRUEBAS")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
passed = 0
|
|
||||||
total = len(results)
|
|
||||||
|
|
||||||
for test_name, success in results:
|
|
||||||
status = "✅ PASÓ" if success else "❌ FALLÓ"
|
|
||||||
print(f" {status:<10} {test_name}")
|
|
||||||
if success:
|
|
||||||
passed += 1
|
|
||||||
|
|
||||||
print(f"\n📊 Resultado final: {passed}/{total} pruebas pasaron")
|
|
||||||
|
|
||||||
if passed == total:
|
|
||||||
print("🎉 ¡Todas las pruebas pasaron! La aplicación está lista.")
|
|
||||||
|
|
||||||
# Ofrecer ejecutar prueba visual
|
|
||||||
try:
|
|
||||||
response = input("\n¿Quieres ejecutar una prueba visual? (s/N): ").strip().lower()
|
|
||||||
if response in ['s', 'sí', 'si', 'y', 'yes']:
|
|
||||||
run_minimal_app()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("\n🚪 Prueba cancelada por el usuario")
|
|
||||||
|
|
||||||
else:
|
|
||||||
print("⚠️ Algunas pruebas fallaron. Revisa los errores antes de ejecutar la aplicación.")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
|
@ -1,72 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Prueba simple del motor PureAlgebraicEngine para verificar la API
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
def test_engine():
|
|
||||||
"""Prueba básica del motor"""
|
|
||||||
print("🧪 Probando PureAlgebraicEngine...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Importar el motor
|
|
||||||
from main_evaluation_puro import PureAlgebraicEngine
|
|
||||||
print(" ✅ Import exitoso")
|
|
||||||
|
|
||||||
# Crear instancia
|
|
||||||
engine = PureAlgebraicEngine()
|
|
||||||
print(" ✅ Instancia creada")
|
|
||||||
|
|
||||||
# Pruebas básicas
|
|
||||||
test_cases = [
|
|
||||||
"2 + 2",
|
|
||||||
"x**2 + y**2",
|
|
||||||
"solve(x**2 - 4, x)",
|
|
||||||
"# Esto es un comentario",
|
|
||||||
"a = 5*x + 3",
|
|
||||||
"x**2 + y**2 = r**2"
|
|
||||||
]
|
|
||||||
|
|
||||||
print("\n📝 Ejecutando casos de prueba:")
|
|
||||||
for i, test_case in enumerate(test_cases, 1):
|
|
||||||
try:
|
|
||||||
result = engine.evaluate_line(test_case)
|
|
||||||
status = "✅" if result.success else "❌"
|
|
||||||
output = result.output if result.success else result.error_message
|
|
||||||
print(f" {i}. {status} '{test_case}' → {output}")
|
|
||||||
|
|
||||||
# Información adicional del resultado
|
|
||||||
if result.success:
|
|
||||||
print(f" Tipo: {result.result_type}, Asignación: {result.is_assignment}, Ecuación: {result.is_equation}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f" {i}. ❌ '{test_case}' → ERROR: {e}")
|
|
||||||
|
|
||||||
print("\n✅ Pruebas del motor completadas")
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Error en prueba del motor: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
return False
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Función principal"""
|
|
||||||
print("🚀 Prueba Simple del Motor Algebraico")
|
|
||||||
print("=" * 50)
|
|
||||||
|
|
||||||
success = test_engine()
|
|
||||||
|
|
||||||
if success:
|
|
||||||
print("\n🎉 ¡Motor funcionando correctamente!")
|
|
||||||
print(" Puedes proceder a usar la aplicación PySide6")
|
|
||||||
else:
|
|
||||||
print("\n⚠️ Problemas detectados en el motor")
|
|
||||||
print(" Revisa los errores antes de usar la aplicación")
|
|
||||||
|
|
||||||
return 0 if success else 1
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
|
@ -0,0 +1,592 @@
|
||||||
|
"""
|
||||||
|
Sistema de resultados interactivos con tags clickeables - VERSIÓN PYSIDE6
|
||||||
|
"""
|
||||||
|
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QTextEdit,
|
||||||
|
QLabel, QLineEdit, QPushButton, QFrame, QScrollArea)
|
||||||
|
from PySide6.QtCore import Qt, QTimer, Signal
|
||||||
|
from PySide6.QtGui import QFont, QTextCursor, QTextCharFormat, QColor
|
||||||
|
from PySide6.QtWebEngineWidgets import QWebEngineView
|
||||||
|
import sympy
|
||||||
|
from typing import Any, Optional, Dict, List, Tuple
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
class PlotResult:
|
||||||
|
"""Placeholder para resultados de plotting - DEFINICIÓN PRINCIPAL"""
|
||||||
|
|
||||||
|
def __init__(self, plot_type: str, args: tuple, kwargs: dict, original_expression: str = None):
|
||||||
|
self.plot_type = plot_type
|
||||||
|
self.args = args
|
||||||
|
self.kwargs = kwargs
|
||||||
|
self.original_expression = original_expression or ""
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"📊 Ver {self.plot_type.title()}"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"PlotResult('{self.plot_type}', {self.args}, {self.kwargs})"
|
||||||
|
|
||||||
|
|
||||||
|
class InteractiveResultManager(QWidget):
|
||||||
|
"""Maneja resultados interactivos con ventanas emergentes - PySide6"""
|
||||||
|
|
||||||
|
plot_requested = Signal(object) # Señal para solicitar mostrar plot en MathJax
|
||||||
|
|
||||||
|
def __init__(self, parent_window=None):
|
||||||
|
super().__init__()
|
||||||
|
self.parent = parent_window
|
||||||
|
self.open_windows: Dict[str, QWidget] = {}
|
||||||
|
self.update_input_callback = None
|
||||||
|
self._setup_styles()
|
||||||
|
|
||||||
|
def _setup_styles(self):
|
||||||
|
"""Configurar estilos para ventanas emergentes"""
|
||||||
|
self.window_style = """
|
||||||
|
QWidget {
|
||||||
|
background-color: #2b2b2b;
|
||||||
|
color: #d4d4d4;
|
||||||
|
}
|
||||||
|
QTextEdit {
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
border: 1px solid #3c3c3c;
|
||||||
|
font-family: 'Consolas';
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
QLineEdit {
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
border: 1px solid #3c3c3c;
|
||||||
|
padding: 5px;
|
||||||
|
font-family: 'Consolas';
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
QPushButton {
|
||||||
|
background-color: #4fc3f7;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-family: 'Consolas';
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #29b6f6;
|
||||||
|
}
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #0288d1;
|
||||||
|
}
|
||||||
|
QLabel {
|
||||||
|
color: #d4d4d4;
|
||||||
|
font-family: 'Consolas';
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def set_update_callback(self, callback):
|
||||||
|
"""Establece el callback para actualizar el panel de entrada"""
|
||||||
|
self.update_input_callback = callback
|
||||||
|
|
||||||
|
def create_interactive_link(self, result: Any) -> Optional[Tuple[str, str, Any]]:
|
||||||
|
"""
|
||||||
|
Crea un link interactivo para un resultado si es necesario
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(link_id, display_text, result_object) si se creó link, None si no es necesario
|
||||||
|
"""
|
||||||
|
link_id = None
|
||||||
|
display_text = ""
|
||||||
|
|
||||||
|
if isinstance(result, PlotResult):
|
||||||
|
link_id = f"plot_{id(result)}"
|
||||||
|
display_text = f"📊 Ver {result.plot_type.title()}"
|
||||||
|
|
||||||
|
elif isinstance(result, sympy.Matrix):
|
||||||
|
link_id = f"matrix_{id(result)}"
|
||||||
|
rows, cols = result.shape
|
||||||
|
display_text = f"📋 Ver Matriz {rows}×{cols}"
|
||||||
|
|
||||||
|
elif isinstance(result, list) and len(result) > 5:
|
||||||
|
link_id = f"list_{id(result)}"
|
||||||
|
display_text = f"📋 Ver Lista ({len(result)} elementos)"
|
||||||
|
|
||||||
|
elif isinstance(result, dict) and len(result) > 3:
|
||||||
|
link_id = f"dict_{id(result)}"
|
||||||
|
display_text = f"🔍 Ver Diccionario ({len(result)} entradas)"
|
||||||
|
|
||||||
|
elif hasattr(result, '__dict__') and len(str(result)) > 100:
|
||||||
|
link_id = f"object_{id(result)}"
|
||||||
|
display_text = f"🔍 Ver Detalles ({type(result).__name__})"
|
||||||
|
|
||||||
|
if link_id and display_text:
|
||||||
|
return (link_id, display_text, result)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def handle_interactive_click(self, result: Any, is_mathjax_click: bool = False):
|
||||||
|
"""Maneja clicks en elementos interactivos"""
|
||||||
|
if isinstance(result, PlotResult):
|
||||||
|
if not is_mathjax_click:
|
||||||
|
# Primera vez: mostrar en MathJax
|
||||||
|
self.plot_requested.emit(result)
|
||||||
|
else:
|
||||||
|
# Click en MathJax: abrir ventana emergente
|
||||||
|
self._show_plot_window(result)
|
||||||
|
else:
|
||||||
|
# Otros tipos siempre abren ventana
|
||||||
|
self._handle_other_interactive_click(result)
|
||||||
|
|
||||||
|
def _handle_other_interactive_click(self, result: Any):
|
||||||
|
"""Maneja clicks en elementos no-plot"""
|
||||||
|
window_key = f"{type(result).__name__}_{id(result)}"
|
||||||
|
|
||||||
|
# Si ya existe la ventana, enfocarla
|
||||||
|
if window_key in self.open_windows:
|
||||||
|
window = self.open_windows[window_key]
|
||||||
|
if window and not window.isHidden():
|
||||||
|
window.raise_()
|
||||||
|
window.activateWindow()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
del self.open_windows[window_key]
|
||||||
|
|
||||||
|
# Crear nueva ventana
|
||||||
|
try:
|
||||||
|
if isinstance(result, sympy.Matrix):
|
||||||
|
self._show_matrix_window(result, window_key)
|
||||||
|
elif isinstance(result, list):
|
||||||
|
self._show_list_window(result, window_key)
|
||||||
|
elif isinstance(result, dict):
|
||||||
|
self._show_dict_window(result, window_key)
|
||||||
|
else:
|
||||||
|
self._show_object_window(result, window_key)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error abriendo ventana interactiva: {e}")
|
||||||
|
|
||||||
|
def _show_plot_window(self, plot_result: PlotResult):
|
||||||
|
"""Muestra ventana con plot matplotlib e interfaz de edición"""
|
||||||
|
window_key = f"plot_{id(plot_result)}"
|
||||||
|
|
||||||
|
# Si ya existe la ventana, enfocarla
|
||||||
|
if window_key in self.open_windows:
|
||||||
|
window = self.open_windows[window_key]
|
||||||
|
if window and not window.isHidden():
|
||||||
|
window.raise_()
|
||||||
|
window.activateWindow()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
del self.open_windows[window_key]
|
||||||
|
|
||||||
|
# Obtener posición de la ventana principal
|
||||||
|
if self.parent:
|
||||||
|
parent_pos = self.parent.pos()
|
||||||
|
parent_size = self.parent.size()
|
||||||
|
plot_window_x = parent_pos.x() + parent_size.width()
|
||||||
|
plot_window_y = parent_pos.y()
|
||||||
|
else:
|
||||||
|
plot_window_x = 100
|
||||||
|
plot_window_y = 100
|
||||||
|
|
||||||
|
# Crear ventana
|
||||||
|
window = QWidget()
|
||||||
|
window.setWindowTitle(f"Plot - {plot_result.plot_type}")
|
||||||
|
window.resize(700, 600)
|
||||||
|
window.move(plot_window_x, plot_window_y)
|
||||||
|
window.setStyleSheet(self.window_style)
|
||||||
|
|
||||||
|
# Layout principal
|
||||||
|
layout = QVBoxLayout(window)
|
||||||
|
|
||||||
|
# Frame superior para edición
|
||||||
|
edit_frame = QFrame()
|
||||||
|
edit_layout = QHBoxLayout(edit_frame)
|
||||||
|
|
||||||
|
# Label y campo de edición
|
||||||
|
label = QLabel("Expresión:")
|
||||||
|
expression_edit = QLineEdit(plot_result.original_expression)
|
||||||
|
redraw_btn = QPushButton("Redibujar")
|
||||||
|
|
||||||
|
edit_layout.addWidget(label)
|
||||||
|
edit_layout.addWidget(expression_edit)
|
||||||
|
edit_layout.addWidget(redraw_btn)
|
||||||
|
|
||||||
|
# Frame para el canvas del plot
|
||||||
|
canvas_frame = QFrame()
|
||||||
|
canvas_layout = QVBoxLayout(canvas_frame)
|
||||||
|
|
||||||
|
layout.addWidget(edit_frame)
|
||||||
|
layout.addWidget(canvas_frame)
|
||||||
|
|
||||||
|
# Crear plot inicial
|
||||||
|
self._create_plot_in_frame(plot_result, canvas_frame)
|
||||||
|
|
||||||
|
# Conectar botón de redibujar
|
||||||
|
def redraw():
|
||||||
|
self._redraw_plot(plot_result, canvas_frame, expression_edit.text())
|
||||||
|
|
||||||
|
redraw_btn.clicked.connect(redraw)
|
||||||
|
|
||||||
|
# Configurar cierre
|
||||||
|
def on_close():
|
||||||
|
edited_expression = expression_edit.text().strip()
|
||||||
|
original_expression = plot_result.original_expression.strip()
|
||||||
|
|
||||||
|
if edited_expression != original_expression and self.update_input_callback:
|
||||||
|
self.update_input_callback(original_expression, edited_expression)
|
||||||
|
|
||||||
|
if window_key in self.open_windows:
|
||||||
|
del self.open_windows[window_key]
|
||||||
|
|
||||||
|
window.closeEvent = lambda event: (on_close(), event.accept())
|
||||||
|
|
||||||
|
# Registrar y mostrar ventana
|
||||||
|
self.open_windows[window_key] = window
|
||||||
|
window.show()
|
||||||
|
|
||||||
|
# Focus en el campo de edición
|
||||||
|
expression_edit.setFocus()
|
||||||
|
expression_edit.selectAll()
|
||||||
|
|
||||||
|
def _redraw_plot(self, plot_result: PlotResult, canvas_frame: QFrame, new_expression: str):
|
||||||
|
"""Redibuja el plot con una nueva expresión"""
|
||||||
|
try:
|
||||||
|
# Limpiar el frame actual
|
||||||
|
layout = canvas_frame.layout()
|
||||||
|
if layout:
|
||||||
|
while layout.count():
|
||||||
|
child = layout.takeAt(0)
|
||||||
|
if child.widget():
|
||||||
|
child.widget().deleteLater()
|
||||||
|
|
||||||
|
# Evaluar la nueva expresión
|
||||||
|
import sympy as sp
|
||||||
|
|
||||||
|
# Crear contexto básico para evaluación
|
||||||
|
eval_context = {
|
||||||
|
'sin': sp.sin, 'cos': sp.cos, 'tan': sp.tan,
|
||||||
|
'exp': sp.exp, 'log': sp.log, 'sqrt': sp.sqrt,
|
||||||
|
'pi': sp.pi, 'e': sp.E, 'x': sp.Symbol('x'), 'y': sp.Symbol('y'),
|
||||||
|
'z': sp.Symbol('z'), 't': sp.Symbol('t')
|
||||||
|
}
|
||||||
|
|
||||||
|
# Evaluar la expresión
|
||||||
|
new_expr = sp.sympify(new_expression, locals=eval_context)
|
||||||
|
|
||||||
|
# Crear nuevo PlotResult con la expresión actualizada
|
||||||
|
new_plot_result = PlotResult(
|
||||||
|
plot_result.plot_type,
|
||||||
|
(new_expr,) + plot_result.args[1:],
|
||||||
|
plot_result.kwargs,
|
||||||
|
new_expression
|
||||||
|
)
|
||||||
|
|
||||||
|
# Actualizar la expresión original en el objeto
|
||||||
|
plot_result.original_expression = new_expression
|
||||||
|
|
||||||
|
# Redibujar
|
||||||
|
self._create_plot_in_frame(new_plot_result, canvas_frame)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Mostrar error en el frame
|
||||||
|
if not canvas_frame.layout():
|
||||||
|
canvas_frame.setLayout(QVBoxLayout())
|
||||||
|
|
||||||
|
error_label = QLabel(f"Error en expresión: {e}")
|
||||||
|
error_label.setStyleSheet("color: #f44747; font-size: 11px;")
|
||||||
|
error_label.setWordWrap(True)
|
||||||
|
canvas_frame.layout().addWidget(error_label)
|
||||||
|
|
||||||
|
def _create_plot_in_frame(self, plot_result: PlotResult, parent_frame: QFrame):
|
||||||
|
"""Crea el plot dentro del frame especificado"""
|
||||||
|
try:
|
||||||
|
if not parent_frame.layout():
|
||||||
|
parent_frame.setLayout(QVBoxLayout())
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(8, 6))
|
||||||
|
|
||||||
|
if plot_result.plot_type == "plot":
|
||||||
|
self._create_2d_plot(fig, ax, plot_result.args, plot_result.kwargs)
|
||||||
|
elif plot_result.plot_type == "plot3d":
|
||||||
|
self._create_3d_plot(fig, plot_result.args, plot_result.kwargs)
|
||||||
|
|
||||||
|
# Embed en PySide6
|
||||||
|
canvas = FigureCanvasQTAgg(fig)
|
||||||
|
parent_frame.layout().addWidget(canvas)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if not parent_frame.layout():
|
||||||
|
parent_frame.setLayout(QVBoxLayout())
|
||||||
|
|
||||||
|
error_label = QLabel(f"Error generando plot: {e}")
|
||||||
|
error_label.setStyleSheet("color: #f44747; font-size: 12px;")
|
||||||
|
error_label.setWordWrap(True)
|
||||||
|
parent_frame.layout().addWidget(error_label)
|
||||||
|
|
||||||
|
def _create_2d_plot(self, fig, ax, args, kwargs):
|
||||||
|
"""Crea plot 2D usando SymPy"""
|
||||||
|
if len(args) >= 1:
|
||||||
|
expr = args[0]
|
||||||
|
|
||||||
|
try:
|
||||||
|
if len(args) >= 2:
|
||||||
|
# Rango especificado: (variable, start, end)
|
||||||
|
var_range = args[1]
|
||||||
|
if isinstance(var_range, tuple) and len(var_range) == 3:
|
||||||
|
var, start, end = var_range
|
||||||
|
x_vals = np.linspace(float(start), float(end), 1000)
|
||||||
|
|
||||||
|
# Evaluar expresión
|
||||||
|
f = sympy.lambdify(var, expr, 'numpy')
|
||||||
|
y_vals = f(x_vals)
|
||||||
|
|
||||||
|
ax.plot(x_vals, y_vals, **kwargs)
|
||||||
|
ax.set_xlabel(str(var))
|
||||||
|
ax.set_ylabel(str(expr))
|
||||||
|
ax.grid(True)
|
||||||
|
ax.set_title(f"Plot: {expr}")
|
||||||
|
else:
|
||||||
|
# Rango por defecto
|
||||||
|
free_symbols = list(expr.free_symbols)
|
||||||
|
if free_symbols:
|
||||||
|
var = free_symbols[0]
|
||||||
|
x_vals = np.linspace(-10, 10, 1000)
|
||||||
|
f = sympy.lambdify(var, expr, 'numpy')
|
||||||
|
y_vals = f(x_vals)
|
||||||
|
|
||||||
|
ax.plot(x_vals, y_vals, **kwargs)
|
||||||
|
ax.set_xlabel(str(var))
|
||||||
|
ax.set_ylabel(str(expr))
|
||||||
|
ax.grid(True)
|
||||||
|
ax.set_title(f"Plot: {expr}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
ax.text(0.5, 0.5, f"Error: {e}",
|
||||||
|
transform=ax.transAxes, ha='center', va='center')
|
||||||
|
ax.set_title("Error en Plot")
|
||||||
|
|
||||||
|
def _create_3d_plot(self, fig, args, kwargs):
|
||||||
|
"""Crea plot 3D"""
|
||||||
|
try:
|
||||||
|
ax = fig.add_subplot(111, projection='3d')
|
||||||
|
|
||||||
|
if len(args) >= 3:
|
||||||
|
expr = args[0]
|
||||||
|
x_range = args[1] # (x, x_start, x_end)
|
||||||
|
y_range = args[2] # (y, y_start, y_end)
|
||||||
|
|
||||||
|
if isinstance(x_range, tuple) and isinstance(y_range, tuple):
|
||||||
|
x_var, x_start, x_end = x_range
|
||||||
|
y_var, y_start, y_end = y_range
|
||||||
|
|
||||||
|
x_vals = np.linspace(float(x_start), float(x_end), 50)
|
||||||
|
y_vals = np.linspace(float(y_start), float(y_end), 50)
|
||||||
|
X, Y = np.meshgrid(x_vals, y_vals)
|
||||||
|
|
||||||
|
f = sympy.lambdify([x_var, y_var], expr, 'numpy')
|
||||||
|
Z = f(X, Y)
|
||||||
|
|
||||||
|
ax.plot_surface(X, Y, Z, **kwargs)
|
||||||
|
ax.set_xlabel(str(x_var))
|
||||||
|
ax.set_ylabel(str(y_var))
|
||||||
|
ax.set_zlabel(str(expr))
|
||||||
|
ax.set_title(f"3D Plot: {expr}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
ax.text2D(0.5, 0.5, f"Error: {e}", transform=ax.transAxes)
|
||||||
|
|
||||||
|
def _show_matrix_window(self, matrix: sympy.Matrix, window_key: str):
|
||||||
|
"""Muestra ventana con matriz formateada"""
|
||||||
|
rows, cols = matrix.shape
|
||||||
|
window_title = f"Matriz {rows}×{cols}"
|
||||||
|
|
||||||
|
window = self._create_base_window(window_title, 600, 500)
|
||||||
|
self.open_windows[window_key] = window
|
||||||
|
|
||||||
|
layout = QVBoxLayout(window)
|
||||||
|
|
||||||
|
# Widget de texto con scroll
|
||||||
|
text_widget = QTextEdit()
|
||||||
|
text_widget.setFont(QFont("Courier New", 12))
|
||||||
|
text_widget.setReadOnly(True)
|
||||||
|
|
||||||
|
# Formatear matriz
|
||||||
|
matrix_str = self._format_matrix(matrix)
|
||||||
|
text_widget.setPlainText(matrix_str)
|
||||||
|
|
||||||
|
layout.addWidget(text_widget)
|
||||||
|
|
||||||
|
# Botones de utilidad
|
||||||
|
button_frame = QFrame()
|
||||||
|
button_layout = QHBoxLayout(button_frame)
|
||||||
|
|
||||||
|
if matrix.is_square:
|
||||||
|
try:
|
||||||
|
det_btn = QPushButton("Determinante")
|
||||||
|
det_btn.clicked.connect(
|
||||||
|
lambda: self._show_matrix_property(matrix, "determinante", matrix.det())
|
||||||
|
)
|
||||||
|
button_layout.addWidget(det_btn)
|
||||||
|
|
||||||
|
inv_btn = QPushButton("Inversa")
|
||||||
|
inv_btn.clicked.connect(
|
||||||
|
lambda: self._show_matrix_property(matrix, "inversa", matrix.inv())
|
||||||
|
)
|
||||||
|
button_layout.addWidget(inv_btn)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
layout.addWidget(button_frame)
|
||||||
|
window.show()
|
||||||
|
|
||||||
|
def _format_matrix(self, matrix: sympy.Matrix) -> str:
|
||||||
|
"""Formatea una matriz para display"""
|
||||||
|
rows, cols = matrix.shape
|
||||||
|
|
||||||
|
# Calcular ancho máximo de elementos
|
||||||
|
max_width = 0
|
||||||
|
for i in range(rows):
|
||||||
|
for j in range(cols):
|
||||||
|
element_str = str(matrix[i, j])
|
||||||
|
max_width = max(max_width, len(element_str))
|
||||||
|
|
||||||
|
max_width = max(max_width, 8) # Mínimo 8 caracteres
|
||||||
|
|
||||||
|
# Construir representación
|
||||||
|
lines = []
|
||||||
|
lines.append("┌" + " " * (max_width * cols + cols - 1) + "┐")
|
||||||
|
|
||||||
|
for i in range(rows):
|
||||||
|
line = "│"
|
||||||
|
for j in range(cols):
|
||||||
|
element_str = str(matrix[i, j])
|
||||||
|
padded = element_str.center(max_width)
|
||||||
|
line += padded
|
||||||
|
if j < cols - 1:
|
||||||
|
line += " "
|
||||||
|
line += "│"
|
||||||
|
lines.append(line)
|
||||||
|
|
||||||
|
lines.append("└" + " " * (max_width * cols + cols - 1) + "┘")
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def _show_matrix_property(self, matrix: sympy.Matrix, prop_name: str, prop_value: Any):
|
||||||
|
"""Muestra propiedad de matriz en ventana separada"""
|
||||||
|
window = self._create_base_window(f"Matriz - {prop_name.title()}", 400, 300)
|
||||||
|
|
||||||
|
layout = QVBoxLayout(window)
|
||||||
|
text_widget = QTextEdit()
|
||||||
|
text_widget.setFont(QFont("Courier New", 12))
|
||||||
|
text_widget.setReadOnly(True)
|
||||||
|
|
||||||
|
if isinstance(prop_value, sympy.Matrix):
|
||||||
|
content = f"{prop_name.title()}:\n\n{self._format_matrix(prop_value)}"
|
||||||
|
else:
|
||||||
|
content = f"{prop_name.title()}: {prop_value}"
|
||||||
|
|
||||||
|
text_widget.setPlainText(content)
|
||||||
|
layout.addWidget(text_widget)
|
||||||
|
|
||||||
|
window.show()
|
||||||
|
|
||||||
|
def _show_list_window(self, lst: list, window_key: str):
|
||||||
|
"""Muestra ventana con lista expandida"""
|
||||||
|
window_title = f"Lista ({len(lst)} elementos)"
|
||||||
|
window = self._create_base_window(window_title, 500, 400)
|
||||||
|
self.open_windows[window_key] = window
|
||||||
|
|
||||||
|
layout = QVBoxLayout(window)
|
||||||
|
text_widget = QTextEdit()
|
||||||
|
text_widget.setFont(QFont("Consolas", 11))
|
||||||
|
text_widget.setReadOnly(True)
|
||||||
|
|
||||||
|
content = "Elementos de la lista:\n\n"
|
||||||
|
for i, item in enumerate(lst):
|
||||||
|
content += f"[{i}] {item}\n"
|
||||||
|
|
||||||
|
text_widget.setPlainText(content)
|
||||||
|
layout.addWidget(text_widget)
|
||||||
|
|
||||||
|
window.show()
|
||||||
|
|
||||||
|
def _show_dict_window(self, dct: dict, window_key: str):
|
||||||
|
"""Muestra ventana con diccionario expandido"""
|
||||||
|
window = self._create_base_window(f"Diccionario ({len(dct)} entradas)", 500, 400)
|
||||||
|
self.open_windows[window_key] = window
|
||||||
|
|
||||||
|
layout = QVBoxLayout(window)
|
||||||
|
text_widget = QTextEdit()
|
||||||
|
text_widget.setFont(QFont("Consolas", 11))
|
||||||
|
text_widget.setReadOnly(True)
|
||||||
|
|
||||||
|
content = "Entradas del diccionario:\n\n"
|
||||||
|
for key, value in dct.items():
|
||||||
|
content += f"{key}: {value}\n"
|
||||||
|
|
||||||
|
text_widget.setPlainText(content)
|
||||||
|
layout.addWidget(text_widget)
|
||||||
|
|
||||||
|
window.show()
|
||||||
|
|
||||||
|
def _show_object_window(self, obj: Any, window_key: str):
|
||||||
|
"""Muestra ventana con detalles de objeto"""
|
||||||
|
window = self._create_base_window(f"Objeto - {type(obj).__name__}", 600, 500)
|
||||||
|
self.open_windows[window_key] = window
|
||||||
|
|
||||||
|
layout = QVBoxLayout(window)
|
||||||
|
text_widget = QTextEdit()
|
||||||
|
text_widget.setFont(QFont("Consolas", 11))
|
||||||
|
text_widget.setReadOnly(True)
|
||||||
|
|
||||||
|
content = f"Objeto: {type(obj).__name__}\n\n"
|
||||||
|
content += f"Valor: {obj}\n\n"
|
||||||
|
content += f"Representación: {repr(obj)}\n\n"
|
||||||
|
|
||||||
|
if hasattr(obj, '__dict__'):
|
||||||
|
content += "Atributos:\n"
|
||||||
|
for attr, value in obj.__dict__.items():
|
||||||
|
content += f" {attr}: {value}\n"
|
||||||
|
|
||||||
|
content += "\nMétodos disponibles:\n"
|
||||||
|
for attr in dir(obj):
|
||||||
|
if not attr.startswith('_') and callable(getattr(obj, attr, None)):
|
||||||
|
content += f" {attr}()\n"
|
||||||
|
|
||||||
|
text_widget.setPlainText(content)
|
||||||
|
layout.addWidget(text_widget)
|
||||||
|
|
||||||
|
window.show()
|
||||||
|
|
||||||
|
def _create_base_window(self, title: str, width: int = 500, height: int = 400) -> QWidget:
|
||||||
|
"""Crea ventana base con estilo consistente"""
|
||||||
|
window = QWidget()
|
||||||
|
window.setWindowTitle(title)
|
||||||
|
window.resize(width, height)
|
||||||
|
window.setStyleSheet(self.window_style)
|
||||||
|
|
||||||
|
# Centrar ventana
|
||||||
|
if self.parent:
|
||||||
|
parent_pos = self.parent.pos()
|
||||||
|
parent_size = self.parent.size()
|
||||||
|
x = parent_pos.x() + (parent_size.width() - width) // 2
|
||||||
|
y = parent_pos.y() + (parent_size.height() - height) // 2
|
||||||
|
window.move(x, y)
|
||||||
|
|
||||||
|
return window
|
||||||
|
|
||||||
|
def close_all_windows(self):
|
||||||
|
"""Cierra todas las ventanas interactivas"""
|
||||||
|
windows_to_close = list(self.open_windows.values())
|
||||||
|
|
||||||
|
for window in windows_to_close:
|
||||||
|
if window and not window.isHidden():
|
||||||
|
window.close()
|
||||||
|
|
||||||
|
self.open_windows.clear()
|
||||||
|
|
||||||
|
# Cerrar todas las figuras de matplotlib
|
||||||
|
try:
|
||||||
|
plt.close('all')
|
||||||
|
except:
|
||||||
|
pass
|
Loading…
Reference in New Issue