MainController

class poriscope.controllers.main_controller.MainController(main_model: MainModel, main_view: MainView)

Bases: QObject

App-shell controller: owns the DataPluginController and every instantiated analysis-tab controller, wires up their signals, and acts as the central relay for the app’s signal-bus dispatch pattern (see handle_global_signal/handle_data_plugin_controller_signal), by which one tab or plugin can invoke a method on another plugin instance, or on the DataPluginController itself, without holding a direct reference to it. Also drives session/plugin-history persistence and restore.

handle_abort_all_analysis() None

Stop running operations in every open analysis tab.

Backs the Analysis -> Abort Analysis menu item, which previously emitted a signal that was connected to nothing and named a single hard-coded tab, so it never aborted anything. Each tab reports its own outcome on the display panel, so nothing is emitted here.

handle_data_plugin_controller_signal(metaclass: str, subclass_key: str, call_function: str, call_args: tuple, return_function: Callable | None, ret_args: tuple) None

Same dispatch mechanism as handle_global_signal, and literally the same code path (see _dispatch_to), except that call_function is looked up on the DataPluginController itself rather than on a resolved plugin instance. metaclass and subclass_key are accepted for signal-signature parity with handle_global_signal and are logged, but are not used to resolve a target here and so do not appear in this path’s error messages. Used when a tab needs to invoke a DataPluginController method (e.g. to instantiate or edit a plugin) rather than a method on an existing plugin instance.

Parameters:
  • metaclass (str) – Not used to resolve a target here (only logged); present for signature parity with handle_global_signal.

  • subclass_key (str) – Not used to resolve a target here (only logged); present for signature parity with handle_global_signal.

  • call_function (str) – Name of the method to call on the DataPluginController.

  • call_args (tuple) – Positional arguments to call_function.

  • return_function (Optional[Callable]) – Optional callable to invoke with the result of call_function.

  • ret_args (tuple) – Additional positional arguments appended after the result when calling return_function.

handle_global_signal(metaclass: str, subclass_key: str, call_function: str, call_args: tuple, return_function: Callable | None, ret_args: tuple) None

Resolve (metaclass, subclass_key) to a live data plugin instance and dispatch call_function to it, so a tab or plugin can invoke a method on another plugin without holding a direct reference to it. Resolution and the call itself both happen inside the error guard, because looking up an unregistered metaclass raises rather than returning None, and this is a Qt slot, which must not let an exception escape into the C++ caller. The dispatch itself is _dispatch_to, shared with handle_data_plugin_controller_signal.

Parameters:
  • metaclass (str) – The metaclass of the target plugin instance.

  • subclass_key (str) – The unique key of the target plugin instance.

  • call_function (str) – Name of the method to call on the resolved instance.

  • call_args (tuple) – Positional arguments to call_function.

  • return_function (Optional[Callable]) – Optional callable to invoke with the result of call_function.

  • ret_args (tuple) – Additional positional arguments appended after the result when calling return_function.

instantiate_analysis_tab(subclass: str) None

Instantiate a new analysis-tab controller of the given subclass and wire it into the app (add its page, sync the sidebar highlight to it, connect its signals, register it in plugin history), or reuse the existing instance if a tab of that type has already been instantiated.

Exceptions raised while instantiating the controller itself are caught and logged here. Exceptions raised afterward, while wiring up or registering the new tab, are not caught by this method and will propagate to the caller.

Parameters:

subclass (str) – The class name of the MetaController subclass to instantiate (e.g. “RawDataController”).

load_session(file_name: str | Path | None = None) None

Load a saved session, replacing whatever is currently instantiated.

Applying the loaded plugin history on top of an already-populated workspace collided with anything the current session already held under the same key or name - a plugin key already registered, a named filter already added - and surfaced as an “already exists” error for state the user never meant to keep. reset_session() clears the workspace first, the same as it does for its own menu action, so a load always starts from nothing regardless of what was open before it. Both “Load Session” (a chosen file) and “Restore Session” (file_name=None, the last saved session) route through this same method.

A message naming what was loaded is left on the status/log panel, since reset_session() above already leaves one there of its own - without this, the user would see that the workspace was cleared but not what, if anything, replaced it.

Parameters:

file_name (Optional[Union[str, Path]]) – Path to the session file to load, or None to restore the last saved session.

refresh_available_plugins() None

Re-scan the plugin directories and push the result to everyone holding it.

The scan ran once, in MainModel’s constructor, and its results were copied into three places at construction: this controller’s data plugin controller, that controller’s model, and the view’s menus. Changing the user plugin folder therefore had no visible effect until the next launch.

Instantiated plugins are untouched. self.data_plugins is the list of instantiated plugins rather than available classes, so it is not part of this refresh, and an existing instance keeps working through its own class reference regardless - see MainModel.refresh_available_plugins for why a re-scan does not hand back the same class object it did before.

reset_app_config() None

Restore the stored settings to their defaults and apply them live.

The model persists the defaults; each is then routed back through the same path a manual edit takes, because rewriting config.json alone would leave the running application on the old values until restart - data plugins keep the previous parent folder, and the logger keeps the previous level. Finally the settings window is refreshed, which would otherwise go on displaying what the user had before.

Saved sessions and log files are not affected.

Resetting the user plugin folder re-scans it immediately, the same way editing it in Settings does: this routes through update_user_plugin_location below, which already calls refresh_available_plugins() at the end of its own path. The plugin menus reflect the default folder right away rather than waiting for the next launch.

reset_session() None

Return the application to the state it has when launched from scratch.

Deletes every instantiated data plugin, closes every analysis tab, drops both in-memory histories and returns to the landing page - so the user gets a clean workspace without quitting and starting the app again.

The saved session files are deliberately left on disk, because that is what launching from scratch does: load_session reads them at startup and nothing applies them until the user chooses Restore Session. Leaving them means this is reversible - reset, then Restore, and the workspace comes back.

Keeping them takes active effort. Both histories save themselves on every change, and deleting a plugin emits a history update per plugin, so the teardown below would otherwise write an empty session over the file before the user ever got the chance to restore it. _suppress_session_save holds that off for the duration.

Anything that refuses to delete is reported rather than passed over, so a partial clear is never announced as a complete one.

The Settings page, if it was open, is removed too rather than kept around - a freshly launched application has never opened it either. Its widget is a singleton rather than something disposable, though, so close_settings_page() detaches it first; see that method for why.

Running workers are stopped first, as they are on quit - deleting a plugin closes its resources, so a worker still running against one would be reading from a handle that has just been closed.

The sidebar highlight, the sidebar layout, the status/log panel and the floating Help window are reset too. None of those follow from tearing down tabs and plugins on their own: the sidebar only ever gains a checked button, never loses one; an expanded sidebar stays expanded; the log panel only ever grows; and Help is a separate top-level window that closing tabs never touches. Left alone, the landing page would still show whichever section was last open, whichever sidebar layout was last chosen, everything logged before the reset, and a Help window a fresh launch would never have open.

The plugin menus are re-scanned too, the same way changing the user plugin folder already does. populate_available_plugins() otherwise only ever runs once, in MainModel’s constructor, so a plugin file dropped into the plugin folder mid-session would be invisible in the menus after a reset even though a genuine relaunch would pick it up.

send_analysis_tabs() None

Send the list of instantiated analysis tabs to MainView.