""" 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()