From 113fafac501df74d9ee46dbf1f5b8e95c7122629 Mon Sep 17 00:00:00 2001
From: Pierre-Antoine Rouby <pierre-antoine.rouby@inrae.fr>
Date: Tue, 16 May 2023 16:24:49 +0200
Subject: [PATCH] Stricklers: Add base of code.

---
 src/Model/Geometry/Reach.py            |   6 +-
 src/Model/River.py                     |   7 +-
 src/Model/Stricklers/Stricklers.py     |  47 ++++++++
 src/Model/Stricklers/StricklersList.py |  62 ++++++++++
 src/View/LateralContribution/Table.py  |  10 +-
 src/View/MainWindow.py                 |   8 +-
 src/View/Stricklers/Table.py           | 149 +++++++++++++++++++++++
 src/View/Stricklers/UndoCommand.py     | 156 +++++++++++++++++++++++++
 src/View/Stricklers/Window.py          | 120 +++++++++++++++++++
 src/View/Stricklers/translate.py       |  16 +++
 src/View/ui/Stricklers.ui              | 100 ++++++++++++++++
 11 files changed, 669 insertions(+), 12 deletions(-)
 create mode 100644 src/Model/Stricklers/Stricklers.py
 create mode 100644 src/Model/Stricklers/StricklersList.py
 create mode 100644 src/View/Stricklers/Table.py
 create mode 100644 src/View/Stricklers/UndoCommand.py
 create mode 100644 src/View/Stricklers/Window.py
 create mode 100644 src/View/Stricklers/translate.py
 create mode 100644 src/View/ui/Stricklers.ui

diff --git a/src/Model/Geometry/Reach.py b/src/Model/Geometry/Reach.py
index f96cfe59..8a174517 100644
--- a/src/Model/Geometry/Reach.py
+++ b/src/Model/Geometry/Reach.py
@@ -202,10 +202,10 @@ class Reach:
         return [profile.kp for profile in self.profiles]
 
     def get_kp_min(self):
-        return min([profile.kp for profile in self.profiles])
+        return min(self.get_kp())
 
     def get_kp_max(self):
-        return max([profile.kp for profile in self.profiles])
+        return max(self.get_kp())
 
 
     # Guidelines
@@ -237,7 +237,7 @@ class Reach:
 
     @timer
     def compute_guidelines(self):
-        """Compute reach guideline and check if is valid for all profiles
+        """Compute reach guidelines
 
         Returns:
             Tuple of complete and incomplete guidelines name.
diff --git a/src/Model/River.py b/src/Model/River.py
index d00de347..a2afe35a 100644
--- a/src/Model/River.py
+++ b/src/Model/River.py
@@ -9,7 +9,7 @@ from Model.Geometry.Reach import Reach
 
 from Model.BoundaryCondition.BoundaryConditionList import BoundaryConditionList
 from Model.LateralContribution.LateralContributionList import LateralContributionList
