167 lines
5.7 KiB
Python
167 lines
5.7 KiB
Python
"""
|
|
EU-Utility - Universal Search (Core Framework Component)
|
|
|
|
Built-in quick search - not a plugin. Plugins register searchable content.
|
|
"""
|
|
|
|
from PyQt6.QtWidgets import (
|
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
|
QLineEdit, QListWidget, QListWidgetItem, QFrame
|
|
)
|
|
from PyQt6.QtCore import Qt, QTimer
|
|
|
|
|
|
class UniversalSearchView(QWidget):
|
|
"""Universal search interface - built into the framework.
|
|
|
|
Plugins can register searchable content via PluginAPI:
|
|
api.register_search_provider(
|
|
name="My Plugin",
|
|
search_func=my_search_function
|
|
)
|
|
|
|
Press Ctrl+Shift+F to open quick search anywhere.
|
|
"""
|
|
|
|
def __init__(self, overlay_window, parent=None):
|
|
super().__init__(parent)
|
|
self.overlay = overlay_window
|
|
self.search_providers = [] # Registered search providers
|
|
|
|
self._setup_ui()
|
|
|
|
def _setup_ui(self):
|
|
"""Create the search UI."""
|
|
layout = QVBoxLayout(self)
|
|
layout.setSpacing(15)
|
|
layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
# Header
|
|
header = QLabel("🔍 Universal Search")
|
|
header.setStyleSheet("font-size: 24px; font-weight: bold; color: white;")
|
|
layout.addWidget(header)
|
|
|
|
# Search input
|
|
self.search_input = QLineEdit()
|
|
self.search_input.setPlaceholderText("Type to search across all plugins...")
|
|
self.search_input.setStyleSheet("""
|
|
QLineEdit {
|
|
background-color: rgba(30, 35, 45, 200);
|
|
color: white;
|
|
border: 2px solid #4a9eff;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
font-size: 16px;
|
|
}
|
|
""")
|
|
self.search_input.textChanged.connect(self._on_search)
|
|
layout.addWidget(self.search_input)
|
|
|
|
# Results list
|
|
self.results_list = QListWidget()
|
|
self.results_list.setStyleSheet("""
|
|
QListWidget {
|
|
background-color: rgba(30, 35, 45, 200);
|
|
border: 1px solid rgba(100, 110, 130, 80);
|
|
border-radius: 8px;
|
|
color: white;
|
|
}
|
|
QListWidget::item {
|
|
padding: 10px;
|
|
border-bottom: 1px solid rgba(100, 110, 130, 40);
|
|
}
|
|
QListWidget::item:selected {
|
|
background-color: #4a9eff;
|
|
}
|
|
""")
|
|
self.results_list.itemClicked.connect(self._on_result_clicked)
|
|
layout.addWidget(self.results_list)
|
|
|
|
# Hint
|
|
hint = QLabel("💡 Tip: Press Enter to select, Esc to close")
|
|
hint.setStyleSheet("color: rgba(255,255,255,100); font-size: 11px;")
|
|
layout.addWidget(hint)
|
|
|
|
# Debounce timer
|
|
self.search_timer = QTimer()
|
|
self.search_timer.setSingleShot(True)
|
|
self.search_timer.timeout.connect(self._perform_search)
|
|
|
|
# Focus search input
|
|
self.search_input.setFocus()
|
|
|
|
def _on_search(self, text: str):
|
|
"""Handle search input change."""
|
|
if len(text) < 2:
|
|
self.results_list.clear()
|
|
return
|
|
|
|
# Debounce search
|
|
self.search_timer.stop()
|
|
self.search_timer.start(300) # 300ms delay
|
|
|
|
def _perform_search(self):
|
|
"""Perform the search."""
|
|
query = self.search_input.text().lower()
|
|
self.results_list.clear()
|
|
|
|
# Search through registered providers
|
|
for provider in self.search_providers:
|
|
try:
|
|
results = provider['search_func'](query)
|
|
for result in results:
|
|
item = QListWidgetItem(f"[{provider['name']}] {result['title']}")
|
|
item.setData(Qt.ItemDataRole.UserRole, result)
|
|
self.results_list.addItem(item)
|
|
except Exception as e:
|
|
print(f"[UniversalSearch] Provider '{provider['name']}' error: {e}")
|
|
|
|
# Add default results if no providers
|
|
if not self.search_providers:
|
|
item = QListWidgetItem("🔌 Install plugins to enable search functionality")
|
|
item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEnabled)
|
|
self.results_list.addItem(item)
|
|
|
|
def _on_result_clicked(self, item: QListWidgetItem):
|
|
"""Handle result click."""
|
|
result = item.data(Qt.ItemDataRole.UserRole)
|
|
if result and 'action' in result:
|
|
result['action']()
|
|
|
|
def register_provider(self, name: str, search_func):
|
|
"""Register a search provider from a plugin.
|
|
|
|
Args:
|
|
name: Provider name (shown in brackets)
|
|
search_func: Function that takes query string and returns list of dicts:
|
|
[
|
|
{
|
|
'title': 'Result Title',
|
|
'description': 'Optional description',
|
|
'action': lambda: do_something()
|
|
}
|
|
]
|
|
"""
|
|
self.search_providers.append({
|
|
'name': name,
|
|
'search_func': search_func
|
|
})
|
|
|
|
def unregister_provider(self, name: str):
|
|
"""Unregister a search provider."""
|
|
self.search_providers = [
|
|
p for p in self.search_providers if p['name'] != name
|
|
]
|
|
|
|
def keyPressEvent(self, event):
|
|
"""Handle key press events."""
|
|
if event.key() == Qt.Key.Key_Escape:
|
|
self.hide()
|
|
elif event.key() == Qt.Key.Key_Return:
|
|
# Select first result
|
|
if self.results_list.count() > 0:
|
|
item = self.results_list.item(0)
|
|
self._on_result_clicked(item)
|
|
else:
|
|
super().keyPressEvent(event)
|