Calc/app/gui_widgets.py

186 lines
6.2 KiB
Python

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