-
+from Model.Stricklers.StricklersList import StricklersList
 
 class RiverNode(Node):
     def __init__(self, id:str, name:str,
@@ -61,6 +61,7 @@ class River(Graph):
         self._current_reach = None
         self._boundary_condition = BoundaryConditionList(status=self._status)
         self._lateral_contribution = LateralContributionList(status=self._status)
+        self._stricklers = StricklersList(status=self._status)
 
     @property
     def boundary_condition(self):
@@ -70,6 +71,10 @@ class River(Graph):
     def lateral_contribution(self):
         return self._lateral_contribution
 
+    @property
+    def striklers(self):
+        return self._stricklers
+
     def has_current_reach(self):
         return self._current_reach is not None
 
diff --git a/src/Model/Stricklers/Stricklers.py b/src/Model/Stricklers/Stricklers.py
new file mode 100644
index 00000000..9799984f
--- /dev/null
+++ b/src/Model/Stricklers/Stricklers.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+
+from tools import trace, timer
+
+class Stricklers(object):
+    def __init__(self, status = None):
+        super(Stricklers, self).__init__()
+
+        self._status = status
+
+        self._name = ""
+        self._comment = ""
+
+        self._minor = 35
+        self._medium = 15
+
+    @property
+    def name(self):
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        self._name = name
+
+    @property
+    def comment(self):
+        return self._comment
+
+    @comment.setter
+    def comment(self, comment):
+        self._comment = comment
+
+    @property
+    def minor(self):
+        return self._minor
+
+    @minor.setter
+    def minor(self, minor):
+        self._minor = int(minor)
+
+    @property
+    def medium(self):
+        return self._medium
+
+    @medium.setter
+    def medium(self, medium):
+        self._medium = int(medium)
diff --git a/src/Model/Stricklers/StricklersList.py b/src/Model/Stricklers/StricklersList.py
new file mode 100644
index 00000000..631ed93a
--- /dev/null
+++ b/src/Model/Stricklers/StricklersList.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+from tools import trace, timer
+
+from Model.Stricklers.Stricklers import Stricklers
+
+class StricklersList(object):
+    def __init__(self, status = None):
+        self._status = status
+
+        self._stricks = []
+
+    def __len__(self):
+        return len(self._stricks)
+
+    @property
+    def stricks(self):
+        return self._stricks.copy()
+
+    def get(self, index):
+        if 0 <= index < len(self._stricks):
+            return self._stricks[index]
+
+        return None
+
+    def insert(self, index, strick):
+        self._stricks.insert(index, strick)
+        self._status.modified()
+
+    def add(self, index):
+        s = Stricklers(status = self._status)
+        self.insert(index, s)
+        return s
+
+    def delete(self, stricks):
+        self._stricks = list(
+            filter(
+                lambda s: s not in stricks,
+                self._stricks
+            )
+        )
+        self._status.modified()
+
+    def delete_i(self, indexes):
+        stricks = set(
+            map(
+                lambda e: e[1],
+                filter(
+                    lambda e: e[0] in indexes,
+                    enumerate(self._stricks)
+                )
+            )
+        )
+        self.delete(stricks)
+
+    def sort(self, reverse:bool = False):
+        self._strick = sorted(
+            self._strick,
+            key = lambda st: st.name,
+            reverse = reverse,
+        )
+        self._status.modified()
diff --git a/src/View/LateralContribution/Table.py b/src/View/LateralContribution/Table.py
index 818605cf..307fc56e 100644
--- a/src/View/LateralContribution/Table.py
+++ b/src/View/LateralContribution/Table.py
@@ -50,9 +50,7 @@ class ComboBoxDelegate(QItemDelegate):
                     )
                 )
             )
-            self.editor.addItems(
-                lst
-            )
+            self.editor.addItems(lst)
         else:
             self.editor.addItems(
                 [_translate("LateralContribution", "Not associate")] +
@@ -182,7 +180,7 @@ class TableModel(QAbstractTableModel):
 
         self._undo.push(
             AddCommand(
-                self._lcs, self._tab,row
+                self._lcs, self._tab, row
             )
         )
 
@@ -194,7 +192,7 @@ class TableModel(QAbstractTableModel):
 
         self._undo.push(
             DelCommand(
-                self._lcs, self._tab,rows
+                self._lcs, self._tab, rows
             )
         )
 
@@ -206,7 +204,7 @@ class TableModel(QAbstractTableModel):
 
         self._undo.push(
             SortCommand(
-                self._lcs, self._tab,False
+                self._lcs, self._tab, False
             )
         )
 
diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py
index 68e8ef37..30c7de24 100644
--- a/src/View/MainWindow.py
+++ b/src/View/MainWindow.py
@@ -24,6 +24,7 @@ from View.Network.Window import NetworkWindow
 from View.Geometry.Window import GeometryWindow
 from View.BoundaryCondition.Window import BoundaryConditionWindow
 from View.LateralContribution.Window import LateralContributionWindow
+from View.Stricklers.Window import StricklersWindow
 
 from Model.Study import Study
 
@@ -125,8 +126,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
             "action_toolBar_boundary_cond": self.open_boundary_cond,
             "action_toolBar_lateral_contrib": self.open_lateral_contrib,
             "action_toolBar_spills": lambda: self.open_dummy("Deversement"),
-            "action_toolBar_sections": lambda: self.open_dummy("Tronçons"),
-            "action_toolBar_frictions": lambda: self.open_dummy("Frottements"),
+            "action_toolBar_frictions": self.open_stricklers,
             "action_toolBar_building": lambda: self.open_dummy("Ouvrages"),
         }
 
@@ -333,6 +333,10 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
         self.lateral = LateralContributionWindow(study = self.model, parent=self)
         self.lateral.show()
 
+    def open_stricklers(self):
+        self.strick = StricklersWindow(study = self.model, parent=self)
+        self.strick.show()
+
     # TODO: Delete me !
     ###############
     # DUMMY STUFF #
