diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/HDFTrajectoryConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/HDFTrajectoryConfigurator.py
index 48a1357d9..b58d8b198 100644
--- a/MDANSE/Src/MDANSE/Framework/Configurators/HDFTrajectoryConfigurator.py
+++ b/MDANSE/Src/MDANSE/Framework/Configurators/HDFTrajectoryConfigurator.py
@@ -48,8 +48,11 @@ def configure(self, value):
self._original_input = value
InputFileConfigurator.configure(self, value)
-
- inputTraj = IInputData.create("HDFTrajectoryInputData", self["value"])
+ try:
+ inputTraj = IInputData.create("HDFTrajectoryInputData", self["value"])
+ except KeyError:
+ self.error_status = f"Could not use {value} as input trajectory"
+ return
self["hdf_trajectory"] = inputTraj
diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/OutputFilesConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/OutputFilesConfigurator.py
index 521ed3499..bbb047b0c 100644
--- a/MDANSE/Src/MDANSE/Framework/Configurators/OutputFilesConfigurator.py
+++ b/MDANSE/Src/MDANSE/Framework/Configurators/OutputFilesConfigurator.py
@@ -52,6 +52,7 @@ def __init__(self, name, formats=None, **kwargs):
self._formats = (
formats if formats is not None else OutputFilesConfigurator._default[-1]
)
+ self._forbidden_files = []
def configure(self, value):
"""
@@ -98,7 +99,16 @@ def configure(self, value):
self["root"] = root
self["formats"] = formats
- self["files"] = ["%s%s" % (root, IFormat.create(f).extension) for f in formats]
+ self["files"] = []
+ for extension in [IFormat.create(f).extension for f in formats]:
+ if extension in root[-len(extension) :]:
+ self["files"].append(root)
+ else:
+ self["files"].append(root + extension)
+ for file in self["files"]:
+ if os.path.abspath(file) in self._forbidden_files:
+ self.error_status = f"File {file} is either open or being written into. Please pick another name."
+ return
self["value"] = self["files"]
self["log_level"] = logs
diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/OutputStructureConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/OutputStructureConfigurator.py
index 2b81e4f66..e7855f563 100644
--- a/MDANSE/Src/MDANSE/Framework/Configurators/OutputStructureConfigurator.py
+++ b/MDANSE/Src/MDANSE/Framework/Configurators/OutputStructureConfigurator.py
@@ -51,6 +51,7 @@ def __init__(self, name, formats=None, **kwargs):
IConfigurator.__init__(self, name, **kwargs)
self._formats = [fmt for fmt in ioformats if ioformats[fmt].can_write]
+ self._forbidden_files = []
def configure(self, value):
"""
@@ -87,6 +88,9 @@ def configure(self, value):
self["root"] = root
self["format"] = format
self["file"] = root
+ if os.path.abspath(self["file"]) in self._forbidden_files:
+ self.error_status = f"File {self['file']} is either open or being written into. Please pick another name."
+ return
self["log_level"] = logs
if logs == "no logs":
self["write_logs"] = False
diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/OutputTrajectoryConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/OutputTrajectoryConfigurator.py
index baf628082..ab575e3c6 100644
--- a/MDANSE/Src/MDANSE/Framework/Configurators/OutputTrajectoryConfigurator.py
+++ b/MDANSE/Src/MDANSE/Framework/Configurators/OutputTrajectoryConfigurator.py
@@ -54,6 +54,7 @@ def __init__(self, name, format=None, **kwargs):
self._format = "MDTFormat"
self._dtype = np.float64
self._compression = "none"
+ self._forbidden_files = []
def configure(self, value: tuple):
self._original_input = value
@@ -95,6 +96,9 @@ def configure(self, value: tuple):
if not self["extension"] in temp_name[-5:]: # capture most extension lengths
temp_name += self["extension"]
self["file"] = temp_name
+ if os.path.abspath(self["file"]) in self._forbidden_files:
+ self.error_status = f"File {self['file']} is either open or being written into. Please pick another name."
+ return
self["dtype"] = self._dtype
self["compression"] = self._compression
self["log_level"] = logs
diff --git a/MDANSE/Src/MDANSE/Framework/Converters/DCD.py b/MDANSE/Src/MDANSE/Framework/Converters/DCD.py
index 648123e9e..63b20b7a9 100644
--- a/MDANSE/Src/MDANSE/Framework/Converters/DCD.py
+++ b/MDANSE/Src/MDANSE/Framework/Converters/DCD.py
@@ -275,6 +275,8 @@ class DCD(Converter):
Converts a DCD trajectory to a HDF trajectory.
"""
+ label = "DCD"
+
settings = collections.OrderedDict()
settings["pdb_file"] = (
"InputFileConfigurator",
diff --git a/MDANSE/Src/MDANSE/Framework/Converters/VASP.py b/MDANSE/Src/MDANSE/Framework/Converters/VASP.py
index 2436db2be..4d15949b2 100644
--- a/MDANSE/Src/MDANSE/Framework/Converters/VASP.py
+++ b/MDANSE/Src/MDANSE/Framework/Converters/VASP.py
@@ -34,7 +34,7 @@ class VASP(Converter):
Converts a VASP trajectory to a HDF trajectory.
"""
- label = "VASP (>=5)"
+ label = "VASP v5"
settings = collections.OrderedDict()
settings["xdatcar_file"] = (
diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/Infrared.py b/MDANSE/Src/MDANSE/Framework/Jobs/Infrared.py
index 52d6eaecb..ecd2a73d3 100644
--- a/MDANSE/Src/MDANSE/Framework/Jobs/Infrared.py
+++ b/MDANSE/Src/MDANSE/Framework/Jobs/Infrared.py
@@ -26,7 +26,7 @@ class Infrared(IJob):
enabled = True
- label = "Dipole AutoCorrelation Function"
+ label = "Infrared Spectrum"
category = (
"Analysis",
diff --git a/MDANSE/Src/MDANSE/Framework/Session/CurrentSession.py b/MDANSE/Src/MDANSE/Framework/Session/CurrentSession.py
index f757a6111..844b7eb8a 100644
--- a/MDANSE/Src/MDANSE/Framework/Session/CurrentSession.py
+++ b/MDANSE/Src/MDANSE/Framework/Session/CurrentSession.py
@@ -35,7 +35,7 @@ def load_session(self, fname: str):
class SessionSettings(AbstractSession):
def __init__(self):
super().__init__()
- self.main_path = "."
+ self.main_path = os.path.abspath(".")
def create_structured_project(self):
self.relative_paths = {
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/AseInputFileWidget.py b/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/AseInputFileWidget.py
index 6033dc98f..147b7283c 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/AseInputFileWidget.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/AseInputFileWidget.py
@@ -13,6 +13,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
+import os
+
from qtpy.QtWidgets import QLineEdit, QPushButton, QFileDialog, QComboBox
from qtpy.QtCore import Slot
from ase.io.formats import filetype
@@ -37,10 +39,10 @@ def __init__(self, *args, **kwargs):
parent = kwargs.get("parent", None)
self.default_path = parent.default_path
except KeyError:
- self.default_path = "."
+ self.default_path = os.path.abspath(".")
LOG.error("KeyError in InputFileWidget - can't get default path.")
except AttributeError:
- self.default_path = "."
+ self.default_path = os.path.abspath(".")
LOG.error("AttributeError in InputFileWidget - can't get default path.")
default_value = kwargs.get("default", "")
if self._tooltip:
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/InputFileWidget.py b/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/InputFileWidget.py
index 6851fa577..d2d113efc 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/InputFileWidget.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/InputFileWidget.py
@@ -33,29 +33,19 @@ def __init__(self, *args, file_dialog=QFileDialog.getOpenFileName, **kwargs):
else:
default_value = ""
parent = kwargs.get("parent", None)
+ self._parent = parent
if parent is not None:
self._job_name = parent._job_name
self._settings = parent._settings
try:
- paths_group = self._settings.group("paths")
- try:
- self.default_path = paths_group.get(self._job_name)
- except KeyError:
- paths_group.add(
- self._job_name,
- ".",
- f"The filesystem path recently used by {self._job_name}",
- )
- self.default_path = "."
+ parent = kwargs.get("parent", None)
+ self.default_path = parent._default_path
+ except KeyError:
+ self.default_path = os.path.abspath(".")
+ LOG.error("KeyError in OutputFilesWidget - can't get default path.")
except AttributeError:
- try:
- self.default_path = parent.default_path
- except KeyError:
- self.default_path = "."
- LOG.error("KeyError in InputFileWidget - can't get default path.")
- except AttributeError:
- self.default_path = "."
- LOG.error("AttributeError in InputFileWidget - can't get default path.")
+ self.default_path = os.path.abspath(".")
+ LOG.error("AttributeError in OutputFilesWidget - can't get default path.")
default_value = kwargs.get("default", "")
if self._tooltip:
self._tooltip_text = self._tooltip
@@ -95,15 +85,10 @@ def valueFromDialog(self):
This will start a FileDialog, take the resulting path,
and emit a signal to update the value show by the GUI.
"""
- paths_group = self._settings.group("paths")
- try:
- self.default_path = paths_group.get(self._job_name)
- except:
- LOG.warning(f"session.get_path failed for {self._job_name}")
new_value = self._file_dialog(
self.parent(), # the parent of the dialog
"Load file", # the label of the window
- self.default_path, # the initial search path
+ self._parent._default_path, # the initial search path
self._qt_file_association, # text string specifying the file name filter.
)
if new_value is not None and new_value[0]:
@@ -113,7 +98,8 @@ def valueFromDialog(self):
LOG.info(
f"Settings path of {self._job_name} to {os.path.split(new_value[0])[0]}"
)
- paths_group.set(self._job_name, os.path.split(new_value[0])[0])
+ if self._parent is not None:
+ self._parent._default_path = os.path.split(new_value[0])[0]
except:
LOG.error(
f"session.set_path failed for {self._job_name}, {os.path.split(new_value[0])[0]}"
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/OutputFilesWidget.py b/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/OutputFilesWidget.py
index d74d9b5ee..be45702c8 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/OutputFilesWidget.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/OutputFilesWidget.py
@@ -13,8 +13,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import glob
-import itertools
import os
import os.path
@@ -35,18 +33,30 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, layout_type="QGridLayout", **kwargs)
default_value = self._configurator.default
try:
- parent = kwargs.get("parent", None)
- self.default_path = parent.default_path
+ self._parent = kwargs.get("parent", None)
+ self.default_path = self._parent._default_path
except KeyError:
- self.default_path = "."
+ self.default_path = os.path.abspath(".")
LOG.error("KeyError in OutputFilesWidget - can't get default path.")
except AttributeError:
- self.default_path = "."
+ self.default_path = os.path.abspath(".")
LOG.error("AttributeError in OutputFilesWidget - can't get default path.")
+ else:
+ self._session = self._parent._parent_tab._session
+ try:
+ self._parent = kwargs.get("parent", None)
+ jobname = str(self._parent._job_instance.label).replace(" ", "")
+ guess_name = os.path.join(self.default_path, jobname + "_result1")
+ except:
+ guess_name = default_value[0]
+ LOG.error("It was not possible to get the job name from the parent")
+ while os.path.exists(guess_name + ".mda"):
+ prefix, number = guess_name.split("_result")
+ guess_name = prefix + "_result" + str(1 + int(number))
self.file_association = "Output file name (*)"
self._value = default_value
- self._field = QLineEdit(default_value[0], self._base)
- self._field.setPlaceholderText(default_value[0])
+ self._field = QLineEdit(guess_name, self._base)
+ self._field.setPlaceholderText(guess_name)
self.type_box = CheckableComboBox(self._base)
self.type_box.addItems(self._configurator.formats)
self.type_box.set_default("MDAFormat")
@@ -92,6 +102,10 @@ def file_dialog(self):
This will start a FileDialog, take the resulting path,
and emit a signal to update the value show by the GUI.
"""
+ try:
+ self.default_path = self._parent._default_path
+ except AttributeError:
+ self.default_path = os.path.abspath(".")
new_value = QFileDialog.getSaveFileName(
self._base, # the parent of the dialog
"Save files", # the label of the window
@@ -102,28 +116,8 @@ def file_dialog(self):
self._field.setText(new_value[0])
self.updateValue()
- @staticmethod
- def _get_unique_filename(directory, basename):
- filesInDirectory = [
- os.path.join(directory, e)
- for e in itertools.chain(
- glob.iglob(os.path.join(directory, "*")),
- glob.iglob(os.path.join(directory, ".*")),
- )
- if os.path.isfile(os.path.join(directory, e))
- ]
- basenames = [os.path.splitext(f)[0] for f in filesInDirectory]
-
- initialPath = path = os.path.join(directory, basename)
- comp = 1
- while True:
- if path in basenames:
- path = "%s(%d)" % (initialPath, comp)
- comp += 1
- continue
- return path
-
def get_widget_value(self):
+ self._configurator._forbidden_files = self._session.reserved_filenames()
filename = self._field.text()
if len(filename) < 1:
filename = self._default_value[0]
@@ -131,15 +125,4 @@ def get_widget_value(self):
formats = self.type_box.checked_values()
log_level = self.logs_combo.currentText()
- return (filename, formats, log_level)
-
- def set_data(self, datakey):
- basename = "%s_%s" % (
- os.path.splitext(os.path.basename(datakey))[0],
- self._parent.type,
- )
- trajectoryDir = os.path.dirname(datakey)
-
- path = OutputFilesWidget._get_unique_filename(trajectoryDir, basename)
-
- self._filename.SetValue(path)
+ return (os.path.abspath(filename), formats, log_level)
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/OutputStructureWidget.py b/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/OutputStructureWidget.py
index 406af50a5..00e3e6db7 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/OutputStructureWidget.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/OutputStructureWidget.py
@@ -13,8 +13,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import glob
-import itertools
import os
import os.path
@@ -37,17 +35,24 @@ def __init__(self, *args, **kwargs):
parent = kwargs.get("parent", None)
self.default_path = parent.default_path
except KeyError:
- self.default_path = "."
+ self.default_path = os.path.abspath(".")
LOG.error("KeyError in OutputTrajectoryWidget - can't get default path.")
except AttributeError:
- self.default_path = "."
+ self.default_path = os.path.abspath(".")
LOG.error(
"AttributeError in OutputTrajectoryWidget - can't get default path."
)
+ try:
+ parent = kwargs.get("parent", None)
+ jobname = str(parent._job_instance.label).replace(" ", "")
+ guess_name = os.path.join(self.default_path, "POSCAR")
+ except:
+ guess_name = default_value[0]
+ LOG.error("It was not possible to get the job name from the parent")
self.file_association = "Output file name (*)"
self._value = default_value
- self._field = QLineEdit(default_value[0], self._base)
- self._field.setPlaceholderText(default_value[0])
+ self._field = QLineEdit(guess_name, self._base)
+ self._field.setPlaceholderText(guess_name)
self.format_box = QComboBox(self._base)
self.format_box.addItems(self._configurator._formats)
self.format_box.setCurrentText(default_value[1])
@@ -108,31 +113,10 @@ def file_dialog(self):
self._field.setText(new_value[0])
self.updateValue()
- @staticmethod
- def _get_unique_filename(directory, basename):
- filesInDirectory = [
- os.path.join(directory, e)
- for e in itertools.chain(
- glob.iglob(os.path.join(directory, "*")),
- glob.iglob(os.path.join(directory, ".*")),
- )
- if os.path.isfile(os.path.join(directory, e))
- ]
- basenames = [os.path.splitext(f)[0] for f in filesInDirectory]
-
- initialPath = path = os.path.join(directory, basename)
- comp = 1
- while True:
- if path in basenames:
- path = "%s(%d)" % (initialPath, comp)
- comp += 1
- continue
- return path
-
def get_widget_value(self):
filename = self._field.text()
if len(filename) < 1:
filename = self._default_value[0]
format = self.format_box.currentText()
log_level = self.logs_combo.currentText()
- return (filename, format, log_level)
+ return (os.path.abspath(filename), format, log_level)
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/OutputTrajectoryWidget.py b/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/OutputTrajectoryWidget.py
index 3f3d722e4..442ea1661 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/OutputTrajectoryWidget.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/InputWidgets/OutputTrajectoryWidget.py
@@ -13,8 +13,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import glob
-import itertools
import os
import os.path
@@ -44,20 +42,32 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, layout_type="QGridLayout", **kwargs)
default_value = self._configurator.default
try:
- parent = kwargs.get("parent", None)
- self.default_path = parent.default_path
+ self._parent = kwargs.get("parent", None)
+ self.default_path = self._parent._default_path
except KeyError:
- self.default_path = "."
+ self.default_path = os.path.abspath(".")
LOG.error("KeyError in OutputTrajectoryWidget - can't get default path.")
except AttributeError:
- self.default_path = "."
+ self.default_path = os.path.abspath(".")
LOG.error(
"AttributeError in OutputTrajectoryWidget - can't get default path."
)
+ else:
+ self._session = self._parent._parent_tab._session
+ try:
+ self._parent = kwargs.get("parent", None)
+ jobname = str(self._parent._job_instance.label).replace(" ", "")
+ guess_name = os.path.join(self.default_path, jobname + "_trajectory1")
+ except:
+ guess_name = default_value[0]
+ LOG.error("It was not possible to get the job name from the parent")
+ while os.path.exists(guess_name + ".mdt"):
+ prefix, number = guess_name.split("_trajectory")
+ guess_name = prefix + "_trajectory" + str(1 + int(number))
self.file_association = "MDT trajectory (*.mdt)"
self._value = default_value
- self._field = QLineEdit(default_value[0], self._base)
- self._field.setPlaceholderText(default_value[0])
+ self._field = QLineEdit(guess_name, self._base)
+ self._field.setPlaceholderText(guess_name)
self.dtype_box = QComboBox(self._base)
self.dtype_box.addItems(["float16", "float32", "float64"])
self.dtype_box.setCurrentText("float64")
@@ -113,6 +123,7 @@ def file_dialog(self):
This will start a FileDialog, take the resulting path,
and emit a signal to update the value show by the GUI.
"""
+ self.default_path = self._parent._default_path
new_value = QFileDialog.getSaveFileName(
self._base, # the parent of the dialog
"Save file", # the label of the window
@@ -123,31 +134,13 @@ def file_dialog(self):
self._field.setText(new_value[0])
self.updateValue()
- @staticmethod
- def _get_unique_filename(directory, basename):
- filesInDirectory = [
- os.path.join(directory, e)
- for e in itertools.chain(
- glob.iglob(os.path.join(directory, "*")),
- glob.iglob(os.path.join(directory, ".*")),
- )
- if os.path.isfile(os.path.join(directory, e))
- ]
- basenames = [os.path.splitext(f)[0] for f in filesInDirectory]
-
- initialPath = path = os.path.join(directory, basename)
- comp = 1
- while True:
- if path in basenames:
- path = "%s(%d)" % (initialPath, comp)
- comp += 1
- continue
- return path
-
def get_widget_value(self):
+ self._configurator._forbidden_files = self._session.reserved_filenames()
filename = self._field.text()
if len(filename) < 1:
filename = self._default_value[0]
+ else:
+ self._parent.set_trajectory(os.path.abspath(filename))
dtype = dtype_lookup[self.dtype_box.currentText()]
compression = self.compression_box.currentText()
logs = self.logs_combo.currentText()
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Session/LocalSession.py b/MDANSE_GUI/Src/MDANSE_GUI/Session/LocalSession.py
index 72bfc2542..9d62f94dd 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/Session/LocalSession.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/Session/LocalSession.py
@@ -77,7 +77,7 @@ def get_parameter(self, key: str) -> str:
return value
def get_path(self, key: str) -> str:
- value = self._paths.get(key, ".")
+ value = self._paths.get(key, os.path.abspath("."))
return value
def set_path(self, key: str, value: str):
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Session/StructuredSession.py b/MDANSE_GUI/Src/MDANSE_GUI/Session/StructuredSession.py
index 29044ef8e..7887091f9 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/Session/StructuredSession.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/Session/StructuredSession.py
@@ -15,7 +15,7 @@
#
import os
-from typing import Dict
+from typing import Dict, List
from qtpy.QtCore import QObject, Signal, Slot, Qt, QModelIndex
from qtpy.QtGui import QStandardItem, QStandardItemModel
@@ -25,7 +25,6 @@
from MDANSE import PLATFORM
from MDANSE.MLogging import LOG
-from MDANSE.Framework.Units import measure, unit_lookup
class UserSettingsModel(QStandardItemModel):
@@ -38,7 +37,7 @@ def __init__(self, *args, settings_filename: str = "", **kwargs):
self._settings = SettingsFile(settings_filename)
self._settings.load_from_file()
else:
- LOG.warning(f"Called UserSettingsModel without settings_filename")
+ LOG.warning("Called UserSettingsModel without settings_filename")
return
self._entries_present = {}
self._groups_present = {}
@@ -51,7 +50,7 @@ def __init__(self, *args, settings_filename: str = "", **kwargs):
# self.scan_model()
def refresh(self):
- self._settings.load_from_file()
+ # self._settings.load_from_file()
self.populate_model()
return
@@ -166,6 +165,7 @@ def modify_item(
@Slot("QStandardItem*")
def on_value_changed(self, item: "QStandardItem"):
+ item_key = item.text()
index = item.index()
column = index.column()
row = index.row()
@@ -202,7 +202,7 @@ def add(self, varname: str, value: str, comment: str):
self._comments[varname] = comment
def set(self, varname: str, value: str):
- if not varname in self._settings:
+ if varname not in self._settings:
LOG.warning(
f"Group {self._name} has no entry {varname}. Add it first using add()."
)
@@ -211,7 +211,7 @@ def set(self, varname: str, value: str):
return True
def set_comment(self, varname: str, value: str):
- if not varname in self._settings:
+ if varname not in self._settings:
LOG.warning(
f"Group {self._name} has no entry {varname}. Add it first using add()."
)
@@ -353,6 +353,7 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._models = {}
self._configs = {}
+ self._reserved_filenames = []
self._state = None
self._main_config_name = "mdanse_general_settings"
self._filename = kwargs.get("filename", self._main_config_name)
@@ -374,6 +375,22 @@ def load(self, fname: str = None):
"""Included for compatibility with LocalSession only.
Now each component loads its own config separately."""
+ def reserved_filenames(self) -> List[str]:
+ return self._reserved_filenames
+
+ @Slot(str)
+ def protect_filename(self, some_filename: str):
+ new_filename = os.path.abspath(some_filename)
+ if new_filename not in self._reserved_filenames:
+ self._reserved_filenames.append(os.path.abspath(new_filename))
+
+ @Slot(str)
+ def free_filename(self, some_filename: str):
+ filename = os.path.abspath(some_filename)
+ if filename in self._reserved_filenames:
+ index = self._reserved_filenames.index(filename)
+ self._reserved_filenames.pop(index)
+
def main_settings(self):
return self._configs[self._main_config_name]
@@ -438,7 +455,7 @@ def populate_defaults(self):
def get_path(self, key: str) -> str:
settings = self._configs[self._main_config_name]
group = settings["paths"]
- value = group.get(key, ".")
+ value = group.get(key, os.path.abspath("."))
return value
def set_path(self, key: str, value: str):
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/TabbedWindow.py b/MDANSE_GUI/Src/MDANSE_GUI/TabbedWindow.py
index dcbeac614..9bd06b89b 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/TabbedWindow.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/TabbedWindow.py
@@ -306,6 +306,9 @@ def createConverterViewer(self):
job_tab.set_job_starter(self._job_holder)
self.tabs.addTab(job_tab._core, name)
self._tabs[name] = job_tab
+ self.tabs.tabBar().tabBarClicked.connect(
+ job_tab.update_action_on_tab_activation
+ )
def createActionsViewer(self):
name = "Actions"
@@ -319,7 +322,8 @@ def createActionsViewer(self):
instrument_model=self._instrument_model,
)
job_tab.set_job_starter(self._job_holder)
- self.tabs.addTab(job_tab._core, name)
+ jobtab_index = self.tabs.addTab(job_tab._core, name)
+ job_tab.set_own_index(jobtab_index)
self._tabs[name] = job_tab
self.tabs.tabBar().tabBarClicked.connect(
job_tab.update_action_on_tab_activation
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/ConverterTab.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/ConverterTab.py
index 90d435b11..ac5e1b10e 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/ConverterTab.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/ConverterTab.py
@@ -55,6 +55,10 @@ def set_job_starter(self, job_starter):
def set_current_trajectory(self, new_name: str):
self._current_trajectory = new_name
+ @Slot()
+ def update_action_on_tab_activation(self):
+ self.action.test_file_outputs()
+
def grouped_settings(self):
results = super().grouped_settings()
results += [
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/GeneralTab.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/GeneralTab.py
index 79a89e5df..300a69085 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/GeneralTab.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/GeneralTab.py
@@ -13,8 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-
-
+import os
from typing import Dict, Tuple
from qtpy.QtCore import QObject, Slot, Signal, QMessageLogger
@@ -95,7 +94,7 @@ def grouped_settings(self):
"""
group1 = [
"Generic settings", # name of the group of settings
- {"path": "."}, # a dictionary of settings
+ {"path": os.path.abspath(".")}, # a dictionary of settings
{
"path": "The path last used by this GUI element."
}, # a dictionary of comments
@@ -167,9 +166,11 @@ def get_path(self, path_key: str):
path = paths_group.get(path_key)
except KeyError:
paths_group.add(
- path_key, ".", f"Filesystem path recently used by {path_key}"
+ path_key,
+ os.path.abspath("."),
+ f"Filesystem path recently used by {path_key}",
)
- path = "."
+ path = os.path.abspath(".")
return path
def set_path(self, path_key: str, path_value: str):
@@ -178,6 +179,7 @@ def set_path(self, path_key: str, path_value: str):
paths_group.add(
path_key, path_value, f"Filesystem path recently used by {path_key}"
)
+ self._session.save()
@Slot()
def save_state(self):
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/JobTab.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/JobTab.py
index 1d2ead18e..8f6e86ec5 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/JobTab.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/JobTab.py
@@ -13,6 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
+import os
from functools import partial
from qtpy.QtCore import Slot
from qtpy.QtWidgets import QWidget, QComboBox, QLabel
@@ -47,6 +48,7 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._current_trajectory = ""
self._job_starter = None
+ self._own_index = -1
self._instrument_index = -1
self._trajectory_combo = QComboBox()
self._trajectory_combo.setEditable(False)
@@ -65,6 +67,9 @@ def __init__(self, *args, **kwargs):
self.action._parent_tab = self
self._visualiser._parent_tab = self
+ def set_own_index(self, index: int):
+ self._own_index = index
+
def set_job_starter(self, job_starter):
self._job_starter = job_starter
self.action.new_thread_objects.connect(self._job_starter.startProcess)
@@ -91,7 +96,7 @@ def set_current_trajectory(self, index: int) -> None:
if traj_model.rowCount() < 1:
# the combobox changed and there are no trajectories, they
# were probably deleted lets clear the action widgets
- self.action.set_trajectory(path=None, trajectory=None)
+ self.action.set_trajectory(trajectory=None)
self.action.clear_panel()
return
@@ -102,7 +107,7 @@ def set_current_trajectory(self, index: int) -> None:
# The combobox was changed we need to update the action
# widgets with the new trajectory
self.action.set_trajectory(
- path=None, trajectory=traj_model._nodes[node_number][0]
+ trajectory=os.path.abspath(traj_model._nodes[node_number][0])
)
current_item = self._core.current_item()
if current_item is not None:
@@ -128,8 +133,11 @@ def update_action_after_instrument_change(self, index: int):
return
self._needs_updating = True
- @Slot()
- def update_action_on_tab_activation(self):
+ @Slot(int)
+ def update_action_on_tab_activation(self, current_index: int):
+ if current_index != self._own_index:
+ return
+ self.action.test_file_outputs()
if self._needs_updating:
current_item = self._core.current_item()
if current_item is not None:
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Models/JobHolder.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Models/JobHolder.py
index 36d6ebd43..0c9158b72 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Models/JobHolder.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Models/JobHolder.py
@@ -181,6 +181,21 @@ def on_finished(self, success: bool):
self._current_state.fail()
self.update_fields()
+ def expected_output(self) -> str:
+ try:
+ len(self._parameters["output_files"][1])
+ except TypeError: # job is a converter
+ file_name = self._parameters["output_files"][0]
+ if ".mdt" not in file_name[-5:]:
+ file_name += ".mdt"
+ return file_name
+ else: # job is an analysis
+ if "MDAFormat" in self._parameters["output_files"][1]:
+ file_name = self._parameters["output_files"][0]
+ if ".mda" not in file_name[-5:]:
+ file_name += ".mda"
+ return file_name
+
@Slot(int)
def on_started(self, target_steps: int):
LOG.info(f"Item received on_started: {target_steps} total steps")
@@ -238,6 +253,8 @@ class JobHolder(QStandardItemModel):
trajectory_for_loading = Signal(str)
results_for_loading = Signal(str)
+ protect_filename = Signal(str)
+ unprotect_filename = Signal(str)
new_job_started = Signal()
def __init__(self, parent: QObject = None):
@@ -302,6 +319,7 @@ def startProcess(self, job_vars: list, load_afterwards=False):
communicator.moveToThread(watcher_thread)
entry_number = self.next_number
item_th.parameters = job_vars[1]
+ # item_th.for_loading.connect(self.unprotect_filename)
if load_afterwards:
if job_vars[0] in Converter.subclasses():
item_th.for_loading.connect(self.trajectory_for_loading)
@@ -324,6 +342,7 @@ def startProcess(self, job_vars: list, load_afterwards=False):
task_name = str("This should have been a job name")
name_item = QStandardItem(task_name)
name_item.setData(entry_number, role=Qt.ItemDataRole.UserRole)
+ self.protect_filename.emit(item_th.expected_output())
self.appendRow(
[
name_item,
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Models/PlotDataModel.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Models/PlotDataModel.py
index 72c6c0023..5cb9b992e 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Models/PlotDataModel.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Models/PlotDataModel.py
@@ -14,6 +14,7 @@
# along with this program. If not, see .
#
from abc import abstractmethod
+import os
import h5py
from qtpy.QtCore import QObject, Slot, Signal, QMutex, QModelIndex, Qt
@@ -70,7 +71,7 @@ def __init__(self, *args, **kwargs):
def data_path(self) -> str:
parent_path = self.parent().data_path()
own_path = self.data(role=Qt.ItemDataRole.UserRole)
- return "/".join([parent_path, own_path])
+ return os.path.join(parent_path, own_path)
def file_number(self) -> int:
return self.parent().file_number()
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/PlotSelectionTab.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/PlotSelectionTab.py
index 93c0cfd14..c43c628cb 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/PlotSelectionTab.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/PlotSelectionTab.py
@@ -78,8 +78,9 @@ def load_files(self):
@Slot(str)
def load_results(self, fname: str):
if len(fname) > 0:
- _, short_name = os.path.split(fname)
+ fname = os.path.abspath(fname)
self._model.add_file(fname)
+ self._session.protect_filename(fname)
@classmethod
def standard_instance(cls):
@@ -118,6 +119,7 @@ def gui_instance(
label_text=label_text,
)
the_tab._visualiser._unit_lookup = the_tab
+ the_tab._view.free_name.connect(session.free_filename)
return the_tab
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/RunTab.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/RunTab.py
index a1248a414..01b752c25 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/RunTab.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/RunTab.py
@@ -90,6 +90,8 @@ def gui_instance(
),
label_text=run_tab_label,
)
+ the_tab._model.protect_filename.connect(session.protect_filename)
+ the_tab._model.unprotect_filename.connect(session.free_filename)
the_tab._model.new_job_started.connect(the_tab.tab_notification)
return the_tab
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/TrajectoryTab.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/TrajectoryTab.py
index 778dc3915..d1473b296 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/TrajectoryTab.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/TrajectoryTab.py
@@ -73,6 +73,7 @@ def load_trajectory(self, fname: str):
self._core.error.emit(repr(e))
else:
self._core._model.append_object(((fname, data), short_name))
+ self._session.protect_filename(fname)
@classmethod
def standard_instance(cls):
@@ -110,6 +111,7 @@ def gui_instance(
layout=partial(MultiPanel, left_panels=[TrajectoryInfo()]),
label_text=label_text,
)
+ the_tab._view.free_name.connect(session.free_filename)
return the_tab
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Views/PlotDataView.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Views/PlotDataView.py
index 5f26bdc04..a19908dbf 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Views/PlotDataView.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Views/PlotDataView.py
@@ -29,6 +29,7 @@ class PlotDataView(QTreeView):
execute_action = Signal(object)
item_details = Signal(object)
error = Signal(str)
+ free_name = Signal(str)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -76,6 +77,12 @@ def populateMenu(self, menu: QMenu, item: QStandardItem):
def deleteNode(self):
model = self.model()
index = self.currentIndex()
+ mda_data_structure = model.inner_object(index)
+ try:
+ filename = mda_data_structure._file.filename
+ except AttributeError:
+ filename = mda_data_structure.file
+ self.free_name.emit(filename)
model.removeRow(index.row())
self.item_details.emit("")
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Views/TrajectoryView.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Views/TrajectoryView.py
index 37d469771..d63d76a9c 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Views/TrajectoryView.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Views/TrajectoryView.py
@@ -27,6 +27,7 @@ class TrajectoryView(QListView):
item_details = Signal(tuple)
item_name = Signal(str)
error = Signal(str)
+ free_name = Signal(str)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -53,8 +54,12 @@ def populateMenu(self, menu: QMenu, item: QStandardItem):
def deleteNode(self):
model = self.model()
index = self.currentIndex()
+ node_number = model.itemFromIndex(index).data()
+ trajectory = model._nodes[node_number][0]
+ self.free_name.emit(trajectory)
model.removeRow(index.row())
self.item_details.emit(("", None))
+ self.free_name
@Slot(QModelIndex)
def item_picked(self, index: QModelIndex):
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/Action.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/Action.py
index e95bf1121..a2d565db7 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/Action.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/Action.py
@@ -13,6 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
+import os
from typing import Optional
import traceback
@@ -89,19 +90,20 @@ class Action(QWidget):
run_and_load = Signal(list)
new_path = Signal(str)
- last_paths = {}
-
def __init__(self, *args, use_preview=False, **kwargs):
self._default_path = None
self._input_trajectory = None
self._parent_tab = None
self._trajectory_configurator = None
self._settings = None
+ self._job_name = None
self._use_preview = use_preview
self._current_instrument = None
- default_path = kwargs.pop("path", None)
+ self._has_been_initialised = False
+ self.execute_button = None
+ self.post_execute_checkbox = None
input_trajectory = kwargs.pop("trajectory", None)
- self.set_trajectory(default_path, input_trajectory)
+ self.set_trajectory(input_trajectory)
super().__init__(*args, **kwargs)
self.layout = QVBoxLayout(self)
@@ -112,7 +114,7 @@ def __init__(self, *args, use_preview=False, **kwargs):
def set_settings(self, settings):
self._settings = settings
- def set_trajectory(self, path: Optional[str], trajectory: Optional[str]) -> None:
+ def set_trajectory(self, trajectory: str) -> None:
"""Set the trajectory path and filename.
Parameters
@@ -122,16 +124,13 @@ def set_trajectory(self, path: Optional[str], trajectory: Optional[str]) -> None
trajectory : str or None
The path and filename of the trajectory
"""
- self._default_path = path
self._input_trajectory = trajectory
- path = None
if self._input_trajectory is not None:
- path, filename = os.path.split(self._input_trajectory)
- if self._default_path is None:
- if path is None:
- self._default_path = "."
- else:
- self._default_path = path
+ self._default_path, _ = os.path.split(self._input_trajectory)
+ else:
+ self._default_path = os.path.abspath(".")
+ if self._job_name is not None:
+ self._parent_tab.set_path(self._job_name, self._default_path)
def set_instrument(self, instrument: SimpleInstrument) -> None:
self._current_instrument = instrument
@@ -160,9 +159,11 @@ def update_panel(self, job_name: str) -> None:
The job name.
"""
self.clear_panel()
+ self._has_been_initialised = False
self._job_name = job_name
- self.last_paths[job_name] = self._parent_tab.get_path(job_name)
+ if self._default_path is None or self._default_path == os.path.abspath("."):
+ self._default_path = self._parent_tab.get_path(job_name)
try:
job_instance = IJob.create(job_name)
except ValueError as e:
@@ -229,16 +230,8 @@ def update_panel(self, job_name: str) -> None:
input_widget.value_updated.connect(self.show_output_prediction)
LOG.info(f"Set up the right widget for {key}")
# self.handlers[key] = data_handler
- configured = False
- iterations = 0
- while not configured:
- configured = True
- for widget in self._widgets:
- widget.value_from_configurator()
- configured = configured and widget._configurator.is_configured()
- iterations += 1
- if iterations > 5:
- break
+ self._has_been_initialised = True
+ self.check_inputs()
if self._use_preview:
self._preview_box = QTextEdit(self)
@@ -274,6 +267,27 @@ def update_panel(self, job_name: str) -> None:
self._widgets_in_layout.append(buttonbase)
self.apply_instrument()
+ def check_inputs(self):
+ configured = False
+ iterations = 0
+ while not configured:
+ configured = True
+ for widget in self._widgets:
+ widget.value_from_configurator()
+ configured = configured and widget._configurator.is_configured()
+ iterations += 1
+ if iterations > 5:
+ break
+
+ @Slot()
+ def test_file_outputs(self):
+ if not self._has_been_initialised:
+ return
+ self.check_inputs()
+ for widget in self._widgets:
+ widget.updateValue()
+ self.allow_execution()
+
def apply_instrument(self):
if self._current_instrument is not None:
q_vector_tuple = self._current_instrument.create_q_vector_params()
@@ -312,26 +326,22 @@ def show_output_prediction(self):
text += f"
[{array[0]}, {array[1]}, {array[2]}, ..., {array[-1]}] ({new_unit})
"
self._preview_box.setHtml(text)
- @Slot(dict)
- def parse_updated_params(self, new_params: dict):
- if "path" in new_params.keys():
- self.default_path = new_params["path"]
- self.new_path.emit(self.default_path)
-
@Slot()
def allow_execution(self):
allow = True
for widget in self._widgets:
if not widget._configurator.valid:
allow = False
- if allow:
- self.execute_button.setEnabled(True)
- else:
- self.execute_button.setEnabled(False)
- if self._job_name == "AverageStructure":
- self.post_execute_checkbox.setEnabled(False)
- else:
- self.post_execute_checkbox.setEnabled(True)
+ if self.execute_button is not None:
+ if allow:
+ self.execute_button.setEnabled(True)
+ else:
+ self.execute_button.setEnabled(False)
+ if self.post_execute_checkbox is not None:
+ if self._job_name == "AverageStructure":
+ self.post_execute_checkbox.setEnabled(False)
+ else:
+ self.post_execute_checkbox.setEnabled(True)
@Slot()
def cancel_dialog(self):
@@ -342,7 +352,7 @@ def save_dialog(self):
try:
cname = self._job_name
except:
- currentpath = "."
+ currentpath = os.path.abspath(".")
else:
currentpath = self._parent_tab.get_path(self._job_name + "_script")
result, ftype = QFileDialog.getSaveFileName(
@@ -356,7 +366,6 @@ def save_dialog(self):
except:
pass
else:
- self.last_paths[cname] = path
self._parent_tab.set_path(self._job_name + "_script", path)
pardict = self.set_parameters(labels=True)
self._job_instance.save(result, pardict)
@@ -376,6 +385,7 @@ def execute_converter(self):
pardict = self.set_parameters()
LOG.info(pardict)
self._parent_tab.set_path(self._job_name, self._default_path)
+ self._parent_tab._session.save()
# when we are ready, we can consider running it
# self.converter_instance.run(pardict)
# this would send the actual instance, which _may_ be wrong
@@ -387,3 +397,7 @@ def execute_converter(self):
self.run_and_load.emit([self._job_name, pardict])
else:
self.new_thread_objects.emit([self._job_name, pardict])
+ self.check_inputs()
+ for widget in self._widgets:
+ widget.updateValue()
+ self.allow_execution()
diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/DataWidget.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/DataWidget.py
index 62a9a2449..046bb36fd 100644
--- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/DataWidget.py
+++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/DataWidget.py
@@ -56,7 +56,7 @@ def __init__(self, *args, **kwargs) -> None:
self._sliderpack = None
self._plotting_context = None
self._slider_max = 100
- self._current_path = "."
+ self._current_path = os.path.abspath(".")
layout = QVBoxLayout(self)
self.setLayout(layout)
self.make_toolbar()