diff --git a/src/View/GeoTIFF/Edit/Window.py b/src/View/GeoTIFF/Edit/Window.py
new file mode 100644
index 00000000..f3ede26a
--- /dev/null
+++ b/src/View/GeoTIFF/Edit/Window.py
@@ -0,0 +1,102 @@
+# Window.py -- Pamhyr
+# Copyright (C) 2024-2025 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 .
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from Modules import Modules
+from View.Tools.PamhyrWindow import PamhyrWindow
+
+from PyQt5.QtWidgets import (
+ QLabel, QPlainTextEdit, QPushButton,
+ QCheckBox,
+)
+
+from View.GeoTIFF.Translate import GeoTIFFTranslate
+from View.GeoTIFF.UndoCommand import (
+ SetCommand
+)
+
+logger = logging.getLogger()
+
+
+class EditGeoTIFFWindow(PamhyrWindow):
+ _pamhyr_ui = "EditGeoTIFF"
+ _pamhyr_name = "Edit GeoTIFF"
+
+ def __init__(self, study=None, config=None, add_file=None,
+ trad=None, undo=None, parent=None):
+
+ name = trad[self._pamhyr_name] + " - " + study.name
+ super(EditGeoTIFFWindow, self).__init__(
+ title=name,
+ study=study,
+ config=config,
+ options=[],
+ parent=parent
+ )
+
+ self._geotiff = geotiff
+ self._hash_data.append(self._geotiff)
+
+ self._undo = undo
+
+ self.setup_values()
+ self.setup_connection()
+
+ def setup_values(self):
+ self.set_check_box("checkBox", self._geotiff.enabled)
+ self.set_line_edit_text("lineEdit_name", self._geotiff.name)
+ self.set_line_edit_text("lineEdit_path", self._geotiff.description)
+ self.set_plaintext_edit_text("plainTextEdit", self._geotiff.text)
+
+ if self._study.is_read_only():
+ self.set_check_box_enable("checkBox", False)
+ self.set_line_edit_enable("lineEdit_name", False)
+ self.set_line_edit_enable("lineEdit_path", False)
+ self.set_plaintext_edit_enable("plainTextEdit", False)
+
+ def setup_connection(self):
+ self.find(QPushButton, "pushButton_cancel")\
+ .clicked.connect(self.close)
+ self.find(QPushButton, "pushButton_ok")\
+ .clicked.connect(self.accept)
+
+ def accept(self):
+ if self._study.is_editable():
+ is_enabled = self.get_check_box("checkBox")
+ name = self.get_line_edit_text("lineEdit_name")
+ path = self.get_line_edit_text("lineEdit_path")
+ coord_bottom = self.get_plaintext_edit_text("plainTextEdit")
+ coord_top = self.get_plaintext_edit_text("plainTextEdit")
+ coord_left = self.get_plaintext_edit_text("plainTextEdit")
+ coord_right = self.get_plaintext_edit_text("plainTextEdit")
+
+ self._undo.push(
+ SetCommand(
+ self._geotiff, enabled=is_enabled,
+ name=name, description=description,
+ coordinates_bottom=coord_bottom,
+ coordinates_top=coord_top,
+ coordinates_left=coord_left,
+ coordinates_right=coord_right,
+ )
+ )
+
+ self._propagate_update(key=Modules.GEOTIFF)
+
+ self.close()
diff --git a/src/View/GeoTIFF/List.py b/src/View/GeoTIFF/List.py
new file mode 100644
index 00000000..8bb158aa
--- /dev/null
+++ b/src/View/GeoTIFF/List.py
@@ -0,0 +1,96 @@
+# List.py -- Pamhyr
+# Copyright (C) 2024-2025 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 .
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from functools import reduce
+from tools import trace, timer
+
+from PyQt5.QtCore import (
+ Qt, QVariant,
+)
+
+from PyQt5.QtGui import (
+ QColor, QBrush,
+)
+
+from View.Tools.PamhyrList import PamhyrListModel
+from View.GeoTIFF.UndoCommand import (
+ AddCommand, DelCommand
+)
+
+logger = logging.getLogger()
+
+
+class ListModel(PamhyrListModel):
+ def get_true_data_row(self, row):
+ el = self._data.get(row)
+
+ return next(
+ map(
+ lambda e: e[0],
+ filter(
+ lambda e: e[1] == el,
+ enumerate(self._data._lst)
+ )
+ ), 0
+ )
+
+ def data(self, index, role):
+ row = index.row()
+ column = index.column()
+
+ file = self._data.files[row]
+
+ if role == Qt.ForegroundRole:
+ color = Qt.gray
+
+ if file.is_enabled():
+ color = QColor("black")
+ else:
+ color = QColor("grey")
+
+ return QBrush(color)
+
+ if role == Qt.ItemDataRole.DisplayRole:
+ text = f"{file.name}: '{file.path}'"
+
+ if not file.is_enabled():
+ text += " (disabled)"
+
+ return text
+
+ return QVariant()
+
+ def add(self, row):
+ row = self.get_true_data_row(row)
+
+ self._undo.push(
+ AddCommand(
+ self._data, row
+ )
+ )
+ self.update()
+
+ def delete(self, row):
+ self._undo.push(
+ DelCommand(
+ self._data, self._data.files[row]
+ )
+ )
+ self.update()
diff --git a/src/View/GeoTIFF/Translate.py b/src/View/GeoTIFF/Translate.py
new file mode 100644
index 00000000..fe39460f
--- /dev/null
+++ b/src/View/GeoTIFF/Translate.py
@@ -0,0 +1,35 @@
+# Translate.py -- Pamhyr
+# Copyright (C) 2024-2025 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 .
+
+# -*- coding: utf-8 -*-
+
+from PyQt5.QtCore import QCoreApplication
+from View.Translate import MainTranslate
+
+_translate = QCoreApplication.translate
+
+
+class GeoTIFFTranslate(MainTranslate):
+ def __init__(self):
+ super(AddFileTranslate, self).__init__()
+
+ self._dict["GeoTIFF files"] = _translate(
+ "GeoTIFF", "GeoTIFF files"
+ )
+
+ self._dict["Edit additional file"] = _translate(
+ "GeoTIFF", "Edit GeoTIFF file"
+ )
diff --git a/src/View/GeoTIFF/UndoCommand.py b/src/View/GeoTIFF/UndoCommand.py
new file mode 100644
index 00000000..9e26c1be
--- /dev/null
+++ b/src/View/GeoTIFF/UndoCommand.py
@@ -0,0 +1,81 @@
+# UndoCommand.py -- Pamhyr
+# Copyright (C) 2024-2025 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 .
+
+# -*- coding: utf-8 -*-
+
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+ QMessageBox, QUndoCommand, QUndoStack,
+)
+
+
+class SetCommand(QUndoCommand):
+ def __init__(self, geotiff, **kwargs):
+ QUndoCommand.__init__(self)
+
+ self._geotiff = geotiff
+ self._new = kwargs
+ self._old = None
+
+ def undo(self):
+ f = self._geotiff
+
+ for key in self._old:
+ f[key] = self._old[key]
+
+ def redo(self):
+ f = self._geotiff
+
+ if self._old is None:
+ self._old = {}
+ for key in self._new:
+ self._old[key] = f[key]
+
+ for key in self._new:
+ f[key] = self._new[key]
+
+
+class AddCommand(QUndoCommand):
+ def __init__(self, files, row):
+ QUndoCommand.__init__(self)
+
+ self._files = files
+ self._row = row
+ self._new = None
+
+ def undo(self):
+ self._new.set_as_deleted()
+
+ def redo(self):
+ if self._new is None:
+ self._new = self._files.new(self._row)
+ else:
+ self._new.set_as_not_deleted()
+
+
+class DelCommand(QUndoCommand):
+ def __init__(self, files, line):
+ QUndoCommand.__init__(self)
+
+ self._files = files
+ self._line = line
+
+ def undo(self):
+ self._line.set_as_not_deleted()
+
+ def redo(self):
+ self._line.set_as_deleted()
diff --git a/src/View/GeoTIFF/Window.py b/src/View/GeoTIFF/Window.py
new file mode 100644
index 00000000..98d1d058
--- /dev/null
+++ b/src/View/GeoTIFF/Window.py
@@ -0,0 +1,120 @@
+# Window.py -- Pamhyr
+# Copyright (C) 2024-2025 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 .
+
+# -*- coding: utf-8 -*-
+
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+ QAction, QListView,
+)
+
+from View.Tools.PamhyrWindow import PamhyrWindow
+
+from View.GeoTIFF.List import ListModel
+from View.GeoTIFF.Translate import GeoTIFFTranslate
+from View.GeoTIFF.Edit.Window import EditGeoTIFFWindow
+
+
+class GeoTIFFListWindow(PamhyrWindow):
+ _pamhyr_ui = "GeoTIFFList"
+ _pamhyr_name = "GeoTIFF files"
+
+ def __init__(self, study=None, config=None,
+ parent=None):
+ trad = GeoTIFFTranslate()
+ name = trad[self._pamhyr_name] + " - " + study.name
+
+ super(GeoTIFFListWindow, self).__init__(
+ title=name,
+ study=study,
+ config=config,
+ trad=trad,
+ options=[],
+ parent=parent
+ )
+
+ self.setup_list()
+ self.setup_connections()
+
+ def setup_list(self):
+ lst = self.find(QListView, f"listView")
+ self._list = ListModel(
+ list_view=lst,
+ data=self._study.river.geotiff,
+ undo=self._undo_stack,
+ trad=self._trad,
+ )
+
+ def setup_connections(self):
+ if self._study.is_editable():
+ self.find(QAction, "action_add").triggered.connect(self.add)
+ self.find(QAction, "action_delete").triggered.connect(self.delete)
+
+ self.find(QAction, "action_edit").triggered.connect(self.edit)
+
+ def update(self):
+ self._list.update()
+
+ def selected_rows(self):
+ lst = self.find(QListView, f"listView")
+ return list(map(lambda i: i.row(), lst.selectedIndexes()))
+
+ def add(self):
+ rows = self.selected_rows()
+ if len(rows) > 0:
+ row = rows[0]
+ else:
+ row = 0
+
+ self._list.add(row)
+
+ def delete(self):
+ rows = self.selected_rows()
+ if len(rows) == 0:
+ return
+
+ self._list.delete(rows[0])
+
+ def edit(self):
+ rows = self.selected_rows()
+
+ for row in rows:
+ add_file = self._study.river.geotiff.files[row]
+
+ if self.sub_window_exists(
+ EditGeoTIFFWindow,
+ data=[self._study, self._config, add_file]
+ ):
+ continue
+
+ win = EditGeoTIFFWindow(
+ study=self._study,
+ config=self._config,
+ add_file=add_file,
+ trad=self._trad,
+ undo=self._undo_stack,
+ parent=self,
+ )
+ win.show()
+
+ def _undo(self):
+ self._undo_stack.undo()
+ self.update()
+
+ def _redo(self):
+ self._undo_stack.redo()
+ self.update()