From f6b68f1482a6b6491c9163cf7e9b1489aa24d6d0 Mon Sep 17 00:00:00 2001 From: Christian Kolset Date: Sun, 2 Aug 2026 00:58:34 -0600 Subject: Consolidate Devices/Channels/Plot into unified ConfigWindow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces three separate floating windows with single tabbed ConfigWindow: - Tab 0: Devices (device cards, add/remove/configure) - Tab 1: Channels (signal table with Dir/IN/OUT badge + pipeline ⚙ per row; derived channels pane) - Tab 2: Plot (layout canvas + pane config) Adds is_output field to ChannelConfig for output channel direction badges. Co-Authored-By: Claude Sonnet 4.6 --- ui/main_window.py | 166 ++++++++++++++++++++++-------------------------------- 1 file changed, 66 insertions(+), 100 deletions(-) (limited to 'ui/main_window.py') diff --git a/ui/main_window.py b/ui/main_window.py index 76c203d..22b01a0 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -105,9 +105,8 @@ from core.profile import Profile, ProfileManager from ui.control_panel import ControlPanel from ui.strip_chart import StripChartWidget from ui.profile_manager_ui import ProfileButton -from ui.windows.devices_window import DevicesWindow -from ui.windows.channels_window import ChannelsWindow -from ui.windows.plot_window import PlotWindow, build_default_layout +from ui.windows.config_window import ConfigWindow +from ui.windows.plot_window import build_default_layout from ui.windows.settings_window import SettingsWindow from plugins.plugin_manager import PluginManager @@ -131,9 +130,7 @@ class MainWindow(QMainWindow): self._settings = load_settings(SettingsWindow._defaults) self._elapsed = 0 - self._win_devices = None - self._win_channels = None - self._win_plot = None + self._win_config = None self._win_settings = None _plugins_dir = os.path.join(os.path.dirname(os.path.dirname( @@ -194,19 +191,21 @@ class MainWindow(QMainWindow): self._view_menu.addAction(self._act_ctrl_panel) self._view_menu.addSeparator() - for label, name, opener in [ - ("Devices", "devices", self._open_devices), - ("Channels", "channels", self._open_channels), - ("Plot Builder", "plot", self._open_plot), + for label, tab_idx in [ + ("Devices", 0), + ("Channels", 1), + ("Plot Builder", 2), ]: act = QAction(label, self); act.setCheckable(True) act.triggered.connect( - lambda checked, nm=name, op=opener: - op() if checked else - (getattr(self, f"_win_{nm}") and getattr(self, f"_win_{nm}").hide()) + lambda checked, ti=tab_idx: + self._open_config(ti) if checked else + (self._win_config and self._win_config.hide()) ) self._view_menu.addAction(act) - setattr(self, f"_act_{name}", act) + self._act_devices = self._view_menu.actions()[-3] + self._act_channels = self._view_menu.actions()[-2] + self._act_plot = self._view_menu.actions()[-1] self._view_plugin_sep = self._view_menu.addSeparator() self._view_plugin_sep.setVisible(False) @@ -368,8 +367,8 @@ class MainWindow(QMainWindow): self._plugin_toolbar_actions[plugin.plugin_id] = tb_actions self._chart.refresh() - if self._win_plot: - self._win_plot.refresh_channels() + if self._win_config: + self._win_config.refresh_plot() self._status.setText(f"Plugin enabled: {plugin.name}") def _uninstall_plugin(self, plugin_id: str): @@ -404,8 +403,8 @@ class MainWindow(QMainWindow): self.engine.remove_device(dev_id) self._chart.refresh() - if self._win_plot: - self._win_plot.refresh_channels() + if self._win_config: + self._win_config.refresh_plot() self._status.setText(f"Plugin disabled: {plugin.name}") def plugin_enable(self, plugin_id: str): @@ -422,55 +421,29 @@ class MainWindow(QMainWindow): # ── Window management ───────────────────────────────────────────────── - def _toggle_win(self, name: str, checked: bool, btn: QPushButton): - creators = { - "devices": self._open_devices, - "channels": self._open_channels, - "plot": self._open_plot, - "settings": self._open_settings, - } - wins = { - "devices": "_win_devices", - "channels": "_win_channels", - "plot": "_win_plot", - "settings": "_win_settings", - } - if checked: - creators[name]() - else: - win = getattr(self, wins[name], None) - if win: win.hide() - - def _open_devices(self): - if self._win_devices is None: - self._win_devices = DevicesWindow(self.registry, self.engine, self) - self._win_devices.device_added.connect(self._on_device_added) - self._win_devices.device_removed.connect(self._on_device_removed) - self._win_devices.device_reconfigured.connect(self._on_device_reconfigured) - self._win_devices.channel_visibility_changed.connect(self._on_channel_visibility_changed) - self._win_devices.channel_name_changed.connect(self._on_channel_name_changed) - self._win_devices.channel_unit_changed.connect(self._on_channel_unit_changed) - self._win_devices.closed.connect(lambda: self._act_devices.setChecked(False)) - self._show_win(self._win_devices, "right") - - def _open_channels(self): - if self._win_channels is None: - self._win_channels = ChannelsWindow(self.registry, self.processor, self) - self._win_channels.derived_changed.connect(self._on_derived_changed) - self._win_channels.closed.connect(lambda: self._act_channels.setChecked(False)) - self._show_win(self._win_channels, "right") - - def _open_plot(self): - if self._win_plot is None: - self._win_plot = PlotWindow(self.registry, self.processor, - self._chart._cfg, self) - self._win_plot.layout_applied.connect(self._chart.apply_layout) - self._win_plot.closed.connect(lambda: self._act_plot.setChecked(False)) - self._win_plot.refresh_channels() # sync any derived channels - else: - self._win_plot.cfg = self._chart._cfg - self._win_plot.refresh_channels() - self._show_win(self._win_plot, "below") + def _open_config(self, tab: int = 0): + if self._win_config is None: + self._win_config = ConfigWindow( + self.registry, self.engine, self.processor, + self._chart._cfg, self, + ) + self._win_config.device_added.connect(self._on_device_added) + self._win_config.device_removed.connect(self._on_device_removed) + self._win_config.device_reconfigured.connect(self._on_device_reconfigured) + self._win_config.channel_visibility_changed.connect(self._on_channel_visibility_changed) + self._win_config.channel_name_changed.connect(self._on_channel_name_changed) + self._win_config.channel_unit_changed.connect(self._on_channel_unit_changed) + self._win_config.pipeline_changed.connect(lambda: None) + self._win_config.derived_changed.connect(self._on_derived_changed) + self._win_config.layout_applied.connect(self._chart.apply_layout) + self._win_config.closed.connect(self._on_config_closed) + self._win_config.tabs.setCurrentIndex(tab) + self._show_win(self._win_config, "right") + + def _on_config_closed(self): + self._act_devices.setChecked(False) + self._act_channels.setChecked(False) + self._act_plot.setChecked(False) def _open_settings(self): if self._win_settings is None: @@ -529,13 +502,10 @@ class MainWindow(QMainWindow): # Clear controls self._ctrl.clear_widgets() - # Refresh open windows - if self._win_devices: - self._win_devices.refresh() - if self._win_channels: - self._win_channels.refresh_derived() - if self._win_plot: - self._win_plot.refresh_channels() + if self._win_config: + self._win_config.refresh_devices() + self._win_config.refresh_derived() + self._win_config.refresh_plot() self._chart.apply_layout(build_default_layout(self.registry, self.processor)) self._clear_history() @@ -578,55 +548,52 @@ class MainWindow(QMainWindow): self._chart.apply_layout(plot_cfg) else: self._chart.refresh() - # Refresh open windows - if self._win_devices: - self._win_devices.refresh() - if self._win_channels: - self._win_channels.refresh_derived() - if self._win_plot: - self._win_plot.refresh_channels() + if self._win_config: + self._win_config.refresh_devices() + self._win_config.refresh_derived() + self._win_config.refresh_plot(self._chart._cfg) self._status.setText(f"Profile loaded: {profile.name}") # ── Device / signal events ──────────────────────────────────────────── def _on_device_added(self): self._chart.refresh() - if self._win_plot: self._win_plot.refresh_channels() - if self._win_channels: self._win_channels.on_device_added() + if self._win_config: + self._win_config.on_device_added() + self._win_config.refresh_plot() self._status.setText("Device added.") def _on_device_removed(self, dev_id: str): self._chart.refresh() - if self._win_channels: self._win_channels.on_device_removed(dev_id) + if self._win_config: + self._win_config.on_device_removed(dev_id) self._status.setText(f"Device '{dev_id}' removed.") def _on_device_reconfigured(self, dev_id: str): self._chart.refresh() - if self._win_plot: self._win_plot.refresh_channels() - if self._win_channels: self._win_channels.on_device_reconfigured(dev_id) + if self._win_config: + self._win_config.on_device_reconfigured(dev_id) + self._win_config.refresh_plot() self._status.setText(f"Device '{dev_id}' reconfigured.") def _on_channel_visibility_changed(self, dev_id: str, ch_id: str, enabled: bool): self._chart.on_channel_enabled_changed(dev_id, ch_id, enabled) - if self._win_channels: - self._win_channels.on_channel_enabled_changed(dev_id, ch_id, enabled) - if self._win_plot: - self._win_plot.refresh_channels() + if self._win_config: + self._win_config.on_channel_enabled_changed(dev_id, ch_id, enabled) + self._win_config.refresh_plot() def _on_channel_name_changed(self, dev_id: str, ch_id: str, name: str): - if self._win_channels: - self._win_channels.on_channel_name_changed(dev_id, ch_id, name) + if self._win_config: + self._win_config.on_channel_name_changed(dev_id, ch_id, name) def _on_channel_unit_changed(self, dev_id: str, ch_id: str, unit: str): - if self._win_channels: - self._win_channels.on_channel_unit_changed(dev_id, ch_id, unit) + if self._win_config: + self._win_config.on_channel_unit_changed(dev_id, ch_id, unit) def _on_derived_changed(self): self._chart.refresh() - if self._win_plot: - # Ensure plot window works on current chart cfg, then sync derived panes - self._win_plot.cfg = self._chart._cfg - self._win_plot.refresh_channels() + if self._win_config: + self._win_config.refresh_plot(self._chart._cfg) # ── Run / Log ───────────────────────────────────────────────────────── @@ -687,8 +654,7 @@ class MainWindow(QMainWindow): self._time_lbl.setText(f"{h:02d}:{m:02d}:{s:02d}") def closeEvent(self, event): - for w in (self._win_devices, self._win_channels, - self._win_plot, self._win_settings): + for w in (self._win_config, self._win_settings): if w: w.close() for plugin in list(self._plugin_mgr.get_loaded()): try: -- cgit v1.2.3