""" Calculadora MAV CAS Híbrida - Aplicación principal PySide6 (Refactorizada) VERSIÓN MODULAR: Código organizado en módulos especializados """ import sys import os import logging from typing import Optional, Dict, Any, List from PySide6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QSplitter, QStatusBar ) from PySide6.QtCore import Qt, QTimer from PySide6.QtGui import QKeySequence, QShortcut, QPixmap, QIcon # Importar componentes del CAS híbrido from .evaluation import PureAlgebraicEngine from .gui_popup import InteractiveResultManager from .type_registry import get_registered_helper_functions from .sympy_Helper import SympyTools as SympyHelper # Importar módulos refactorizados from .gui_widgets import InputTextEdit, OutputTextEdit, ExpandableLatexButton, AutocompletePopup from .gui_latex import LatexPanel from .gui_autocomplete import AutocompleteManager from .gui_evaluation import EvaluationManager from .gui_menus import MenuManager from .gui_settings import SettingsManager class HybridCalculatorPySide6(QMainWindow): """Aplicación principal del CAS híbrido - VERSIÓN COMPLETA""" SETTINGS_FILE = "./.data/settings.json" HISTORY_FILE = "./.data/history.txt" def __init__(self): super().__init__() # Configurar logging logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s') self.logger = logging.getLogger(__name__) # Configurar icono de la aplicación self._setup_application_icon() # Motor y managers self.engine = PureAlgebraicEngine() self.interactive_manager = None # Inicializar managers self.settings_manager = SettingsManager(self) self.menu_manager = MenuManager(self) self.autocomplete_manager = AutocompleteManager(self) self.evaluation_manager = EvaluationManager(self) # Configuración self.settings = self.settings_manager.settings self.debug = self.settings.get("debug_mode", False) # Estado del panel LaTeX self.latex_panel_visible = self.settings.get("latex_panel_visible", False) self._latex_equations = [] # Configurar helpers self._setup_dynamic_helpers() # Configurar UI self._setup_ui() # Configurar componentes usando managers self.menu_manager.setup_menu() self._setup_interactive_manager() # Cargar historial y configuración self.settings_manager.initialize() self.logger.info("✅ Calculadora MAV PySide6 (versión modular) inicializada") def _setup_dynamic_helpers(self): """Configura helpers dinámicamente desde el registro de tipos""" try: self.HELPERS = get_registered_helper_functions() self.HELPERS.append(SympyHelper.Helper) self.logger.info(f"Helpers dinámicos cargados: {len(self.HELPERS)}") except Exception as e: self.logger.error(f"Error cargando helpers dinámicos: {e}") self.HELPERS = [SympyHelper.Helper] def _setup_ui(self): """Configura la interfaz de usuario completa""" self.setWindowTitle("Calculadora MAV - CAS Híbrido") self.setGeometry(100, 100, 1000, 700) # Widget central central_widget = QWidget() self.setCentralWidget(central_widget) # Layout principal horizontal main_layout = QHBoxLayout(central_widget) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) # Splitter principal para entrada y salida self.main_splitter = QSplitter(Qt.Horizontal) # Panel de entrada self.input_text = InputTextEdit(self) self.input_text.setPlaceholderText("Introduce expresiones matemáticas...") self.input_text.textChanged.connect(self._on_input_changed) # Panel de salida self.output_text = OutputTextEdit() self.output_text.link_clicked.connect(self._handle_output_link_click) # Añadir al splitter self.main_splitter.addWidget(self.input_text) self.main_splitter.addWidget(self.output_text) # Configurar tamaños iniciales self.main_splitter.setSizes([450, 450]) # Sincronizar scroll self._setup_scroll_sync() # Añadir splitter al layout main_layout.addWidget(self.main_splitter) # Botón expandible para LaTeX self.latex_button = ExpandableLatexButton() self.latex_button.clicked.connect(self._toggle_latex_panel) main_layout.addWidget(self.latex_button) # Panel LaTeX (inicialmente oculto) self.latex_panel = LatexPanel(self) self.latex_panel.setMinimumWidth(300) self.latex_panel.setMaximumWidth(500) if self.latex_panel_visible: main_layout.addWidget(self.latex_panel) self.latex_button.setChecked(True) else: # Asegurar que el panel está oculto por defecto self.latex_panel.hide() self.latex_button.setChecked(False) # Los formatos de salida se configuran automáticamente en EvaluationManager # Barra de estado self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self._update_status("🔢 Calculadora MAV - Sistema Algebraico Puro") # Aplicar tema oscuro self._apply_dark_theme() def _setup_scroll_sync(self): """Sincroniza el scroll entre entrada y salida""" def sync_input_to_output(): if hasattr(self, '_syncing'): return self._syncing = True self.output_text.verticalScrollBar().setValue( self.input_text.verticalScrollBar().value() ) self._syncing = False def sync_output_to_input(): if hasattr(self, '_syncing'): return self._syncing = True self.input_text.verticalScrollBar().setValue( self.output_text.verticalScrollBar().value() ) self._syncing = False self.input_text.verticalScrollBar().valueChanged.connect(sync_input_to_output) self.output_text.verticalScrollBar().valueChanged.connect(sync_output_to_input) def _setup_interactive_manager(self): """Configura el manager de interactividad""" self.interactive_manager = InteractiveResultManager() def _apply_dark_theme(self): """Aplica el tema oscuro completo""" dark_style = """ QMainWindow { background-color: #1a1a1a; color: #d4d4d4; } QPlainTextEdit, QTextEdit { background-color: #1e1e1e; color: #d4d4d4; border: 1px solid #3c3c3c; font-family: 'Consolas', 'Courier New', monospace; font-size: 11px; padding: 5px; selection-background-color: #264f78; } QSplitter { background-color: #1a1a1a; } QSplitter::handle { background-color: #3c3c3c; width: 2px; height: 2px; } QSplitter::handle:hover { background-color: #4fc3f7; } QMenuBar { background-color: #2d2d30; color: #cccccc; border-bottom: 1px solid #3c3c3c; } QMenuBar::item { background-color: transparent; padding: 4px 8px; } QMenuBar::item:selected { background-color: #4fc3f7; color: white; } QMenu { background-color: #2d2d30; color: #cccccc; border: 1px solid #3c3c3c; } QMenu::item { padding: 4px 20px; } QMenu::item:selected { background-color: #4fc3f7; color: white; } QStatusBar { background-color: #007acc; color: white; border-top: 1px solid #3c3c3c; } """ self.setStyleSheet(dark_style) def _on_input_changed(self): """Maneja cambios en el texto de entrada""" # Delegar al autocomplete manager self.autocomplete_manager.on_input_changed() # Programar evaluación con debounce self.evaluation_manager.schedule_evaluation() def _evaluate_and_update(self): """Evalúa y actualiza la salida - método requerido por EvaluationManager""" self.evaluation_manager.evaluate_and_update() def _handle_key_press(self, event) -> bool: """Maneja eventos de teclado para autocompletado""" return self.autocomplete_manager.handle_key_press(event) def _handle_output_link_click(self, link_id: str, result_object): """Maneja clicks en links del output""" # Delegar al evaluation manager self.evaluation_manager.handle_output_link_click(link_id, result_object) def _toggle_latex_panel(self): """Togglea la visibilidad del panel LaTeX""" main_layout = self.centralWidget().layout() if self.latex_panel_visible: # Ocultar panel main_layout.removeWidget(self.latex_panel) self.latex_panel.hide() self.latex_button.setChecked(False) self.latex_panel_visible = False else: # Mostrar panel main_layout.addWidget(self.latex_panel) self.latex_panel.show() self.latex_button.setChecked(True) self.latex_panel_visible = True # Actualizar panel con ecuaciones pendientes self._sync_latex_equations_on_show() # Guardar configuración self.settings_manager.set_setting("latex_panel_visible", self.latex_panel_visible) def _sync_latex_equations_on_show(self): """Sincroniza las ecuaciones LaTeX cuando se muestra el panel""" try: # Limpiar panel self.latex_panel.clear_equations() # Añadir todas las ecuaciones pendientes for eq in self.evaluation_manager.get_latex_equations(): self.latex_panel.add_equation(eq['type'], eq['content']) except Exception as e: self.logger.error(f"Error sincronizando ecuaciones LaTeX: {e}") def _update_status(self, message: str, timeout: int = 0): """Actualiza la barra de estado""" self.status_bar.showMessage(message, timeout) def closeEvent(self, event): """Maneja el cierre de la aplicación""" # Delegar al settings manager self.settings_manager.save_all() event.accept() def _setup_application_icon(self): """Configura el icono de la aplicación""" # Buscar el icono en el directorio raíz del proyecto icon_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), ".res", "icon.png") if os.path.exists(icon_path): self.setWindowIcon(QIcon(icon_path)) self.logger.debug(f"✅ Icono cargado desde: {icon_path}") else: self.logger.warning(f"⚠️ Icono no encontrado en: {icon_path}") def main(): """Función principal para ejecutar la aplicación""" app = QApplication(sys.argv) # Configurar la aplicación app.setApplicationName("Calculadora MAV") app.setApplicationVersion("3.0") app.setOrganizationName("MAV") # Crear y mostrar la ventana principal calculator = HybridCalculatorPySide6() calculator.show() # Ejecutar el loop principal sys.exit(app.exec()) if __name__ == "__main__": main()