From 12cca24d8b4fe71a35aefca9550e6a497895175a Mon Sep 17 00:00:00 2001
From: Theophile Terraz <theophile.terraz@inrae.fr>
Date: Mon, 5 Aug 2024 15:50:22 +0200
Subject: [PATCH] add shift button in geometry window

---
 src/Model/Geometry/ProfileXYZ.py              |   6 +
 src/View/Geometry/ShiftDialog.py              |  60 ++++++++
 src/View/Geometry/Table.py                    |   9 ++
 src/View/Geometry/Translate.py                |   9 ++
 src/View/Geometry/UndoCommand.py              |  29 ++++
 src/View/Geometry/Window.py                   |  28 +++-
 src/View/InitialConditions/Window.py          |   2 +-
 src/View/ui/GeometryReach.ui                  |   9 ++
 src/View/ui/GeometryReachShift.ui             | 134 ++++++++++++++++++
 ...{UpdateKPOptions.ui => UpdateRKOptions.ui} |   0
 10 files changed, 281 insertions(+), 5 deletions(-)
 create mode 100644 src/View/Geometry/ShiftDialog.py
 create mode 100644 src/View/ui/GeometryReachShift.ui
 rename src/View/ui/{UpdateKPOptions.ui => UpdateRKOptions.ui} (100%)

diff --git a/src/Model/Geometry/ProfileXYZ.py b/src/Model/Geometry/ProfileXYZ.py
index d3e98097..0e29c8c3 100644
--- a/src/Model/Geometry/ProfileXYZ.py
+++ b/src/Model/Geometry/ProfileXYZ.py
@@ -779,3 +779,9 @@ class ProfileXYZ(Profile, SQLSubModel):
                         self.point(i),
                         self.point(i+1)
                     )
+
+    def shift(self, x, y, z):
+        for p in self.points:
+            p.x = p.x + x
+            p.y = p.y + y
+            p.z = p.z + z
diff --git a/src/View/Geometry/ShiftDialog.py b/src/View/Geometry/ShiftDialog.py
new file mode 100644
index 00000000..4953855a
--- /dev/null
+++ b/src/View/Geometry/ShiftDialog.py
@@ -0,0 +1,60 @@
+# ShiftDialog.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from View.Tools.PamhyrWindow import PamhyrDialog
+
+
+class ShiftDialog(PamhyrDialog):
+    _pamhyr_ui = "GeometryReachShift"
+    _pamhyr_name = "Shift"
+
+    def __init__(self, trad=None, parent=None):
+        super(ShiftDialog, self).__init__(
+            title=trad[self._pamhyr_name],
+            trad=trad,
+            options=[],
+            parent=parent
+        )
+
+        self._init_default_values()
+
+    def _init_default_values(self):
+        self._dx = 0.0
+        self._dy = 0.0
+        self._dz = 0.0
+
+    @property
+    def dx(self):
+        return self._dx
+
+    @property
+    def dy(self):
+        return self._dy
+
+    @property
+    def dz(self):
+        return self._dz
+
+    def accept(self):
+        self._dx = self.get_double_spin_box("doubleSpinBox_X")
+        self._dy = self.get_double_spin_box("doubleSpinBox_Y")
+        self._dz = self.get_double_spin_box("doubleSpinBox_Z")
+        super().accept()
+
+    def reject(self):
+        self.close()
diff --git a/src/View/Geometry/Table.py b/src/View/Geometry/Table.py
index 2676037d..fd91707a 100644
--- a/src/View/Geometry/Table.py
+++ b/src/View/Geometry/Table.py
@@ -267,3 +267,12 @@ class GeometryReachTableModel(PamhyrTableModel):
             )
         )
         self.layoutChanged.emit()
+
+    def shift(self, rows, dx, dy, dz):
+
+        self._undo.push(
+            ShiftCommand(
+                self._data, rows, dx, dy, dz
+            )
+        )
+        self.layoutChanged.emit()
diff --git a/src/View/Geometry/Translate.py b/src/View/Geometry/Translate.py
index b2bc2eec..7944d661 100644
--- a/src/View/Geometry/Translate.py
+++ b/src/View/Geometry/Translate.py
@@ -82,3 +82,12 @@ class GeometryTranslate(MainTranslate):
         self._dict["Meshing"] = _translate(
             "Geometry", "Meshing"
         )
+        self._dict["UpdateRK"] = _translate(
+            "Geometry", "UpdateRK"
+        )
+        self._dict["Purge"] = _translate(
+            "Geometry", "Purge"
+        )
+        self._dict["Shift"] = _translate(
+            "Geometry", "Shift"
+        )
diff --git a/src/View/Geometry/UndoCommand.py b/src/View/Geometry/UndoCommand.py
index 772c84e1..d3cc29ef 100644
--- a/src/View/Geometry/UndoCommand.py
+++ b/src/View/Geometry/UndoCommand.py
@@ -281,3 +281,32 @@ class PurgeCommand(QUndoCommand):
     def redo(self):
         for profile in self._reach._profiles:
             profile.purge(self._np_purge)
+
+
+class ShiftCommand(QUndoCommand):
+    def __init__(self, reach, rows, dx, dy, dz):
+        QUndoCommand.__init__(self)
+
+        self._reach = reach
+        self._rows = rows
+        self._dx = dx
+        self._dy = dy
+        self._dz = dz
+
+        self._old = []
+        for profile in self._reach.profiles:
+            self._old.append(profile.points.copy())
+
+    def undo(self):
+        for i in self._rows:
+            profile = self._reach.profiles[i]
+            self._reach.profiles[i].shift(-self._dx,
+                                          -self._dy,
+                                          -self._dz)
+
+    def redo(self):
+        for i in self._rows:
+            profile = self._reach.profiles[i]
+            self._reach.profiles[i].shift(self._dx,
+                                          self._dy,
+                                          self._dz)
diff --git a/src/View/Geometry/Window.py b/src/View/Geometry/Window.py
index 20fe4aa2..1a621808 100644
--- a/src/View/Geometry/Window.py
+++ b/src/View/Geometry/Window.py
@@ -58,6 +58,7 @@ from View.Geometry.PlotRKZ import PlotRKZ
 from View.Geometry.MeshingDialog import MeshingDialog
 from View.Geometry.UpdateRKDialog import UpdateRKDialog
 from View.Geometry.PurgeDialog import PurgeDialog
