Skip to content

Commit

Permalink
Merge pull request #1042 from nens/ben_flow_summary
Browse files Browse the repository at this point in the history
flow summary tool
  • Loading branch information
benvanbasten-ns authored Sep 12, 2024
2 parents f05944c + 7a2bebb commit 8e080c9
Show file tree
Hide file tree
Showing 9 changed files with 605 additions and 54 deletions.
84 changes: 59 additions & 25 deletions gui/threedi_plugin_dockwidget.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
from pathlib import Path

from threedi_results_analysis.threedi_plugin_model import ThreeDiGridItem, ThreeDiResultItem
from threedi_results_analysis.utils.constants import TOOLBOX_QGIS_SETTINGS_GROUP
from threedi_results_analysis.gui.threedi_plugin_grid_result_dialog import ThreeDiPluginGridResultDialog
from qgis.core import QgsSettings
from qgis.PyQt import uic
from qgis.PyQt.QtCore import pyqtSignal
from qgis.PyQt.QtCore import pyqtSlot
from qgis.PyQt.QtCore import QModelIndex
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtGui import QPixmap
from qgis.PyQt.QtWidgets import QAction
from qgis.PyQt.QtWidgets import QDockWidget
from qgis.PyQt.QtCore import QModelIndex
from qgis.PyQt.QtCore import pyqtSignal, pyqtSlot
from qgis.PyQt.QtWidgets import QMenu
from qgis.PyQt.QtWidgets import QAction
from qgis.core import QgsSettings
from qgis.PyQt.QtGui import QPixmap
from threedi_results_analysis import PLUGIN_DIR
from threedi_results_analysis.gui.threedi_plugin_grid_result_dialog import (
ThreeDiPluginGridResultDialog,
)
from threedi_results_analysis.threedi_plugin_model import ThreeDiGridItem
from threedi_results_analysis.threedi_plugin_model import ThreeDiResultItem
from threedi_results_analysis.utils.constants import TOOLBOX_QGIS_SETTINGS_GROUP

import logging


logger = logging.getLogger(__name__)

