""" Widgets personalizados para la Calculadora MAV CAS Híbrida """ from PySide6.QtWidgets import ( QPlainTextEdit, QTextEdit, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QFrame, QListWidget, QListWidgetItem, QTextBrowser ) from PySide6.QtCore import Qt, QTimer, Signal from PySide6.QtGui import QFont, QTextCursor from PySide6.QtWebEngineWidgets import QWebEngineView import logging from typing import List, Tuple class InputTextEdit(QPlainTextEdit): """Editor de texto personalizado con eventos mejorados""" def __init__(self, parent=None): super().__init__(parent) self.parent_app = parent self.setLineWrapMode(QPlainTextEdit.NoWrap) self.setFont(QFont("Consolas", 11)) def keyPressEvent(self, event): """Override para manejar autocompletado""" if hasattr(self.parent_app, '_handle_key_press'): # Dejar que el parent maneje primero para autocompletado if self.parent_app._handle_key_press(event): return super().keyPressEvent(event) class OutputTextEdit(QTextEdit): """Editor de salida con soporte para links clickeables""" link_clicked = Signal(str, object) # link_id, result_object def __init__(self, parent=None): super().__init__(parent) self.setReadOnly(True) self.setFont(QFont("Consolas", 11)) self.clickable_links = {} # {(start, end): (link_id, object)} def mousePressEvent(self, event): """Detecta clicks en links""" if event.button() == Qt.LeftButton: cursor = self.cursorForPosition(event.pos()) pos = cursor.position() # Buscar si el click fue en un link for (start, end), (link_id, obj) in self.clickable_links.items(): if start <= pos <= end: self.link_clicked.emit(link_id, obj) return super().mousePressEvent(event) class ExpandableLatexButton(QPushButton): """Botón expandible para mostrar/ocultar panel LaTeX""" def __init__(self, parent=None): super().__init__("📐", parent) self.setFixedWidth(25) self.setToolTip("Mostrar/ocultar panel LaTeX (F12)") self.setStyleSheet(""" QPushButton { background-color: #3c3c3c; color: #80c7f7; border: none; font-size: 14px; padding: 5px; } QPushButton:hover { background-color: #4fc3f7; color: white; } QPushButton:checked { background-color: #4fc3f7; color: white; } """) self.setCheckable(True) class AutocompletePopup(QWidget): """Popup de autocompletado modeless""" item_selected = Signal(str) # Emite el texto seleccionado def __init__(self, parent=None): super().__init__(None, Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) self.setAttribute(Qt.WA_ShowWithoutActivating, True) self.setFocusPolicy(Qt.NoFocus) # Lista de sugerencias self.listbox = QListWidget(self) self.listbox.setFocusPolicy(Qt.NoFocus) self.listbox.itemDoubleClicked.connect(self._on_item_double_clicked) # Layout layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.listbox) # Estilo self.setStyleSheet(""" QWidget { background-color: #3c3f41; border: 1px solid #4fc3f7; border-radius: 4px; } QListWidget { background-color: #3c3f41; color: #bbbbbb; border: none; font-family: 'Consolas'; font-size: 10px; outline: none; } QListWidget::item { padding: 3px 8px; border: none; } QListWidget::item:selected { background-color: #007acc; color: white; } QListWidget::item:hover { background-color: #094771; color: #e0e0e0; } """) self._suggestions = [] self._selected_index = 0 def set_suggestions(self, suggestions: List[Tuple[str, str]]): """Establece las sugerencias [(nombre, descripción), ...]""" self._suggestions = suggestions self.listbox.clear() for name, desc in suggestions: self.listbox.addItem(f"{name} — {desc}") if self.listbox.count() > 0: self.listbox.setCurrentRow(0) self._selected_index = 0 def navigate(self, direction: int): """Navega por las sugerencias (direction: -1=arriba, 1=abajo)""" if self.listbox.count() == 0: return new_index = (self._selected_index + direction) % self.listbox.count() self._selected_index = new_index self.listbox.setCurrentRow(new_index) def get_selected_text(self) -> str: """Obtiene el texto de la sugerencia seleccionada""" if 0 <= self._selected_index < len(self._suggestions): return self._suggestions[self._selected_index][0] return "" def _on_item_double_clicked(self, item): """Maneja doble click en un item""" text = item.text().split(" —")[0].strip() self.item_selected.emit(text) def adjust_size(self): """Ajusta el tamaño del popup según el contenido""" if self.listbox.count() == 0: return # Calcular tamaño necesario max_width = 300 for i in range(self.listbox.count()): item = self.listbox.item(i) width = self.listbox.fontMetrics().horizontalAdvance(item.text()) + 20 max_width = max(max_width, width) max_width = min(max_width, 600) height = min(self.listbox.count() * 20 + 10, 200) self.setFixedSize(max_width, height)