diff --git a/loopstructural/3d_icon.png b/loopstructural/3d_icon.png new file mode 100644 index 0000000..bc2bc71 Binary files /dev/null and b/loopstructural/3d_icon.png differ diff --git a/loopstructural/gui/dlg_settings.py b/loopstructural/gui/dlg_settings.py index 5f7bef2..c4caaaf 100644 --- a/loopstructural/gui/dlg_settings.py +++ b/loopstructural/gui/dlg_settings.py @@ -85,6 +85,7 @@ def apply(self): # misc settings.debug_mode = self.opt_debug.isChecked() + settings.separate_dock_widgets = self.opt_separate_dock_widgets.isChecked() settings.interpolator_nelements = self.n_elements_spin_box.value() settings.interpolator_npw = self.npw_spin_box.value() settings.interpolator_cpw = self.cpw_spin_box.value() @@ -106,6 +107,7 @@ def load_settings(self): # global self.opt_debug.setChecked(settings.debug_mode) + self.opt_separate_dock_widgets.setChecked(settings.separate_dock_widgets) self.lbl_version_saved_value.setText(settings.version) # self.interpolator_type_combo.setCurrentText(settings.interpolator_type) self.n_elements_spin_box.setValue(settings.interpolator_nelements) diff --git a/loopstructural/gui/dlg_settings.ui b/loopstructural/gui/dlg_settings.ui index b910f9f..edba4d3 100644 --- a/loopstructural/gui/dlg_settings.ui +++ b/loopstructural/gui/dlg_settings.ui @@ -226,6 +226,37 @@ + + + + + 0 + 25 + + + + + 16777215 + 30 + + + + Show modelling and visualisation in separate dock widgets. + + + true + + + + + + Use separate dock widgets for modelling and visualisation + + + false + + + diff --git a/loopstructural/gui/loop_widget.py b/loopstructural/gui/loop_widget.py index a0fb574..bf78f50 100644 --- a/loopstructural/gui/loop_widget.py +++ b/loopstructural/gui/loop_widget.py @@ -51,3 +51,23 @@ def __init__( ) tabWidget.addTab(self.modelling_widget, "Modelling") tabWidget.addTab(self.visualisation_widget, "Visualisation") + + def get_modelling_widget(self): + """Return the modelling widget instance. + + Returns + ------- + ModellingWidget + The modelling widget. + """ + return self.modelling_widget + + def get_visualisation_widget(self): + """Return the visualisation widget instance. + + Returns + ------- + VisualisationWidget + The visualisation widget. + """ + return self.visualisation_widget diff --git a/loopstructural/plugin_main.py b/loopstructural/plugin_main.py index 12bbd18..795eef1 100644 --- a/loopstructural/plugin_main.py +++ b/loopstructural/plugin_main.py @@ -35,7 +35,7 @@ from loopstructural.gui.loop_widget import LoopWidget from loopstructural.main.data_manager import ModellingDataManager from loopstructural.main.model_manager import GeologicalModelManager -from loopstructural.toolbelt import PlgLogger +from loopstructural.toolbelt import PlgLogger, PlgOptionsManager # ############################################################################ # ########## Classes ############### @@ -127,9 +127,13 @@ def initGui(self): self.tr("LoopStructural Modelling"), self.iface.mainWindow(), ) + self.action_visualisation = QAction( + QIcon(os.path.dirname(__file__) + "/3D_icon.png"), + self.tr("LoopStructural Visualisation"), + self.iface.mainWindow(), + ) self.toolbar.addAction(self.action_modelling) - # -- Menu self.iface.addPluginToMenu(__title__, self.action_settings) self.iface.addPluginToMenu(__title__, self.action_help) @@ -150,36 +154,102 @@ def initGui(self): self.iface.pluginHelpMenu().addAction(self.action_help_plugin_menu_documentation) ## --- dock widget - self.loop_dockwidget = QDockWidget(self.tr("Loop"), self.iface.mainWindow()) - self.loop_widget = LoopWidget( - self.iface.mainWindow(), - mapCanvas=self.iface.mapCanvas(), - logger=self.log, - data_manager=self.data_manager, - model_manager=self.model_manager, - ) + # Get the setting for separate dock widgets + settings = PlgOptionsManager.get_plg_settings() + + if settings.separate_dock_widgets: + # Create separate dock widgets for modelling and visualisation + self.loop_widget = LoopWidget( + self.iface.mainWindow(), + mapCanvas=self.iface.mapCanvas(), + logger=self.log, + data_manager=self.data_manager, + model_manager=self.model_manager, + ) + self.toolbar.addAction(self.action_visualisation) - self.loop_dockwidget.setWidget(self.loop_widget) - self.iface.addDockWidget(Qt.RightDockWidgetArea, self.loop_dockwidget) - right_docks = [ - d - for d in self.iface.mainWindow().findChildren(QDockWidget) - if self.iface.mainWindow().dockWidgetArea(d) == Qt.RightDockWidgetArea - ] - # If there are other dock widgets, tab this one with the first one found - if right_docks: - for dock in right_docks: - if dock != self.loop_dockwidget: - self.iface.mainWindow().tabifyDockWidget(dock, self.loop_dockwidget) - # Optionally, bring your plugin tab to the front - self.loop_dockwidget.raise_() - break - self.loop_dockwidget.show() - - self.loop_dockwidget.close() - - # -- Connect actions - self.action_modelling.triggered.connect(self.loop_dockwidget.toggleViewAction().trigger) + # Create modelling dock + self.modelling_dockwidget = QDockWidget( + self.tr("Loop - Modelling"), self.iface.mainWindow() + ) + self.modelling_dockwidget.setWidget(self.loop_widget.get_modelling_widget()) + self.iface.addDockWidget(Qt.RightDockWidgetArea, self.modelling_dockwidget) + + # Create visualisation dock + self.visualisation_dockwidget = QDockWidget( + self.tr("Loop - Visualisation"), self.iface.mainWindow() + ) + self.visualisation_dockwidget.setWidget(self.loop_widget.get_visualisation_widget()) + self.iface.addDockWidget(Qt.RightDockWidgetArea, self.visualisation_dockwidget) + + # Tab them with other right docks if available + right_docks = [ + d + for d in self.iface.mainWindow().findChildren(QDockWidget) + if self.iface.mainWindow().dockWidgetArea(d) == Qt.RightDockWidgetArea + ] + if right_docks: + for dock in right_docks: + if dock != self.modelling_dockwidget and dock != self.visualisation_dockwidget: + self.iface.mainWindow().tabifyDockWidget(dock, self.modelling_dockwidget) + self.modelling_dockwidget.raise_() + break + + # Tab visualisation with modelling + self.iface.mainWindow().tabifyDockWidget( + self.modelling_dockwidget, self.visualisation_dockwidget + ) + + self.modelling_dockwidget.show() + self.visualisation_dockwidget.show() + self.modelling_dockwidget.close() + self.visualisation_dockwidget.close() + + # Connect action to toggle modelling dock + self.action_modelling.triggered.connect( + self.modelling_dockwidget.toggleViewAction().trigger + ) + self.action_visualisation.triggered.connect( + self.visualisation_dockwidget.toggleViewAction().trigger + ) + # Store reference to main dock as None for unload compatibility + self.loop_dockwidget = None + else: + # Create single dock widget with tabs (default behavior) + self.loop_dockwidget = QDockWidget(self.tr("Loop"), self.iface.mainWindow()) + self.loop_widget = LoopWidget( + self.iface.mainWindow(), + mapCanvas=self.iface.mapCanvas(), + logger=self.log, + data_manager=self.data_manager, + model_manager=self.model_manager, + ) + + self.loop_dockwidget.setWidget(self.loop_widget) + self.iface.addDockWidget(Qt.RightDockWidgetArea, self.loop_dockwidget) + right_docks = [ + d + for d in self.iface.mainWindow().findChildren(QDockWidget) + if self.iface.mainWindow().dockWidgetArea(d) == Qt.RightDockWidgetArea + ] + # If there are other dock widgets, tab this one with the first one found + if right_docks: + for dock in right_docks: + if dock != self.loop_dockwidget: + self.iface.mainWindow().tabifyDockWidget(dock, self.loop_dockwidget) + # Optionally, bring your plugin tab to the front + self.loop_dockwidget.raise_() + break + self.loop_dockwidget.show() + + self.loop_dockwidget.close() + + # -- Connect actions + self.action_modelling.triggered.connect(self.loop_dockwidget.toggleViewAction().trigger) + + # Store references to separate docks as None for unload compatibility + self.modelling_dockwidget = None + self.visualisation_dockwidget = None def tr(self, message: str) -> str: """Translate a string using Qt translation API. @@ -198,6 +268,17 @@ def tr(self, message: str) -> str: def unload(self): """Clean up when plugin is disabled or uninstalled.""" + # -- Clean up dock widgets + if self.loop_dockwidget: + self.iface.removeDockWidget(self.loop_dockwidget) + del self.loop_dockwidget + if self.modelling_dockwidget: + self.iface.removeDockWidget(self.modelling_dockwidget) + del self.modelling_dockwidget + if self.visualisation_dockwidget: + self.iface.removeDockWidget(self.visualisation_dockwidget) + del self.visualisation_dockwidget + # -- Clean up menu self.iface.removePluginMenu(__title__, self.action_help) self.iface.removePluginMenu(__title__, self.action_settings) diff --git a/loopstructural/toolbelt/preferences.py b/loopstructural/toolbelt/preferences.py index 108ac87..f40cfd4 100644 --- a/loopstructural/toolbelt/preferences.py +++ b/loopstructural/toolbelt/preferences.py @@ -33,6 +33,7 @@ class PlgSettingsStructure: interpolator_regularisation: float = 1.0 interpolator_cpw: float = 1.0 interpolator_npw: float = 1.0 + separate_dock_widgets: bool = False class PlgOptionsManager: diff --git a/tests/qgis/test_plg_preferences.py b/tests/qgis/test_plg_preferences.py index c76ab46..f951749 100644 --- a/tests/qgis/test_plg_preferences.py +++ b/tests/qgis/test_plg_preferences.py @@ -36,6 +36,11 @@ def test_plg_preferences_structure(self): self.assertIsInstance(settings.version, str) self.assertEqual(settings.version, __version__) + # dock widgets setting + self.assertTrue(hasattr(settings, "separate_dock_widgets")) + self.assertIsInstance(settings.separate_dock_widgets, bool) + self.assertEqual(settings.separate_dock_widgets, False) + # ############################################################################ # ####### Stand-alone run ########