diff --git a/src/View/Stricklers/Table.py b/src/View/Stricklers/Table.py
new file mode 100644
index 00000000..29f8fd53
--- /dev/null
+++ b/src/View/Stricklers/Table.py
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+
+from tools import trace, timer
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox,
+)
+
+from View.Stricklers.UndoCommand import (
+    SetNameCommand, SetCommentCommand,
+    SetMinorCommand, SetMediumCommand,
+    AddCommand, DelCommand, SortCommand,
+)
+
+from View.Stricklers.translate import *
+
+_translate = QCoreApplication.translate
+
+
+class TableModel(QAbstractTableModel):
+    def __init__(self, data=None, undo=None, tab=""):
+        super(QAbstractTableModel, self).__init__()
+        self._headers = list(table_headers.keys())
+        self._data = data
+        self._undo = undo
+
+    def flags(self, index):
+        options = Qt.ItemIsEnabled | Qt.ItemIsSelectable
+        options |= Qt.ItemIsEditable
+
+        return options
+
+    def rowCount(self, parent):
+        return len(self._data)
+
+    def columnCount(self, parent):
+        return len(self._headers)
+
+    def data(self, index, role):
+        if role != Qt.ItemDataRole.DisplayRole:
+            return QVariant()
+
+        row = index.row()
+        column = index.column()
+
+        if self._headers[column] == "name":
+            return self._data.get(row).name
+        elif self._headers[column] == "comment":
+            return self._data.get(row).comment
+        elif self._headers[column] == "minor":
+            return self._data.get(row).minor
+        elif self._headers[column] == "medium":
+            return self._data.get(row).medium
+
+        return QVariant()
+
+    def headerData(self, section, orientation, role):
+        if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
+            return table_headers[self._headers[section]]
+
+        return QVariant()
+
+    def setData(self, index, value, role=Qt.EditRole):
+        if not index.isValid() or role != Qt.EditRole:
+            return False
+
+        row = index.row()
+        column = index.column()
+
+        if self._headers[column] == "name":
+            self._undo.push(
+                SetNameCommand(
+                    self._data, row, value
+                )
+            )
+        elif self._headers[column] == "comment":
+            self._undo.push(
+                SetCommentCommand(
+                    self._data, row, value
+                )
+            )
+        elif self._headers[column] == "minor":
+            self._undo.push(
+                SetMinorCommand(
+                    self._data, row, value
+                )
+            )
+        elif self._headers[column] == "medium":
+            self._undo.push(
+                SetMediumCommand(
+                    self._data, row, value
+                )
+            )
+
+        self.dataChanged.emit(index, index)
+        return True
+
+    def add(self, row, parent=QModelIndex()):
+        self.beginInsertRows(parent, row, row - 1)
+
+        self._undo.push(
+            AddCommand(
+                self._data, row
+            )
+        )
+
+        self.endInsertRows()
+        self.layoutChanged.emit()
+
+    def delete(self, rows, parent=QModelIndex()):
+        self.beginRemoveRows(parent, rows[0], rows[-1])
+
+        self._undo.push(
+            DelCommand(
+                self._data, rows
+            )
+        )
+
+        self.endRemoveRows()
+        self.layoutChanged.emit()
+
+    def sort(self, _reverse, parent=QModelIndex()):
+        self.layoutAboutToBeChanged.emit()
+
+        self._undo.push(
+            SortCommand(
+                self._data, False
+            )
+        )
+
+        self.layoutAboutToBeChanged.emit()
+        self.layoutChanged.emit()
+
+    def undo(self):
+        self._undo.undo()
+        self.layoutChanged.emit()
+
+    def redo(self):
+        self._undo.redo()
+        self.layoutChanged.emit()
diff --git a/src/View/Stricklers/UndoCommand.py b/src/View/Stricklers/UndoCommand.py
new file mode 100644
index 00000000..df3f98e4
--- /dev/null
+++ b/src/View/Stricklers/UndoCommand.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+
+from copy import deepcopy
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+    QMessageBox, QUndoCommand, QUndoStack,
+)
+
+from Model.LateralContribution.LateralContribution import LateralContribution
+from Model.LateralContribution.LateralContributionList import LateralContributionList
+
+class SetNameCommand(QUndoCommand):
+    def __init__(self, data, index, new_value):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._index = index
+        self._old = self._data.get(self._index).name
+        self._new = new_value
+
+    def undo(self):
+        self._data.get(self._index).name = self._old
+
+    def redo(self):
+        self._data.get(self._index).name = self._new
+
+class SetCommentCommand(QUndoCommand):
+    def __init__(self, data, index, new_value):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._index = index
+        self._old = self._data.get(self._index).comment
+        self._new = new_value
+
+    def undo(self):
+        self._data.get(self._index).comment = self._old
+
+    def redo(self):
+        self._data.get(self._index).comment = self._new
+
+class SetMinorCommand(QUndoCommand):
+    def __init__(self, data, index, new_value):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._index = index
+        self._old = self._data.get(self._index).minor
+        self._new = new_value
+
+    def undo(self):
+        self._data.get(self._index).minor = self._old
+
+    def redo(self):
+        self._data.get(self._index).minor = self._new
+
+class SetMediumCommand(QUndoCommand):
+    def __init__(self, data, index, new_value):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._index = index
+        self._old = self._data.get(self._index).medium
+        self._new = new_value
+
+    def undo(self):
+        self._data.get(self._index).medium = self._old
+
+    def redo(self):
+        self._data.get(self._index).medium = self._new
+
+
+class AddCommand(QUndoCommand):
+    def __init__(self, data, index):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._index = index
+        self._new = None
+
+    def undo(self):
+        self._data.delete_i([self._index])
+
+    def redo(self):
+        if self._new is None:
+            self._new = self._data.add(self._index)
+        else:
+            self._data.insert(self._index, self._new)
+
+class DelCommand(QUndoCommand):
+    def __init__(self, data, rows):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._rows = rows
+
+        self._el = []
+        for row in rows:
+            self._el.append((row, self._data.get(row)))
+        self._el.sort()
+
+    def undo(self):
+        for row, el in self._el:
+            self._data.insert(row, el)
+
+    def redo(self):
+        self._data.delete_i(self._rows)
+
+class SortCommand(QUndoCommand):
+    def __init__(self, data, _reverse):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._reverse = _reverse
+
+        self._old = self._data.get_tab(self._tab)
+        self._indexes = None
+
+    def undo(self):
+        ll = self._data.get_tab(self._tab)
+        self._data.sort(
+            self._tab,
+            key=lambda x: self._indexes[ll.index(x)]
+        )
+
+    def redo(self):
+        self._data.sort(
+            self._tab,
+            reverse=self._reverse,
+            key=lambda x: x.name
+        )
+        if self._indexes is None:
+            self._indexes = list(
+                map(
+                    lambda p: self._old.index(p),
+                    self._data.get_tab(self._tab)
+                )
+            )
+            self._old = None
+
+class PasteCommand(QUndoCommand):
+    def __init__(self, data, row, el):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._row = row
+        self._el = deepcopy(el)
+        self._el.reverse()
+
+    def undo(self):
+        self._data.delete(self._el)
+
+    def redo(self):
+        for el in self._el:
+            self._data.insert(self._row, el)
diff --git a/src/View/Stricklers/Window.py b/src/View/Stricklers/Window.py
new file mode 100644
index 00000000..3f072dfb
--- /dev/null
+++ b/src/View/Stricklers/Window.py
@@ -0,0 +1,120 @@
+# -*- coding: utf-8 -*-
+
+from tools import trace, timer
+
+from View.ASubWindow import ASubMainWindow
+from View.ListedSubWindow import ListedSubWindow
+
+from PyQt5.QtGui import (
+    QKeySequence,
+)
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
+)
+
+from Model.Stricklers.Stricklers import Stricklers
+
+from View.Stricklers.UndoCommand import PasteCommand
+from View.Stricklers.Table import TableModel
+
+_translate = QCoreApplication.translate
+
+
+class StricklersWindow(ASubMainWindow, ListedSubWindow):
+    def __init__(self, title="Stricklers", study=None, parent=None):
+        title = title + " - " + study.name
+
+        super(StricklersWindow, self).__init__(
+            name=title, ui="Stricklers", parent=parent
+        )
+
+        self._study = study
+
+        self.setup_sc()
+        self.setup_table()
+        self.setup_connections()
+
+        self.ui.setWindowTitle(title)
+
+    def setup_sc(self):
+        self._undo_stack = QUndoStack()
+
+        self.undo_sc = QShortcut(QKeySequence.Undo, self)
+        self.redo_sc = QShortcut(QKeySequence.Redo, self)
+        self.copy_sc = QShortcut(QKeySequence.Copy, self)
+        self.paste_sc = QShortcut(QKeySequence.Paste, self)
+
+    def setup_table(self):
+        self._table = {}
+
+        for t in ["app", "study"]:
+            table = self.find(QTableView, f"tableView_{t}")
+            self._table[t] = TableModel(
+                data = self._study.river.striklers,
+                undo = self._undo_stack,
+            )
+            table.setModel(self._table[t])
+            table.setSelectionBehavior(QAbstractItemView.SelectRows)
+            table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+            table.setAlternatingRowColors(True)
+
+    def setup_connections(self):
+        self.find(QAction, "action_add").triggered.connect(self.add)
+        self.find(QAction, "action_del").triggered.connect(self.delete)
+        self.find(QAction, "action_sort").triggered.connect(self.sort)
+
+        self.undo_sc.activated.connect(self.undo)
+        self.redo_sc.activated.connect(self.redo)
+        self.copy_sc.activated.connect(self.copy)
+        self.paste_sc.activated.connect(self.paste)
+
+    def index_selected_rows(self):
+        table = self.find(QTableView, f"tableView_study")
+        return list(
+            set(
+                map(
+                    lambda i: i.row(),
+                    table.selectedIndexes()
+                )
+            )
+        )
+
+
+    def add(self):
+        rows = self.index_selected_rows()
+        if len(rows) == 0:
+            self._table['study'].add(0)
+        else:
+            self._table['study'].add(rows[0])
+
+    def delete(self):
+        rows = self.index_selected_rows()
+        if len(rows) == 0:
+            return
+
+        self._table['study'].delete(rows)
+
+    def sort(self):
+        self._table['study'].sort()
+
+    def copy(self):
+        print("TODO")
+
+    def paste(self):
+        print("TODO")
+
+    def undo(self):
+        self._table['study'].undo()
+
+    def redo(self):
+        self._table['study'].redo()
diff --git a/src/View/Stricklers/translate.py b/src/View/Stricklers/translate.py
new file mode 100644
index 00000000..4664e163
--- /dev/null
+++ b/src/View/Stricklers/translate.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from PyQt5.QtCore import QCoreApplication
+
+from Model.LateralContribution.LateralContributionTypes import (
+    NotDefined, LateralContrib, Rain, Evaporation,
+)
+
+_translate = QCoreApplication.translate
+
+table_headers = {
+    "name": _translate("LateralContribution", "Name"),
+    "minor": _translate("LateralContribution", "Minor bed"),
+    "medium": _translate("LateralContribution", "Medium bed"),
+    "comment": _translate("LateralContribution", "Comment"),
+}
diff --git a/src/View/ui/Stricklers.ui b/src/View/ui/Stricklers.ui
new file mode 100644
index 00000000..b6ec590b
--- /dev/null
+++ b/src/View/ui/Stricklers.ui
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>820</width>
+    <height>480</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QGridLayout" name="gridLayout_3">
+    <item row="0" column="0">
+     <widget class="QSplitter" name="splitter">
+      <property name="orientation">
+       <enum>Qt::Vertical</enum>
+      </property>
+      <widget class="QGroupBox" name="groupBox_2">
+       <property name="title">
+        <string>Study stricklers</string>
+       </property>
+       <layout class="QGridLayout" name="gridLayout">
+        <item row="0" column="0">
+         <widget class="QTableView" name="tableView_study"/>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QGroupBox" name="groupBox">
+       <property name="title">
+        <string>Application stricklers</string>
+       </property>
+       <layout class="QGridLayout" name="gridLayout_2">
+        <item row="0" column="0">
+         <widget class="QTableView" name="tableView_app"/>
+        </item>
+       </layout>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QToolBar" name="toolBar">
+   <property name="windowTitle">
+    <string>toolBar</string>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+   <addaction name="action_add"/>
+   <addaction name="action_del"/>
+   <addaction name="action_sort"/>
+  </widget>
+  <action name="action_add">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/gtk-add.png</normaloff>ressources/gtk-add.png</iconset>
+   </property>
+   <property name="text">
+    <string>Add</string>
+   </property>
+   <property name="toolTip">
+    <string>Add new stricklers</string>
+   </property>
+  </action>
+  <action name="action_del">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/gtk-remove.png</normaloff>ressources/gtk-remove.png</iconset>
+   </property>
+   <property name="text">
+    <string>Delete</string>
+   </property>
+   <property name="toolTip">
+    <string>Delete selected stricklers</string>
+   </property>
+  </action>
+  <action name="action_sort">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/gtk-sort-ascending.png</normaloff>ressources/gtk-sort-ascending.png</iconset>
+   </property>
+   <property name="text">
+    <string>Sort</string>
+   </property>
+   <property name="toolTip">
+    <string>Sort stricklers</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
-- 
GitLab