FORM_CLASS, _ = uic.loadUiType(
Expand Down Expand Up @@ -65,15 +70,57 @@ def __init__(self, parent, iface):
self.dialog.grid_file_selected.connect(self.grid_file_selected)
self.dialog.result_grid_file_selected.connect(self.result_grid_file_selected)

self.custom_actions = {}

def add_custom_actions(self, actions):
self.custom_actions |= actions

def customMenuRequested(self, pos):
index = self.treeView.indexAt(pos)
menu = QMenu(self)
action_delete = QAction("Delete", self)
action_remove = QAction("Remove", self)
action_remove.triggered.connect(lambda _, sel_index=index: self._remove_current_index_clicked(sel_index))
menu.addAction(action_remove)

for custom_action in self.custom_actions:
if custom_action.isSeparator():
menu.addSeparator()
continue
else:
menu.addAction(custom_action)
custom_action.triggered.disconnect()
custom_action.triggered.connect(lambda _, sel_index=index: self._current_index_clicked(sel_index))

action_delete.triggered.connect(lambda _, sel_index=index: self._remove_current_index_clicked(sel_index))
menu.addAction(action_delete)
menu.popup(self.treeView.viewport().mapToGlobal(pos))

@pyqtSlot(QModelIndex)
def _current_index_clicked(self, index=None):
# note that index is the "current", not the "selected"
if not index:
index = self.treeView.selectionModel().currentIndex()
if index is not None and index.isValid():
item = self.treeView.model().itemFromIndex(index)
action = self.sender()
if isinstance(item, ThreeDiGridItem):
self.custom_actions[action][0](item)
elif isinstance(item, ThreeDiResultItem):
self.custom_actions[action][1](item)
else:
raise RuntimeError("Unknown model item type")

def _remove_current_index_clicked(self, index=None):
# note that index is the "current", not the "selected"
if not index:
index = self.treeView.selectionModel().currentIndex()
if index is not None and index.isValid():
item = self.treeView.model().itemFromIndex(index)
if isinstance(item, ThreeDiGridItem):
self.grid_removal_selected.emit(item)
elif isinstance(item, ThreeDiResultItem):
self.result_removal_selected.emit(item)
else:
raise RuntimeError("Unknown model item type")

def set_model(self, model):

tree_view = self.treeView
Expand All @@ -94,19 +141,6 @@ def _add_clicked(self):
self.dialog.refresh()
self.dialog.exec()

def _remove_current_index_clicked(self, index=None):
# note that index is the "current", not the "selected"
if not index:
index = self.treeView.selectionModel().currentIndex()
if index is not None and index.isValid():
item = self.treeView.model().itemFromIndex(index)
if isinstance(item, ThreeDiGridItem):
self.grid_removal_selected.emit(item)
elif isinstance(item, ThreeDiResultItem):
self.result_removal_selected.emit(item)
else:
raise RuntimeError("Unknown model item type")

def _align_starts_clicked(self):
self.align_starts_checked.emit(self.alignStartsCheckBox.isChecked())

Expand Down
Binary file added icons/icon_summary.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 48 additions & 20 deletions threedi_plugin.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,47 @@
from qgis.core import QgsApplication
from qgis.core import QgsMapLayer
from qgis.core import QgsPathResolver
from qgis.core import QgsProject
from qgis.core import QgsSettings
from qgis.PyQt.QtCore import QObject
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction
from qgis.PyQt.QtXml import QDomDocument, QDomElement
from qgis.PyQt.QtXml import QDomDocument
from qgis.PyQt.QtXml import QDomElement
from qgis.utils import iface
from qgis.core import QgsApplication, QgsProject, QgsPathResolver, QgsSettings, QgsMapLayer
from threedi_results_analysis.gui.threedi_plugin_dockwidget import (
ThreeDiPluginDockWidget,
)
from threedi_results_analysis.misc_tools import About
from threedi_results_analysis.misc_tools import ShowLogfile
from threedi_results_analysis.misc_tools import ToggleResultsManager
from threedi_results_analysis.processing.providers import ThreediProvider
from threedi_results_analysis.gui.threedi_plugin_dockwidget import ThreeDiPluginDockWidget
from threedi_results_analysis.threedi_plugin_layer_manager import ThreeDiPluginLayerManager
from threedi_results_analysis.threedi_plugin_model import ThreeDiPluginModel
from threedi_results_analysis.threedi_plugin_model_validation import ThreeDiPluginModelValidator
from threedi_results_analysis.temporal import TemporalManager
from threedi_results_analysis.threedi_plugin_model_serialization import ThreeDiPluginModelSerializer
from threedi_results_analysis.threedi_plugin_layer_manager import (
ThreeDiPluginLayerManager,
)
from threedi_results_analysis.threedi_plugin_model import ThreeDiPluginModel
from threedi_results_analysis.threedi_plugin_model_serialization import (
ThreeDiPluginModelSerializer,
)
from threedi_results_analysis.threedi_plugin_model_validation import (
ThreeDiPluginModelValidator,
)
from threedi_results_analysis.tool_animation.map_animator import MapAnimator
from threedi_results_analysis.tool_flow_summary.flow_summary import FlowSummaryTool
from threedi_results_analysis.tool_graph.graph import ThreeDiGraph
from threedi_results_analysis.tool_sideview.sideview import ThreeDiSideView
from threedi_results_analysis.tool_statistics.statistics import StatisticsTool
from threedi_results_analysis.tool_water_balance import WaterBalanceTool
from threedi_results_analysis.tool_watershed.watershed_analysis import ThreeDiWatershedAnalyst
from threedi_results_analysis.tool_watershed.watershed_analysis import (
ThreeDiWatershedAnalyst,
)
from threedi_results_analysis.utils import color
from threedi_results_analysis.utils.qprojects import ProjectStateMixin

import logging


logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -65,6 +82,9 @@ def initGui(self):
self.actions = []
self.menu = "&3Di Results Analysis"

assert not hasattr(self, "dockwidget") # Should be destroyed on unload
self.dockwidget = ThreeDiPluginDockWidget(None, iface)

# Set toolbar and init a few toolbar widgets
self.toolbar = self.iface.addToolBar("ThreeDiResultAnalysis")
self.toolbar.setObjectName("ThreeDiResultAnalysisToolBar")
Expand All @@ -79,34 +99,34 @@ def initGui(self):
self.watershed_tool = ThreeDiWatershedAnalyst(iface, self.model)
self.logfile_tool = ShowLogfile(iface)
self.temporal_manager = TemporalManager(self.model)
self.flow_summary_tool = FlowSummaryTool(self.dockwidget.get_tools_widget(), iface, self.model)

self.tools = [ # second item indicates enabled on startup
(self.about_tool, True),
(self.toggle_results_manager, True),
(self.flow_summary_tool, False),
(self.graph_tool, False),
(self.sideview_tool, False),
(self.stats_tool, False),
(self.water_balance_tool, False),
(self.watershed_tool, False),
(self.logfile_tool, True),
(self.logfile_tool, True)
]

# Styling (TODO: can this be moved to where it is used?)
for color_ramp in color.COLOR_RAMPS:
color.add_color_ramp(color_ramp)

for tool, enabled in self.tools:
self._add_action(
tool,
tool.icon_path,
text=tool.menu_text,
callback=tool.run,
parent=self.iface.mainWindow(),
enabled_flag=enabled
)

assert not hasattr(self, "dockwidget") # Should be destroyed on unload
self.dockwidget = ThreeDiPluginDockWidget(None, iface)
if tool.icon_path is not None:
self._add_action(
tool,
tool.icon_path,
text=tool.menu_text,
callback=tool.run,
parent=self.iface.mainWindow(),
enabled_flag=enabled
)

# Connect the signals

Expand Down Expand Up @@ -149,6 +169,11 @@ def initGui(self):
self.model.result_added.connect(self.map_animator.results_changed)
self.temporal_manager.updated.connect(self.map_animator.update_results)

# flow summary signals
self.model.result_removed.connect(self.flow_summary_tool.result_removed)
self.model.result_changed.connect(self.flow_summary_tool.result_changed)
self.model.result_added.connect(self.flow_summary_tool.result_added)

# graph signals
self.model.result_added.connect(self.graph_tool.result_added)
self.model.result_removed.connect(self.graph_tool.result_removed)
Expand Down Expand Up @@ -183,6 +208,9 @@ def initGui(self):
self.model.result_changed.connect(self.water_balance_tool.result_changed)
self.model.grid_changed.connect(self.water_balance_tool.grid_changed)

for tool, _ in self.tools:
self.dockwidget.add_custom_actions(tool.get_custom_actions())

# Further administrative signals that need to happens last:
# https://doc.qt.io/qt-5/signalsandslots.html#signals
# If several slots are connected to one signal, the slots will be executed one after the other,
Expand Down
14 changes: 9 additions & 5 deletions threedi_plugin_model.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
from pathlib import Path
from typing import List
from functools import cached_property
from qgis.PyQt.QtCore import pyqtSignal, pyqtSlot
from qgis.PyQt.QtCore import QModelIndex, Qt
from qgis.PyQt.QtGui import QStandardItem, QStandardItemModel
from pathlib import Path
from qgis.PyQt.QtCore import pyqtSignal
from qgis.PyQt.QtCore import pyqtSlot
from qgis.PyQt.QtCore import QModelIndex
from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtGui import QStandardItem
from qgis.PyQt.QtGui import QStandardItemModel
from threedi_results_analysis.datasource.threedi_results import ThreediResult
from typing import List

import logging
import uuid


logger = logging.getLogger(__name__)

already_used_ids = []
Expand Down
33 changes: 29 additions & 4 deletions threedi_plugin_tool.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from qgis.PyQt.QtCore import QObject
from qgis.PyQt.QtXml import QDomElement, QDomDocument
import logging

logger = logging.getLogger(__name__)
from qgis.PyQt.QtWidgets import QAction
from qgis.PyQt.QtXml import QDomDocument
from qgis.PyQt.QtXml import QDomElement
from threedi_results_analysis.threedi_plugin_model import ThreeDiGridItem
from threedi_results_analysis.threedi_plugin_model import ThreeDiResultItem
from typing import Callable
from typing import Dict
from typing import Tuple


class ThreeDiPluginTool(QObject):
Expand All @@ -27,3 +31,24 @@ def read(self, _: QDomElement) -> bool:
def on_unload(self):
"""Called when the plugin is unloaded. Tool can cleanup necessary items"""
pass

def get_custom_actions(self) -> Dict[QAction, Tuple[Callable[[ThreeDiGridItem], None], Callable[[ThreeDiResultItem], None]]]:
"""Called to retrieve the tool specific actions for the context-menu (right-button click) in Result Manager tree, including
optional separators. Tool needs to provide an implementation for both a grid item and for a result item, e.g:
@pyqtSlot(ThreeDiGridItem)
def add_summary_grid(self, item:ThreeDiGridItem) -> None:
logger.info(f"grid {item.id}")
@pyqtSlot(ThreeDiResultItem)
def add_summary_result(self, item:ThreeDiGridItem) -> None:
logger.info(f"result {item.id}")
def get_custom_actions(self) -> Dict[QAction, Tuple[Callable[[ThreeDiGridItem], None], Callable[[ThreeDiResultItem], None]]]:
separator = QAction()
separator.setSeparator(True)
return {separator: (None, None),
QAction("Show flow summary"): (self.add_summary_grid, self.add_summary_result)
}
"""
return {}
Empty file added tool_flow_summary/__init__.py
Empty file.
Loading

0 comments on commit 8e080c9

Please sign in to comment.