mirror of
https://github.com/jtang613/IDAssist
synced 2026-08-09 12:43:55 +00:00
Add Qt5/Qt6 compatibility layer for IDA 9.1+ support
Add qt_compat.py shim that tries PySide6 first, falls back to PyQt5, and re-exports all Qt classes under unified names. Replace all direct PySide6 imports across 22 files with qt_compat imports. Normalize scoped enums to flat enums for PyQt5 compatibility, replace .exec() with exec_dialog() wrapper, and fix QTimeZone.utc() portability. Bump version to 1.2.0 and add IDA 9.1 to supported versions.
This commit is contained in:
+2
-2
@@ -3,7 +3,7 @@
|
||||
"plugin": {
|
||||
"name": "IDAssist",
|
||||
"entryPoint": "idassist_plugin.py",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"description": "AI-powered reverse engineering plugin with LLM analysis, semantic knowledge graphs, RAG search, and SymGraph collaboration",
|
||||
"license": "MIT",
|
||||
"urls": {
|
||||
@@ -16,7 +16,7 @@
|
||||
}
|
||||
],
|
||||
"categories": ["integration-with-third-parties-interoperability", "api-scripting-and-automation"],
|
||||
"idaVersions": ["9.2", "9.3", "9.4"],
|
||||
"idaVersions": ["9.1", "9.2", "9.3", "9.4"],
|
||||
"logoPath": "docs/screenshots/icon.png",
|
||||
"keywords": ["llm", "ai", "reverse-engineering", "analysis", "knowledge-graph", "symgraph"],
|
||||
"pythonDependencies": [
|
||||
|
||||
@@ -8,7 +8,7 @@ Follows the same patterns as ExplainController and QueryController.
|
||||
"""
|
||||
|
||||
from typing import Optional, Dict, Any, List
|
||||
from PySide6.QtCore import QThread, Signal, Qt
|
||||
from ..qt_compat import QThread, Signal, Qt
|
||||
from src.services.binary_context_service import BinaryContextService, ViewLevel
|
||||
from src.services.actions_service import ActionsService
|
||||
from src.services.settings_service import SettingsService
|
||||
|
||||
@@ -9,7 +9,7 @@ Follows the same patterns as ExplainLLMThread but specialized for Actions tools.
|
||||
|
||||
import asyncio
|
||||
from typing import List, Dict, Any
|
||||
from PySide6.QtCore import QThread, Signal
|
||||
from ..qt_compat import QThread, Signal
|
||||
from ..services.models.llm_models import ToolCall, ChatRequest, ChatMessage, MessageRole
|
||||
|
||||
from src.ida_compat import log
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from typing import Optional, Dict, Any, List
|
||||
from datetime import datetime
|
||||
import asyncio
|
||||
from PySide6.QtCore import QThread, Signal, QTimer
|
||||
from ..qt_compat import QThread, Signal, QTimer
|
||||
from src.services.binary_context_service import BinaryContextService, ViewLevel
|
||||
from src.services.llm_providers.provider_factory import LLMProviderFactory
|
||||
from src.services.settings_service import SettingsService
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from typing import Optional, Dict, Any, List, Callable
|
||||
import asyncio
|
||||
from PySide6.QtCore import QDateTime, QThread, QTimer, Signal, Qt
|
||||
from ..qt_compat import QDateTime, QThread, QTimer, Signal, Qt, utc_timezone
|
||||
from src.ida_compat import log
|
||||
from src.services.binary_context_service import BinaryContextService, ViewLevel
|
||||
from src.services.llm_providers.provider_factory import LLMProviderFactory
|
||||
@@ -688,11 +688,9 @@ class QueryController:
|
||||
def _convert_utc_to_local_display(self, utc_timestamp: str) -> str:
|
||||
"""Convert UTC timestamp from database to local display format"""
|
||||
try:
|
||||
from PySide6.QtCore import QDateTime, QTimeZone
|
||||
|
||||
# Parse the UTC timestamp and explicitly set it as UTC time
|
||||
qdt_utc = QDateTime.fromString(utc_timestamp, "yyyy-MM-dd hh:mm:ss")
|
||||
qdt_utc.setTimeZone(QTimeZone.utc()) # This time is in UTC
|
||||
qdt_utc.setTimeZone(utc_timezone()) # This time is in UTC
|
||||
|
||||
# Convert to local time zone
|
||||
qdt_local = qdt_utc.toLocalTime()
|
||||
@@ -706,8 +704,7 @@ class QueryController:
|
||||
|
||||
def _add_chat_to_history_direct(self, chat_id: int, description: str, timestamp: str):
|
||||
"""Add chat to history table directly without triggering signals (for bulk loading)"""
|
||||
from PySide6.QtWidgets import QTableWidgetItem
|
||||
from PySide6.QtCore import Qt
|
||||
from ..qt_compat import QTableWidgetItem
|
||||
|
||||
row_count = self.view.history_table.rowCount()
|
||||
self.view.history_table.insertRow(row_count)
|
||||
@@ -1095,7 +1092,7 @@ class QueryController:
|
||||
return
|
||||
|
||||
# Show confirmation dialog
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
from ..qt_compat import QMessageBox
|
||||
chat_list = "\n".join([f"• {name}" for name in chat_names[:5]]) # Show first 5
|
||||
if len(chat_names) > 5:
|
||||
chat_list += f"\n• ... and {len(chat_names) - 5} more"
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
import os
|
||||
from typing import List, Optional
|
||||
from PySide6.QtWidgets import QFileDialog, QMessageBox
|
||||
from PySide6.QtCore import QThread, Signal
|
||||
from ..qt_compat import QFileDialog, QMessageBox, QThread, Signal, exec_dialog
|
||||
|
||||
from src.services.rag_service import rag_service
|
||||
from src.services.models.rag_models import IngestRequest, SearchRequest, SearchType
|
||||
@@ -80,7 +79,7 @@ class RAGController:
|
||||
])
|
||||
file_dialog.setWindowTitle("Select Documents to Add to RAG Index")
|
||||
|
||||
if file_dialog.exec():
|
||||
if exec_dialog(file_dialog):
|
||||
selected_files = file_dialog.selectedFiles()
|
||||
if selected_files:
|
||||
self._ingest_documents(selected_files)
|
||||
|
||||
@@ -9,7 +9,7 @@ in background with Qt signal/slot communication.
|
||||
|
||||
import asyncio
|
||||
from typing import List, Dict, Any, Optional
|
||||
from PySide6.QtCore import QThread, Signal
|
||||
from ..qt_compat import QThread, Signal
|
||||
|
||||
from src.services.react.react_orchestrator import ReActOrchestrator
|
||||
from src.services.models.react_models import ReActResult, ReActConfig
|
||||
|
||||
@@ -4,7 +4,7 @@ import asyncio
|
||||
import json
|
||||
from typing import Any, Dict, List, Optional, Set
|
||||
|
||||
from PySide6.QtCore import QThread, Signal
|
||||
from ..qt_compat import QThread, Signal
|
||||
|
||||
from src.services.analysis_db_service import AnalysisDBService
|
||||
from src.services.settings_service import SettingsService
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import asyncio
|
||||
from PySide6.QtWidgets import QMessageBox, QInputDialog, QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QCheckBox, QSpinBox, QComboBox
|
||||
from PySide6.QtCore import QObject, QThread, Signal
|
||||
from ..qt_compat import (QMessageBox, QInputDialog, QDialog, QVBoxLayout, QHBoxLayout,
|
||||
QLabel, QLineEdit, QPushButton, QCheckBox, QSpinBox, QComboBox,
|
||||
QObject, QThread, Signal, exec_dialog)
|
||||
|
||||
from src.ida_compat import log
|
||||
|
||||
@@ -561,7 +562,7 @@ class ProviderDialog(QDialog):
|
||||
|
||||
def on_authenticate_clicked(self):
|
||||
"""Handle OAuth authentication button click"""
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
from ..qt_compat import QMessageBox
|
||||
|
||||
current_data = self.provider_type_combo.currentData()
|
||||
try:
|
||||
@@ -586,8 +587,7 @@ class ProviderDialog(QDialog):
|
||||
|
||||
def _authenticate_anthropic(self):
|
||||
"""Handle Anthropic OAuth authentication with automatic callback"""
|
||||
from PySide6.QtWidgets import QMessageBox, QProgressDialog
|
||||
from PySide6.QtCore import Qt
|
||||
from ..qt_compat import QMessageBox, QProgressDialog, Qt
|
||||
|
||||
try:
|
||||
from ..services.llm_providers.oauth_worker import AnthropicOAuthWorker
|
||||
@@ -636,7 +636,7 @@ class ProviderDialog(QDialog):
|
||||
|
||||
def _on_anthropic_auth_complete(self, result: dict):
|
||||
"""Handle successful Anthropic OAuth authentication"""
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
from ..qt_compat import QMessageBox
|
||||
|
||||
self._oauth_auth_completed = True
|
||||
|
||||
@@ -654,7 +654,7 @@ class ProviderDialog(QDialog):
|
||||
|
||||
def _on_anthropic_auth_failed(self, error: str):
|
||||
"""Handle failed Anthropic OAuth authentication"""
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
from ..qt_compat import QMessageBox
|
||||
|
||||
self._oauth_auth_completed = True
|
||||
|
||||
@@ -681,7 +681,7 @@ class ProviderDialog(QDialog):
|
||||
|
||||
def _authenticate_anthropic_manual(self):
|
||||
"""Manual fallback for Anthropic OAuth authentication"""
|
||||
from PySide6.QtWidgets import QMessageBox, QInputDialog
|
||||
from ..qt_compat import QMessageBox, QInputDialog
|
||||
|
||||
try:
|
||||
from ..services.llm_providers.oauth_utils import (
|
||||
@@ -754,8 +754,7 @@ class ProviderDialog(QDialog):
|
||||
|
||||
def _authenticate_codex(self):
|
||||
"""Handle OpenAI Codex (ChatGPT) OAuth authentication with automatic callback"""
|
||||
from PySide6.QtWidgets import QMessageBox, QProgressDialog
|
||||
from PySide6.QtCore import Qt
|
||||
from ..qt_compat import QMessageBox, QProgressDialog, Qt
|
||||
|
||||
try:
|
||||
from ..services.llm_providers.oauth_worker import OpenAIOAuthWorker
|
||||
@@ -804,7 +803,7 @@ class ProviderDialog(QDialog):
|
||||
|
||||
def _on_codex_auth_complete(self, result: dict):
|
||||
"""Handle successful OpenAI OAuth authentication"""
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
from ..qt_compat import QMessageBox
|
||||
|
||||
self._oauth_auth_completed = True
|
||||
|
||||
@@ -822,7 +821,7 @@ class ProviderDialog(QDialog):
|
||||
|
||||
def _on_codex_auth_failed(self, error: str):
|
||||
"""Handle failed OpenAI OAuth authentication"""
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
from ..qt_compat import QMessageBox
|
||||
|
||||
self._oauth_auth_completed = True
|
||||
|
||||
@@ -858,7 +857,7 @@ class ProviderDialog(QDialog):
|
||||
|
||||
def _authenticate_codex_manual(self):
|
||||
"""Manual fallback for OpenAI Codex OAuth authentication"""
|
||||
from PySide6.QtWidgets import QMessageBox, QInputDialog
|
||||
from ..qt_compat import QMessageBox, QInputDialog
|
||||
|
||||
try:
|
||||
from ..services.llm_providers.oauth_codex_utils import (
|
||||
@@ -939,8 +938,7 @@ class ProviderDialog(QDialog):
|
||||
|
||||
def _authenticate_gemini(self):
|
||||
"""Handle Google Gemini OAuth authentication with automatic callback"""
|
||||
from PySide6.QtWidgets import QMessageBox, QProgressDialog
|
||||
from PySide6.QtCore import Qt
|
||||
from ..qt_compat import QMessageBox, QProgressDialog, Qt
|
||||
|
||||
try:
|
||||
from ..services.llm_providers.oauth_worker import GeminiOAuthWorker
|
||||
@@ -990,7 +988,7 @@ class ProviderDialog(QDialog):
|
||||
|
||||
def _on_gemini_auth_complete(self, result: dict):
|
||||
"""Handle successful Gemini OAuth authentication"""
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
from ..qt_compat import QMessageBox
|
||||
|
||||
self._oauth_auth_completed = True
|
||||
|
||||
@@ -1011,7 +1009,7 @@ class ProviderDialog(QDialog):
|
||||
|
||||
def _on_gemini_auth_failed(self, error: str):
|
||||
"""Handle failed Gemini OAuth authentication"""
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
from ..qt_compat import QMessageBox
|
||||
|
||||
self._oauth_auth_completed = True
|
||||
|
||||
@@ -1051,7 +1049,7 @@ class ProviderDialog(QDialog):
|
||||
|
||||
def _authenticate_gemini_manual(self):
|
||||
"""Manual fallback for Gemini OAuth authentication using headless mode"""
|
||||
from PySide6.QtWidgets import QMessageBox, QInputDialog
|
||||
from ..qt_compat import QMessageBox, QInputDialog
|
||||
import asyncio
|
||||
|
||||
try:
|
||||
@@ -1323,7 +1321,7 @@ class SettingsController(QObject):
|
||||
|
||||
def add_llm_provider(self):
|
||||
dialog = ProviderDialog(self.view)
|
||||
if dialog.exec() == QDialog.Accepted:
|
||||
if exec_dialog(dialog) == QDialog.Accepted:
|
||||
try:
|
||||
data = dialog.get_provider_data()
|
||||
is_cli_provider = data.get('provider_type') == 'anthropic_cli'
|
||||
@@ -1361,7 +1359,7 @@ class SettingsController(QObject):
|
||||
provider = providers[row]
|
||||
dialog = ProviderDialog(self.view, provider)
|
||||
|
||||
if dialog.exec() == QDialog.Accepted:
|
||||
if exec_dialog(dialog) == QDialog.Accepted:
|
||||
data = dialog.get_provider_data()
|
||||
is_cli_provider = data.get('provider_type') == 'anthropic_cli'
|
||||
if not data['name'] or not data['model']:
|
||||
@@ -1498,7 +1496,7 @@ class SettingsController(QObject):
|
||||
|
||||
def add_mcp_provider(self):
|
||||
dialog = MCPProviderDialog(self.view)
|
||||
if dialog.exec() == QDialog.Accepted:
|
||||
if exec_dialog(dialog) == QDialog.Accepted:
|
||||
try:
|
||||
data = dialog.get_provider_data()
|
||||
if not all([data['name'], data['url']]):
|
||||
@@ -1526,7 +1524,7 @@ class SettingsController(QObject):
|
||||
provider = providers[row]
|
||||
dialog = MCPProviderDialog(self.view, provider)
|
||||
|
||||
if dialog.exec() == QDialog.Accepted:
|
||||
if exec_dialog(dialog) == QDialog.Accepted:
|
||||
data = dialog.get_provider_data()
|
||||
if not all([data['name'], data['url']]):
|
||||
self.show_error("Validation Error", "Name and URL are required fields.")
|
||||
|
||||
@@ -9,8 +9,7 @@ querying, pushing, and pulling symbols and graph data.
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Dict, List, Optional, Any, Callable
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
from PySide6.QtCore import QThread, Signal, QObject
|
||||
from ..qt_compat import QMessageBox, QThread, Signal, QObject
|
||||
|
||||
from src.services.analysis_db_service import analysis_db_service
|
||||
from src.services.graphrag.graph_store import GraphStore
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
Qt5/Qt6 compatibility shim.
|
||||
|
||||
PySide6 (IDA >= 9.2) is tried first; falls back to PyQt5 (IDA <= 9.1).
|
||||
Consumer files should use explicit imports:
|
||||
from ..qt_compat import QWidget, Signal, exec_dialog, ...
|
||||
"""
|
||||
|
||||
try:
|
||||
from PySide6.QtCore import * # noqa: F401,F403
|
||||
from PySide6.QtGui import * # noqa: F401,F403
|
||||
from PySide6.QtWidgets import * # noqa: F401,F403
|
||||
from PySide6.QtCore import Signal
|
||||
from PySide6.QtGui import QAction
|
||||
QT_BINDING = "PySide6"
|
||||
except ImportError:
|
||||
from PyQt5.QtCore import * # noqa: F401,F403
|
||||
from PyQt5.QtGui import * # noqa: F401,F403
|
||||
from PyQt5.QtWidgets import * # noqa: F401,F403
|
||||
from PyQt5.QtCore import pyqtSignal as Signal # noqa: F401
|
||||
from PyQt5.QtWidgets import QAction # noqa: F401
|
||||
QT_BINDING = "PyQt5"
|
||||
|
||||
QT_AVAILABLE = QT_BINDING is not None
|
||||
|
||||
|
||||
def exec_dialog(obj, *args):
|
||||
"""Cross-binding .exec() wrapper (PyQt5 uses .exec_())."""
|
||||
if hasattr(obj, "exec"):
|
||||
return obj.exec(*args)
|
||||
return obj.exec_(*args)
|
||||
|
||||
|
||||
def utc_timezone():
|
||||
"""Cross-binding UTC QTimeZone."""
|
||||
if hasattr(QTimeZone, "utc"):
|
||||
return QTimeZone.utc()
|
||||
return QTimeZone(0)
|
||||
@@ -17,11 +17,11 @@ import asyncio
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
try:
|
||||
from PySide6.QtCore import QThread, Signal
|
||||
PYSIDE_AVAILABLE = True
|
||||
from ...qt_compat import QThread, Signal
|
||||
QT_AVAILABLE = True
|
||||
except ImportError:
|
||||
PYSIDE_AVAILABLE = False
|
||||
# Stub for type hints when PySide6 not available
|
||||
QT_AVAILABLE = False
|
||||
# Stub for type hints when Qt not available
|
||||
class QThread:
|
||||
pass
|
||||
class Signal:
|
||||
@@ -52,8 +52,8 @@ class OAuthWorkerBase(QThread):
|
||||
timeout: Seconds to wait for authentication (default 5 minutes)
|
||||
parent: Optional parent QObject
|
||||
"""
|
||||
if not PYSIDE_AVAILABLE:
|
||||
raise ImportError("PySide6 required for OAuth worker")
|
||||
if not QT_AVAILABLE:
|
||||
raise ImportError("Qt bindings required for OAuth worker")
|
||||
|
||||
super().__init__(parent)
|
||||
self.timeout = timeout
|
||||
|
||||
@@ -212,7 +212,7 @@ class MCPClientService:
|
||||
deadline = time.time() + timeout
|
||||
while not future.done() and time.time() < deadline:
|
||||
try:
|
||||
from PySide6.QtWidgets import QApplication
|
||||
from ..qt_compat import QApplication
|
||||
app = QApplication.instance()
|
||||
if app:
|
||||
app.processEvents()
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
||||
QLineEdit, QTableWidget, QTableWidgetItem, QListWidget,
|
||||
QListWidgetItem, QCheckBox, QHeaderView, QAbstractItemView,
|
||||
QSplitter, QSizePolicy)
|
||||
from PySide6.QtCore import Signal, Qt
|
||||
from PySide6.QtGui import QColor
|
||||
from ..qt_compat import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
||||
QLineEdit, QTableWidget, QTableWidgetItem, QListWidget,
|
||||
QListWidgetItem, QCheckBox, QHeaderView, QAbstractItemView,
|
||||
QSplitter, QSizePolicy, Signal, Qt, QColor)
|
||||
|
||||
|
||||
class ActionsTabView(QWidget):
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QPushButton, QTextBrowser, QTextEdit, QLineEdit, QSizePolicy, QCheckBox,
|
||||
QApplication, QGroupBox, QGridLayout, QSplitter, QFrame)
|
||||
from PySide6.QtCore import Signal, Qt
|
||||
from PySide6.QtGui import QKeySequence, QFontDatabase
|
||||
from ..qt_compat import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QPushButton, QTextBrowser, QTextEdit, QLineEdit, QSizePolicy, QCheckBox,
|
||||
QApplication, QGroupBox, QGridLayout, QSplitter, QFrame,
|
||||
Signal, Qt, QKeySequence, QFontDatabase, exec_dialog)
|
||||
import markdown
|
||||
import re
|
||||
|
||||
@@ -42,8 +41,7 @@ class MarkdownCopyBrowser(QTextBrowser):
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
"""Override context menu to replace Copy action with markdown copy"""
|
||||
from PySide6.QtWidgets import QMenu
|
||||
from PySide6.QtGui import QAction
|
||||
from ..qt_compat import QMenu, QAction
|
||||
|
||||
menu = self.createStandardContextMenu()
|
||||
|
||||
@@ -55,7 +53,7 @@ class MarkdownCopyBrowser(QTextBrowser):
|
||||
action.triggered.connect(self._copy_markdown)
|
||||
break
|
||||
|
||||
menu.exec(event.globalPos())
|
||||
exec_dialog(menu, event.globalPos())
|
||||
menu.deleteLater()
|
||||
|
||||
|
||||
@@ -176,9 +174,9 @@ class ExplainTabView(QWidget):
|
||||
self.explain_editor.hide() # Hidden by default
|
||||
|
||||
# ESC key discards edits and returns to view mode
|
||||
from PySide6.QtGui import QShortcut
|
||||
from ..qt_compat import QShortcut
|
||||
esc_shortcut = QShortcut(QKeySequence(Qt.Key_Escape), self.explain_editor)
|
||||
esc_shortcut.setContext(Qt.ShortcutContext.WidgetShortcut)
|
||||
esc_shortcut.setContext(Qt.WidgetShortcut)
|
||||
esc_shortcut.activated.connect(self.cancel_edit_mode)
|
||||
|
||||
def create_line_explanation_panel(self):
|
||||
@@ -297,7 +295,7 @@ class ExplainTabView(QWidget):
|
||||
self.explain_browser.set_markdown_source(self.markdown_content)
|
||||
|
||||
# Restore panel sizes — give all remaining space to the active text widget
|
||||
from PySide6.QtCore import QTimer
|
||||
from ..qt_compat import QTimer
|
||||
is_edit = self.is_edit_mode
|
||||
def _restore_sizes():
|
||||
total = sum(self.main_splitter.sizes())
|
||||
@@ -321,7 +319,7 @@ class ExplainTabView(QWidget):
|
||||
self.explain_editor.hide()
|
||||
self.explain_browser.show()
|
||||
self.edit_save_button.setText("Edit")
|
||||
from PySide6.QtCore import QTimer
|
||||
from ..qt_compat import QTimer
|
||||
def _restore_sizes():
|
||||
total = sum(self.main_splitter.sizes())
|
||||
active_size = total - line_size - security_size
|
||||
@@ -347,7 +345,7 @@ class ExplainTabView(QWidget):
|
||||
self.explain_browser.setHtml(self.markdown_to_html(markdown_text))
|
||||
|
||||
# Defer scroll restoration until after Qt processes the layout change
|
||||
from PySide6.QtCore import QTimer
|
||||
from ..qt_compat import QTimer
|
||||
if should_auto_scroll:
|
||||
# Auto-scroll to bottom if user was following the explanation
|
||||
QTimer.singleShot(0, self._scroll_to_bottom)
|
||||
|
||||
@@ -88,7 +88,7 @@ class IDAssistForm(idaapi.PluginForm):
|
||||
def OnCreate(self, form):
|
||||
"""Called by IDA when the form is first created."""
|
||||
try:
|
||||
from PySide6.QtWidgets import QTabWidget, QVBoxLayout, QWidget
|
||||
from src.qt_compat import QTabWidget, QVBoxLayout, QWidget
|
||||
|
||||
# Convert IDA form handle to a PySide6 widget
|
||||
parent = self.FormToPyQtWidget(form)
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
||||
QTextBrowser, QTextEdit, QLineEdit, QTableWidget, QTableWidgetItem,
|
||||
QSplitter, QPlainTextEdit, QHeaderView, QAbstractItemView, QSizePolicy, QCheckBox,
|
||||
QApplication)
|
||||
from PySide6.QtCore import Signal, Qt, QDateTime
|
||||
from PySide6.QtGui import QKeySequence, QFontDatabase
|
||||
from ..qt_compat import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
||||
QTextBrowser, QTextEdit, QLineEdit, QTableWidget, QTableWidgetItem,
|
||||
QSplitter, QPlainTextEdit, QHeaderView, QAbstractItemView, QSizePolicy, QCheckBox,
|
||||
QApplication, Signal, Qt, QDateTime, QKeySequence, QFontDatabase, exec_dialog)
|
||||
import markdown
|
||||
import re
|
||||
|
||||
@@ -44,8 +42,7 @@ class MarkdownCopyBrowser(QTextBrowser):
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
"""Override context menu to replace Copy action with markdown copy"""
|
||||
from PySide6.QtWidgets import QMenu
|
||||
from PySide6.QtGui import QAction
|
||||
from ..qt_compat import QMenu, QAction
|
||||
|
||||
menu = self.createStandardContextMenu()
|
||||
|
||||
@@ -57,7 +54,7 @@ class MarkdownCopyBrowser(QTextBrowser):
|
||||
action.triggered.connect(self._copy_markdown)
|
||||
break
|
||||
|
||||
menu.exec(event.globalPos())
|
||||
exec_dialog(menu, event.globalPos())
|
||||
menu.deleteLater()
|
||||
|
||||
|
||||
@@ -187,9 +184,9 @@ class QueryTabView(QWidget):
|
||||
self.query_editor.hide() # Hidden by default
|
||||
|
||||
# ESC key discards edits and returns to view mode
|
||||
from PySide6.QtGui import QShortcut
|
||||
from ..qt_compat import QShortcut
|
||||
esc_shortcut = QShortcut(QKeySequence(Qt.Key_Escape), self.query_editor)
|
||||
esc_shortcut.setContext(Qt.ShortcutContext.WidgetShortcut)
|
||||
esc_shortcut.setContext(Qt.WidgetShortcut)
|
||||
esc_shortcut.activated.connect(self.cancel_edit_mode)
|
||||
|
||||
def create_history_table(self):
|
||||
@@ -487,7 +484,7 @@ class QueryTabView(QWidget):
|
||||
|
||||
# Defer scroll restoration until after Qt processes the layout change
|
||||
# This ensures scrollbar.maximum() reflects the new content size
|
||||
from PySide6.QtCore import QTimer
|
||||
from ..qt_compat import QTimer
|
||||
if should_auto_scroll:
|
||||
# User wants to follow new content - scroll to bottom
|
||||
def scroll_to_bottom():
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
|
||||
QTableWidget, QTableWidgetItem, QHeaderView,
|
||||
QAbstractItemView, QLabel, QLineEdit, QComboBox,
|
||||
QTextEdit, QSplitter, QGroupBox)
|
||||
from PySide6.QtCore import Signal, Qt
|
||||
from ..qt_compat import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
|
||||
QTableWidget, QTableWidgetItem, QHeaderView,
|
||||
QAbstractItemView, QLabel, QLineEdit, QComboBox,
|
||||
QTextEdit, QSplitter, QGroupBox, Signal, Qt)
|
||||
|
||||
|
||||
class RagTabView(QWidget):
|
||||
|
||||
@@ -8,9 +8,8 @@ Provides organized UI for triggering various analysis operations:
|
||||
- Individual Operations: Security, Network Flow, Community Detection, Refresh Names
|
||||
"""
|
||||
|
||||
from PySide6.QtCore import Signal
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QPushButton, QLabel,
|
||||
from ...qt_compat import (
|
||||
Signal, QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QPushButton, QLabel,
|
||||
QProgressBar, QFrame, QCheckBox
|
||||
)
|
||||
|
||||
|
||||
@@ -5,12 +5,10 @@ import math
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from PySide6.QtCore import Qt, Signal, QPointF
|
||||
from PySide6.QtGui import (
|
||||
from ..qt_compat import (
|
||||
Qt, Signal, QPointF,
|
||||
QFont, QFontDatabase, QTextCursor, QBrush, QColor, QPen,
|
||||
QPainterPath, QPainter, QPolygonF, QMouseEvent
|
||||
)
|
||||
from PySide6.QtWidgets import (
|
||||
QPainterPath, QPainter, QPolygonF, QMouseEvent,
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QTabWidget,
|
||||
QGroupBox, QGridLayout, QSplitter, QListWidget, QListWidgetItem, QTableWidget,
|
||||
QTableWidgetItem, QComboBox, QTextEdit, QCheckBox, QSpinBox, QGraphicsView,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
||||
QTableWidget, QTableWidgetItem, QComboBox, QTextEdit,
|
||||
QLineEdit, QCheckBox, QHeaderView, QAbstractItemView,
|
||||
QGroupBox, QScrollArea, QFileDialog)
|
||||
from PySide6.QtCore import Signal, Qt
|
||||
from ..qt_compat import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
||||
QTableWidget, QTableWidgetItem, QComboBox, QTextEdit,
|
||||
QLineEdit, QCheckBox, QHeaderView, QAbstractItemView,
|
||||
QGroupBox, QScrollArea, QFileDialog, Signal, Qt)
|
||||
from ..services.service_registry import get_service_registry
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from PySide6.QtWidgets import QTextBrowser, QAbstractScrollArea, QApplication
|
||||
from PySide6.QtGui import QTextCursor, QKeySequence
|
||||
from ..qt_compat import (QTextBrowser, QAbstractScrollArea, QApplication,
|
||||
QTextCursor, QKeySequence, exec_dialog)
|
||||
|
||||
from ..services.streaming.render_update import RenderUpdate, UpdateType
|
||||
from ..services.streaming.streaming_renderer import MARKDOWN_CSS
|
||||
@@ -91,7 +91,7 @@ class StreamingMarkdownBrowser(QTextBrowser):
|
||||
action.triggered.disconnect()
|
||||
action.triggered.connect(self._copy_markdown)
|
||||
break
|
||||
menu.exec(event.globalPos())
|
||||
exec_dialog(menu, event.globalPos())
|
||||
menu.deleteLater()
|
||||
|
||||
# --- Streaming Functionality ---
|
||||
@@ -116,7 +116,7 @@ class StreamingMarkdownBrowser(QTextBrowser):
|
||||
delta_html = html_content[len(self._last_rendered_html):]
|
||||
if delta_html:
|
||||
cursor = self.textCursor()
|
||||
cursor.movePosition(QTextCursor.MoveOperation.End)
|
||||
cursor.movePosition(QTextCursor.End)
|
||||
cursor.insertHtml(delta_html)
|
||||
self._force_layout()
|
||||
else:
|
||||
@@ -153,7 +153,7 @@ class StreamingMarkdownBrowser(QTextBrowser):
|
||||
# Append pending via cursor (avoids second setHtml)
|
||||
if update.pending_html:
|
||||
cursor = self.textCursor()
|
||||
cursor.movePosition(QTextCursor.MoveOperation.End)
|
||||
cursor.movePosition(QTextCursor.End)
|
||||
cursor.insertHtml(update.pending_html)
|
||||
self._force_layout()
|
||||
else:
|
||||
@@ -163,13 +163,13 @@ class StreamingMarkdownBrowser(QTextBrowser):
|
||||
# Delete existing pending content
|
||||
if self._pending_start < self.document().characterCount():
|
||||
cursor.setPosition(self._pending_start)
|
||||
cursor.movePosition(QTextCursor.MoveOperation.End,
|
||||
QTextCursor.MoveMode.KeepAnchor)
|
||||
cursor.movePosition(QTextCursor.End,
|
||||
QTextCursor.KeepAnchor)
|
||||
cursor.removeSelectedText()
|
||||
|
||||
# Insert new pending content
|
||||
if update.pending_html:
|
||||
cursor.movePosition(QTextCursor.MoveOperation.End)
|
||||
cursor.movePosition(QTextCursor.End)
|
||||
cursor.insertHtml(update.pending_html)
|
||||
|
||||
self._force_layout()
|
||||
|
||||
@@ -6,15 +6,14 @@ This view provides the UI for querying, pushing, and pulling
|
||||
symbols and graph data from SymGraph.
|
||||
"""
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
from ..qt_compat import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
|
||||
QTableWidget, QTableWidgetItem, QHeaderView,
|
||||
QAbstractItemView, QLabel, QGroupBox, QRadioButton,
|
||||
QCheckBox, QButtonGroup, QSplitter, QFrame, QSlider,
|
||||
QStackedWidget, QProgressBar, QTabWidget
|
||||
QStackedWidget, QProgressBar, QTabWidget,
|
||||
Signal, Qt, QFont
|
||||
)
|
||||
from PySide6.QtCore import Signal, Qt
|
||||
from PySide6.QtGui import QFont
|
||||
|
||||
from ..services.models.symgraph_models import ConflictAction, ConflictEntry, PushScope
|
||||
|
||||
|
||||
Reference in New Issue
Block a user