Calc/app/gui_menus.py

517 lines
19 KiB
Python

"""
Sistema de Menús y Diálogos para la Calculadora MAV CAS Híbrida
"""
import os
import time
from pathlib import Path
from PySide6.QtWidgets import (
QMenuBar, QMenu, QMessageBox, QFileDialog
)
from PySide6.QtGui import QAction, QKeySequence
from PySide6.QtCore import QTimer
from PySide6.QtWidgets import QApplication
class MenuManager:
"""Gestor del sistema de menús"""
def __init__(self, main_window):
self.main_window = main_window
self.logger = main_window.logger
def setup_menu(self):
"""Configura el menú completo"""
menubar = self.main_window.menuBar()
# Menú Archivo
file_menu = menubar.addMenu("Archivo")
new_action = QAction("Nuevo", self.main_window)
new_action.setShortcut(QKeySequence.New)
new_action.triggered.connect(self.new_session)
file_menu.addAction(new_action)
file_menu.addSeparator()
load_action = QAction("Cargar...", self.main_window)
load_action.setShortcut(QKeySequence.Open)
load_action.triggered.connect(self.load_file)
file_menu.addAction(load_action)
save_action = QAction("Guardar como...", self.main_window)
save_action.setShortcut(QKeySequence.Save)
save_action.triggered.connect(self.save_file)
file_menu.addAction(save_action)
file_menu.addSeparator()
exit_action = QAction("Salir", self.main_window)
exit_action.triggered.connect(self.main_window.close)
file_menu.addAction(exit_action)
# Menú Editar
edit_menu = menubar.addMenu("Editar")
clear_input_action = QAction("Limpiar entrada", self.main_window)
clear_input_action.triggered.connect(self.clear_input)
edit_menu.addAction(clear_input_action)
clear_output_action = QAction("Limpiar salida", self.main_window)
clear_output_action.triggered.connect(self.clear_output)
edit_menu.addAction(clear_output_action)
edit_menu.addSeparator()
clear_history_action = QAction("Limpiar historial", self.main_window)
clear_history_action.triggered.connect(self.clear_history)
edit_menu.addAction(clear_history_action)
# Menú Ver
view_menu = menubar.addMenu("Ver")
toggle_latex_action = QAction("📐 Panel LaTeX", self.main_window)
toggle_latex_action.setShortcut(QKeySequence("F12"))
toggle_latex_action.triggered.connect(self.main_window._toggle_latex_panel)
view_menu.addAction(toggle_latex_action)
view_menu.addSeparator()
system_info_action = QAction("Información del sistema", self.main_window)
system_info_action.triggered.connect(self.show_types_info)
view_menu.addAction(system_info_action)
# Menú Herramientas
tools_menu = menubar.addMenu("Herramientas")
reload_types_action = QAction("Recargar Tipos Personalizados", self.main_window)
reload_types_action.triggered.connect(self.reload_types)
tools_menu.addAction(reload_types_action)
tools_menu.addSeparator()
# Menú de diagnóstico
diag_menu = tools_menu.addMenu("Diagnóstico")
mathjax_diag_action = QAction("🔍 Diagnóstico MathJax", self.main_window)
mathjax_diag_action.triggered.connect(self._diagnose_mathjax)
diag_menu.addAction(mathjax_diag_action)
latex_status_action = QAction("📊 Estado Panel LaTeX", self.main_window)
latex_status_action.triggered.connect(self._show_latex_panel_status)
diag_menu.addAction(latex_status_action)
diag_menu.addSeparator()
copy_debug_action = QAction("📋 Copiar Debug al Portapapeles", self.main_window)
copy_debug_action.setShortcut(QKeySequence("Ctrl+Shift+C"))
copy_debug_action.triggered.connect(self._copy_debug_to_clipboard)
diag_menu.addAction(copy_debug_action)
# Menú Tipos
types_menu = menubar.addMenu("Tipos")
types_info_action = QAction("Información de tipos", self.main_window)
types_info_action.triggered.connect(self.show_types_info)
types_menu.addAction(types_info_action)
types_menu.addSeparator()
types_syntax_action = QAction("Sintaxis de tipos", self.main_window)
types_syntax_action.triggered.connect(self.show_types_syntax)
types_menu.addAction(types_syntax_action)
# Menú Ayuda
help_menu = menubar.addMenu("Ayuda")
quick_guide_action = QAction("Guía rápida", self.main_window)
quick_guide_action.triggered.connect(self.show_quick_guide)
help_menu.addAction(quick_guide_action)
syntax_help_action = QAction("Sintaxis", self.main_window)
syntax_help_action.triggered.connect(self.show_syntax_help)
help_menu.addAction(syntax_help_action)
sympy_funcs_action = QAction("Funciones SymPy", self.main_window)
sympy_funcs_action.triggered.connect(self.show_sympy_functions)
help_menu.addAction(sympy_funcs_action)
help_menu.addSeparator()
about_action = QAction("Acerca de", self.main_window)
about_action.triggered.connect(self.show_about)
help_menu.addAction(about_action)
# ========== FUNCIONES DE MENÚ ARCHIVO ==========
def new_session(self):
"""Inicia una nueva sesión"""
self.clear_input()
self.clear_output()
self.main_window.latex_panel.clear_equations()
if hasattr(self.main_window, '_latex_equations'):
self.main_window._latex_equations.clear()
self.main_window._update_status("✨ Nueva sesión iniciada")
def load_file(self):
"""Carga archivo en el editor"""
filepath, _ = QFileDialog.getOpenFileName(
self.main_window, "Cargar archivo", "",
"Archivos de texto (*.txt);;Archivos Python (*.py);;Todos los archivos (*.*)"
)
if filepath:
try:
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
self.main_window.input_text.setPlainText(content)
self.main_window._evaluate_and_update()
self.main_window._update_status(f"📁 Archivo cargado: {Path(filepath).name}")
except Exception as e:
QMessageBox.critical(self.main_window, "Error", f"No se pudo cargar el archivo:\n{e}")
def save_file(self):
"""Guarda contenido del editor"""
filepath, _ = QFileDialog.getSaveFileName(
self.main_window, "Guardar archivo", "",
"Archivos de texto (*.txt);;Archivos Python (*.py);;Todos los archivos (*.*)"
)
if filepath:
try:
content = self.main_window.input_text.toPlainText()
with open(filepath, "w", encoding="utf-8") as f:
f.write(content)
self.main_window._update_status(f"💾 Archivo guardado: {Path(filepath).name}")
except Exception as e:
QMessageBox.critical(self.main_window, "Error", f"No se pudo guardar el archivo:\n{e}")
# ========== FUNCIONES DE MENÚ EDITAR ==========
def clear_input(self):
"""Limpia panel de entrada"""
self.main_window.input_text.clear()
self.main_window._clear_output()
def clear_output(self):
"""Limpia panel de salida y LaTeX"""
self.main_window._clear_output()
self.main_window.latex_panel.clear_equations()
if hasattr(self.main_window, '_latex_equations'):
self.main_window._latex_equations.clear()
def clear_history(self):
"""Limpia el archivo de historial"""
try:
if os.path.exists(self.main_window.HISTORY_FILE):
os.remove(self.main_window.HISTORY_FILE)
self.main_window._update_status("✓ Historial limpiado")
except Exception as e:
QMessageBox.critical(self.main_window, "Error", f"No se pudo limpiar el historial:\n{e}")
# ========== FUNCIONES DE MENÚ HERRAMIENTAS ==========
def reload_types(self):
"""Recarga el sistema de tipos"""
try:
self.logger.info("Recargando sistema de tipos...")
self.main_window._setup_dynamic_helpers()
self.main_window._evaluate_and_update()
self.main_window._update_status("✓ Sistema de tipos recargado")
except Exception as e:
self.logger.error(f"Error recargando tipos: {e}")
QMessageBox.critical(self.main_window, "Error", f"Error recargando tipos:\n{e}")
def show_types_info(self):
"""Muestra información sobre tipos disponibles"""
try:
context_info = self.main_window.engine.get_context_info()
info_text = f"""INFORMACIÓN DEL SISTEMA ALGEBRAICO PURO
Ecuaciones en el sistema: {context_info.get('equations', 0)}
Variables definidas: {context_info.get('variables', 0)}
Variables activas: {', '.join(context_info.get('variable_names', []))}
CARACTERÍSTICAS:
• Sistema de ecuaciones puras con SymPy
• Todas las asignaciones son ecuaciones
• Resolución automática de sistemas
• Evaluación numérica inteligente
• Atajo x=? equivale a solve(x)
"""
self._show_info_dialog("Información del Sistema", info_text)
except Exception as e:
QMessageBox.critical(self.main_window, "Error", f"Error obteniendo información:\n{e}")
def show_types_syntax(self):
"""Muestra sintaxis de tipos disponibles"""
try:
types_info = self.main_window.engine.get_available_types()
syntax_text = "SINTAXIS DE TIPOS DISPONIBLES\n\n"
# Aquí iría el código para mostrar sintaxis
# Similar al original pero adaptado para PySide6
self._show_info_dialog("Sintaxis de Tipos", syntax_text)
except Exception as e:
QMessageBox.critical(self.main_window, "Error", f"Error obteniendo sintaxis:\n{e}")
# ========== FUNCIONES DE DIAGNÓSTICO ==========
def _diagnose_mathjax(self):
"""Ejecuta diagnóstico de MathJax"""
if not hasattr(self.main_window.latex_panel, '_webview_available') or not self.main_window.latex_panel._webview_available:
QMessageBox.warning(self.main_window, "Diagnóstico", "Panel LaTeX no usa WebEngine (usando fallback)")
return
# Aquí iría el código de diagnóstico
# Por ahora solo mostrar estado
status = "WebEngine disponible" if self.main_window.latex_panel._webview_available else "Usando fallback HTML"
equations = len(self.main_window._latex_equations) if hasattr(self.main_window, '_latex_equations') else 0
info = f"""DIAGNÓSTICO MATHJAX
Estado: {status}
Ecuaciones en memoria: {equations}
Panel visible: {self.main_window.latex_panel_visible}
Para depuración completa, revise la consola del navegador
en el WebEngineView.
"""
self._show_info_dialog("Diagnóstico MathJax", info)
def _show_latex_panel_status(self):
"""Muestra estado del panel LaTeX"""
panel_exists = hasattr(self.main_window, 'latex_panel')
panel_visible = self.main_window.latex_panel_visible if panel_exists else False
webview_available = self.main_window.latex_panel._webview_available if panel_exists else False
equations_count = len(self.main_window._latex_equations) if hasattr(self.main_window, '_latex_equations') else 0
status_message = f"""ESTADO DEL PANEL LATEX
COMPONENTES:
• Panel creado: {'' if panel_exists else ''}
• Panel visible: {'' if panel_visible else ''}
• WebEngine disponible: {'' if webview_available else ''}
CONTENIDO:
• Ecuaciones en memoria: {equations_count}
PARA SOLUCIONAR:
1. Si las ecuaciones están en memoria pero no se ven:
→ Cerrar y reabrir el panel LaTeX
2. Si WebEngine no está disponible:
→ Instalar con: pip install PySide6-WebEngine
"""
self._show_info_dialog("Estado Panel LaTeX", status_message)
def _copy_debug_to_clipboard(self):
"""Copia información de debug completa al portapapeles"""
try:
# Obtener contenido de entrada
input_content = self.main_window.input_text.toPlainText()
# Obtener contenido de salida (texto plano)
output_content = self.main_window.output_text.toPlainText()
# Obtener información del sistema
context_info = self.main_window.engine.get_context_info()
# Obtener ecuaciones LaTeX si están disponibles
latex_equations = ""
if hasattr(self.main_window, '_latex_equations') and self.main_window._latex_equations:
latex_equations = "\\n".join([
f"[{eq['type']}] {eq['content']}"
for eq in self.main_window._latex_equations
])
# Crear reporte de debug completo
debug_report = f"""=== REPORTE DEBUG CALCULADORA MAV ===
Timestamp: {time.strftime('%Y-%m-%d %H:%M:%S')}
=== ENTRADA ===
{input_content}
=== SALIDA ===
{output_content}
=== INFORMACIÓN DEL SISTEMA ===
Ecuaciones en sistema: {context_info.get('equations', 0)}
Variables definidas: {context_info.get('variables', 0)}
Variables activas: {', '.join(context_info.get('variable_names', []))}
=== PANEL LATEX ===
Ecuaciones LaTeX: {len(self.main_window._latex_equations) if hasattr(self.main_window, '_latex_equations') else 0}
{latex_equations}
=== CONFIGURACIÓN ===
WebEngine disponible: {self.main_window.latex_panel._webview_available}
MathJax listo: {getattr(self.main_window.latex_panel, '_mathjax_ready', False)}
Panel LaTeX visible: {self.main_window.latex_panel_visible}
=== FIN REPORTE ==="""
# Copiar al portapapeles
clipboard = QApplication.clipboard()
clipboard.setText(debug_report)
# Mostrar confirmación
self.main_window._update_status("📋 Información de debug copiada al portapapeles", 3000)
except Exception as e:
self.logger.error(f"Error copiando debug: {e}")
QMessageBox.critical(self.main_window, "Error", f"Error copiando debug al portapapeles:\\n{e}")
# ========== FUNCIONES DE MENÚ AYUDA ==========
def show_quick_guide(self):
"""Muestra guía rápida"""
guide = """# Calculadora MAV - CAS Híbrido
## Sistema de Tipos Dinámico
El sistema detecta automáticamente tipos disponibles en custom_types/
## Sintaxis Nueva con Corchetes
- Sintaxis: Tipo[valor] en lugar de Tipo("valor")
- Ejemplos: Hex[FF], Bin[1010], Dec[10.5], Chr[A]
- Use menú Tipos → Información de tipos para ver tipos disponibles
## Ecuaciones Automáticas
- x**2 + 2*x = 8 (detectado automáticamente)
- a + b = 10 (agregado al sistema)
- variable=? (atajo para solve(variable))
## Funciones SymPy Disponibles
- solve(), diff(), integrate(), limit(), series()
- sin(), cos(), tan(), exp(), log(), sqrt()
- Matrix(), plot(), plot3d()
## Resultados Interactivos
- 📊 Ver Plot (click para ventana matplotlib)
- 📋 Ver Matriz (click para vista expandida)
- 📋 Ver Lista (click para contenido completo)
## Variables Automáticas
- Todas las variables son símbolos SymPy
- x = 5 crea Symbol('x') con valor 5
- Evaluación simbólica + numérica automática
## Autocompletado Dinámico
- Escriba "." después de cualquier objeto para ver métodos
- El sistema usa los tipos registrados automáticamente
"""
self._show_info_dialog("Guía Rápida", guide)
def show_syntax_help(self):
"""Muestra ayuda de sintaxis"""
syntax = """# Sintaxis del CAS Híbrido
## Sistema de Tipos Dinámico
Los tipos se detectan automáticamente desde custom_types/
Use menú Tipos → Información de tipos para ver tipos disponibles
## Sintaxis con Corchetes (Dinámica)
Tipo[valor] # Sintaxis general
Tipo[arg1; arg2] # Múltiples argumentos
## Métodos Disponibles (Dinámicos)
Tipo[...].método() # Métodos específicos del tipo
objeto.método[] # Método sin argumentos
## Ecuaciones (detección automática)
expresión = expresión # Ecuación simple
expresión == expresión # Igualdad SymPy
expresión > expresión # Desigualdad SymPy
## Resolver
solve(ecuación, variable)
variable=? # Atajo para solve(variable)
## Variables SymPy Puras
x = valor # Crea Symbol('x')
expresión # Evaluación simbólica automática
"""
self._show_info_dialog("Sintaxis", syntax)
def show_sympy_functions(self):
"""Muestra funciones SymPy disponibles"""
functions = """# Funciones SymPy Disponibles
## Matemáticas Básicas
sin(x), cos(x), tan(x)
asin(x), acos(x), atan(x)
sinh(x), cosh(x), tanh(x)
exp(x), log(x), sqrt(x)
abs(x), sign(x), factorial(x)
## Cálculo
diff(expr, var) # Derivada
integrate(expr, var) # Integral indefinida
integrate(expr, (var, a, b)) # Integral definida
limit(expr, var, punto) # Límite
series(expr, var, punto, n) # Serie de Taylor
## Álgebra
solve(ecuación, variable)
simplify(expr), expand(expr)
factor(expr), collect(expr, var)
cancel(expr), apart(expr, var)
## Álgebra Lineal
Matrix([[a, b], [c, d]])
det(matrix), inv(matrix)
## Plotting
plot(expr, (var, inicio, fin))
plot3d(expr, (x, x1, x2), (y, y1, y2))
## Constantes
pi, E, I (imaginario), oo (infinito)
"""
self._show_info_dialog("Funciones SymPy", functions)
def show_about(self):
"""Muestra información sobre la aplicación"""
about = """Calculadora MAV - CAS Híbrido
Versión: 2.1 PySide6 (Sistema de Tipos Dinámico)
Motor: SymPy + Auto-descubrimiento de Tipos
Características:
• Motor algebraico completo (SymPy)
• Sistema de tipos dinámico y extensible
• Sintaxis simplificada con corchetes
• Detección automática de ecuaciones
• Resultados interactivos clickeables
• Auto-descubrimiento de tipos en custom_types/
• Variables SymPy puras
• Plotting integrado
• Autocompletado dinámico
Desarrollado para cálculo matemático avanzado
con soporte especializado para redes,
programación y análisis numérico.
"""
QMessageBox.about(self.main_window, "Acerca de", about)
def _show_info_dialog(self, title: str, content: str):
"""Muestra diálogo de información con scroll"""
dialog = QMessageBox(self.main_window)
dialog.setWindowTitle(title)
dialog.setIcon(QMessageBox.Information)
dialog.setText(content[:200] + "..." if len(content) > 200 else content)
dialog.setDetailedText(content)
dialog.exec()