+from View.Geometry.ShiftDialog import ShiftDialog
 from View.Geometry.Translate import GeometryTranslate
 from View.Geometry.Profile.Window import ProfileWindow
 
@@ -194,6 +195,7 @@ class GeometryWindow(PamhyrWindow):
             "action_meshing": self.edit_meshing,
             "action_update_rk": self.update_rk,
             "action_purge": self.purge,
+            "action_shift": self.shift,
         }
 
         for action in actions:
@@ -499,10 +501,6 @@ class GeometryWindow(PamhyrWindow):
         self._table.move_down(row)
         self.select_current_profile()
 
-    def purge(self):
-        self._table.purge()
-        self.update_redraw()
-
     def purge(self):
         try:
             dlg = PurgeDialog(
@@ -515,6 +513,28 @@ class GeometryWindow(PamhyrWindow):
             logger_exception(e)
             return
 
+    def shift(self):
+        rows = sorted(
+            list(
+                set(
+                    [index.row() for index in self.tableView.selectedIndexes()]
+                )
+            )
+        )
+        try:
+            dlg = ShiftDialog(
+                trad=self._trad,
+                parent=self
+            )
+            if dlg.exec():
+                self._table.shift(rows,
+                                  dlg.dx,
+                                  dlg.dy,
+                                  dlg.dz)
+        except Exception as e:
+            logger_exception(e)
+            return
+
     def duplicate(self):
         rows = [
             row.row() for row in
diff --git a/src/View/InitialConditions/Window.py b/src/View/InitialConditions/Window.py
index 1113b121..e6563169 100644
--- a/src/View/InitialConditions/Window.py
+++ b/src/View/InitialConditions/Window.py
@@ -194,7 +194,7 @@ class InitialConditionsWindow(PamhyrWindow):
         return rows[0].row()
 
     def update(self):
-        self.update(propagate=False)
+        self._update(propagate=False)
 
     def _update(self, propagate=True):
         self._update_plot()
diff --git a/src/View/ui/GeometryReach.ui b/src/View/ui/GeometryReach.ui
index 97be0806..2d175bdc 100644
--- a/src/View/ui/GeometryReach.ui
+++ b/src/View/ui/GeometryReach.ui
@@ -92,6 +92,7 @@
    <addaction name="action_meshing"/>
    <addaction name="action_update_rk"/>
    <addaction name="action_purge"/>
+   <addaction name="action_shift"/>
   </widget>
   <action name="action_import">
    <property name="icon">
@@ -226,6 +227,14 @@
     <string>Purge cross-sections to keep a given number of points</string>
    </property>
   </action>
+  <action name="action_shift">
+   <property name="text">
+    <string>Shift</string>
+   </property>
+   <property name="toolTip">
+    <string>Shift selected sections coordinates</string>
+   </property>
+  </action>
  </widget>
  <resources/>
  <connections/>
diff --git a/src/View/ui/GeometryReachShift.ui b/src/View/ui/GeometryReachShift.ui
new file mode 100644
index 00000000..575dbb98
--- /dev/null
+++ b/src/View/ui/GeometryReachShift.ui
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Dialog</class>
+ <widget class="QDialog" name="Dialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>381</width>
+    <height>144</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Dialog</string>
+  </property>
+  <property name="layoutDirection">
+   <enum>Qt::LeftToRight</enum>
+  </property>
+  <layout class="QGridLayout" name="gridLayout_4">
+   <item row="3" column="0">
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <layout class="QGridLayout" name="gridLayout">
+     <item row="1" column="0">
+      <widget class="QLabel" name="label_2">
+       <property name="text">
+        <string>Y coordinate</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="0">
+      <widget class="QLabel" name="label_1">
+       <property name="text">
+        <string>X coordinate</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QDoubleSpinBox" name="doubleSpinBox_Y">
+       <property name="decimals">
+        <number>4</number>
+       </property>
+       <property name="minimum">
+        <double>-99999999.000000000000000</double>
+       </property>
+       <property name="maximum">
+        <double>99999999.000000000000000</double>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QDoubleSpinBox" name="doubleSpinBox_X">
+       <property name="decimals">
+        <number>4</number>
+       </property>
+       <property name="minimum">
+        <double>-99999999.000000000000000</double>
+       </property>
+       <property name="maximum">
+        <double>99999999.000000000000000</double>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="0">
+      <widget class="QLabel" name="label_3">
+       <property name="enabled">
+        <bool>true</bool>
+       </property>
+       <property name="text">
+        <string>Z coordinate</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="1">
+      <widget class="QDoubleSpinBox" name="doubleSpinBox_Z">
+       <property name="decimals">
+        <number>4</number>
+       </property>
+       <property name="minimum">
+        <double>-99999999.000000000000000</double>
+       </property>
+       <property name="maximum">
+        <double>99999999.000000000000000</double>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>Dialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>Dialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
diff --git a/src/View/ui/UpdateKPOptions.ui b/src/View/ui/UpdateRKOptions.ui
similarity index 100%
rename from src/View/ui/UpdateKPOptions.ui
rename to src/View/ui/UpdateRKOptions.ui
-- 
GitLab