pylance fixes and terminal plugin launching
This commit is contained in:
@@ -1337,7 +1337,7 @@ class MainApplication(QMainWindow):
|
||||
|
||||
def terminal_gui(self):
|
||||
if self.terminal is None or not self.terminal.isVisible():
|
||||
self.terminal = TerminalWindow(self)
|
||||
self.terminal = TerminalWindow(self, self.plugin_manager)
|
||||
self.terminal.show()
|
||||
|
||||
def update_optode_positions(self):
|
||||
|
||||
+8
-4
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Filename: plugin_manager.py
|
||||
Description: Manager file for anything plugin related
|
||||
Note: Compliant with pylance strict type checking
|
||||
|
||||
Author: Tyler de Zeeuw
|
||||
License: GPL-3.0
|
||||
@@ -117,7 +118,7 @@ class PluginManager(QObject):
|
||||
|
||||
print(f"[PluginManager] {summary_msg}")
|
||||
|
||||
parent_widget = getattr(self, "main_window", None) or self
|
||||
parent_widget = getattr(self, "main_window", None)
|
||||
QMessageBox.warning(
|
||||
parent_widget,
|
||||
"Plugin Load Failures",
|
||||
@@ -166,7 +167,9 @@ class PluginManager(QObject):
|
||||
for action in menubar.actions():
|
||||
clean_text = action.text().replace("&", "").strip().lower()
|
||||
if clean_text == "plugins":
|
||||
plugins_menu = action.menu()
|
||||
menu = action.menu()
|
||||
if isinstance(menu, QMenu):
|
||||
plugins_menu = menu
|
||||
break
|
||||
|
||||
# If it doesn't exist yet, create it
|
||||
@@ -191,8 +194,9 @@ class PluginManager(QObject):
|
||||
plugins_menu.addSeparator()
|
||||
manager_action = plugins_menu.addAction("Manage Plugins...")
|
||||
|
||||
if hasattr(self.main_window, "plugins_gui") and callable(self.main_window.plugins_gui):
|
||||
manager_action.triggered.connect(self.main_window.plugins_gui)
|
||||
plugins_gui_func = getattr(self.main_window, "plugins_gui", None)
|
||||
if callable(plugins_gui_func):
|
||||
manager_action.triggered.connect(plugins_gui_func)
|
||||
|
||||
def unload_plugins(self) -> None:
|
||||
"""Clears current active plugin instances."""
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
src\analysis\participantfoldchannels.py 158
|
||||
src\analysis\participantfoldchannels.py 84
|
||||
src\shared\flaresbasewidget.py 1155
|
||||
flares.py 2900
|
||||
main_unit_tests.py 152
|
||||
|
||||
@@ -8,29 +8,31 @@ License: GPL-3.0
|
||||
|
||||
# Built-in Imports
|
||||
import os
|
||||
from pathlib import Path
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from pathlib import Path
|
||||
from multiprocessing import Process, current_process, Manager
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||
|
||||
# External library imports
|
||||
from matplotlib.backend_bases import Event
|
||||
import numpy as np
|
||||
from pandas import DataFrame
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.image as mpimg
|
||||
from matplotlib.figure import Figure
|
||||
from matplotlib.backend_bases import Event
|
||||
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
|
||||
|
||||
from PySide6.QtWidgets import QFrame, QGridLayout, QHBoxLayout, QLabel, QLayout, QProgressBar, QPushButton, QScrollArea, QSizePolicy, QWidget, QDialog, QVBoxLayout
|
||||
from PySide6.QtCore import QThread, Qt, QSize, QTimer, QObject, Signal
|
||||
from PySide6.QtGui import QCloseEvent, QMouseEvent, QPixmap, QImage
|
||||
from pandas import DataFrame
|
||||
|
||||
from mne.io.base import BaseRaw
|
||||
from src.shared.flaresbasewidget import FlaresBaseWidget
|
||||
from src.shared.shareddata import APP_NAME, resource_path
|
||||
from flares import fold_channels
|
||||
|
||||
|
||||
class MultiProgressDialog(QDialog):
|
||||
@@ -82,7 +84,6 @@ def single_participant_worker(
|
||||
""" Runs inside its own dedicated process """
|
||||
p_name = os.path.basename(file_path)
|
||||
try:
|
||||
from flares import fold_channels
|
||||
# Perform the heavy fold_channels logic
|
||||
channel_results = fold_channels(raw=raw_data, p_name=p_name, progress_queue=progress_queue)
|
||||
|
||||
@@ -284,7 +285,7 @@ class StaticChannelCanvas(FigureCanvas):
|
||||
|
||||
|
||||
class StandaloneLegendDialog(QWidget):
|
||||
def __init__(self, canvas_engine, title_prefix, parent=None):
|
||||
def __init__(self, canvas_engine: Any, title_prefix: str, parent: QWidget | None = None) -> None:
|
||||
super().__init__(None)
|
||||
self.setWindowTitle("Full View - Brodmann Legend")
|
||||
self.setMinimumSize(500, 600)
|
||||
@@ -302,7 +303,14 @@ class InteractiveParticipantGridCanvas(FigureCanvas):
|
||||
"""The Big Grid Canvas.
|
||||
Dynamically scales row and column configurations to maintain a crisp 16:9 layout orientation.
|
||||
"""
|
||||
def __init__(self, channels_data, color_map, is_fullscreen_copy=False, parent=None):
|
||||
def __init__(
|
||||
self,
|
||||
channels_data: dict[str, list[dict[str, Any]]],
|
||||
color_map: dict[str, str],
|
||||
is_fullscreen_copy: bool = False,
|
||||
parent: QWidget | None = None
|
||||
) -> None:
|
||||
|
||||
self.channels_data = channels_data
|
||||
self.color_map = color_map
|
||||
self.is_fullscreen_copy = is_fullscreen_copy
|
||||
@@ -494,7 +502,7 @@ class InteractiveParticipantGridCanvas(FigureCanvas):
|
||||
self._fullscreen_refs.append(fullscreen_window)
|
||||
|
||||
|
||||
def _calculate_total_brodmann_profile(self, channels_data: Dict[str, Any]):
|
||||
def _calculate_total_brodmann_profile(self, channels_data: dict[str, list[dict[str, Any]]]) -> list[dict[str, Any]]:
|
||||
"""Sums and normalizes the specificity profile across all channels."""
|
||||
totals = {}
|
||||
num_channels = len(channels_data)
|
||||
@@ -503,7 +511,7 @@ class InteractiveParticipantGridCanvas(FigureCanvas):
|
||||
return []
|
||||
|
||||
# Sum up specificities across all channels
|
||||
for channel_name, data_list in channels_data.items():
|
||||
for _, data_list in channels_data.items():
|
||||
for entry in data_list:
|
||||
landmark = entry['Landmark']
|
||||
specificity = entry['Specificity']
|
||||
@@ -532,7 +540,7 @@ class InteractiveParticipantGridCanvas(FigureCanvas):
|
||||
|
||||
return normalized_data_list
|
||||
|
||||
def _open_expanded_view(self, channel_name, data_list):
|
||||
def _open_expanded_view(self, channel_name: str, data_list: list[dict[str, Any]]) -> None:
|
||||
# 1. Create a plain QWidget with NO parent (None)
|
||||
# This instantly makes it a top-level desktop window
|
||||
popup = QWidget(None)
|
||||
@@ -746,11 +754,12 @@ class ProcessOrchestrator(QObject):
|
||||
setup_finished = Signal(object, object, object, list)
|
||||
setup_failed = Signal(str)
|
||||
|
||||
def __init__(self,
|
||||
selected_files,
|
||||
haemo_dict: dict[str, BaseRaw],
|
||||
worker_func
|
||||
):
|
||||
def __init__(
|
||||
self,
|
||||
selected_files: list[str],
|
||||
haemo_dict: dict[str, Any],
|
||||
worker_func: Callable[..., Any]
|
||||
) -> None:
|
||||
|
||||
super().__init__()
|
||||
self.selected_files = selected_files
|
||||
@@ -810,9 +819,6 @@ class ParticipantFoldChannelsWidget(FlaresBaseWidget):
|
||||
|
||||
self.index_texts = [
|
||||
"0 (Fold Channels)",
|
||||
# "1 (second image)",
|
||||
# "2 (third image)",
|
||||
# "3 (fourth image)",
|
||||
]
|
||||
|
||||
self.image_index_dropdown = self._create_multiselect_dropdown(self.index_texts)
|
||||
@@ -888,13 +894,8 @@ class ParticipantFoldChannelsWidget(FlaresBaseWidget):
|
||||
|
||||
self.multi_progress.add_participant(os.path.basename(file_path), total_channels)
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
print(f"Before: {datetime.now()}")
|
||||
self.multi_progress.show()
|
||||
|
||||
print(f"After 1: {datetime.now()}")
|
||||
|
||||
if current_process().name == 'MainProcess':
|
||||
# Create a clean background thread worker execution channel
|
||||
self.orchestrator_thread = QThread()
|
||||
@@ -914,7 +915,6 @@ class ParticipantFoldChannelsWidget(FlaresBaseWidget):
|
||||
self.orchestrator.setup_failed.connect(self.orchestrator.deleteLater)
|
||||
|
||||
self.orchestrator_thread.start()
|
||||
print(f"After 4: {datetime.now()}")
|
||||
|
||||
def on_orchestration_success(
|
||||
self,
|
||||
@@ -1102,7 +1102,7 @@ class ParticipantFoldChannelsWidget(FlaresBaseWidget):
|
||||
vbox = QVBoxLayout(container)
|
||||
|
||||
title = QLabel("<b>Brodmann Area Legend</b>")
|
||||
title.setAlignment(Qt.AlignCenter)
|
||||
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
vbox.addWidget(title)
|
||||
|
||||
pixmap = self._bytes_to_pixmap(legend_bytes)
|
||||
@@ -1110,10 +1110,10 @@ class ParticipantFoldChannelsWidget(FlaresBaseWidget):
|
||||
# Legends are usually tall, so we scale it differently or keep it smaller
|
||||
legend_label.setPixmap(pixmap.scaled(
|
||||
self.thumb_size,
|
||||
Qt.KeepAspectRatio,
|
||||
Qt.SmoothTransformation
|
||||
Qt.AspectRatioMode.KeepAspectRatio,
|
||||
Qt.TransformationMode.SmoothTransformation
|
||||
))
|
||||
legend_label.setAlignment(Qt.AlignCenter)
|
||||
legend_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
legend_label.mousePressEvent = lambda e, p=pixmap: self._open_full_size(p, "Brodmann Legend")
|
||||
|
||||
vbox.addWidget(legend_label)
|
||||
|
||||
+32
-16
@@ -1,13 +1,15 @@
|
||||
"""
|
||||
Filename: plugins.py
|
||||
Description: Plugins window
|
||||
Note: Compliant with pylance strict type checking
|
||||
|
||||
Author: Tyler de Zeeuw
|
||||
License: GPL-3.0
|
||||
"""
|
||||
|
||||
# Built-in imports
|
||||
from typing import Any
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
# External library imports
|
||||
from PySide6.QtCore import Qt, QThread, Signal
|
||||
@@ -104,12 +106,14 @@ class PluginsWindow(QWidget):
|
||||
self._clear_details_panel()
|
||||
return
|
||||
|
||||
name = info.get("name", "Unknown")
|
||||
version = info.get("version", "1.0.0")
|
||||
author = info.get("author", "Unknown")
|
||||
desc = info.get("description", "No description provided.")
|
||||
is_disabled = info.get("is_disabled", False)
|
||||
path = info.get("path", "")
|
||||
plugin_info = cast(dict[str, Any], info)
|
||||
|
||||
name = str(plugin_info.get("name", "Unknown"))
|
||||
version = str(plugin_info.get("version", "1.0.0"))
|
||||
author = str(plugin_info.get("author", "Unknown"))
|
||||
desc = str(plugin_info.get("description", "No description provided."))
|
||||
is_disabled = bool(plugin_info.get("is_disabled", False))
|
||||
path = str(plugin_info.get("path", ""))
|
||||
|
||||
self.lbl_plugin_title.setText(name)
|
||||
self.lbl_plugin_meta.setText(f"<b>Version:</b> {version} | <b>Author:</b> {author}")
|
||||
@@ -134,25 +138,37 @@ class PluginsWindow(QWidget):
|
||||
selected = self.installed_list.currentItem()
|
||||
if not selected:
|
||||
return
|
||||
info = selected.data(Qt.ItemDataRole.UserRole)
|
||||
if isinstance(info, dict) and "path" in info:
|
||||
self.manager.toggle_plugin_state(info["path"])
|
||||
raw_info = selected.data(Qt.ItemDataRole.UserRole)
|
||||
if isinstance(raw_info, dict):
|
||||
info = cast(dict[str, Any], raw_info)
|
||||
plugin_path = info.get("path")
|
||||
if isinstance(plugin_path, str) and plugin_path:
|
||||
self.manager.toggle_plugin_state(Path(plugin_path))
|
||||
|
||||
def uninstall_plugin(self) -> None:
|
||||
selected = self.installed_list.currentItem()
|
||||
if not selected:
|
||||
return
|
||||
info = selected.data(Qt.ItemDataRole.UserRole)
|
||||
if not isinstance(info, dict) or "path" not in info:
|
||||
|
||||
raw_info = selected.data(Qt.ItemDataRole.UserRole)
|
||||
if not isinstance(raw_info, dict):
|
||||
return
|
||||
|
||||
info = cast(dict[str, Any], raw_info)
|
||||
plugin_path = info.get("path")
|
||||
plugin_name = info.get("name", "this plugin")
|
||||
|
||||
if not isinstance(plugin_path, str) or not plugin_path:
|
||||
return
|
||||
|
||||
reply = QMessageBox.question(
|
||||
self, "Confirm Uninstall",
|
||||
f"Are you sure you want to delete '{info['name']}'?",
|
||||
self,
|
||||
"Confirm Uninstall",
|
||||
f"Are you sure you want to delete '{plugin_name}'?",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
)
|
||||
if reply == QMessageBox.StandardButton.Yes:
|
||||
self.manager.uninstall_plugin(info["path"])
|
||||
self.manager.uninstall_plugin(Path(plugin_path))
|
||||
|
||||
def fetch_remote_plugins(self) -> None:
|
||||
"""Asynchronously fetches remote plugins on a background thread."""
|
||||
@@ -197,7 +213,7 @@ class PluginsWindow(QWidget):
|
||||
p_id = plugin.get("id", "")
|
||||
version = plugin.get("version", "v0.0")
|
||||
desc = plugin.get("description", "")
|
||||
platforms = plugin.get("platforms", [])
|
||||
platforms = str(plugin.get("platforms", []))
|
||||
min_v_str = plugin.get("min_app_version", "0.0.0")
|
||||
|
||||
is_platform_ok = not platforms or self.manager.current_platform in platforms
|
||||
|
||||
@@ -17,8 +17,10 @@ from PySide6.QtWidgets import QWidget, QVBoxLayout, QTextEdit, QLineEdit, QMainW
|
||||
from PySide6.QtCore import QProcess, Qt, QThread, Signal
|
||||
|
||||
from file_ext_registration import register_file_association, is_windows_admin
|
||||
from plugin_manager import PluginManager
|
||||
from src.shared.shareddata import API_URL, API_URL_SECONDARY, APP_NAME, CURRENT_VERSION, PLATFORM_NAME
|
||||
from src.window.about import AboutWindow
|
||||
from src.window.plugins import PluginsWindow
|
||||
from updater import UpdateManager
|
||||
|
||||
|
||||
@@ -42,7 +44,7 @@ class _AssocWorker(QThread):
|
||||
|
||||
|
||||
class TerminalWindow(QWidget):
|
||||
def __init__(self, parent: QWidget | None = None) -> None:
|
||||
def __init__(self, parent: QWidget | None, plugin_manager: PluginManager) -> None:
|
||||
super().__init__(parent, Qt.WindowType.Window)
|
||||
self.setWindowTitle(f"Terminal - {APP_NAME.upper()}")
|
||||
self.resize(320, 180)
|
||||
@@ -58,6 +60,7 @@ class TerminalWindow(QWidget):
|
||||
self.setLayout(layout)
|
||||
|
||||
self._process: QProcess | None = None
|
||||
self.plugin_manager = plugin_manager
|
||||
|
||||
self.commands: dict[str, Callable[..., Any]] = {
|
||||
"hello": self.cmd_hello,
|
||||
@@ -66,6 +69,7 @@ class TerminalWindow(QWidget):
|
||||
"about": self.cmd_about,
|
||||
"assoc": self.cmd_assoc,
|
||||
"update": self.cmd_update,
|
||||
"plugins": self.cmd_plugins,
|
||||
"utest": self.cmd_utest,
|
||||
}
|
||||
|
||||
@@ -122,6 +126,10 @@ class TerminalWindow(QWidget):
|
||||
self.about = AboutWindow(self)
|
||||
self.about.show()
|
||||
|
||||
def cmd_plugins(self, *args: Any) -> None:
|
||||
self.about = PluginsWindow(self, self.plugin_manager)
|
||||
self.about.show()
|
||||
|
||||
def cmd_update(self, *args: Any) -> str:
|
||||
main_win = self.parent()
|
||||
if not isinstance(main_win, QMainWindow):
|
||||
|
||||
Reference in New Issue
Block a user